You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
4.6 KiB
Python
163 lines
4.6 KiB
Python
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()}
|