Note: the API is likely to change in the future; your suggestions are welcome!
Features (as well as other parts of the frontend) reuse the
JupyterLab plugins system.
Each plugin is a TypeScript package exporting
one or more JupyterFrontEndPlugin
s (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
interfaceFor further integration with the JupyterLab, you can request additional JupyterLab tokens (consult JupyterLab documentation on core tokens).
You can specify a list of extensions to be disabled the the feature manager
passing their plugin identifiers in supersedes
field of IFeatureOptions
.
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.
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.
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.
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.
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.
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).
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:
declare module '*.svg' {
const script: string;
export default script;
}
in your src/
. You should probably keep the icons in your style/
directory.
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.
Provide all other metadata required by the ICompletionTheme
interface and
register it on ILSPCompletionThemeManager
instance using register_theme()
method.
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.
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 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.
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.
Listeners can be added via traitlets
configuration, e.g.
## 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'],
},
}
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.
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 serversmethod
: a regular expression of LSP JSON-RPC method names