Jupyter[Lab] Language Server Protocol

This is the documentation for:

Both are managed on GitHub, where you can find the issue tracker.

Installation

[3]:

Please Read This First

Delivering LSP features to your JupyterLab requires three pieces:

jupyter-lsp

  • runs in your jupyter_server web application on your server to handle requests from the browser to language servers

  • to run, you need:

  • python >=3.6,<3.9.0a0

  • jupyter_server >=1.1.2

jupyterlab-lsp

  • runs in your browser, as an extension to JupyterLab

  • to install it, you need:

  • jupyterlab >=3.0.0,<4.0.0a0

Language Servers

  • run on your server

  • probably in another language runtime than python

  • some can be automatically detected if installed

  • others also need to be configured

Fast Paths

Here are two approaches based on Jupyter documentation. If these do not meet your needs, try The Harder Way.

[4]:

conda (minimal python)

conda create -c conda-forge -n lsp 'python >=3.6,<3.9.0a0' 'jupyterlab=3.0.0' 'jupyterlab-lsp=3.4.0' 'jupyter-lsp-python=1.1.3'
# jupyter-lsp-python includes both the server extension (jupyter-lsp) and pyls third-party server (python-language-server)
# if you swap it with another pre-made bundle, jupyter-lsp-r, you will get the server extension and r-languageserver;
# alternatively, manually install a language server of your choice (see the table below).
conda activate lsp

Then run

jupyter lab

Your browser should open to your local server.

docker (data science)

This approach is based roughly on the Jupyter docker-stacks documentation, which should be consulted for more about connecting volumes, passwords, and other advanced features:

Note: docker instructions were not updated for JupyterLab 3.0 and extension 3.0. Please consider submitting a PR to fix it.

[5]:
Dockerfile
# This already contains the python, r, julia, latex, and nodejs runtimes
FROM jupyter/datascience-notebook@sha256:73a577b006b496e1a1c02f5be432f4aab969c456881c4789e0df77c89a0a60c2

RUN conda install --quiet --yes --freeze-installed -c conda-forge \
    'python-language-server' \
    'jupyterlab=3.0.0' \
    'r-languageserver' \
    'texlab' \
    'chktex' \
    'jupyter-lsp=1.1.3' \
  && jupyter labextension install --no-build \
    '@krassowski/jupyterlab-lsp@3.4.0' \
  && jupyter lab build --dev-build=False --minimize=True \
  && conda clean --all -f -y \
  && rm -rf \
    $CONDA_DIR/share/jupyter/lab/staging \
    /home/$NB_USER/.cache/yarn \
  && fix-permissions $CONDA_DIR \
  && fix-permissions /home/$NB_USER
docker-compose.yml
version: '2'

services:
  lsp-lab:
    build: .
    ports:
      - '18888:8888'
Build and Start
docker-compose up

You should now be able to access http://localhost:18888/lab, using the token provided in the log.

The Harder Way

Get A Working JupyterLab environment

Refer to the official JupyterLab Installation Documentation for your installation approach.

pip

conda

pipenv

poetry

*

lab

lab

*

*

*

* PRs welcome!

Verify your lab works:

jupyter lab --version
jupyter lab
[6]:
Install Jupyter[Lab] LSP
conda
conda install jupyterlab-lsp=3.4.0
pip
pip install jupyterlab-lsp=3.4.0

Next Step: Language Servers

Now that you have jupyterlab-lsp, jupyter-lsp and all of their dependencies, you’ll need some language servers. See:

Language Servers

jupyter-lsp does not come with any Language Servers! However, we will try to use them if they are installed and we know about them. For the language servers in the tables below, use one of the suggested package managers to install them: these implementations are tested to work with jupyter-lsp.

  • You can disable this feature by configuring autodetect

If you do not see a language you would like, but can find it one of these lists:

…you might be able to add it via configuration or build your own spec for the server in question.

Notebook-Optimized Language Servers

These servers have support for notebooks and file editors. The pyls and r-languageserver are well-tested, while jedi and Julia servers are experimental. Please only install one language server per language (jedi or pyls for Python - not both).

[5]:
Languages Implementation Installation
python
jedi-language-server
  • pip: pip install -U jedi-language-server
  • conda: conda install -c conda-forge jedi-language-server
julia
julia-language-server
  • julia: using Pkg; Pkg.add("LanguageServer")
python
pyls
  • pip: pip install 'python-language-server[all]'
  • conda: conda install -c conda-forge python-language-server
r
r-languageserver
  • cran: install.packages("languageserver")
  • conda: conda install -c conda-forge r-languageserver

NodeJS-based Language Servers

These servers have mostly been tested with file editors.

[6]:
Languages Implementation Installation
bash
sh
bash-language-server
  • npm: npm install --save-dev bash-language-server
  • yarn: yarn add --dev bash-language-server
  • jlpm: jlpm add --dev bash-language-server
dockerfile
dockerfile-language-server-nodejs
  • npm: npm install --save-dev dockerfile-language-server-nodejs
  • yarn: yarn add --dev dockerfile-language-server-nodejs
  • jlpm: jlpm add --dev dockerfile-language-server-nodejs
javascript
jsx
typescript
typescript-jsx
typescriptreact
javascriptreact
javascript-typescript-langserver
  • npm: npm install --save-dev javascript-typescript-langserver
  • yarn: yarn add --dev javascript-typescript-langserver
  • jlpm: jlpm add --dev javascript-typescript-langserver
sql
sql-language-server
  • npm: npm install --save-dev sql-language-server
  • yarn: yarn add --dev sql-language-server
  • jlpm: jlpm add --dev sql-language-server
markdown
ipythongfm
gfm
unified-language-server
  • npm: npm install --save-dev unified-language-server
  • yarn: yarn add --dev unified-language-server
  • jlpm: jlpm add --dev unified-language-server
css
less
scss
vscode-css-languageserver-bin
  • npm: npm install --save-dev vscode-css-languageserver-bin
  • yarn: yarn add --dev vscode-css-languageserver-bin
  • jlpm: jlpm add --dev vscode-css-languageserver-bin
