Skip to content

Commit d86925f

Browse files
authored
Merge pull request #539 from krassowski/documentation-language-servers
Improve language servers configuration documentation
2 parents 4e6d164 + db8c8eb commit d86925f

File tree

7 files changed

+110
-63
lines changed

7 files changed

+110
-63
lines changed

CONTRIBUTING.md

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -322,44 +322,57 @@ python scripts/lint.py
322322

323323
### Specs
324324

325-
It is convenient to collect common patterns for connecting to installed language
326-
servers as `pip`-installable packages that Just Work with `jupyter-lsp`.
325+
While language servers can be configured by the user using a simple JSON or Python [configuration file](./Configuring.ipynb#language_servers),
326+
it is preferable to provide users with an option that does not require manual configuration. The language server specifications (specs)
327+
wrap the configuration (as would be defined by the user) into a Python class or function that can be either:
327328

328-
If an advanced user installs, locates, and configures, their own language
329-
server it will always win vs an auto-configured one.
329+
- distributed using PyPI/conda-forge and made conveniently available to users for `pip install` and/or `conda install`
330+
- contributed to the collection of built-in specs of jupyter-lsp by opening a PR (preferable for popular language servers, say >100 users)
330331

331-
#### Writing a spec
332+
In either case the detection of available specifications uses Python `entry_points` (see the `[options.entry_points]` section in jupyter-lsp [setup.cfg]).
332333

333-
> See the built-in [specs][] for implementations and some [helpers][].
334+
> If an advanced user installs, locates, and configures, their own language server it will always win vs an auto-configured one.
334335
335-
[specs]: https://github.com/krassowski/jupyterlab-lsp/tree/master/python_packages/jupyter_lsp/jupyter_lsp/specs
336-
[helpers]: https://github.com/krassowski/jupyterlab-lsp/blob/master/python_packages/jupyter_lsp/jupyter_lsp/specs/utils.py
336+
#### Writing a spec
337337

338-
A spec is a python function that accepts a single argument, the
339-
`LanguageServerManager`, and returns a dictionary of the form:
338+
A spec is a Python callable (a function, or a class with `__call__` method) that accepts a single argument, the
339+
`LanguageServerManager` instance, and returns a dictionary of the form:
340340

341341
```python
342342
{
343343
"python-language-server": { # the name of the implementation
344-
"version": 1, # the version of the spec schema
344+
"version": SPEC_VERSION, # the version of the spec schema (an integer)
345345
"argv": ["python", "-m", "pyls"], # a list of command line arguments
346-
"languages": ["python"] # a list of languages it supports
346+
"languages": ["python"], # a list of languages it supports
347+
"mime_types": ["text/python", "text/x-ipython"]
347348
}
348349
}
349350
```
350351

351-
The absolute minimum listing requires `argv` (a list of shell tokens to launch
352-
the server) and `languages` (which languages to respond to), but many number of
353-
other options to enrich the user experience are available in the
354-
[schema][] and are exercised by the current `entry_points`-based [specs][].
352+
The above example is only intended as an illustration and not as an up-to-date guide.
353+
For details on the dictionary contents, see the [schema][] definition and [built-in specs][].
354+
Basic concepts (meaning of the `argv` and `languages` arguments) are also explained in the [configuration files](./Configuring.ipynb#language_servers) documentation.
355355

356-
[schema]: https://github.com/krassowski/jupyterlab-lsp/blob/master/python_packages/jupyter_lsp/jupyter_lsp/schema/schema.json
356+
When contributing a specification we recommend to make use of the helper classes and other [utilities][] that take care of the common use-cases:
357+
358+
- `ShellSpec` helps to create specs for servers that can be started from command-line
359+
- `PythonModuleSpec` is useful for servers which are Python modules
360+
- `NodeModuleSpec` will take care of finding Node.js modules
361+
362+
See the built-in [built-in specs][] for example implementations.
357363

358364
The spec should only be advertised if the command _could actually_ be run:
359365

360-
- its runtime (e.g. `julia`, `nodejs`, `python`, `r`, `ruby`) is installed
366+
- its runtime/interpreter (e.g. `julia`, `nodejs`, `python`, `r`, `ruby`) is installed
361367
- the language server itself is installed (e.g. `python-language-server`)
362368

369+
otherwise an empty dictionary (`{}`) should be returned.
370+
371+
[built-in specs]: https://github.com/krassowski/jupyterlab-lsp/tree/master/python_packages/jupyter_lsp/jupyter_lsp/specs
372+
[setup.cfg]: https://github.com/krassowski/jupyterlab-lsp/blob/master/python_packages/jupyter_lsp/setup.cfg
373+
[schema]: https://github.com/krassowski/jupyterlab-lsp/blob/master/python_packages/jupyter_lsp/jupyter_lsp/schema/schema.json
374+
[utilities]: https://github.com/krassowski/jupyterlab-lsp/blob/master/python_packages/jupyter_lsp/jupyter_lsp/specs/utils.py
375+
363376
##### Common Concerns
364377

365378
- some language servers need to have their connection mode specified
@@ -369,10 +382,11 @@ The spec should only be advertised if the command _could actually_ be run:
369382
- `LanguageServerManager.nodejs` will provide the location of our best
370383
guess at where a user's `nodejs` might be found
371384
- some language servers are hard to start purely from the command line
372-
- use a helper script to encapsulate some complexity.
373-
- See the [r spec][] for an example
385+
- use a helper script to encapsulate some complexity, or
386+
- use a command argument of the interpreter is available (see the [r spec][] and [julia spec] for examples)
374387

375388
[r spec]: https://github.com/krassowski/jupyterlab-lsp/blob/master/python_packages/jupyter_lsp/jupyter_lsp/specs/r_languageserver.py
389+
[julia spec]: https://github.com/krassowski/jupyterlab-lsp/blob/master/python_packages/jupyter_lsp/jupyter_lsp/specs/julia_language_server.py
376390

377391
##### Example: making a pip-installable `cool-language-server` spec
378392

@@ -402,7 +416,8 @@ def cool(app):
402416
"cool-language-server": {
403417
"version": 1,
404418
"argv": [cool_language_server],
405-
"languages": ["cool"]
419+
"languages": ["cool"],
420+
"mime_types": ["text/cool", "text/x-cool"]
406421
}
407422
}
408423
```

docs/CHANGELOG.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"## CHANGELOG"
7+
"## Changelog"
88
]
99
},
1010
{

docs/Configuring.ipynb

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"```\n",
2626
"\n",
2727
"They will be merged from bottom to top, and the directory where you launch your\n",
28-
"`notebook` server wins, making it easy to check in to version control."
28+
"`notebook` or `lab` server wins, making it easy to check in to version control."
2929
]
3030
},
3131
{
@@ -66,8 +66,15 @@
6666
" `mkdir \"new directory\"` should be split into `[\"mkdir\", \"new directory\"]`;\n",
6767
" If you have Python installed, you can use `shlex.split(\"your command\")` to\n",
6868
" get such an array.\n",
69-
"- the `languages` which the server will respond to, and\n",
69+
"- the `languages` which the language server will respond to, and\n",
7070
"- the schema `version` of the spec (currently `2`)\n",
71+
"- `mime_types` by which the notebooks and files will be matched to the langauge\n",
72+
" server:\n",
73+
" - for notebooks the MIME type is derived from `language_info`/`mimetype`\n",
74+
" element of [kernel_info][] response (with fallback on to cell metadata if\n",
75+
" missing from kernel response)\n",
76+
" - for files the implemntation is frontend-specific; in JupyterLab the MIME\n",
77+
" type is obtained from the MIME type registry.\n",
7178
"\n",
7279
"```python\n",
7380
"# ./jupyter_server_config.json ---------- unique! -----------\n",
@@ -80,12 +87,16 @@
8087
" \"a-language-server-implementation\": {\n",
8188
" \"version\": 2,\n",
8289
" \"argv\": [\"/absolute/path/to/a-language-server\", \"--stdio\"],\n",
83-
" \"languages\": [\"a-language\"]\n",
90+
" \"languages\": [\"a-language\"],\n",
91+
" \"mime_types\": [\"text/language\", \"text/x-language\"]\n",
8492
" }\n",
8593
" }\n",
8694
" }\n",
8795
"}\n",
88-
"```"
96+
"```\n",
97+
"\n",
98+
"[kernel_info]:\n",
99+
" https://jupyter-client.readthedocs.io/en/stable/messaging.html#kernel-info"
89100
]
90101
},
91102
{
@@ -111,13 +122,15 @@
111122
" # if installed as a binary\n",
112123
" \"argv\": [shutil.which(\"a-language-server\")],\n",
113124
" \"languages\": [\"a-language\"],\n",
114-
" \"version\": 2\n",
125+
" \"version\": 2,\n",
126+
" \"mime_types\": [\"text/a-language\"]\n",
115127
" },\n",
116128
" \"another-language-implementation\": {\n",
117129
" # if run like a script\n",
118130
" \"argv\": [shutil.which(\"another-language-interpreter\"), \"another-language-server\"],\n",
119131
" \"languages\": [\"another-language\"],\n",
120-
" \"version\": 2\n",
132+
" \"version\": 2,\n",
133+
" \"mime_types\": [\"text/another-language\"]\n",
121134
" }\n",
122135
"}\n",
123136
"```"
@@ -144,7 +157,7 @@
144157
"> default: `True`\n",
145158
"\n",
146159
"If `True`, `jupyter-lsp` will look for all\n",
147-
"[known language servers](#installing-language-servers). User-configured\n",
160+
"[known language servers](./Language%20Servers.ipynb). User-configured\n",
148161
"`language_servers` of the same implementation will be preferred over\n",
149162
"`autodetect`ed ones."
150163
]
@@ -199,24 +212,21 @@
199212
"source": [
200213
"### Python `entry_points`\n",
201214
"\n",
202-
"`pip`-installable packages in the same environment as the Jupyter `notebook`\n",
203-
"server can be automatically detected as providing\n",
204-
"[language_servers](#language_servers). These are a little more involved, but\n",
205-
"also more powerful: see more in [Contributing](Contributing.ipynb#Specs).\n",
206-
"Servers configured this way are loaded _before_ those defined in\n",
207-
"[configuration files](#Configuration-Files), so that a user can fine-tune their\n",
208-
"available servers."
215+
"`pip`-installable packages in the same environment as the Jupyter server can be\n",
216+
"automatically detected as providing [language_servers](#language_servers). These\n",
217+
"are a little more involved, but also more powerful: see more in\n",
218+
"[Contributing](Contributing.ipynb#Specs). Servers configured this way are loaded\n",
219+
"_before_ those defined in [configuration files](#Configuration-Files), so that a\n",
220+
"user can fine-tune their available servers."
209221
]
210222
},
211223
{
212224
"cell_type": "markdown",
213-
"metadata": {
214-
"collapsed": false
215-
},
225+
"metadata": {},
216226
"source": [
217-
"### Example: Scala Language Server (metals) integration with jupyterlab-lsp\n",
227+
"### Example: Scala Language Server (metals) integration\n",
218228
"\n",
219-
"Step 1: Get a Scala-based kernel installed.\n",
229+
"**Step 1:** Get a Scala-based kernel installed.\n",
220230
"\n",
221231
"2 possible options: Almond kernel or the Spark magic kernel.\n",
222232
"\n",
@@ -241,7 +251,7 @@
241251
"jupyter-kernelspec install sparkmagic/kernels/sparkkernel\n",
242252
"```\n",
243253
"\n",
244-
"Step 2: Install metals server in the working directory:\n",
254+
"**Step 2:** Install metals server in the working directory:\n",
245255
"\n",
246256
"Metals has a coursier based installation.\n",
247257
"\n",
@@ -250,10 +260,12 @@
250260
"./coursier bootstrap org.scalameta:metals_2.12:0.7.0 --force-fetch -o metals -f\n",
251261
"```\n",
252262
"\n",
253-
"(Might need to use the --force-fetch flag if you are getting dependency issues.)\n",
263+
"(Might need to use the `--force-fetch` flag if you are getting dependency\n",
264+
"issues.)\n",
254265
"\n",
255-
"Step 3: Configure the metals server in jupyterlab-lsp. Enter the following in\n",
256-
"the jupyter_server_config.json:\n",
266+
"**Step 3:** Configure the metals server in jupyterlab-lsp. Enter the following\n",
267+
"in the `jupyter/jupyter_server_config.d/metals-ls.json` (in one of the jupyter\n",
268+
"configuration directories):\n",
257269
"\n",
258270
"```python\n",
259271
"{\n",
@@ -271,8 +283,8 @@
271283
"```\n",
272284
"\n",
273285
"You are good to go now! Just start `jupyter lab` and create a notebook with\n",
274-
"either the Spark or the Scala kernel and you should be able to see the metals\n",
275-
"server initialised from the bottom left corner."
286+
"either the Spark or the Scala kernel and the metals server should automatically\n",
287+
"initialise (the status indicator should show \"Fully initialized\")."
276288
]
277289
}
278290
],

