Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix detection of R languageserver #463

Merged
merged 3 commits into from
Jan 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
- bug fixes:

- send periodic pings on websocket channels to maintain connection ([#459], thanks @franckchen)
- R languageserver is no longer incorrectly shown as available when not installed ([#463])

[#459]: https://github.com/krassowski/jupyterlab-lsp/pull/459
[#463]: https://github.com/krassowski/jupyterlab-lsp/pull/463

### `@krassowski/jupyterlab-lsp 3.0.0` (2021-01-06)

Expand Down
7 changes: 6 additions & 1 deletion docs/Configuring.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,13 @@
"source": [
"The absolute minimum language server spec requires:\n",
"\n",
"- `argv`, a list of shell tokens to launch the server in `stdio` mode (as\n",
"- `argv`, a list of _shell tokens_ to launch the server in `stdio` mode (as\n",
" opposed to `tcp`),\n",
" - _shell tokens_ are arrays of strings representing command line commands with\n",
" arguments, for example `ls -l` is represented as `[\"ls\", \"-l\"]` while\n",
" `mkdir \"new directory\"` should be split into `[\"mkdir\", \"new directory\"]`;\n",
" If you have Python installed, you can use `shlex.split(\"your command\")` to\n",
" get such an array.\n",
"- the `languages` which the server will respond to, and\n",
"- the schema `version` of the spec (currently `2`)\n",
"\n",
Expand Down
6 changes: 3 additions & 3 deletions docs/Extending.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
"### How to add a new LSP feature?\n",
"\n",
"Features (as well as other parts of the frontend) reuse the\n",
"[JupyterLab plugins system](https://jupyterlab.readthedocs.io/en/stable/developer/extension_dev.html#plugins).\n",
"[JupyterLab plugins system](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#plugins).\n",
"Each plugin is a [TypeScript](https://www.typescriptlang.org/) package exporting\n",
"one or more `JupyterFrontEndPlugin`s (see\n",
"[the JupyterLab extesion developer tutorial](https://jupyterlab.readthedocs.io/en/stable/developer/extension_tutorial.html)\n",
"[the JupyterLab extesion developer tutorial](https://jupyterlab.readthedocs.io/en/stable/extension/extension_tutorial.html)\n",
"for an overview). Each feature has to register itself with the `FeatureManager`\n",
"(which is provided after requesting `ILSPFeatureManager` token) using\n",
"`register(options: IFeatureOptions)` method.\n",
Expand All @@ -41,7 +41,7 @@
"\n",
"For further integration with the JupyterLab, you can request additional\n",
"JupyterLab tokens (consult JupyterLab documentation on\n",
"[core tokens](https://jupyterlab.readthedocs.io/en/stable/developer/extension_dev.html#core-tokens)).\n",
"[core tokens](https://jupyterlab.readthedocs.io/en/stable/extension/extension_points.html#core-tokens)).\n",
"\n",
"#### How to override the default implementation of a feature?\n",
"\n",
Expand Down

This file was deleted.

15 changes: 12 additions & 3 deletions python_packages/jupyter_lsp/jupyter_lsp/specs/r_languageserver.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
from .utils import HELPERS, ShellSpec
from .utils import ShellSpec


class RLanguageServer(ShellSpec):
package = "languageserver"
key = "r-languageserver"
cmd = "Rscript"
args = ["--slave", str(HELPERS / "languageserver.R")]

@property
def args(self):
return ["--slave", "-e", f"{self.package}::run()"]

@property
def is_installed_args(self):
return ["-e", f"cat(system.file(package='{self.package}'))"]

languages = ["r"]
spec = dict(
display_name=key,
Expand All @@ -14,7 +23,7 @@ class RLanguageServer(ShellSpec):
issues="https://github.com/REditorSupport/languageserver/issues",
),
install=dict(
cran='install.packages("languageserver")',
cran=f'install.packages("{package}")',
conda="conda install -c conda-forge r-languageserver",
),
)
28 changes: 25 additions & 3 deletions python_packages/jupyter_lsp/jupyter_lsp/specs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import shutil
import sys
from pathlib import Path
from typing import List, Text
from subprocess import check_output
from typing import List, Text, Union

from ..schema import SPEC_VERSION
from ..types import (
Expand All @@ -17,13 +18,17 @@
# when building docs, let all specs go through
BUILDING_DOCS = os.environ.get("JUPYTER_LSP_BUILDING_DOCS") is not None

# String corresponding to a fragment of a shell command
# arguments list such as returned by `shlex.split`
Token = Text


class SpecBase:
"""Base for a spec finder that returns a spec for starting a language server"""

key = ""
languages = [] # type: List[Text]
args = [] # type: List[Text]
args = [] # type: List[Token]
spec = {} # type: LanguageServerSpec

def __call__(
Expand All @@ -39,6 +44,23 @@ class ShellSpec(SpecBase): # pragma: no cover

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 = [] # type: List[Token]

def is_installed(self, cmd: Union[str, None]) -> bool:
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 __call__(self, mgr: LanguageServerManagerAPI) -> KeyedLanguageServerSpecs:
for ext in ["", ".cmd", ".bat", ".exe"]:
cmd = shutil.which(self.cmd + ext)
Expand All @@ -48,7 +70,7 @@ def __call__(self, mgr: LanguageServerManagerAPI) -> KeyedLanguageServerSpecs:
if not cmd and BUILDING_DOCS: # pragma: no cover
cmd = self.cmd

if not cmd: # pragma: no cover
if not self.is_installed(cmd): # pragma: no cover
return {}

return {
Expand Down
19 changes: 19 additions & 0 deletions python_packages/jupyter_lsp/jupyter_lsp/tests/test_detect.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import shutil

from jupyter_lsp.specs.r_languageserver import RLanguageServer


def test_no_detect(manager):
"""should not enable anything by default"""
manager.autodetect = False
Expand All @@ -9,3 +14,17 @@ def test_no_detect(manager):
def test_detect(manager):
manager.initialize()
assert len(manager.sessions) == len(manager.language_servers)


def test_r_package_detection():
existing_runner = shutil.which("Rscript")

with_installed_server = RLanguageServer()
assert with_installed_server.is_installed(cmd=existing_runner) is True
assert with_installed_server.is_installed(cmd=None) is False

class NonInstalledRServer(RLanguageServer):
package = "languageserver-fork"

non_installed_server = NonInstalledRServer()
assert non_installed_server.is_installed(cmd=existing_runner) is False