html
vscode-html-languageserver-bin
  • npm: npm install --save-dev vscode-html-languageserver-bin
  • yarn: yarn add --dev vscode-html-languageserver-bin
  • jlpm: jlpm add --dev vscode-html-languageserver-bin
json
vscode-json-languageserver-bin
  • npm: npm install --save-dev vscode-json-languageserver-bin
  • yarn: yarn add --dev vscode-json-languageserver-bin
  • jlpm: jlpm add --dev vscode-json-languageserver-bin
yaml
yaml-language-server
  • npm: npm install --save-dev yaml-language-server
  • yarn: yarn add --dev yaml-language-server
  • jlpm: jlpm add --dev yaml-language-server

NodeJS is a prerequisite for installation of any of the above language servers; you can get it with:

conda install -c conda-forge nodejs
# or one of the following, as an administrator
choco install nodejs            # Windows with Chocolatey
sudo apt-get install nodejs     # Debian/Ubuntu
sudo brew install nodejs        # MacOS with Homebrew
sudo dnf install nodejs         # Fedora
sudo yum install nodejs         # RHEL/CentOS

Example: Getting All the NodeJS-based Language Servers

A number of language servers are built on the reference implementation, powered by NodeJS. The most reliable place to install these is in a node_modules in the directory where you launch jupyter lab.

For example, to install all the servers which are tested as part of jupyterlab-lsp:

jlpm add --dev \
    bash-language-server \
    vscode-css-languageserver-bin \
    dockerfile-language-server-nodejs \
    vscode-html-languageserver-bin \
    javascript-typescript-langserver \
    vscode-json-languageserver-bin \
    yaml-language-server

This will create (or add to):

  • package.json (check this in!)

  • yarn.lock (check this in!)

  • node_modules/ (add to your VCS ignore file)

If you wish to install these someplace else, you may need to specify where you install them with extra_node_roots.

Other Scientific Languages

These servers have been mostly tested with file editor.

[7]:
Languages Implementation Installation
tex
latex
texlab
  • conda: conda install -c conda-forge texlab chktex

Example: Getting a \(\LaTeX\) stack

conda install -y conda-forge tectonic texlab chktex

This will install:

  • tectonic, a cross-platform \(\LaTeX\) processing tool

  • note, it will download a large number of packages when first executed

  • texlab, a Language Server for .tex files that supports completion and reference navigation

  • chktex, a .tex style checker

Configuring backend

Configuration Files

Like the Jupyter Notebook server, JupyterHub, and other Jupyter interactive computing tools, jupyter-lsp can be configured via Python or JSON files in well-known locations. You can find out where to put them on your system with:

jupyter --paths

They will be merged from bottom to top, and the directory where you launch your notebook server wins, making it easy to check in to version control.

Configuration Options

language_servers

jupyter-lsp does not come with any Language Servers! However, we will try to use known language servers if they are installed and we know about them: you can disable this behavior by configuring autodetect.

If you don’t see an implementation for the language server you need, continue reading!

Please consider contributing your language server spec to jupyter-lsp!

The absolute minimum language server spec requires:

  • argv, a list of shell tokens to launch the server in stdio mode (as opposed to tcp),

  • shell tokens are arrays of strings representing command line commands with arguments, for example ls -l is represented as ["ls", "-l"] while mkdir "new directory" should be split into ["mkdir", "new directory"]; If you have Python installed, you can use shlex.split("your command") to get such an array.

  • the languages which the server will respond to, and

  • the schema version of the spec (currently 2)

# ./jupyter_server_config.json                   ---------- unique! -----------
#                                               |                              |
# or e.g.                                       V                              V
# $PREFIX/etc/jupyter/jupyter_server_config.d/a-language-server-implementation.json
{
  "LanguageServerManager": {
    "language_servers": {
      "a-language-server-implementation": {
        "version": 2,
        "argv": ["/absolute/path/to/a-language-server", "--stdio"],
        "languages": ["a-language"]
      }
    }
  }
}

A number of other options we hope to use to enrich the user experience are available in the schema.

More complex configurations that can’t be hard-coded may benefit from the python approach:

# jupyter_server_config.py
import shutil

# c is a magic, lazy variable
c.LanguageServerManager.language_servers = {
    "a-language-server-implementation": {
        # if installed as a binary
        "argv": [shutil.which("a-language-server")],
        "languages": ["a-language"],
        "version": 2
    },
    "another-language-implementation": {
        # if run like a script
        "argv": [shutil.which("another-language-interpreter"), "another-language-server"],
        "languages": ["another-language"],
        "version": 2
    }
}

nodejs

default: None

An absolute path to your nodejs executable. If None, nodejs will be detected in a number of well-known places.

autodetect

default: True

If True, jupyter-lsp will look for all known language servers. User-configured language_servers of the same implementation will be preferred over autodetected ones.

node_roots

default: []

Absolute paths to search for directories named node_modules, such as nodejs-backed language servers. The order is, roughly:

  • the folder where notebook or lab was launched

  • the JupyterLab staging folder

  • wherever conda puts global node modules

  • wherever some other conventions put it

extra_node_roots

default: []

Additional places jupyter-lsp will look for node_modules. These will be checked before node_roots, and should not contain the trailing node_modules.

virtual_documents_dir

default: os.getenv(“JP_LSP_VIRTUAL_DIR”, “.virtual_documents”)

Path to virtual documents relative to the content manager root directory.

Its default value can be set with JP_LSP_VIRTUAL_DIR environment variable and fallback to .virtual_documents.

Python entry_points

pip-installable packages in the same environment as the Jupyter notebook server can be automatically detected as providing language_servers. These are a little more involved, but also more powerful: see more in Contributing. Servers configured this way are loaded before those defined in configuration files, so that a user can fine-tune their available servers.

Example: Scala Language Server (metals) integration with jupyterlab-lsp

Step 1: Get a Scala-based kernel installed.

2 possible options: Almond kernel or the Spark magic kernel.

Almond kernel install:

$ curl -Lo coursier https://git.io/coursier-cli
$ chmod +x coursier
$ ./coursier launch --fork almond -- --install
$ rm -f coursier

Spark Magic kernel:

pip install sparkmagic

Now, install the spark kernel:

jupyter-kernelspec install sparkmagic/kernels/sparkkernel