docs/Language Servers.ipynb

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,28 @@
66
"source": [
77
"## Language Servers\n",
88
"\n",
9-
"`jupyter-lsp` does not come with any Language Servers! However, we will try to\n",
10-
"use them if they _are_ installed and we know about them. For the language\n",
11-
"servers in the tables below, use one of the suggested package managers to\n",
12-
"install them: these implementations are tested to work with `jupyter-lsp`.\n",
9+
"By default `jupyter-lsp` does not come with any language servers preinstalled.\n",
10+
"However, we will try to use them if they _are_ installed and we know about them\n",
11+
"(i.e. someone contributed a full specification).\n",
1312
"\n",
14-
"- _You can disable this feature by configuring_\n",
15-
" [autodetect](./Configuring.ipynb#autodetect)\n",
13+
"> You can disable auto-detection by configuring\n",
14+
"> [autodetect](./Configuring.ipynb#autodetect)\n",
1615
"\n",
17-
"If you do not see a language you would like, but can find it one of these lists:\n",
16+
"You can add another language server for languages that are not listed on this\n",
17+
"page:\n",
1818
"\n",
19-
"- the [official list][lsp-implementations] of language servers\n",
20-
"- a [community-curated list][langserver] of language servers\n",
19+
"- using a minimal JSON or Python\n",
20+
" [configuration file](./Configuring.ipynb#language_servers) (good for\n",
21+
" experimenting or configuring a niche server), or\n",
22+
"- contributing a [full specification](./Contributing.ipynb#Specs) (to enable\n",
23+
" better integration and help other users of the same language)\n",
2124
"\n",
22-
"...you might be able to add it\n",
23-
"[via configuration](./Configuring.ipynb#language_servers) or\n",
24-
"[build your own spec](./Contributing.ipynb#Specs) for the server in question.\n",
25+
"The existing language servers are listed on the [official\n",
26+
"list][lsp-implementations] and on the [community-curated list][langserver].\n",
27+
"\n",
28+
"For the language servers in the tables below, use one of the suggested package\n",
29+
"managers to install them: these implementations are tested to work with\n",
30+
"`jupyter-lsp`.\n",
2531
"\n",
2632
"[language-server]:\n",
2733
" https://microsoft.github.io/language-server-protocol/specification\n",
@@ -141,6 +147,15 @@
141147
")"
142148
]
143149
},
150+
{
151+
"cell_type": "markdown",
152+
"metadata": {},
153+
"source": [
154+
"The Scala language server (`metals`) is not currently auto-detected, but can be\n",
155+
"configured as demonstrated in the\n",
156+
"[configuration example](<./Configuring.ipynb#Example:-Scala-Language-Server-(metals)-integration>)."
157+
]
158+
},
144159
{
145160
"cell_type": "markdown",
146161
"metadata": {},

docs/Roadmap.ipynb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
"### Front End\n",
1919
"\n",
2020
"- improved code navigation when there are multiple jump targets\n",
21-
"- autocompleter sorting based on LSP suggestions\n",
2221
"- system of settings, including options:\n",
2322
" - to change the verbosity of signature documentation hints (number of lines to\n",
2423
" be shown)\n",
2524
" - custom foreign extractors allowing to customize behaviour for magics\n",
2625
"- code actions (allowing to \"quick fix\" a typo, etc.)\n",
26+
"- code formatting\n",
2727
"- gutter with linter results\n",
2828
"- use the kernel session for autocompletion in FileEditor if available (PR\n",
2929
" welcome)"
@@ -35,7 +35,6 @@
3535
"source": [
3636
"### Backend\n",
3737
"\n",
38-
"- release on `conda`\n",
3938
"- [#49](https://github.com/krassowski/jupyterlab-lsp/issues/49) cookiecutter for\n",
4039
" pip-installable specs\n",
4140
"- add hook system to allow serverextensions/kernels to modify, inspect and react\n",

docs/_static/css/custom.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ body div.nboutput.container div[class*='highlight'] pre {
3232
}
3333

3434
.wy-nav-content {
35-
max-width: 100%;
35+
/* Increase the width of the content from the default 800px;
36+
not using 100% as it makes reading sentences unbearable on 4K screens */
37+
max-width: 1100px;
38+
}
39+
40+
.wy-nav-content p {
41+
max-width: 950px;
3642
}
3743

3844
.nbinput .prompt,

docs/index.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"source": [
5757
"## Project Information\n",
5858
"\n",
59-
"- [CHANGELOG](./CHANGELOG.ipynb)\n",
59+
"- [Changelog](./CHANGELOG.ipynb)\n",
6060
"- [Roadmap](./Roadmap.ipynb)\n",
6161
"- [Architecture](Architecture.ipynb)"
6262
]

0 commit comments

Comments
 (0)