#!/usr/bin/env python # coding: utf-8 # ## 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](https://jupyterlab.readthedocs.io/en/stable/developer/extension_dev.html#plugins). # Each plugin is a [TypeScript](https://www.typescriptlang.org/) package exporting # one or more `JupyterFrontEndPlugin`s (see # [the JupyterLab extesion developer tutorial](https://jupyterlab.readthedocs.io/en/stable/developer/extension_tutorial.html) # 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](https://jupyterlab.readthedocs.io/en/stable/developer/extension_dev.html#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)` 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. # # 2. Prepare `CompletionKind` → `IconSvgString` 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. # # 3. Provide all other metadata required by the `ICompletionTheme` interface and # register it on `ILSPCompletionThemeManager` instance using `register_theme()` # method. # # 4. 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](https://github.com/krassowski/jupyterlab-lsp/tree/master/packages/theme-vscode). # ## Extend jupyter-lsp # # ### Language Server Specs # # Language Server Specs can be [configured](./Configuring.ipynb) 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](./Contributing.ipynb#specs), 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][add_callback] and/or a # [queue](https://www.tornadoweb.org/en/stable/queues.html) should be used. # # [add_callback]: # https://www.tornadoweb.org/en/stable/ioloop.html#tornado.ioloop.IOLoop.add_callback # #### Add a Listener with `entry_points` # # Listeners can be added via [entry_points][] by a package installed in the same # environment as `notebook`: # # ```toml # ## 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. # # [entry_points]: https://packaging.python.org/specifications/entry-points/ # ##### Add a Listener with Jupyter Configuration # # Listeners can be added via `traitlets` configuration, e.g. # # ```yaml # ## jupyter_notebook_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. # # ```python # 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