Step 2: Install metals server in the working directory:

Metals has a coursier based installation.

curl -Lo coursier https://git.io/coursier-cli && chmod +x coursier
./coursier bootstrap org.scalameta:metals_2.12:0.7.0 --force-fetch -o metals -f

(Might need to use the –force-fetch flag if you are getting dependency issues.)

Step 3: Configure the metals server in jupyterlab-lsp. Enter the following in the jupyter_server_config.json:

{
  "LanguageServerManager": {
    "language_servers": {
      "metals": {
        "version": 2,
        "argv": ["<$path_to_metals_server(eg:/Users/skakker/projects/jupyterlab-lsp/metals)>"],
        "languages": ["scala"],
        "mime_types": ["text/x-scala"]
      }
    }
  }
}

You are good to go now! Just start jupyter lab and create a notebook with either the Spark or the Scala kernel and you should be able to see the metals server initialised from the bottom left corner.

Contributing

[1]:

jupyter-lsp and jupyterlab-lsp are open source software, and all contributions conforming to good sense, good taste, and the Jupyter Code of Conduct are welcome, and will be reviewed by the contributors, time-permitting.

You can contribute to the project through:

  • creating language server specs

  • you can publish them yourself (it might be a single file)…

  • or advocate for adding your spec to the github repository and its various distributions

    • these are great first issues, as you might not need to know any python or javascript

  • proposing parts of the architecture that can be extended

  • improving documentation

  • tackling Big Issues from the future roadmap

  • improving testing

  • reviewing pull requests

Set up the environment

Development requires, at a minimum:

  • nodejs >=12,<15

  • python >=3.6,<3.9.0a0

  • Python 3.7 and 3.8 are also tested on CI

  • Python 3.6 has issues on Windows

  • jupyterlab >=3.0.0,<4.0.0a0

It is recommended to use a virtual environment (e.g. virtualenv or conda env) for development.

To use the same environment as the binder demo (recommended):

conda env update -n jupyterlab-lsp --file binder/environment.yml # create a conda env
source activate jupyterlab-lsp                                   # activate it

Or with pip:

pip install -r requirements/dev.txt  # in a virtualenv, probably
sudo apt-get install nodejs          # ... e.g. on debian/ubuntu

The Easy Way

Once your environment is created and activated, on Linux/OSX you can run:

bash binder/postBuild

This performs all the basic setup steps, and is used for the binder demo.

The Hard Way

Install jupyter-lsp from source in your virtual environment:

python -m pip install -e python_packages/jupyter_lsp --ignore-installed --no-deps -vv

Enable the server extension:

jupyter server extension enable --sys-prefix --py jupyter_lsp

Install npm dependencies, build TypeScript packages, and link to JupyterLab for development:

jlpm bootstrap
# if you installed `jupyterlab_lsp` before uninstall it before running the next line
jupyter labextension develop python_packages/jupyterlab_lsp/ --overwrite

Note: on Windows you may need to enable Developer Mode first, as discussed in `jupyterlab#9564 <https://github.com/jupyterlab/jupyterlab/issues/9564>`__

Frontend Development

To rebuild the schemas, packages, and the JupyterLab app:

jlpm build
jupyter lab build

To watch the files and build continuously:

jlpm watch   # leave this running...
jupyter lab --watch  # ...in another terminal

Now after each change to TypesScript files wait until both watchers finish compilation, and then refresh the JupyterLab in your browser.

Note: the backend schema is not included in watch, and is only refreshed by build

To check and fix code style:

jlpm lint

To run test the suite (after running jlpm build or watch):

jlpm test

To run tests matching specific phrase, forward -t argument over yarn and lerna to the test runners with two --:

jlpm test -- -- -t match_phrase

Server Development

Testing jupyter-lsp

python scripts/utest.py

Documentation

To build the documentation:

python scripts/docs.py

To watch documentation sources and build continuously:

python scripts/docs.py --watch

To check internal links in the docs after building:

python scripts/docs.py --check --local-only

To check internal and external links in the docs after building:

python scripts/docs.py --check

Note: you may get spurious failures due to rate limiting, especially in CI, but it's good to test locally

Browser-based Acceptance Tests

The browser tests will launch JupyterLab on a random port and exercise the Language Server features with Robot Framework and SeleniumLibrary. It is recommended to peruse the Robot Framework User’s Guide (and the existing .robot files in atest) before working on tests in anger.

First, ensure you’ve prepared JupyterLab for jupyterlab-lsp frontend and server development.

Prepare the environment:

conda env update -n jupyterlab-lsp --file requirements/atest.yml

or with pip

pip install -r requirements/atest.txt  # ... and install geckodriver, somehow
sudo apt-get install firefox-geckodriver    # ... e.g. on debian/ubuntu

Run the tests:

python scripts/atest.py

The Robot Framework reports and screenshots will be in atest/output, with <operating system>_<python version>_<attempt>.<log|report>.html and subsequent screenshots being the most interesting artifact, e.g.

atest/
  output/
    linux_37_1.log.html
    linux_37_1.report.html
    linux_37_1/
      screenshots/

Customizing the Acceptance Test Run

By default, all of the tests will be run, once.

The underlying robot command supports a vast number of options and many support wildcards (* and ?) and boolean operators (NOT, OR). For more, start with simple patterns.

Run a suite
python scripts/atest.py --suite "05_Features.Completion"
Run a single test
python scripts/atest.py --test "Works When Kernel Is Idle"
Run test with a tag

Tags are preferable to file names and test name matching in many settings, as they are aggregated nicely between runs.

python scripts/atest.py --include feature:completion

… or only Python completion

python scripts/atest.py --include feature:completionANDlanguage:python
Just Keep Testing with ATEST_RETRIES

Run tests, and rerun only failed tests up to two times:

ATEST_RETRIES=2 python scripts/atest.py --include feature:completion

After running a bunch of tests, it may be helpful to combine them back together into a single log.html and report.html with rebot. Like atest.py, combine.py also passes through extra arguments

python scripts/combine.py

Troubleshooting

  • If you see the following error message:

python   Parent suite setup failed:   TypeError: expected str, bytes or os.PathLike object, not NoneType

