utils.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import os
  2. import shutil
  3. import sys
  4. from pathlib import Path
  5. from subprocess import check_output
  6. from typing import List, Text, Union
  7. from ..schema import SPEC_VERSION
  8. from ..types import (
  9. KeyedLanguageServerSpecs,
  10. LanguageServerManagerAPI,
  11. LanguageServerSpec,
  12. SpecBase,
  13. Token,
  14. )
  15. # helper scripts for known tricky language servers
  16. HELPERS = Path(__file__).parent / "helpers"
  17. # when building docs, let all specs go through
  18. BUILDING_DOCS = os.environ.get("JUPYTER_LSP_BUILDING_DOCS") is not None
  19. class ShellSpec(SpecBase): # pragma: no cover
  20. """Helper for a language server spec for executables on $PATH in the
  21. notebook server environment.
  22. """
  23. cmd = ""
  24. # [optional] arguments passed to `cmd` which upon execution should print
  25. # out a non-empty string if the the required language server package
  26. # is installed, or nothing if it is missing and user action is required.
  27. is_installed_args: List[Token] = []
  28. def is_installed(self, mgr: LanguageServerManagerAPI) -> bool:
  29. cmd = self.solve()
  30. if not cmd:
  31. return False
  32. if not self.is_installed_args:
  33. return bool(cmd)
  34. else:
  35. check_result = check_output([cmd, *self.is_installed_args]).decode(
  36. encoding="utf-8"
  37. )
  38. return check_result != ""
  39. def solve(self) -> Union[str, None]:
  40. for ext in ["", ".cmd", ".bat", ".exe"]:
  41. cmd = shutil.which(self.cmd + ext)
  42. if cmd:
  43. break
  44. return cmd
  45. def __call__(self, mgr: LanguageServerManagerAPI) -> KeyedLanguageServerSpecs:
  46. cmd = self.solve()
  47. spec = dict(self.spec)
  48. if not cmd:
  49. troubleshooting = [f"{self.cmd} not found."]
  50. if "troubleshoot" in spec:
  51. troubleshooting.append(spec["troubleshoot"])
  52. spec["troubleshoot"] = "\n\n".join(troubleshooting)
  53. if not cmd and BUILDING_DOCS: # pragma: no cover
  54. cmd = self.cmd
  55. return {
  56. self.key: {
  57. "argv": [cmd, *self.args] if cmd else [self.cmd, *self.args],
  58. "languages": self.languages,
  59. "version": SPEC_VERSION,
  60. **spec,
  61. }
  62. }
  63. class PythonModuleSpec(SpecBase):
  64. """Helper for a python-based language server spec in the notebook server
  65. environment
  66. """
  67. python_module = ""
  68. def is_installed(self, mgr: LanguageServerManagerAPI) -> bool:
  69. spec = self.solve()
  70. if not spec:
  71. return False
  72. if not spec.origin: # pragma: no cover
  73. return False
  74. return True
  75. def solve(self):
  76. return __import__("importlib").util.find_spec(self.python_module)
  77. def __call__(self, mgr: LanguageServerManagerAPI) -> KeyedLanguageServerSpecs:
  78. is_installed = self.is_installed(mgr)
  79. return {
  80. self.key: {
  81. "argv": (
  82. [sys.executable, "-m", self.python_module, *self.args]
  83. if is_installed
  84. else []
  85. ),
  86. "languages": self.languages,
  87. "version": SPEC_VERSION,
  88. **self.spec,
  89. }
  90. }
  91. class NodeModuleSpec(SpecBase):
  92. """Helper for a nodejs-based language server spec in one of several
  93. node_modules
  94. """
  95. node_module = ""
  96. script: List[Text] = []
  97. def is_installed(self, mgr: LanguageServerManagerAPI) -> bool:
  98. node_module = self.solve(mgr)
  99. return bool(node_module)
  100. def solve(self, mgr: LanguageServerManagerAPI):
  101. return mgr.find_node_module(self.node_module, *self.script)
  102. def __call__(self, mgr: LanguageServerManagerAPI) -> KeyedLanguageServerSpecs:
  103. node_module = self.solve(mgr)
  104. spec = dict(self.spec)
  105. troubleshooting = ["Node.js is required to install this server."]
  106. if "troubleshoot" in spec: # pragma: no cover
  107. troubleshooting.append(spec["troubleshoot"])
  108. spec["troubleshoot"] = "\n\n".join(troubleshooting)
  109. is_installed = self.is_installed(mgr)
  110. return {
  111. self.key: {
  112. "argv": ([mgr.nodejs, node_module, *self.args] if is_installed else []),
  113. "languages": self.languages,
  114. "version": SPEC_VERSION,
  115. **spec,
  116. }
  117. }
  118. # these are not desirable to publish to the frontend
  119. # and will be replaced with the simplest schema-compliant values
  120. SKIP_JSON_SPEC = {"argv": [""], "debug_argv": [""], "env": {}}
  121. def censored_spec(spec: LanguageServerSpec) -> LanguageServerSpec:
  122. return {k: SKIP_JSON_SPEC.get(k, v) for k, v in spec.items()}