- 文档
- 教程
【免费下载链接】vscode-docs
Public documentation for Visual Studio Code
2021 年 8 月,Visual Studio Code 团队正式宣告其 Notebook 支持迈入"成年期"——原生 Notebook 体验在 Stable 版本中面向全部用户开放,编辑器核心能力、快捷键、主题与扩展体系全面贯通。这篇文章以 VS Code 团队官方博客《The Coming of Age of Notebooks》为主线,结合本仓库的 Notebook API 文档、Jupyter Notebooks 用户指南与 1.59 版本发布说明,梳理原生 Notebook 的设计理念、UI 变迁、API 架构与自定义扩展路径,帮助读者理解"VS Code 中的 Notebook 为什么长这样"以及"如何用 API 构建自己的 Notebook 体验"。
背景:Notebook 的前世今生与数据科学团队协作
Notebook——将文本、可执行代码与代码输出融为一体的一种文档形式——并不是一个新概念。1984 年,Donald Knuth 提出"文学编程(Literate Programming)"思想;1988 年,Wolfram Mathematica 引入基于 Kernel 的 Notebook UI。真正让 Notebook 走向大众的,是过去十年数据科学浪潮中 Jupyter Notebook 的崛起:它成为数据科学社区的事实标准工具,被用于虚拟草稿纸、数据准备任务,乃至复杂的机器学习模型开发。
与此同时,一个显著趋势正在发生:数据科学正从个人工作变成团队协作。开发者越来越多地与数据科学家并肩作战——共同准备模型训练数据集、把探索性代码重构为生产级代码、将模型推理集成到核心产品中。VS Code 团队自身也是如此:他们每天用 Jupyter 扩展分析海量使用数据、验证假设,还用领域专属的 GitHub Issues Notebook 跨仓库追踪 issue 与工作项,评估每个版本的上线就绪度。可以说,Notebook 已经成为 VS Code 项目自身运转的关键工具。
原生 Notebook 支持:从"插件"到"内核级能力"
VS Code 团队很早就开始在核心(core)中构建 Notebook 支持,目标明确:更快、更安全,并且让 VS Code 的扩展生态在 Notebook 中正常工作。在此之前,Notebook 在 VS Code 中更像一个"外挂"体验;而现在,团队希望它成为工具不可分割的一部分。
迁移过程与用户反馈
- Insiders 先行:Insiders 版本用户得以持续跟进体验的演进。
- Stable 渐进:Stable 版本上线时已有 40% 的用户在使用新的 Notebook 体验,反馈总体积极。
- 全员切换:团队决定像 Nigel Tufnel 一样"把音量调到 11",将全部用户迁移到新实现上。
遗憾的是,这次切换几乎没有给用户任何预警。很多用户早上打开 VS Code,发现自己熟悉的 Jupyter Notebook 界面"变样了"。团队在博客中为此公开致歉,并承认"有更好的变更发布方式"——让用户清楚知道发生了什么、为什么、何时发生。
这段经历也给所有平台型产品的团队留下一条经验:重大 UI 变更需要更透明的发布节奏与更充分的用户告知,而不是静默切换。
为什么"不一样":让 Notebook 融入 VS Code 的心智模型
最初的 Notebook 实现刻意贴近经典 Jupyter 体验——外观和行为都差不多,这是一个"温暖、熟悉"的合理起点。但随着对 VS Code 用户工作方式的深入了解,团队意识到:两种体验应当"相似多于差异"。
核心原则是:Notebook 在 VS Code 中应该让人感觉自然,让用户能在"编写代码文件"和"用 Python 建模宇宙"之间无缝切换。这意味着 Notebook 要尽可能复用 VS Code 的内置隐喻与熟悉快捷键:
- 在代码单元格中编写代码,体验应与完整文本编辑器一致,无论使用何种语言;
- 设置不应是 Notebook 专属的一套;
- 快速修复(Quick Fixes)、大纲(Outline)、源操作(Source Actions)、重构(Refactorings)、多光标(Multiple-cursors)、自动换行(Word Wrapping)、收缩/展开选区(Shrink and Expand Selection)、列选择模式(Column Selection Mode)、大小写转换(Change Casing)等编辑器能力完全一致;
- 用户熟悉的编辑器扩展(如 Bracket Pair Colorizer、Snippets)应开箱即用;
- 应能像比较源文件一样,对 Notebook 进行图形化的并排差异比较。
面向 Notebook 的扩展生态
团队设想了一个丰富的 Notebook 扩展生态:用户可以像发现主题和新的语言支持一样,在 Marketplace 中搜索 Kernel 或自定义可视化器。API 甚至支持为全新领域创建非 Jupyter 的自定义 Notebook。博客中提到的两个实例:
- REST Book扩展:可编写并持久化 REST 调用,输出支持自定义可视化(JSON、HTML 以及自定义文档);
- GitHub Issues Notebooks扩展:基于不同的 issue 查询创建 Notebook 来管理项目,VS Code 团队自身也用它来组织发布流程(仓库内的
endgame.github-issuesNotebook 即用于发布"终局"追踪)。
在本仓库中,完整的 API 指南位于 Notebook API 文档,它详细定义了扩展如何打开 Notebook 文件、执行代码单元格并以多种丰富、交互式格式渲染输出。
Notebook API 架构:Serializer、Controller 与 Renderer
一个 Notebook 由一系列单元格(cells)及其输出(outputs)构成。单元格分为Markdown 单元格与代码单元格,由 VS Code 核心渲染;输出格式多样——纯文本、JSON、图片、HTML 等由 VS Code 核心渲染,应用专属数据或交互式小程序则交由扩展渲染。三大核心组件各司其职:
| 组件 | 职责 |
|---|---|
NotebookSerializer | 从文件系统读取 Notebook 的序列化字节并反序列化为NotebookData(单元格列表),也负责把NotebookData再序列化写回文件系统 |
NotebookController | 接收代码单元格内容,执行后产生零个或多个输出(格式涵盖纯文本、格式化文档、交互式小程序) |
NotebookRenderer | 针对特定 mimetype 的输出数据提供渲染视图,应用专属格式与交互式小程序由它负责 |
三者协作关系(序列化器 ⇄ 文档数据、控制器 → 输出、渲染器 → 展示)详见 Notebook API 架构总览图。
Serializer:读写 Notebook 文件
NotebookSerializer负责双向转换:把 Notebook 文件的字节反序列化成包含 Markdown 与代码单元格的NotebookData,以及反向把NotebookData序列化为字节落盘。注册方式:先在package.json的contributes.notebooks声明 Notebook 类型与文件选择器,再在扩展激活时调用vscode.workspace.registerNotebookSerializer。官方文档给出了一个以.notebook扩展名打开 Jupyter 格式文件的完整示例(见 Notebook API 文档的 Serializer 章节),核心代码如下:
{ "contributes": { "notebooks": [ { "type": "my-notebook", "displayName": "My Notebook", "selector": [ { "filenamePattern": "*.notebook" } ] } ] } }class SampleSerializer implements vscode.NotebookSerializer { async deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): Promise<vscode.NotebookData> { var contents = new TextDecoder().decode(content); let raw: RawNotebookCell[]; try { raw = (<RawNotebook>JSON.parse(contents)).cells; } catch { raw = []; } const cells = raw.map(item => new vscode.NotebookCellData( item.cell_type === 'code' ? vscode.NotebookCellKind.Code : vscode.NotebookCellKind.Markup, item.source.join('\n'), item.cell_type === 'code' ? 'python' : 'markdown' )); return new vscode.NotebookData(cells); } async serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Promise<Uint8Array> { let contents: RawNotebookCell[] = []; for (const cell of data.cells) { contents.push({ cell_type: cell.kind === vscode.NotebookCellKind.Code ? 'code' : 'markdown', source: cell.value.split(/\r?\n/g) }); } return new TextEncoder().encode(JSON.stringify(contents)); } }注意:该示例仅序列化单元格文本,不会持久化输出;要保存输出,还需在NotebookData中一并序列化/反序列化单元格的输出。要真正执行单元格,则需要实现NotebookController。
Controller:执行代码单元格
NotebookController负责执行代码单元格并产生输出。它与某个序列化器/Notebook 类型直接绑定——创建控制器时设置notebookType属性,并在扩展激活时将其推入订阅列表。核心实现要点(完整示例见 Notebook API 文档的 Controller 章节):
class Controller { readonly controllerId = 'my-notebook-controller-id' readonly notebookType = 'my-notebook'; readonly label = 'My Notebook'; readonly supportedLanguages = ['python']; private readonly _controller: vscode.NotebookController; private _executionOrder = 0; constructor() { this._controller = vscode.notebooks.createNotebookController(this.controllerId, this.notebookType, this.label); this._controller.supportedLanguages = this.supportedLanguages; this._controller.supportsExecutionOrder = true; this._controller.executeHandler = this._execute.bind(this); } private async _doExecution(cell: vscode.NotebookCell): Promise<void> { const execution = this._controller.createNotebookCellExecution(cell); execution.executionOrder = ++this._executionOrder; execution.start(Date.now()); // 记录执行开始时间 /* 此处为实际执行逻辑 */ execution.replaceOutput([new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.text('Dummy output text!')])]); execution.end(true, Date.now()); } }若将提供NotebookController的扩展与其序列化器分开发布,建议在package.json的keywords中加入notebookKernel<ViewTypeUpperCamelCased>形式的关键词(例如为github-issuesNotebook 类型提供替代内核时添加notebookKernelGithubIssues),以提升扩展在打开对应类型 Notebook 时的可发现性。
输出类型:Text、Error 与 Rich
Kernel 输出的格式分为三类,一次单元格执行可产生多个输出并以列表展示(详见 Notebook API 文档的输出类型章节):
- Text Output:最简单的输出格式,仅含
text字段,按纯文本渲染,类似常见 REPL:vscode.NotebookCellOutputItem.text('This is the output...') - Error Output:以一致、易读的方式展示运行时错误,支持标准
Error对象:try { /* Some code */ } catch (error) { vscode.NotebookCellOutputItem.error(error) } - Rich Output:最高级的输出形态,同一输出可携带多种按 mimetype 区分的表示。例如一个表示 GitHub Issue 的输出可同时包含
text/html(格式化视图)、text/x-json(机器可读视图)与application/github-issue(供NotebookRenderer使用的交互视图):execution.replaceOutput([new vscode.NotebookCellOutput([ vscode.NotebookCellOutputItem.text('<b>Hello</b> World', 'text/html'), vscode.NotebookCellOutputItem.json({ hello: 'world' }), vscode.NotebookCellOutputItem.json({ 'custom-data-for-custom-renderer': 'data' }, 'application/custom'), ])]);
VS Code 核心默认可渲染以下 mimetype:application/javascript、text/html、image/svg+xml、text/markdown、image/png、image/jpeg、text/plain;而text/x-json、text/x-javascript、text/x-html、text/x-rust等text/x-LANGUAGE_ID格式则以内置编辑器(Monaco)中的代码形式渲染。若要渲染其他 mimetype,则必须注册对应的NotebookRenderer。
Renderer:自定义输出可视化
Notebook 渲染器负责接收特定 mimetype 的输出数据并提供渲染视图,复杂度可从简单静态 HTML 到完全交互的 applet。官方建议用 Yeoman 模板快速起步:npm install -g yo generator-code后运行yo code并选择New Notebook Renderer (TypeScript)。渲染器通过contributes.notebookRenderer声明(详见 Notebook API 文档的 Renderer 章节):
{ "contributes": { "notebookRenderer": [ { "id": "github-issue-renderer", "displayName": "GitHub Issue Renderer", "entrypoint": "./out/renderer.js", "mimeTypes": [ "ms-vscode.github-issue-notebook/github-issue" ] } ] } }关键实现细节:
- 输出渲染器始终运行在独立
iframe中,与 VS Code 其余 UI 隔离,避免相互干扰或拖慢 VS Code;entrypoint指向单个脚本文件,可手写或用 Webpack/Rollup/Parcel 等打包器生成; - 入口脚本需导出
ActivationFunction(来自vscode-notebook-renderer,TypeScript 用户可安装@types/vscode-notebook-renderer),在 VS Code 就绪后渲染 UI:import type { ActivationFunction } from 'vscode-notebook-renderer'; export const activate: ActivationFunction = (context) => ({ renderOutputItem(data, element) { element.innerText = JSON.stringify(data.json()) } }) - 复杂 UI 可手动创建 DOM 或用 Preact 等框架渲染进输出元素;
- 若持有 iframe 外的元素或异步过程,可用
disposeOutputItem清理(输出被清空、单元格被删除或为新输出渲染前触发);注意同一 Notebook 的所有输出都渲染在同一个 iframe 的不同元素中,使用document.querySelector时必须限定在具体输出元素(如element.querySelector)内,避免互相冲突。
交互式 Notebook:渲染器可通过 preload 脚本与控制器通信——VS Code 会在 iframe 中同时加载该脚本,暴露全局函数postKernelMessage与onDidReceiveMessage。控制器用rendererScripts注册脚本,渲染器在package.json中通过dependencies声明脚本依赖,脚本侧通过postKernelMessage发送命令、onDidReceiveKernelMessage接收回包;控制器侧则监听NotebookController.onDidReceiveMessage事件响应。渲染器还应先检查控制器暴露的全局对象是否存在(其他 Notebook/控制器未必实现),再做降级处理。
与扩展宿主通信:当渲染器与控制器分属两个扩展、需要向扩展宿主发起请求(如打开独立编辑器)时,将contributes.notebookRenderer中的requiresMessaging设为optional(可选值:always必须消息、optional有则更佳但不强依赖、never不需要)。后两者更优,可保证渲染器在无扩展宿主的环境中也具备可移植性。渲染器内通过context.postMessage发送,扩展宿主通过notebooks.createRendererMessaging('<renderer-id>')的onDidReceiveMessage接收。两个注意点:扩展应添加onRenderer:<your renderer id>到activationEvents以保证消息送达前已在扩展宿主中激活;渲染器发给扩展宿主的消息不保证全部送达(用户可能在消息到达前关闭 Notebook)。
调试支持
某些控制器(如编程语言内核)可能希望支持逐单元格调试。Notebook kernel 可基于调试适配器协议(DAP)实现,直接实现 DAP,或委托/转换协议给现有 Notebook 调试器(如vscode-simple-jupyter-notebook样例),更简单的做法是复用现有未修改的调试扩展并即时转换 DAP(如vscode-nodebook样例)。本仓库的 调试器扩展指南 提供了完整的调试扩展开发说明。
从 1.59 版本看 Notebook 的落地形态
博客发布于 2021 年 8 月,正值 VS Code 1.59 版本发布周期。结合 1.59 版本发布说明 可以还原该时期的 Notebook 功能状态:
- 内置 Jupyter Notebook 支持:读取
*.ipynb文件的代码从 Jupyter 扩展移入新的内置扩展。这意味着在干净安装的 VS Code 中即可直接打开 Jupyter Notebook,无需安装完整 Jupyter 扩展;但若要执行单元格或查看使用 ipywidgets 等复杂渲染类型的输出,仍需安装 Jupyter 扩展。 - Notebook 布局改进:窗口过窄时,Notebook 编辑器工具栏的次要操作会移入溢出菜单(...);
notebook.undoRedoPerCell默认值改为true(逐单元格撤销/重做);代码单元格更新默认样式,并新增背景色以区分单元格,主题可通过notebook.cellEditorBackground自定义该颜色;notebook.globalToolbarShowLabel可切换工具栏文本标签的显示。 - Jupyter Interactive Window:升级版内置 Interactive Window 在 1.59 成为默认界面,支持主题、自定义键位、Snippets 与扩展兼容;旧界面可通过
"jupyter.enableNativeInteractiveWindow": false保留。 - Run By Line:Jupyter Notebook 的"逐行运行"功能(简化版调试模式)仍属实验特性,需设置
"jupyter.experimental.debugging": true、安装 ipykernel 6,并在单元格工具栏选择Run By Line。
自定义 Notebook 布局与体验
博客最后提示用户:可通过设置自定义 Notebook 体验,在设置编辑器中搜索@tag:notebookLayout即可找到相关设置。除上面提到的notebook.undoRedoPerCell、notebook.cellEditorBackground、notebook.globalToolbarShowLabel之外,Notebook 大纲(Table of Contents)相关设置包括notebook.outline.showMarkdownHeadersOnly、notebook.outline.showCodeCells、notebook.outline.showCodeCellSymbols,具体可参见 Jupyter Notebooks 用户指南。
延伸阅读:Jupyter Notebooks 用户指南与数据科学工作流
对用户侧的使用而言,本仓库的 Jupyter Notebooks 用户指南 是配套的实操文档,覆盖了完整工作流:环境设置与Workspace Trust、创建/打开 Notebook、运行单个/多个/分区单元格、保存与导出、代码单元格的编辑(创建、模式切换、增删移动、撤销、代码/Markdown 切换、清空输出/重启内核、行号开关)、大纲(Table of Contents)、Notebook 中的 IntelliSense、变量浏览器与数据查看器(Data Viewer)、绘图保存、自定义 Notebook 差异比较(diffing)、Notebook 调试(Run by Line、Debug Cell)以及连接远程 Jupyter 服务器。数据科学方向还可参考 数据科学教程、Python 交互窗口 与 Jupyter 内核管理。
结语:Notebook 的"成年礼"
用团队自己的话说,VS Code 中的 Notebook 已经走过了"尴尬的少年期",进入自信而强健的(年轻)成年期。从 Jupyter 迁移过来的用户可能需要一点适应时间,但换来的是:Notebook 与 VS Code 编辑器体验的深度统一、内置的 Jupyter 支持、以及通过 Notebook API(Serializer + Controller + Renderer)构建任意领域自定义 Notebook 的完整能力。无论是用 Jupyter Notebook 做数据探索,还是为全新领域打造自定义 Notebook 体验,Notebook API 文档 都是继续深入的最佳起点——正如团队在文末所说:Happy Notebooking!
- 文档
- 教程
【免费下载链接】vscode-docs
Public documentation for Visual Studio Code
相关推荐
从工具到生态:Jupyter Notebook的商业化演进之路
从工具到生态:Jupyter Notebook的商业化演进之路 Jupyter Notebook作为一款强大的交互式笔记本工具,已从简单的代码编辑工具发展成为一
后端前端数据科学推荐使用:Visual Studio Code的Jupyter扩展
推荐使用:Visual Studio Code的Jupyter扩展 在这个数据科学与编程的时代,拥有一个强大的代码编辑器和灵活的笔记本工具至关重要。这就是我们要
开发工具Visual Studio Code 扩展指南全景:从 Hello World 到生产级扩展的 API 使用地图
Visual Studio Code 扩展指南全景:从 Hello World 到生产级扩展的 API 使用地图 本篇技术指南对应 Visual Studio
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考