笔记本#
背景#
2016 年 6 月 16 日的 JupyterLab 架构概述,提供了笔记本架构的概述。
JupyterLab 应用程序中包含的最复杂的插件是笔记本插件。
该 NotebookWidgetFactory 从模型构建一个新的 NotebookPanel,并使用默认小部件填充工具栏。
笔记本插件的结构#
笔记本插件提供了一个模型和小部件,用于处理笔记本文件。
模型#
该 NotebookModel 包含一个可观察的单元格列表。
一个 单元格模型 可以是
一个代码单元格
一个 Markdown 单元格
一个原始单元格
一个代码单元格包含一个输出模型列表。单元格列表和输出列表可以观察到变化。
单元格操作#
NotebookModel 单元格列表支持单步操作,例如移动、添加或删除单元格。NotebookModel 还支持复合单元格列表操作,例如撤消/重做。目前,撤消/重做仅支持单元格,不支持笔记本属性,例如笔记本元数据。目前,单个单元格输入内容的撤消/重做由 CodeMirror 编辑器的撤消功能支持。(注意:CodeMirror 编辑器的撤消不涵盖单元格元数据更改。)
元数据#
笔记本模型和单元格模型(即笔记本单元格)支持通过方法 getMetadata
、setMetadata
和 deleteMetadata
获取和设置元数据(参见 NotebookModel 和 单元格模型)。您可以通过 sharedModel.metadataChanged
属性监听元数据的变化(参见 单元格共享模型 和 笔记本共享模型)。
笔记本小部件#
创建 NotebookModel 后,NotebookWidgetFactory 从模型构建一个新的 NotebookPanel。NotebookPanel 小部件被添加到 DockPanel 中。NotebookPanel 包含
NotebookPanel 还添加了完成逻辑。
笔记本工具栏维护一个要添加到工具栏的小部件列表。笔记本小部件包含笔记本的渲染,并处理与笔记本本身的大部分交互逻辑(例如跟踪交互,例如选定和活动单元格,以及当前的编辑/命令模式)。
NotebookModel 单元格列表提供对单元格列表进行细粒度更改的方法。
使用 NotebookActions 的高级操作#
高级操作包含在 NotebookActions 命名空间中,该命名空间具有函数,在给定笔记本小部件时,可以运行单元格并选择下一个单元格,合并或拆分光标处的单元格,删除选定的单元格等。
小部件层次结构#
笔记本小部件包含一个 单元格小部件 列表,对应于其单元格列表中的单元格模型。
每个单元格小部件都包含一个 InputArea,
其中包含一个 CodeEditorWrapper,
其中包含一个 JavaScript CodeMirror 实例。
一个 CodeCell 还包含一个 OutputArea。OutputArea 负责渲染 OutputAreaModel 列表中的输出。OutputArea 使用特定于笔记本的 RenderMimeRegistry 对象来渲染 display_data
输出消息。
笔记本小部件在 DOM 中用一个 <div>
元素表示,该元素具有 CSS 类 jp-Notebook
和 jp-NotebookPanel-notebook
。它包含一系列单元格小部件。
代码单元格具有以下 DOM 结构
渲染的 Markdown 单元格具有以下 DOM 结构
活动 Markdown 单元格具有以下 DOM 结构
注意
HTML 导出器使用的默认 nbconvert 模板生成与 JupyterLab 笔记本相同的 DOM,允许直接使用 JupyterLab CSS。在 JupyterLab 中,输入区域使用 CodeMirror 渲染,使用自定义主题来利用 JupyterLab 的 CSS 变量。在 nbconvert 的情况下,代码单元格使用 Pygments Python 库渲染,该库生成带有语法高亮的静态 HTML。 jupyterlab_pygments Pygments 主题模仿 JupyterLab 的默认 CodeMirror 主题。
注意
展示不同单元格类型 DOM 结构的 SVG 图形是用 Draw.io 生成的,包含允许它们直接用 Draw.io 打开和编辑的元数据。
渲染输出消息#
Rendermime 插件提供了一个可插拔的系统来渲染输出消息。默认渲染器提供用于 Markdown、HTML、图像、文本等。扩展可以通过在 rendermime 注册表中注册处理程序和 MIME 类型来注册要在整个应用程序中使用的渲染器。当创建笔记本时,它会复制全局 Rendermime 单例,以便可以添加特定于笔记本的渲染器。ipywidgets 小部件管理器是添加特定于笔记本的渲染器的扩展的示例,因为渲染小部件取决于特定于笔记本的小部件状态。
键盘交互模型#
笔记本中的多个元素可以接收焦点:- 主工具栏,- 单元格,- 单元格组件(编辑器、工具栏、输出)。
当焦点位于单元格输入编辑器之外时,笔记本会切换到所谓的“命令”模式。在命令模式下,用户可以访问额外的键盘快捷键,从而可以快速访问特定于单元格和笔记本的操作。这些快捷键仅在笔记本处于命令模式且活动元素不可编辑时有效,这由笔记本节点上没有 .jp-mod-readWrite
类来表示。如果活动元素是可编辑的,则会设置此类,这可以通过与 :read-write
伪选择器匹配来确定,并考虑打开的阴影 DOM 中嵌套的任何元素,但不考虑关闭的阴影 DOM 以及具有自定义键事件处理程序的不可编辑元素(例如 <div contenteditable="false" onkeydown="alert()" tabindex="0"></div>
)。如果您的输出小部件(例如使用 IPython.display.HTML
创建,或由笔记本或控制台中的单元格输出上的 MIME 渲染器创建)使用关闭的阴影 DOM 或具有自定义键事件处理程序的不可编辑元素,您可能希望在主机元素上设置 lm-suppress-shortcuts
数据属性以防止命令模式操作的副作用,例如
<div
contenteditable="false"
onkeydown="alert()"
tabindex="1"
data-lm-suppress-shortcuts="true"
>
Click on me and press "A" with and without "lm-suppress-shortcuts"
</div>
如何扩展 Notebook 插件#
我们将逐步介绍两种 Notebook 扩展
在工具栏中添加按钮
在 Notebook 标题中添加小部件
添加 ipywidgets 扩展
在 Notebook 标题中添加小部件#
从扩展模板开始。
pip install "copier~=9" jinja2-time
mkdir myextension
cd myextension
copier copy --trust https://github.com/jupyterlab/extension-template .
安装依赖项。请注意,扩展是针对已发布的 npm 包构建的,而不是针对开发版本构建的。
jlpm add -D @jupyterlab/notebook @jupyterlab/application @jupyterlab/ui-components @jupyterlab/docregistry @lumino/disposable @lumino/widgets
将以下内容复制到 src/index.ts
import { IDisposable, DisposableDelegate } from '@lumino/disposable';
import { Widget } from '@lumino/widgets';
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { NotebookPanel, INotebookModel } from '@jupyterlab/notebook';
/**
* The plugin registration information.
*/
const plugin: JupyterFrontEndPlugin<void> = {
activate,
id: 'my-extension-name:widgetPlugin',
description: 'Add a widget to the notebook header.',
autoStart: true
};
/**
* A notebook widget extension that adds a widget in the notebook header (widget below the toolbar).
*/
export class WidgetExtension
implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel>
{
/**
* Create a new extension object.
*/
createNew(
panel: NotebookPanel,
context: DocumentRegistry.IContext<INotebookModel>
): IDisposable {
const widget = new Widget({ node: Private.createNode() });
widget.addClass('jp-myextension-myheader');
panel.contentHeader.insertWidget(0, widget);
return new DisposableDelegate(() => {
widget.dispose();
});
}
}
/**
* Activate the extension.
*/
function activate(app: JupyterFrontEnd): void {
app.docRegistry.addWidgetExtension('Notebook', new WidgetExtension());
}
/**
* Export the plugin as default.
*/
export default plugin;
/**
* Private helpers
*/
namespace Private {
/**
* Generate the widget node
*/
export function createNode(): HTMLElement {
const span = document.createElement('span');
span.textContent = 'My custom header';
return span;
}
}
并将以下内容复制到 style/base.css
.jp-myextension-myheader {
min-height: 20px;
background-color: lightsalmon;
}
运行以下命令
pip install -e .
jupyter labextension develop . --overwrite
jupyter lab
打开一个 Notebook 并观察新的“标题”小部件。
ipywidgets 第三方扩展#
这个讨论会有点令人困惑,因为我们一直在使用术语 小部件 来指代 lumino 小部件。在下面的讨论中,Jupyter 交互式小部件 将被称为 ipywidgets。lumino 小部件 和 Jupyter 交互式小部件 之间没有内在关系。
ipywidgets 扩展使用 文档注册表 为 Notebook 小部件 扩展注册一个工厂。createNew()
函数使用 NotebookPanel 和 DocumentContext 调用。然后,插件创建一个 ipywidget 管理器(它使用上下文与内核和内核的通信管理器交互)。然后,插件使用 Notebook 实例的 rendermime 注册一个 ipywidget 渲染器(它特定于该特定 Notebook)。
当在内核中创建 ipywidget 模型时,会向浏览器发送一个通信消息,并由 ipywidget 管理器处理以创建浏览器端 ipywidget 模型。当模型在内核中显示时,一个 display_data
输出会发送到浏览器,其中包含 ipywidget 模型 ID。要求在该 Notebook 的 rendermime 中注册的渲染器渲染输出。渲染器要求 ipywidget 管理器实例渲染相应的模型,该模型返回一个 JavaScript Promise。渲染器创建一个容器 lumino 小部件,它同步地将其传递回 OutputArea,然后在 Promise 解决时用渲染的 ipywidget 填充容器。