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