{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Extend jupyterlab-lsp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> Note: the API is likely to change in the future; your suggestions are welcome!\n", "\n", "### 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/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/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", "\n", "Your feature specification should follow the `IFeature` interface, which can be\n", "divided into three major parts:\n", "\n", "- `editorIntegrationFactory`: constructors for the feature-CodeEditor\n", " integrators (implementing the `IFeatureEditorIntegration` interface), one for\n", " each supported CodeEditor (e.g. CodeMirror or Monaco); for CodeMirror\n", " integration you can base your feature integration on the abstract\n", " `CodeMirrorIntegration` class.\n", "- `labIntegration`: an optional object integrating feature with the JupyterLab\n", " interface\n", "- optional fields for easy integration of some of the common JupyterLab systems,\n", " such as:\n", " - settings system\n", " - commands system (including context menu)\n", "\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/extension/extension_points.html#core-tokens)).\n", "\n", "#### How to override the default implementation of a feature?\n", "\n", "You can specify a list of extensions to be disabled the the feature manager\n", "passing their plugin identifiers in `supersedes` field of `IFeatureOptions`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How to integrate a new code editor implementation?\n", "\n", "`CodeMirrorEditor` code editor is supported by default, but any JupyterLab\n", "editor implementing the `CodeEditor.IEditor` interface can be adapted for the\n", "use with the LSP extension. To add your custom code editor (e.g. Monaco) after\n", "implementing a `CodeEditor.IEditor` interface wrapper (which you would have\n", "anyways for the JupyterLab integration), you need to also implement a virtual\n", "editor (`IVirtualEditor` interface) for it.\n", "\n", "#### Why virtual editor?\n", "\n", "The virtual editor takes multiple instances of your editor (e.g. in a notebook)\n", "and makes them act like a single editor. For example, when \"onKeyPress\" event is\n", "bound on the VirtualEditor instance, it should be bound onto each actual code\n", "editor; this allows the features to be implemented without the knowledge about\n", "the number of editor instances on the page.\n", "\n", "#### How to register the implementation?\n", "\n", "A `virtualEditorManager` will be provided if you request\n", "`ILSPVirtualEditorManager` token; use\n", "`registerEditorType(options: IVirtualEditorType)` method passing a name\n", "that you will also use to identify the code editor, the editor class, and your\n", "VirtualEditor constructor." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How to integrate a new `DocumentWidget`?\n", "\n", "JupyterLab editor widgets (such as _Notebook_ or _File Editor_) implement\n", "`IDocumentWidget` interface. Each such widget has to adapted by a\n", "`WidgetAdapter` to enable its use with the LSP extension. The role of the\n", "`WidgetAdapter` is to extract the document metadata (language, mimetype) and the\n", "underlying code editor (e.g. CodeMirror or Monaco) instances so that other parts\n", "of the LSP extension can interface with them without knowing about the\n", "implementation details of the DocumentWidget (or even about the existence of a\n", "Notebook construct!).\n", "\n", "Your custom `WidgetAdapter` implementation has to register itself with\n", "`WidgetAdapterManager` (which can be requested with `ILSPAdapterManager` token),\n", "calling `registerAdapterType(options: IAdapterTypeOptions)` method. Among the\n", "options, in addition to the custom `WidgetAdapter`, you need to provide a\n", "tracker (`IWidgetTracker`) which will notify the extension via a signal when a\n", "new instance of your document widget is getting created." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How to add a custom magic or foreign extractor?\n", "\n", "It is now possible to register custom code replacements using\n", "`ILSPCodeOverridesManager` token and to register custom foreign code extractors\n", "using `ILSPCodeExtractorsManager` token, however this API is considered\n", "provisional and subject to change.\n", "\n", "#### Future plans for transclusions handling\n", "\n", "We will strive to make it possible for kernels to register their custom\n", "syntax/code transformations easily, but the frontend API will remain available\n", "for the end-users who write their custom syntax modifications with actionable\n", "side-effects (e.g. a custom IPython magic which copies a variable from the host\n", "document to the embedded document)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How to add custom icons for the completer?\n", "\n", "1. Prepare the icons in the SVG format (we use 16 x 16 pixels, but you should be\n", " fine with up to 24 x 24). You can load them for webpack in typescript using\n", " imports if you include a `typings.d.ts` file with the following content:\n", "\n", " ```typescript\n", " declare module '*.svg' {\n", " const script: string;\n", " export default script;\n", " }\n", " ```\n", "\n", " in your `src/`. You should probably keep the icons in your `style/`\n", " directory.\n", "\n", "2. Prepare `CompletionKind` → `IconSvgString` mapping for the light (and\n", " optionally dark) theme, implementing the `ICompletionIconSet` interface. We\n", " have an additional `Kernel` completion kind that is used for completions\n", " provided by kernel that had no recognizable type provided.\n", "\n", "3. Provide all other metadata required by the `ICompletionTheme` interface and\n", " register it on `ILSPCompletionThemeManager` instance using `register_theme()`\n", " method.\n", "\n", "4. Provide any additional CSS styling targeting the JupyterLab completer\n", " elements inside of `.lsp-completer-theme-{id}`, e.g.\n", " `.lsp-completer-theme-material .jp-Completer-icon svg` for the material\n", " theme. Remember to include the styles by importing the in one of the source\n", " files.\n", "\n", "For an example of a complete theme see\n", "[theme-vscode](https://github.com/krassowski/jupyterlab-lsp/tree/master/packages/theme-vscode)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extend jupyter-lsp\n", "\n", "### Language Server Specs\n", "\n", "Language Server Specs can be [configured](./Configuring.html) by Jupyter users,\n", "or distributed by third parties as python or JSON files. Since we'd like to see\n", "as many Language Servers work out of the box as possible, consider\n", "[contributing a spec](./Contributing.html#specs), if it works well for you!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Message Listeners\n", "\n", "Message listeners may choose to receive LSP messages immediately after being\n", "received from the client (e.g. `jupyterlab-lsp`) or a language server. All\n", "listeners of a message are scheduled concurrently, and the message is passed\n", "along **once all listeners return** (or fail). This allows listeners to, for\n", "example, modify files on disk before the language server reads them.\n", "\n", "If a listener is going to perform an expensive activity that _shouldn't_ block\n", "delivery of a message, a non-blocking technique like\n", "[IOLoop.add_callback][add_callback] and/or a\n", "[queue](https://www.tornadoweb.org/en/stable/queues.html) should be used.\n", "\n", "[add_callback]:\n", " https://www.tornadoweb.org/en/stable/ioloop.html#tornado.ioloop.IOLoop.add_callback" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Add a Listener with `entry_points`\n", "\n", "Listeners can be added via [entry_points][] by a package installed in the same\n", "environment as `notebook`:\n", "\n", "```toml\n", "## setup.cfg\n", "\n", "[options.entry_points]\n", "jupyter_lsp_listener_all_v1 =\n", " some-unique-name = some.module:some_function\n", "jupyter_lsp_listener_client_v1 =\n", " some-other-unique-name = some.module:some_other_function\n", "jupyter_lsp_listener_server_v1 =\n", " yet-another-unique-name = some.module:yet_another_function\n", "```\n", "\n", "At present, the entry point names generally have no impact on functionality\n", "aside from logging in the event of an error on import.\n", "\n", "[entry_points]: https://packaging.python.org/specifications/entry-points/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Add a Listener with Jupyter Configuration\n", "\n", "Listeners can be added via `traitlets` configuration, e.g.\n", "\n", "```yaml\n", "## jupyter_server_config.jsons\n", "{\n", " 'LanguageServerManager':\n", " {\n", " 'all_listeners': ['some.module.some_function'],\n", " 'client_listeners': ['some.module.some_other_function'],\n", " 'server_listeners': ['some.module.yet_another_function'],\n", " },\n", "}\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Add a listener with the Python API\n", "\n", "`lsp_message_listener` can be used as a decorator, accessed as part of a\n", "`serverextension`.\n", "\n", "This listener receives _all_ messages from the client and server, and prints\n", "them out.\n", "\n", "```python\n", "from jupyter_lsp import lsp_message_listener\n", "\n", "def load_jupyter_server_extension(nbapp):\n", "\n", " @lsp_message_listener(\"all\")\n", " async def my_listener(scope, message, language_server, manager):\n", " print(\"received a {} {} message from {}\".format(\n", " scope, message[\"method\"], language_server\n", " ))\n", "```\n", "\n", "`scope` is one of `client`, `server` or `all`, and is required." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Listener options\n", "\n", "Fine-grained controls are available as part of the Python API. Pass these as\n", "named arguments to `lsp_message_listener`.\n", "\n", "- `language_server`: a regular expression of language servers\n", "- `method`: a regular expression of LSP JSON-RPC method names" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.9" } }, "nbformat": 4, "nbformat_minor": 4 }