|
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, |
|
) |
|
|
|
|
|
HELPERS = Path(__file__).parent / "helpers" |
|
|
|
|
|
BUILDING_DOCS = os.environ.get("JUPYTER_LSP_BUILDING_DOCS") is not None |
|
|
|
|
|
class ShellSpec(SpecBase): |
|
"""Helper for a language server spec for executables on $PATH in the |
|
notebook server environment. |
|
""" |
|
|
|
cmd = "" |
|
|
|
|
|
|
|
|
|
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: |
|
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: |
|
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: |
|
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, |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
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()} |
|
|