笔记本#
背景#
2016年6月16日的JupyterLab架构概述提供了笔记本架构的概览。
在JupyterLab应用程序中包含的最复杂的插件是Notebook插件。
NotebookWidgetFactory 从模型中构建一个新的 NotebookPanel 并用默认的小部件填充工具栏。
Notebook 插件的结构#
Notebook 插件提供了一个模型和小部件来处理笔记本文件。
模型#
NotebookModel 包含一个可观察的单元格列表。
一个cell model 可以是:
一个代码单元格
一个Markdown单元格
原始单元格
一个代码单元包含一个输出模型的列表。可以观察单元列表和输出列表的变化。
单元格操作#
NotebookModel 单元格列表支持单步操作,例如移动、添加或删除单元格。NotebookModel 还支持复合单元格列表操作,例如撤销/重做。目前,撤销/重做仅支持单元格,不支持笔记本属性,例如笔记本元数据。目前,CodeMirror 编辑器的撤销功能支持单个单元格输入内容的撤销/重做。(注意:CodeMirror 编辑器的撤销不涵盖单元格元数据更改。)
元数据#
笔记本模型和单元格模型(即笔记本单元格)支持通过方法getMetadata、setMetadata和deleteMetadata获取和设置元数据(参见NotebookModel和cell model)。您可以通过sharedModel.metadataChanged属性监听元数据的变化(参见cell shared model和notebook shared model)。
笔记本小部件#
在创建NotebookModel之后,NotebookWidgetFactory从该模型构造一个新的NotebookPanel。NotebookPanel小部件被添加到DockPanel中。NotebookPanel包含:
a 工具栏
一个 Notebook widget。
NotebookPanel 还增加了自动补全逻辑。
Notebook工具栏维护了一个小部件列表,用于添加到工具栏中。Notebook小部件包含笔记本的渲染,并处理与笔记本本身的大部分交互逻辑(例如跟踪诸如选中和活动单元格的交互,以及当前的编辑/命令模式)。
NotebookModel 单元格列表提供了对单元格列表进行细粒度更改的方法。
使用NotebookActions的更高级操作#
更高级别的操作包含在 NotebookActions 命名空间中,当给定一个笔记本小部件时,该命名空间具有运行单元格并选择下一个单元格、在光标处合并或拆分单元格、删除选定的单元格等功能的函数。
小部件层次结构#
一个Notebook小部件包含一个cell widgets列表,对应于其单元格列表中的单元格模型。
每个单元格小部件包含一个 InputArea,
其中包含一个 CodeEditorWrapper,
其中包含一个 JavaScript CodeMirror 实例。
一个
CodeCell
还包含一个
OutputArea。
OutputArea 负责渲染
OutputAreaModel
列表中的输出。OutputArea 使用一个笔记本特定的
RenderMimeRegistry
对象来渲染 display_data 输出消息。
Notebook 小部件在 DOM 中用一个带有 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注册表中注册一个处理程序和mimetype来注册渲染器,以便在整个应用程序中使用。当创建一个笔记本时,它会复制全局的Rendermime单例,以便可以添加特定于笔记本的渲染器。ipywidgets小部件管理器是一个添加特定于笔记本的渲染器的扩展示例,因为渲染小部件依赖于特定于笔记本的小部件状态。
键盘交互模型#
在Notebook中,多个元素可以接收焦点: - 主工具栏, - 单元格, - 单元格组件(编辑器、工具栏、输出)。
当焦点不在单元格输入编辑器内时,Notebook 会切换到所谓的“命令”模式。在命令模式下,用户可以访问额外的键盘快捷键,从而快速执行与单元格和笔记本相关的操作。这些快捷键仅在笔记本处于命令模式且活动元素不可编辑时有效,这通过笔记本节点上缺少 .jp-mod-readWrite 类来表示。如果活动元素是可编辑的,则通过匹配 :read-write 伪选择器来设置此类,并考虑嵌套在开放影子 DOM 中的任何元素,但不考虑封闭影子 DOM 或具有自定义键盘事件处理程序的不可编辑元素(例如 )。如果您的输出小部件(例如使用 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插件#
我们将介绍两个笔记本扩展:
向工具栏添加按钮
向笔记本标题添加一个小部件
添加一个ipywidgets扩展
向笔记本标题添加小部件#
从扩展模板开始。
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
打开一个笔记本并观察新的“Header”小部件。
ipywidgets 第三方扩展#
这个讨论可能会有点混乱,因为我们一直在使用术语widget来指代lumino widgets。在下面的讨论中,Jupyter交互式小部件将被称为ipywidgets。lumino widgets和Jupyter交互式小部件之间没有内在的联系。
ipywidgets 扩展使用 Document Registry 注册了一个用于笔记本 widget 扩展的工厂。createNew() 函数被调用时传入一个 NotebookPanel 和 DocumentContext。然后插件创建一个 ipywidget 管理器(它使用上下文与内核和内核的通信管理器进行交互)。接着,插件将 ipywidget 渲染器注册到笔记本实例的 rendermime(这是特定于该笔记本的)。
当在内核中创建一个ipywidget模型时,会向浏览器发送一个comm消息,并由ipywidget管理器处理以创建一个浏览器端的ipywidget模型。当模型在内核中显示时,会向浏览器发送一个带有ipywidget模型ID的display_data输出。注册在该笔记本的rendermime中的渲染器被要求渲染输出。渲染器请求ipywidget管理器实例渲染相应的模型,该实例返回一个JavaScript promise。渲染器创建一个容器lumino widget,它同步地将其返回给OutputArea,然后在promise解析时用渲染的ipywidget填充容器。