123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- .. _developer-extension-points:
- Common Extension Points
- -----------------------
- Most of the component parts of JupyterLab are designed to be extensible,
- and they provide public APIs via that can be requested in extensions via tokens.
- A list of tokens that extension authors can request is documented in :ref:`tokens`.
- This is not an exhaustive account of how to extend the application components,
- it is instead intended to be a guide for some of the most common extension points.
- Commands
- ~~~~~~~~
- Perhaps the most common way to add functionality to JupyterLab is via commands.
- These are lightweight objects that include a function to execute combined with
- additional such as how they are labeled and when they are enabled.
- The application has a single command registry, keyed by string command IDs,
- to which you can add your custom commands.
- The commands added to the command registry can then be used to populate
- several of the JupyterLab user interface elements, including menus and the launcher.
- Here is a sample block of code that adds a command to the application (given by ``app``):
- .. code:: typescript
- const commandID = 'my-command';
- const toggled = false;
- app.commands.addCommand(commandID, {
- label: 'My Cool Command',
- isEnabled: true,
- isVisible: true,
- isToggled: () => toggled,
- iconClass: 'some-css-icon-class',
- execute: () => {
- console.log(`Activated ${commandID}`);
- toggled = !toggled;
- });
- This example adds a new command, which, when triggered, calls the ``execute`` function.
- ``isEnabled`` indicates whether the command is enabled, and is determined whether
- renderings of it are greyed out.
- ``isToggled`` indicates whether to render a check mark next to the command.
- ``isVisible`` indicates whether to render the command at all.
- ``iconClass`` specifies a CSS class which can be used to display an icon next to renderings of the command.
- Each of ``isEnabled``, ``isToggled``, and ``isVisible`` can be either
- a boolean value or a function that returns a boolean value, in case you want
- to do some logic in order to determine those conditions.
- Likewise, each of ``label`` and ``iconClass`` can be either
- a string value or a function that returns a string value.
- There are several more options which can be passed into the command registry when
- adding new commands. These are documented
- `here <http://phosphorjs.github.io/phosphor/api/commands/interfaces/commandregistry.icommandoptions.html>`__.
- Once a command has been added to the application, it can then be added
- to various places in the application user interface.
- Command Palette
- ~~~~~~~~~~~~~~~
- In order to add a command to the command palette, you need to request the
- ``ICommandPalette`` token in your extension.
- Here is an example showing how to add a command to the command palette (given by ``palette``):
- .. code:: typescript
- palette.addItem({
- command: commandID,
- category: 'my-category'
- args: {}
- });
- The command ID is the same ID as you used when registering the command.
- You must also provide a ``category``, which determines the subcategory of
- the command palette in which to render the command.
- It can be a preexisting category (e.g., ``'notebook'``), or a new one of your own choosing.
- The ``args`` are a JSON object that will be passed into your command's functions at render/execute time.
- You can use these to customize the behavior of your command depending on how it is invoked.
- For instance, you can pass in ``args: { isPalette: true }``.
- Your command ``label`` function can then check the ``args`` it is provided for ``isPalette``,
- and return a different label in that case.
- This can be useful to make a single command flexible enough to work in multiple contexts.
- Main Menu
- ~~~~~~~~~
- There are three main ways to extend JupyterLab's main menu.
- 1. You can add your own menu to the menu bar.
- 2. You can add new commands to the existing menus.
- 3. You can register your extension with one of the existing semantic menu items.
- In all three cases, you should request the ``IMainMenu`` token for your extension.
- Adding a New Menu
- ^^^^^^^^^^^^^^^^^
- To add a new menu to the menu bar, you need to create a new
- `Phosphor menu <https://phosphorjs.github.io/phosphor/api/widgets/classes/menu.html>`__.
- You can then add commands to the menu in a similar way to the command palette,
- and add that menu to the main menu bar:
- .. code:: typescript
- const menu = new Menu({ commands: app.commands });
- menu.addItem({
- command: commandID,
- args: {},
- });
- mainMenu.addMenu(menu, { rank: 40 });
- As with the command palette, you can optionally pass in ``args`` to customize the
- rendering and execution behavior of the command in the menu context.
- Adding a New Command to an Existing Menu
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- In many cases you will want to add your commands to the existing JupyterLab menus
- rather than creating a separate menu for your extension.
- Because the top-level JupyterLab menus are shared among many extensions,
- the API for adding items is slightly different.
- In this case, you provide a list of commands and a rank,
- and these commands will be displayed together in a separate group with an existing menu.
- For instance, to add a command group with ``firstCommandID`` and ``secondCommandID``
- to the File menu, you would do the following:
- .. code:: typescript
- mainMenu.fileMenu.addGroup([
- {
- command: firstCommandID,
- },
- {
- command: secondCommandID,
- }
- ], 40 /* rank */);
- Registering a Semantic Menu Item
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- There are some commands in the JupyterLab menu system that are considered
- common and important enough that they are treated differently.
- For instance, we anticipate that many activities may want to provide a command
- to close themselves and perform some cleanup operation (like closing a console and shutting down its kernel).
- Rather than having a proliferation of similar menu items for this common operation
- of "closing-and-cleanup", we provide a single command that can adapt itself to this use case,
- which we term "semantic menu items".
- For this example, it is the File Menu ``closeAndCleaners`` set.
- Here is an example of using the ``closeAndCleaners`` semantic menu item:
- .. code:: typescript
- mainMenu.fileMenu.closeAndCleaners.add({
- tracker,
- action: 'Shutdown',
- name: 'My Activity',
- closeAndCleanup: current => {
- current.close();
- return current.shutdown();
- }
- });
- In this example, ``tracker`` is a :ref:`widget-tracker`, which allows the menu
- item to determine whether to delegate the menu command to your activity,
- ``name`` is a name given to your activity in the menu label,
- ``action`` is a verb given to the cleanup operation in the menu label,
- and ``closeAndCleanup`` is the actual function that performs the cleanup operation.
- More examples for how to register semantic menu items are found throughout the JupyterLab code base.
- The available semantic menu items are:
- - ``IEditMenu.IUndoer``: an activity that knows how to undo and redo.
- - ``IEditMenu.IClearer``: an activity that knows how to clear its content.
- - ``IEditMenu.IGoToLiner``: an activity that knows how to jump to a given line.
- - ``IFileMenu.ICloseAndCleaner``: an activity that knows how to close and clean up after itself.
- - ``IFileMenu.IConsoleCreator``: an activity that knows how to create an attached code console for itself.
- - ``IHelpMenu.IKernelUser``: an activity that knows how to get a related kernel session.
- - ``IKernelMenu.IKernelUser``: an activity that can perform various kernel-related operations.
- - ``IRunMenu.ICodeRunner``: an activity that can run code from its content.
- - ``IViewMenu.IEditorViewer``: an activity that knows how to set various view-related options on a text editor that it owns.
- Context Menu
- ~~~~~~~~~~~~
- The application context menu is shown when the user right-clicks,
- and is populated with menu items that are most relevant to the thing that the user clicked.
- The context menu system determines which items to show based on
- `CSS selectors <https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors>`__.
- It propagates up the DOM tree and tests whether a given HTML element
- matches the CSS selector provided by a given command.
- Here is an example showing how to add a command to the application context menu:
- .. code:: typescript
- app.contextMenu.addItem({
- command: commandID,
- selector: '.jp-Notebook'
- })
- In this example, the command indicated by ``commandID`` is shown whenever the user
- right-clicks on a DOM element matching ``.jp-Notebook`` (that is to say, a notebook).
- The selector can be any valid CSS selector, and may target your own UI elements, or existing ones.
- A list of CSS selectors currently used by context menu commands is given in :ref:`css-selectors`.
- Keyboard Shortcuts
- ~~~~~~~~~~~~~~~~~~
- There are two ways of adding keyboard shortcuts in JupyterLab.
- If you don't want the shortcuts to be user-configurable,
- you can add them directly to the application command registry:
- .. code:: typescript
-
- app.commands.addKeyBinding({
- command: commandID,
- args: {},
- keys: ['Accel T'],
- selector: '.jp-Notebook'
- });
- In this example ``my-command`` command is mapped to ``Accel T``,
- where ``Accel`` corresponds to ``Cmd`` on a Mac and ``Ctrl`` on Windows and Linux computers.
- The behavior for keyboard shortcuts is very similar to that of the context menu:
- the shortcut handler propagates up the DOM tree from the focused element
- and tests each element against the registered selectors. If a match is found,
- then that command is executed with the provided ``args``.
- Full documentation for the options for ``addKeyBinding`` can be found
- `here <http://phosphorjs.github.io/phosphor/api/commands/interfaces/commandregistry.ikeybindingoptions.html>`__.
- JupyterLab also provides integration with its settings system for keyboard shortcuts.
- Your extension can provide a settings schema with a ``jupyter.lab.shortcuts`` key,
- declaring default keyboard shortcuts for a command:
- .. code:: json
- {
- "jupyter.lab.shortcuts": [
- {
- "command": "my-command",
- "keys": ["Accel T"],
- "selector": ".jp-mod-searchable"
- }
- ]
- }
- Shortcuts added to the settings system will be editable by users.
- Launcher
- ~~~~~~~~
- As with menus, keyboard shortcuts, and the command palette, new items can be added
- to the application launcher via commands.
- You can do this by requesting the ``ILauncher`` token in your extension:
- .. code:: typescript
- launcher.add({
- command: commandID,
- category: 'Other',
- rank: 0
- });
- In addition to providing a command ID, you also provide a category in which to put your item,
- (e.g. 'Notebook', or 'Other'), as well as a rank to determine its position among other items.
- Left/Right Areas
- ~~~~~~~~~~~~~~~~
- The left and right areas of JupyterLab are intended to host more persistent user interface
- elements than the main area. That being said, extension authors are free to add whatever
- components they like to these areas. The outermost-level of the object that you add is expected
- to be a Phosphor ``Widget``, but that can host any content you like (such as React components).
- As an example, the following code executes an application command to a terminal widget
- and then adds the terminal to the right area:
- .. code:: typescript
- app.commands
- .execute('terminal:create-new')
- .then((terminal: WidgetModuleType.Terminal) => {
- app.shell.add(terminal, 'right');
- });
- Status Bar
- ~~~~~~~~~~
- .. _widget-tracker:
- Widget Tracker
- ~~~~~~~~~~~~~~
- Often extensions will want to interact with documents and activities created by other extensions.
- For instance, an extension may want to inject some text into a notebook cell,
- or set a custom keymap, or close all documents of a certain type.
- Actions like these are typically done by widget trackers.
- Extensions keep track of instances of their activities in ``WidgetTrackers``,
- which are then provided as tokens so that other extensions may request them.
- For instance, if you want to interact with notebooks, you should request the ``INotebookTracker`` token.
- You can then use this tracker to iterate over, filter, and search all open notebooks.
- You can also use it to be notified via signals when notebooks are added and removed from the tracker.
- Widget tracker tokens are provided for many activities in JupyterLab, including
- notebooks, consoles, text files, mime documents, and terminals.
- If you are adding your own activities to JupyterLab, you might consider providing
- a ``WidgetTracker`` token of your own, so that other extensions can make use of it.
- RenderMime Registry and Documents
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|