it may indicate that you have no firefox, or geckodriver installed (or discoverable in the search path).

  • If a test suite for a specific language fails it may indicate that you have no appropriate server language installed (see LANGUAGESERVERS)

  • If you are seeing errors like Element is blocked by .jp-Dialog, caused by the JupyterLab Build suggested dialog, (likely if you have been using jlpm watch) ensure you have a “clean” lab (with production assets) with:

bash   jupyter lab clean   jlpm build   jlpm lab:link   jupyter lab build --dev-build=False --minimize=True

and re-run the tests.

  • To display logs on the screenshots, configure the built-in ILSPLogConsole console, to use the 'floating' implementation.

  • If you see:

    SessionNotCreatedException: Message: Unable to find a matching set of capabilities

geckodriver >=0.27.0 requires an actual Firefox executable. Several places will be checked (including where conda-forge installs, as in CI): to test a Firefox not on your PATH, set the following environment variable:

bash   export FIREFOX_BINARY=/path/to/firefox      # ... unix   set FIREFOX_BINARY=C:\path\to\firefox.exe   # ... windows

Formatting

Minimal code style is enforced with pytest-flake8 during unit testing. If installed, pytest-black and pytest-isort can help find potential problems, and lead to cleaner commits, but are not enforced during CI tests (but are checked during lint).

You can clean up your code, and check for using the project’s style guide with:

python scripts/lint.py

Specs

It is convenient to collect common patterns for connecting to installed language servers as pip-installable packages that Just Work with jupyter-lsp.

If an advanced user installs, locates, and configures, their own language server it will always win vs an auto-configured one.

Writing a spec

See the built-in specs for implementations and some helpers.

A spec is a python function that accepts a single argument, the LanguageServerManager, and returns a dictionary of the form:

{
  "python-language-server": {            # the name of the implementation
      "version": 1,                      # the version of the spec schema
      "argv": ["python", "-m", "pyls"],  # a list of command line arguments
      "languages": ["python"]            # a list of languages it supports
  }
}

The absolute minimum listing requires argv (a list of shell tokens to launch the server) and languages (which languages to respond to), but many number of other options to enrich the user experience are available in the schema and are exercised by the current entry_points-based specs.

The spec should only be advertised if the command could actually be run:

  • its runtime (e.g. julia, nodejs, python, r, ruby) is installed

  • the language server itself is installed (e.g. python-language-server)

Common Concerns
  • some language servers need to have their connection mode specified

  • the stdio interface is the only one supported by jupyter_lsp

    • PRs welcome to support other modes!

  • because of its VSCode heritage, many language servers use nodejs

  • LanguageServerManager.nodejs will provide the location of our best guess at where a user’s nodejs might be found

  • some language servers are hard to start purely from the command line

  • use a helper script to encapsulate some complexity.

    • See the r spec for an example

Example: making a pip-installable cool-language-server spec

Consider the following (absolutely minimal) directory structure:

- setup.py
- jupyter_lsp_my_cool_language_server.py

You should consider adding a LICENSE, some documentation, etc.

Define your spec:

# jupyter_lsp_my_cool_language_server.py
from shutil import which


def cool(app):
    cool_language_server = shutil.which("cool-language-server")

    if not cool_language_server:
        return {}

    return {
        "cool-language-server": {
            "version": 1,
            "argv": [cool_language_server],
            "languages": ["cool"]
        }
    }

Tell pip how to package your spec:

# setup.py
import setuptools
setuptools.setup(
    name="jupyter-lsp-my-cool-language-server",
    py_modules=["jupyter_lsp_my_cool_language_server"],
    entry_points={
        "jupyter_lsp_spec_v1": [
            "cool-language-server":
              "jupyter_lsp_my_cool_language_server:cool"
        ]
    }
)

Test it!

python -m pip install -e .

Build it!

python setup.py sdist bdist_wheel
Debugging

Adjust loggingLevel in the Advanced Settings Editor -> Language Server to see more log messages.

For robot tests set:

Configure JupyterLab Plugin  {"loggingConsole": "floating", "loggingLevel": "debug"}

Extend jupyterlab-lsp

Note: the API is likely to change in the future; your suggestions are welcome!

How to add a new LSP feature?

Features (as well as other parts of the frontend) reuse the JupyterLab plugins system. Each plugin is a TypeScript package exporting one or more JupyterFrontEndPlugins (see the JupyterLab extesion developer tutorial for an overview). Each feature has to register itself with the FeatureManager (which is provided after requesting ILSPFeatureManager token) using register(options: IFeatureOptions) method.

Your feature specification should follow the IFeature interface, which can be divided into three major parts:

  • editorIntegrationFactory: constructors for the feature-CodeEditor integrators (implementing the IFeatureEditorIntegration interface), one for each supported CodeEditor (e.g. CodeMirror or Monaco); for CodeMirror integration you can base your feature integration on the abstract CodeMirrorIntegration class.

  • labIntegration: an optional object integrating feature with the JupyterLab interface

  • optional fields for easy integration of some of the common JupyterLab systems, such as:

  • settings system

  • commands system (including context menu)

For further integration with the JupyterLab, you can request additional JupyterLab tokens (consult JupyterLab documentation on core tokens).

How to override the default implementation of a feature?

You can specify a list of extensions to be disabled the the feature manager passing their plugin identifiers in supersedes field of IFeatureOptions.

How to integrate a new code editor implementation?

CodeMirrorEditor code editor is supported by default, but any JupyterLab editor implementing the CodeEditor.IEditor interface can be adapted for the use with the LSP extension. To add your custom code editor (e.g. Monaco) after implementing a CodeEditor.IEditor interface wrapper (which you would have anyways for the JupyterLab integration), you need to also implement a virtual editor (IVirtualEditor interface) for it.

Why virtual editor?

The virtual editor takes multiple instances of your editor (e.g. in a notebook) and makes them act like a single editor. For example, when “onKeyPress” event is bound on the VirtualEditor instance, it should be bound onto each actual code editor; this allows the features to be implemented without the knowledge about the number of editor instances on the page.

How to register the implementation?

A virtualEditorManager will be provided if you request ILSPVirtualEditorManager token; use registerEditorType(options: IVirtualEditorType<IEditor>) method passing a name that you will also use to identify the code editor, the editor class, and your VirtualEditor constructor.

How to integrate a new DocumentWidget?

JupyterLab editor widgets (such as Notebook or File Editor) implement IDocumentWidget interface. Each such widget has to adapted by a WidgetAdapter to enable its use with the LSP extension. The role of the WidgetAdapter is to extract the document metadata (language, mimetype) and the underlying code editor (e.g. CodeMirror or Monaco) instances so that other parts of the LSP extension can interface with them without knowing about the implementation details of the DocumentWidget (or even about the existence of a Notebook construct!).

Your custom WidgetAdapter implementation has to register itself with WidgetAdapterManager (which can be requested with ILSPAdapterManager token), calling registerAdapterType(options: IAdapterTypeOptions) method. Among the options, in addition to the custom WidgetAdapter, you need to provide a tracker (IWidgetTracker) which will notify the extension via a signal when a new instance of your document widget is getting created.

How to add a custom magic or foreign extractor?

It is now possible to register custom code replacements using ILSPCodeOverridesManager token and to register custom foreign code extractors using ILSPCodeExtractorsManager token, however this API is considered provisional and subject to change.

Future plans for transclusions handling

We will strive to make it possible for kernels to register their custom syntax/code transformations easily, but the frontend API will remain available for the end-users who write their custom syntax modifications with actionable side-effects (e.g. a custom IPython magic which copies a variable from the host document to the embedded document).

How to add custom icons for the completer?

  1. Prepare the icons in the SVG format (we use 16 x 16 pixels, but you should be fine with up to 24 x 24). You can load them for webpack in typescript using imports if you include a typings.d.ts file with the following content:

typescript    declare module '*.svg' {      const script: string;      export default script;    }

in your src/. You should probably keep the icons in your style/ directory.

  1. Prepare CompletionKindIconSvgString mapping for the light (and optionally dark) theme, implementing the ICompletionIconSet interface. We have an additional Kernel completion kind that is used for completions provided by kernel that had no recognizable type provided.

  2. Provide all other metadata required by the ICompletionTheme interface and register it on ILSPCompletionThemeManager instance using register_theme() method.

  3. Provide any additional CSS styling targeting the JupyterLab completer elements inside of .lsp-completer-theme-{id}, e.g. .lsp-completer-theme-material .jp-Completer-icon svg for the material theme. Remember to include the styles by importing the in one of the source files.

For an example of a complete theme see theme-vscode.

Extend jupyter-lsp

Language Server Specs

Language Server Specs can be configured by Jupyter users, or distributed by third parties as python or JSON files. Since we’d like to see as many Language Servers work out of the box as possible, consider contributing a spec, if it works well for you!

Message Listeners

Message listeners may choose to receive LSP messages immediately after being received from the client (e.g. jupyterlab-lsp) or a language server. All listeners of a message are scheduled concurrently, and the message is passed along once all listeners return (or fail). This allows listeners to, for example, modify files on disk before the language server reads them.

If a listener is going to perform an expensive activity that shouldn’t block delivery of a message, a non-blocking technique like IOLoop.add_callback and/or a queue should be used.

Add a Listener with entry_points

Listeners can be added via entry_points by a package installed in the same environment as notebook:

## setup.cfg

[options.entry_points]
jupyter_lsp_listener_all_v1 =
  some-unique-name = some.module:some_function
jupyter_lsp_listener_client_v1 =
  some-other-unique-name = some.module:some_other_function
jupyter_lsp_listener_server_v1 =
  yet-another-unique-name = some.module:yet_another_function

At present, the entry point names generally have no impact on functionality aside from logging in the event of an error on import.

Add a Listener with Jupyter Configuration

Listeners can be added via traitlets configuration, e.g.

## jupyter_server_config.jsons
{
  'LanguageServerManager':
    {
      'all_listeners': ['some.module.some_function'],
      'client_listeners': ['some.module.some_other_function'],
      'server_listeners': ['some.module.yet_another_function'],
    },
}
Add a listener with the Python API

lsp_message_listener can be used as a decorator, accessed as part of a serverextension.

This listener receives all messages from the client and server, and prints them out.

from jupyter_lsp import lsp_message_listener

def load_jupyter_server_extension(nbapp):

    @lsp_message_listener("all")
    async def my_listener(scope, message, language_server, manager):
        print("received a {} {} message from {}".format(
          scope, message["method"], language_server
        ))

scope is one of client, server or all, and is required.

Listener options

Fine-grained controls are available as part of the Python API. Pass these as named arguments to lsp_message_listener.

  • language_server: a regular expression of language servers

  • method: a regular expression of LSP JSON-RPC method names

Releasing

jupyterlab-lsp and jupyter-lsp releases may require building both the python package and nodejs packages.

Updating Version Strings

Use the bump_versions script to manage the version strings:

python scripts/bump_versions.py

Check the version strings across the various files:

python scripts/integrity.py
  • TODO: create a release.py script #88

The PyPI version (jupyter-lsp) must be updated in the following places:

  • python_packages/jupyter_lsp/jupyter_lsp/_version.py (canonical)

  • .github/workflows/job.test.yml

  • CHANGELOG.md

The npm version of jupyterlab-lsp must be updated in the following places:

  • packages/jupyterlab-lsp/package.json > version (canonical)

  • .github/workflows/job.test.yml

  • packages/metapackage/package.json

  • CHANGELOG.md

The npm version of lsp-ws-connection must be updated in the following places:

  • packages/lsp-ws-connection/package.json > version (canonical)

  • packages/jupyterlab-lsp/package.json

  • CHANGELOG.md

The JupyterLab version (if a newer is supported or required) needs to be updated in:

  • packages/jupyterlab-lsp/package.json > devDependencies > @jupyterlab/application (canonical)

  • binder/environment.yml

  • requirements/lab.txt

  • .github/workflows/job.test.yml

  • README.md

Releasing:

cd packages/lsp-ws-connection
npm publish --access public
cd -
cd packages/completion-theme
npm publish --access public
cd -
cd packages/theme-material
npm publish --access public
cd -
cd packages/theme-vscode
npm publish --access public
cd -
cd packages/jupyterlab-lsp
npm publish --access public
cd -
./scripts/publish_pypi.sh

CHANGELOG

[1]:

@krassowski/jupyterlab-lsp 3.4.0 (2020-02-14)

  • features:

  • the priority of the completions from kernel can now be changed by switching new kernelCompletionsFirst setting (#520)

  • completer panel will now always render markdown documentation if available (#520)

    • the implementation re-renders the panel as it is the best we can do until jupyterlab#9663 is merged

  • the completer now uses filterText and sortText if available to better filter and sort completions (#520, #523)

  • completer suppressInvokeIn setting was removed; suppressContinuousHintingIn and suppressTriggerCharacterIn settings were added (#521)

  • suppressContinuousHintingIn by default includes def to improve the experience when writing function names (#521)

  • long file paths are now collapsed if composed of more than two segments to avoid status popover and diagnostics panel getting too wide (#524)

  • bug fixes:

  • user-invoked completion in strings works again (#521)

  • completer documentation will now consistently show up after filtering the completion items (#520)

  • completions containing HTML-like syntax will be displayed properly (an upstream issue) (#520, #523)

  • diagnostics panel will no longer break when foreign documents (e.g. %%R cell magics) are removed (#522)

@krassowski/jupyterlab-lsp 3.3.1 (2020-02-07)

  • bug fixes:

  • completion and signature suggestions get invalidated when editor changes (#507)

  • signature suggestions now invalidate on cursor move to another line or backwards too (#507)

  • LaTeX is now rendered in documentation panel of completer (#506)

  • completion response returned as plain text use pre tag to retain whitespace formatting (#506)

  • pre-formatted code font size was reduced to match font-size of the text in completion panel (#506)

  • completer no longer spans the entire screen if it has long entries (#506)

jupyter-lsp 1.1.3 (2020-02-07)

  • features:

  • add config for the classic notebook server extension (#504)

@krassowski/jupyterlab-lsp 3.3.0 (2021-01-31)

  • features:

  • added a timeout for kernel completion, with the default of 600ms (#496)

  • added an option to skip waiting for kernel completions if busy, off by default (#496)

  • bug fixes:

  • delayed completion suggestions will no longer show up if cursor moved to another line (#496)

  • changes in notebooks after kernel restart or file rename will now be recorded by the language server again (#496)

  • when either of kernel providers: kernel or LSP server fails, the completion from the other will still be shown (#496)

jupyter-lsp 1.1.2 (2021-01-31)

  • bug fixes:

  • fixed issues with language server messages being truncated in certain circumstances on Windows

@krassowski/jupyterlab-lsp 3.2.0 (2021-01-24)

  • features:

  • documentation panel in completer now works for R language too: implemented completionItem/resolve (#487)

  • kernel types returned by IPython and IJulia are now mapped to LSP types; you can customize the mappings in settings (#487)

  • bug fixes:

  • diagnostics panel works after kernel restart properly (#485)

  • workaround was added to enable jedi-language-server diagnostics (#485)

  • Julia language server will not crash when saving a non-Julia file: fixed sendSaved notification scope (#491)

jupyter-lsp 1.1.1 (2021-01-24)

  • bug fixes:

  • PythonModuleSpec no longer raises exception when the server module does not exist (#485)

@krassowski/jupyterlab-lsp 3.1.0 (2021-01-17)

  • features

  • make the extension work with jupyterlab-classic - experimental, not all features are functional yet (#465)

  • new status “Server extension missing” and a dialog with advice was added to help users with atypical configurations (#476)

  • for developers: the verbosity of console logs is now controllable from settings and set to warn by default (#480)

  • bug fixes:

  • namespace completions in R (after double and triple colon) now work properly (#449)

  • improved status icon contrast when status item is active (#465)

  • connection manager now properly keeps track of notebooks when multiple notebooks are open (#474)

  • new cells added after kernel restart now work properly; kernel changes are handled correctly (#478)

  • increase total timeout for language server connection (#479)

  • fix status communication during initialization (#479)

jupyter-lsp 1.1.0 (2021-01-17)

  • features

  • added experimental detection of Julia and Jedi language servers (#481)

  • 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)

  • fix completion of very large namespaces (e.g. in R’s base or in JavaScript) due to truncated message relay (#477)

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

  • features

  • support for JupyterLab 3.0 (#452, #402)

jupyter-lsp 1.0.0 (2021-01-06)

  • features

  • support for JupyterLab 3.0 (#452, #402)

@krassowski/jupyterlab-lsp 2.1.2 (2021-01-02)

  • features

  • highlights can now be auto-removed from the cells/editors on blur (set removeOnBlur to true in settings) (#446)

  • bug fixes

  • improved performance of completion and highlights by minimising the number of highlight requests and GUI redraws (token checking, debouncing, acting on a single response only) (#433)

  • highlights now update after cell focus/blur events even if those do not trigger cursor movement (#433)

  • trigger characters auto-invoke now works in continuous hinting mode again (#434)

@krassowski/jupyterlab-lsp 2.1.1 (2020-12-15)

  • bug fixes

  • fix crash “blank screen” caused by Mac command character included in jump-to schema file (#429)

jupyter-lsp 0.9.3 (2020-12-13)

  • features

  • the virtual documents’ folder can be configured with JP_LSP_VIRTUAL_DIR or LanguageServerManager.virtual_documents_dir, with a potential benefit for JupyterHub installations (the default value remains contents.root_dir / .virtual_documents) (#416, thanks @fcollonval)

@krassowski/jupyterlab-lsp 2.1.0 (2020-12-13)

  • features

  • added “click to jump” functionality (by default using Alt modifier) (#423)

  • added “jump back” command, by default activated with Alt + o (#423)

  • .virtual_documents location can now be customized (#416)

  • tokens are now exported making them available for import from other extensions (#414, thanks @martinRenou)

  • bug fixes

  • context menu commands are now correctly registered where previously specific conditions were leading to race conditions (#399, thanks @mnowacki-b)

  • handles characters that need escaping (spaces, non-ASCII characters) more robustly in files and folder names (#403, thanks @bollwyvl and @avaissi)

  • moving cells now triggers the document update immediately leading to immediate diagnostics update (#421)

  • changing cell type to raw or markdown and then back to code properly unbinds/binds event handlers and updates document (#421)

  • pasted cells are added to the LSP document immediately, without the need for the user to enter them (#421)

  • improved error message when language server cannot be found (#413, thanks @martinRenou)

  • developer documentation got improved (#412, thanks @karlaspuldaro)

@krassowski/code-jumpers 1.0.0 (2020-12-13)

  • breaking changes

  • split away from @krassowski/jupyterlab_go_to_definition, renamed to @krassowski/code-jumpers (#423):

    • removed unused code

    • refactored history operations to track files and always use global location data

  • renamed uri to contents_path to help avoid programmer issues with characters requiring URI encoding (#406)

@krassowski/jupyterlab-lsp 2.0.8 (2020-10-25)

  • bug fixes

  • custom cell syntax highlighting is now properly removed when no longer needed (#387)

  • the completer in continuous hinting now works well with the pasted text (#389)

  • continuous hinting suggestions will no longer show up if the only hint is the same as the current token (#391)

  • available options for hover modifier keys are now listed in the setting descriptions (#377)

@krassowski/jupyterlab-lsp 2.0.7 (2020-09-18)

  • bug fixes

  • fix syntax highlighting in hover tooltips and reduce unnecessary padding and margin (#363)

  • greatly improve performance of hover action (#363)

  • improve support for expanded hovers tooltips using deprecated API (#363)

  • do not hide hover tooltips too eagerly (allowing selecting text/easy scrolling of longer tooltips) (#363)

@krassowski/jupyterlab-lsp 2.0.6 (2020-09-15)

  • bug fixes

  • fix syntax highlighting of %%language cells slowing down editing in notebooks (#361)

@krassowski/jupyterlab-lsp 2.0.5 (2020-09-11)

  • bug fixes

  • fix too aggressive overrides of IPython’s pinfo (?) and pinfo2 (??) (#352)

@krassowski/jupyterlab-lsp 2.0.4 (2020-09-11)

  • bug fixes

  • improve code overrides for IPython line magics (#346)

  • implement missing code overrides for IPython’s pinfo (?) and pinfo2 (??) syntactic sugar (#346)

@krassowski/jupyterlab-lsp 2.0.2 (2020-09-07)

  • bug fixes

  • fix code overrides not being registered properly (#340)

@krassowski/jupyterlab-lsp 2.0.1 (2020-09-07)

  • bug fixes

  • bump version of lsp-ws-connection dependency to fix the LaTeX server issues (see #337)

jupyter-lsp 0.9.2 (2020-09-03)

  • autodetects the sql language server for .sql files (#328)

  • diagnostics are provided by sqlint which requires Node 11+ to work well (in contrast to currently required Node 10+).

@krassowski/jupyterlab-lsp 2.0.0 (2020-09-03)

  • features

  • support for JupyterLab 2.2 (#301)

  • completer now displays server-provided documentation, and a kernel icon for kernel suggestions without type information (#301)

  • add two icons themes for the completer (material and vscode) (#322)

  • the documentation by the completer can be turned on or off (#315)

  • continuous hinting (Hinterland mode) can be enabled in settings (#315)

  • tokens in which the completer should not be triggered can be changed (#315)

  • configuration for the following features is now exposed in the settings editor (#318):

    • diagnostics (display, filtering)

    • hover (modifier key)

  • rename operation status reporting got improved (#318)

  • replaced the generic status icons with code check icon (coloured differently according to the status) (#318)

  • added icons for all the features and their commands (#318)

  • refactored the codebase with a new architecture which allows dynamic features, document widget adapter, and code editor registration (#318)

  • the document in the connections list in the statusbar popover are now represented by easy-to-understand DocumentLocator (breadcrumbs) widget rather than an internal id (bacc006)

  • syntax highlighting mode is adjusted to the language with the majority of the code in an editor (#319)

  • copy diagnostics message and filter diagnostics from context menu of Diagnostic Panel (#330)

  • bug fixes

  • path-autocompletion issues were resolved upstream and this release adopts these changes

  • the missing caret and document connection icons were restored in the statusbar popover (#318)

  • pressing “Cancel” rename during rename now correctly aborts the rename operation (#318)

  • when a language server for a foreign document is not available an explanation is displayed (rather than the “Connecting…” status as before) (4e5b2ad)

  • when jump target is not found a message is now shown instead of raising an exception (00448d0)

  • fixed status message expiration and replacement (8798f2d), (#329)

  • fixed some context command rank issues introduced after an attempt of migration to nulls (#318)

@krassowski/jupyterlab-lsp 1.1.2 (2020-08-05)

  • bug fixes

  • emits console warnings instead of throwing errors in hover handlers and connections (#299, #300)

  • improve URL checks in message handling to enable LaTeX diagnostics to work when configured (#288)

jupyter-lsp 0.9.1 (2020-08-05)

  • autodetects the texlab language server for .tex files (#288)

  • diagnostics should be provided by chktex on save, but don’t yet appear, but can be configured through the Advanced Settings Editor to appear on save or change

@krassowski/jupyterlab-lsp 1.1.0 (2020-07-20)

  • features

  • language servers can now be configured from the Advanced Settings Editor (#245)

  • bug fixes

  • fixes currently-highlighted token in dark editor themes against light lab theme (and vice versa) (#195)

  • restores sorting order-indicating caret icons in diagnostics panel table (#261)

  • handles document open and change operation ordering more predictably (#284)

  • fixes some pyflakes issues caused by line magics substitution (#293)

  • updated the link to the documentation of language servers (#294)

jupyter-lsp 0.9.0 (2020-07-20)

  • features

  • language servers can now be configured from the Advanced Settings Editor (#245)

  • bug fixes

  • handles document open and change operation ordering more predictably (#284)

lsp-ws-connection 0.5.0 (2020-07-20)

  • features

  • language servers can now be configured from the Advanced Settings Editor (#245)

  • bug fixes

  • handles document open and change operation ordering more predictably (#284)

@krassowski/jupyterlab-lsp 1.0.0 (2020-03-14)

  • features

  • supports JupyterLab 2.0

@krassowski/jupyterlab_go_to_definition 1.0.0 (2020-03-14)

  • features

  • supports JupyterLab 2.0

@krassowski/jupyterlab-lsp 0.8.0 (2020-03-12)

  • features

  • opens a maximum of one WebSocket per language server (#165, #199)

  • lazy-loads language server protocol machinery (#165)

  • waits much longer for slow-starting language servers (#165)

  • cleans up documents, handlers, events, and signals more aggressively (#165)

  • ignores malformed diagnostic ranges, enabling markdown support (#165)

  • passes tests on Python 3.8 on Windows (#165)

  • improves support for rpy2 magic cells with parameters ( #206 )

  • bug fixes

  • reports files are open only after installing all handlers to avoid missing messages (#201)

lsp-ws-connection 0.4.0 (2020-03-12)

  • breaking changes

  • no longer assumes one document per connection (#165)

  • requires documents be opened explicitly (#165)

  • use of the eventEmitter pattern mostly deprecated in favor of Promises (#165)

jupyter-lsp 0.8.0 (2020-03-12)

  • breaking changes

  • websockets are now serviced by implementation key, rather than language under lsp/ws/<server key> (#199)

  • introduces schema version 2, reporting status by server at lsp/status (#199)

  • bugfixes:

  • handles language server reading/writing and shadow file operations in threads (#199)

jupyter-lsp 0.7.0

  • bugfixes

  • didSave no longer causes unwanted messages in logs ( #187 )

@krassowski/jupyterlab-lsp 0.7.1

  • features

  • users can now choose which columns to display in the diagnostic panel, using a context menu action ( #159 )

  • start the diagnostics panel docked at the bottom and improve the re-spawning of the diagnostics panel ( #166 )

  • bugfixes

  • fixed various small bugs in the completer ( #162 )

  • fix documentation display in signature for LSP servers which return MarkupContent ( #164 )

lsp-ws-connection 0.3.1

  • added sendSaved() method (textDocument/didSave) ( #147 )

  • fixed getSignatureHelp() off-by-one error ( #140 )

@krassowski/jupyterlab-lsp 0.7.0

  • features

  • reduced space taken up by the statusbar indicator ( #106 )

  • implemented statusbar popover with connections statuses ( #106 )

  • generates types for server data responses from JSON schema ( #110 )

  • added ‘rename’ function for notebooks, using shadow filesystem ( #115 )

  • added a UX workaround for rope rename issues when there is a SyntaxError in the Python code ( #127 )

  • added a widget panel with diagnostics (inspections), allowing to sort and explore diagnostics, and to go to the respective location in code (with a click); accessible from the context menu ( #129 )

  • all commands are now accessible from the command palette ( #142 )

  • bash LSP now also covers %%bash magic cell in addition to %%sh ( #144 )

  • rpy2 magics received enhanced support for argument parsing in both parent Python document (re-written overrides) and exctracted R documents (improved foreign code extractor) ( #148, #153 )

  • console logs can now easily be redirected to a floating console windows for debugging of the browser tests (see CONTRIBUTING.md)

  • bugfixes

  • diagnostics in foreign documents are now correctly updated ( 133fd3d )

  • diagnostics are now always correctly displayed in the document they were intended for

  • the workaround for relative root path is now also applied on Mac ( #139 )

  • fixed LSP of R in Python (%%R magic cell from rpy2) ( #144 )

  • completion now work properly when the kernel is shut down ( #146 )

  • a lowercase completion option selected from an uppercase token will now correctly substitute the incomplete token ( #143 )

  • didSave() is emitted on file save, enabling the workaround used by R language server to lazily load library(tidyverse) ( #95, #147, )

  • signature feature is now correctly working in notebooks ( #140 )

lsp-ws-connection 0.3.0

  • infrastructure

  • brought into monorepo #107

  • dev

  • allows initializeParams to be overloaded by subclasses

  • adopts

    • typescript 3.7

    • prettier

    • tslint

  • added initialization checks before executing sendChange to comply with LSP specs #115

jupyter-lsp 0.7.0b0

  • features

  • adds a language server status endpoint ( #81 )

  • adds more descriptive information to the language server spec ( #90 )

  • adds an extensible listener API ( #99, #100 )

@krassowski/jupyterlab-lsp 0.6.1

  • features

  • adds an indicator to the statusbar

  • and many other improvements, see the release notes

  • dependencies

  • removes unused npm dependencies

@krassowski/jupyterlab-lsp 0.6.0

  • features

  • allows “rename” action in file editor

  • bugfixes

  • handles some non-standard diagnostic responses

  • testing

  • adds browser-based testing for file editor

  • dependencies

  • requires jupyter-lsp

jupyter-lsp 0.6.0b0

  • features

  • starts language servers on demand

  • accepts configuration via Jupyter config system (traitlets) and python entry_points

  • autodetects language servers for bash, CSS, LESS, SASS, Dockerfile, YAML, JS, TypeScript, JSX, TSX, JSON, YAML

Roadmap

If a feature you would like is not on the lists above, please feel free to suggest it by opening a new issue.

Front End

  • improved code navigation when there are multiple jump targets

  • autocompleter sorting based on LSP suggestions

  • system of settings, including options:

  • to change the verbosity of signature documentation hints (number of lines to be shown)

  • custom foreign extractors allowing to customize behaviour for magics

  • code actions (allowing to “quick fix” a typo, etc.)

  • gutter with linter results

  • use the kernel session for autocompletion in FileEditor if available (PR welcome)

Backend

  • release on conda

  • #49 cookiecutter for pip-installable specs

  • add hook system to allow serverextensions/kernels to modify, inspect and react to LSP messages

Architecture

As-Is

These are how we think everything works in the current master branch.

Front End

[2]:
_images/Architecture_4_0.svg
Positioning system

positioning system

Back End

[3]:
_images/Architecture_7_0.svg

Proposals

Some fragments of how the architecture could change in the future, and why (or why not) they might be a good idea.

Reorganize client source with lerna and typescript projects #76

TBD

Add DiagnosticsManager, refactor DiagnosticPanel #176

TBD

Multiple sources of LSP messages on frontend and backend #184

TBD

Use mime types from server spec for language detection #190

TBD

Formalize and extend language transclusion #191

TBD