Pi agent-core harness 与 Claude Code 的 Session 和 Context 对比
用户第二次向 agent 发消息时,运行时需要确定上一轮发生了什么、应从哪段历史继续,以及模型输入超过窗口时应删除或总结哪些内容。本文据此对比 Pi 的 harness 和 Claude Code。 本文分析的 Pi 对象是 ,不包含 Pi CLI 的 会话实现。两边都会将会话写入 JSONL…
共99篇文章
用户第二次向 agent 发消息时,运行时需要确定上一轮发生了什么、应从哪段历史继续,以及模型输入超过窗口时应删除或总结哪些内容。本文据此对比 Pi 的 harness 和 Claude Code。 本文分析的 Pi 对象是 ,不包含 Pi CLI 的 会话实现。两边都会将会话写入 JSONL…
本文是「Pi 源码拆解」系列第 6 篇。系列目录: 2026 06-19 Pi 源码拆解(一):极简 Coding Agent Harness 的分层设计 06-19 Pi 源码拆解(二): Agent 运行时机制 06-20 Pi 源码拆解(三):Session和Context管理 06-20 Pi…
本文是「Pi 源码拆解」系列第 5 篇。系列目录: 2026 06-19 Pi 源码拆解(一):极简 Coding Agent Harness 的分层设计 06-19 Pi 源码拆解(二): Agent 运行时机制 06-20 Pi 源码拆解(三):Session和Context管理 06-20 Pi…
本文是「Pi 源码拆解」系列第 4 篇。系列目录: 2026 06-19 Pi 源码拆解(一):极简 Coding Agent Harness 的分层设计 06-19 Pi 源码拆解(二): Agent 运行时机制 06-20 Pi 源码拆解(三):Session和Context管理 06-20 Pi…
本文是「Pi 源码拆解」系列第 3 篇。系列目录: 2026 06-19 Pi 源码拆解(一):极简 Coding Agent Harness 的分层设计 06-19 Pi 源码拆解(二): Agent 运行时机制 06-20 Pi 源码拆解(三):Session和Context管理(本篇) 第…
本文是「Pi 源码拆解」系列第 2 篇。系列目录: 2026 06-19 Pi 源码拆解(一):极简 Coding Agent Harness 的分层设计 06-19 Pi 源码拆解(二): Agent 运行时机制(本篇) 上一篇介绍了 pi 的包结构和「刻意不做」清单。本文分析 的 agent…
本文是「Pi 源码拆解」系列第 1 篇。系列目录: 2026 06-19 Pi 源码拆解(一):Coding Agent Harness 的包边界与扩展机制(本篇) 我此前写过 Claude Code 源码拆解系列。Claude Code 将权限系统、MCP、子代理、plan mode…
不装套件直接使用 Claude Code 时,常见三类问题。会话变长后,窗口会积累过时信息,模型可能基于错误前提决策。需求描述与模型理解不一致时,产出越复杂,偏差越可能扩大,而且通常在产出完成后才发现。缺少类型检查、测试和浏览器验证时,模型无法获得执行结果的反馈。 这些 Skill…
Claude Code 源码拆解系列:20 篇索引 2026 年 3 月底,Claude Code 的 TypeScript 源码随 npm 包的 source map 泄露,约 1,900 个文件、51 万行。我将源码整理到 ximing/claude-code-source…
系列目录 「JSVM3 原理与思考」系列,基于 jsvm3 源码整理: 从 AST 直驱到字节码:JSVM3 的编译与执行分层 JSVM3 如何执行字节码:指令、栈帧与求值栈 JSVM3 如何实现函数调用、作用域与声明提升 JSVM3 如何编译循环、跳转与 try/catch/finally…
系列目录 「JSVM3 原理与思考」系列,基于 jsvm3 源码整理: 从 AST 直驱到字节码:JSVM3 的编译与执行分层 JSVM3 如何执行字节码:指令、栈帧与求值栈 JSVM3 如何实现函数调用、作用域与声明提升 JSVM3 如何编译循环、跳转与 try/catch/finally…
系列目录 「JSVM3 原理与思考」系列,基于 jsvm3 源码整理: 从 AST 直驱到字节码:JSVM3 的编译与执行分层 JSVM3 如何执行字节码:指令、栈帧与求值栈 JSVM3 如何实现函数调用、作用域与声明提升 JSVM3 如何编译循环、跳转与 try/catch/finally…
系列目录 「JSVM3 原理与思考」系列,基于 jsvm3 源码整理: 从 AST 直驱到字节码:JSVM3 的编译与执行分层 JSVM3 如何执行字节码:指令、栈帧与求值栈 JSVM3 如何实现函数调用、作用域与声明提升(本篇) 第…
系列目录 「JSVM3 原理与思考」系列,基于 jsvm3 源码整理: 从 AST 直驱到字节码:JSVM3 的编译与执行分层 JSVM3 如何执行字节码:指令、栈帧与求值栈(本篇) 第…
系列目录 「JSVM3 原理与思考」系列,基于 jsvm3 源码整理: 从 AST 直驱到字节码:JSVM3 的编译与执行分层(本篇) JSVM2 系列已经说明过 AST 直驱解释器的实现和边界:Babel 解析出 AST,Visitor 前序遍历节点并在遍历中求值。详见《JSVM2:为什么小程序需要一台 JS…
上一篇说明了 ES2015 的裁剪范围。解释器能否用于生产,取决于其在目标输入分布上的验证结果。jsvm2 使用五层测试和编译期工具链验证语义覆盖范围,本文说明该验证体系。 五层测试体系 测试用例在开发过程中逐步增加,每一层对应一类需要验证的问题。 第一层是规范用例。 目录下放了 7 个文件,文件名就是 ES…
前几篇说明了骨架、作用域、控制流和函数。本文说明表达式求值。其主要问题有两个:赋值左值是引用而非值;ES2015 包含大量语法,需要按输入范围裁剪。前半部分讨论左值和运算符,后半部分核对 ES2015 的实际支持范围。 左值是引用 这行代码,右边求值得 1,很容易。左边麻烦。在 jsvm2 里,Identifier…
前几篇说明了解释器的骨架、作用域与闭包和控制流信号。本文说明函数实现。jsvm2 将 call/apply/bind、instanceof、原型链和构造器返回对象等语义交给宿主函数处理;解释器实现作用域捕获、this…
前两篇说明了骨架(visitor、Path 与作用域链)和变量(提升与闭包)。本文处理语句执行中的控制转移:throw、return、break 和 continue。在 jsvm2 中,它们通过两条不同通道传播。 throw 走宿主通道 throw 的实现只有三行(tryCatch.ts:6-…
上一篇说明了 visitor 按节点类型分发、Path 传递 scope 和 ctx,以及 Scope 链管理变量绑定。本文说明变量提升、闭包、 循环中 与 的差异,以及未实现的 TDZ…
上一篇说明了自研原因和整体链路:源码经 @babel/parser 生成 AST,再由解释器求值。本文展开解释器实现。其分发入口只有 5 行,主要结构是 Path 和 Scope:前者参考 Babel,后者对应 ECMA-262 的环境记录。这套结构的雏形见[2020 年的综述](/post/2020/使用TS…
2020 年我写过一篇《使用TS 开发 JS 虚拟机》,介绍用 TypeScript 实现基于 AST 的 JS 解释器。项目在 2021 年 7 月正式立项重写,并于 2022 年 2 月发布 1.0.0。本系列整理其实现过程和已知边界;性能与执行模型问题在 JSVM…
这是系列第 16 篇,也是最后一篇。系列从 6 月 8 日 Alpha 公布当天的 Fiber 数据结构开始追踪 master 分支,至今接近四个月。本文分析 Offscreen。第 2 篇介绍 Lane 位图时提到最高位 ,当时注释仅说明它用于 Offscreen…
9 月 20 日,8209de26 从 master 删除 实现,reconciler、hooks、root 的注册逻辑和测试共删除 3900 余行,commit message 为「This API was replaced by useSyncExternalStore」。第二天,82c8fa9…
这两周 master 没有调度器侧的大改动,但有两项实验性进展:9 月 14 日合入的 263cfa6e([Experimental] Add useInsertionEffect,#21913)为 commit 阶段增加了一类 effect;Server Components(内部名 Flight)在 react…
上一篇分析了 Fizz 如何将 HTML 分段发送到浏览器。流式 SSR 可以提前显示页面,但 HTML 到达时 React 可能尚未完成 hydration,事件监听也尚未绑定。用户此时点击未注水按钮,在 React 17 的 下事件会丢失,click handler 不会执行。master…
此前的文章主要分析客户端 reconciler。服务端渲染在这几个月也发生了变更:6 月 2 日 76f85b3e 将 Fizz bundle 从 experimental 构建移入 stable 构建(入口当时为 react-dom/unstable-fizz.node);6 月 14 日 dbe3363c…
第 6 篇分析 重构时,事件入口仍包含一层 。7 月 12 日的 bfa50f8272(Inline discreteUpdates)移除了这层封装:离散事件回调不再预先修改执行上下文,入口函数直接修改模块级变量。事件系统通过事件优先级与调度器连接。本文同时分析 Context 传播,因为两条路径都使用 lane…
写可访问性标记时经常要生成 DOM id: 和 配对, 指向另一个元素的 id。没有框架支持时,一般手写一个计数器: 这套计数器在纯客户端渲染中可用,但 SSR 会产生不一致。例如服务器渲染时计数器从 0 到 5,HTML 包含 field-0 至 field-5;客户端 hydration 也从…
8 月初 master 合入了两项 DevTools 改动:8 月 2 日的 42251331d8 合入 scheduling profiler,8 月 5 日的 a8725a3e62 为 profiler 中的 React measures 增加 lane 标签和时长。DevTools…
7 月 master 中,Suspense 有两项相关改动:7 月 20 日的 9ab90de6 将部分可见性切换逻辑从 Suspense fiber 移到内部的 Offscreen fiber;7 月 13 日的 9090257e6e 修复了渲染出错重试后 未恢复的问题。本文梳理组件 throw 出 promise…
以下组件包含一个按钮;点击后创建 setTimeout,并在回调中连续调用两次 setState: React 17 中点击一次会输出两行 render,两次 setState 分别触发一次完整的渲染和提交。React 18 alpha 的结果取决于挂载入口:createRoot…
上一篇介绍了 createRoot 转为正式入口。同一周,master 对同步刷新 API 进行了两次相反的修改:7 月 1 日的两个 commit 将 和 两个内部入口合并到 flushSync,7 月 7 日的 commit 又整体回滚这两个改动,且 commit message…
前四篇分析了 reconciler 内部的 Fiber 结构、Lane 模型、调度决策和工作循环。本文转向应用入口。6 月下半月的三个 commit 调整了入口 API:6 月 9 日 为 ReactDOM.render 和 ReactDOM.hydrate 添加废弃警告,6 月 1…
系列前三篇介绍了 Fiber 数据结构和 Lane 模型,分别说明更新如何表示和选择。本文分析被选中的 lane 如何执行,即工作循环(work loop)层的实现;相关源码为 和独立的 包。参考代码为 master 分支 2021-06-24 的 ,并发相关实现位于 fork。 系列目录 日期 标题 06-0…
系列目录 日期 标题 06-08 从 Stack Reconciler 到 Fiber:追踪 React 18 开发,先看数据结构 06-10 React 18 追踪:Lane 模型(上),31 个二进制位取代 expirationTime 06-17 React 18 追踪:Lane…
6 月 8 日 React 团队发了 The Plan for React 18,正式公布 React 18 Alpha,同时建了一个 React 18 Working Group 让社区提前试用和反馈。master 分支上对应的变化很直接:48a11a3efc 把 @next 频道的版本号从 0.0.0- 改成 1…
React 17 发布已经半年多。这个版本没有新增 API,官方将其定位为后续升级的准备。React 团队今天发布 The Plan for React 18,公布 React 18 Alpha; 可以安装,同时成立 React 18 Working Group 供社区试用和反馈。公告列出以 createRoot…
背景 小程序和低代码平台需要执行动态下发的代码。当时调研到的部分 VM 未完整通过 ES5 或 ES2015 测试用例,不能满足生产环境的兼容性要求,因此实现了自己的 JSVM 引擎。 目前已放到 github 上 :jsvm 2 传送门。 目前除了 WithStatement 语句外,其他 es…
第 59 篇对比了 ProseMirror、Draft.js、Slate、Quill 的文档模型。本系列共 60 篇,从仓库结构和文档 JSON 开始,分析核心四包、从 keymap 到 tables…
此前已分析核心四包及其扩展。本篇将 ProseMirror 与 Draft.js、Slate、Quill 对比,考察相同问题在不同架构中的实现。对比范围包括文档存储、修改表达、合法性校验和扩展机制。 ProseMirror…
本文从单个包上移到工程组织层:22 个包各自使用独立 git 仓库,另有 、、 三个辅助仓库。项目未使用 lerna 或 changesets,meta 仓库以一个 384 行脚本统一管理。参考代码为 meta 仓库 的 c7f2f1d,主要分析 ;构建和测试脚本为 的 60d1bac…
上一篇分析了 的插件装配。本篇分析各包测试使用的基础设施。 的测试中常见如下写法: 函数调用构造文档,字符串中的尖括号标签表示位置,第三个参数表示期望文档。 用 和 实现这套接口,共约一百六十行。本文说明 builder 如何从 schema 生成、标签如何换算为位置、mark builder…
本文进入工程与测试部分,分析 。第 3 篇的 demo 用它以几十行代码创建编辑器;本文基于此前已分析的依赖包说明其装配层。参考代码为 的 b6fcf7a…
、 等命令通过 的格网坐标修改文档结构。列宽拖拽不改变结构,只更新 attr。实现位于两个文件: 处理鼠标事件、拖拽状态和手柄装饰; 的表格 NodeView 将文档中的 同步到 的 元素。参考代码为 的 eb522f2;、 分别为 ffad5d9、ca4c78e。 系列目录 日期 标题 05-1…
的编辑命令集中在 ,包括加列、删行、合并、拆分和导航等二十余个导出。命令通常先用 将选区换算为矩形,在格网坐标中计算目标位置,再换算为文档位置,并通过 、、 修改文档。本文使用前两篇的 和 分析这些命令。参考代码为 的 eb522f2。 系列目录 日期 标题 05-10 ProseMirror…
上一篇介绍了表格 schema 和 :合并单元格被计算为格网,、、 在格网坐标上执行。本篇分析 ,说明矩形单元格选区的表示、渲染和文档修改后的映射。参考代码为 的 eb522f2;、 分别为 ffad5d9、ca4c78e。 系列目录 日期 标题 05-10 ProseMirror…
本文开始分析 。合并单元格使行列坐标不能直接对应兄弟节点下标,表格选区是矩形,删除列还需要同步修改多个单元格的 attrs。该包将二维表格结构计算为扁平格网;命令先在格网上计算坐标,再换算为文档位置。 是仅定义表格 schema 的历史包,已由 取代。参考代码为 的 eb522f…
markdown 处理文档与外部文本的双向转换。本文说明 prosemirror-search 提供的编辑器查找替换功能。整个包只有两个源文件:query.ts 负责匹配和替换内容的计算,search.ts 负责插件状态、命令和装饰。对外导出 SearchQuery 类、search…
changeset 用 span 表示两份文档之间的差异。本文说明文档与 Markdown 文本之间的转换。prosemirror-markdown 做两件事:把文档序列化成 Markdown 字符串,把 Markdown 字符串解析回文档。参考代码是 prosemirror-markdown 的 6b95bfe…
collab 处理本地 step 的发送和远端 step 的重整。协作场景还需要汇总一段时间内的修改,标明删除和插入的内容。prosemirror-changeset 提供这一计算:输入是起始文档和一串 StepMap…
上一篇介绍了 CollabState 的 version 与 unconfirmed、receiveTransaction 的前缀确认,以及 rebaseSteps 的撤销、应用远端步骤和重做流程。本篇说明调用 API、history 重建和 demo 的收发时序。包的 API…
prosemirror-collab 不包含网络实现:没有 WebSocket、HTTP 或定时器。它处理客户端的协作状态和 step 重整。源码只有 src/collab.ts 一个文件,185 行,对外是 collab、sendableSteps、receiveTransaction、getVersion…
此前的扩展包分别处理按键、命令、撤销、输入规则、文档结构和光标位置,基本不负责界面呈现。prosemirror-menu 是系列中的 UI 包,用于将 commands 体系中的命令组织为工具栏。包不大,src 下三个实现文件:menu.ts 定义菜单元素体系,menubar.ts 负责插件装配,icons.ts…
gapcursor 处理块间的光标位置。dropcursor 在拖拽内容经过编辑器时标出松手后的插入位置。浏览器原生拖拽反馈通常只显示鼠标箭头上的图标;在文档中拖动图片或从外部拖入文件时,文档内不会显示插入位置。dropcursor 画的就是这个插入点指示。整个包只有 src/dropcursor.ts…
块级节点之间没有文本容器时,光标需要落在两个块之间的位置。gapcursor 处理这类位置。整个包 src 下只有两个源文件:src/gapcursor.ts 定义选区类型,src/index.ts 定义插件,合计 230 来行。参考代码是 prosemirror-gapcursor 的 72657d…
上一篇说明 schema-basic 将三个列表节点交由 schema-list 提供,本文分析该包。参考代码为 prosemirror-schema-list 的 1501619。整个包只有 ,不足二百七十行,导出 orderedList、bulletList、listItem 三个节点 spec…
上一篇引出 schema-basic。此前的 model 文章说明了 NodeSpec 字段和内容表达式如何编译为匹配自动机,但尚未完整分析一份 schema 实例。prosemirror-schema-basic 提供官方基础结构,example-setup 和多数 demo…
上一篇分析 history 的 undo/redo 栈,本文分析 inputrules。该包实现多种 Markdown 风格输入:在空段落输入「# 空格」转换为标题,输入「> 空格」转换为引用块,两个减号转换为破折号,直引号转换为弯引号。参考代码为 prosemirror-inputrules 的 e3e554…
前两篇分析 keymap 和 commands,说明按键如何生成 transaction。本文分析 transaction 的记录与撤销。参考代码为 prosemirror-history 的 445409b…
上一篇说明 keymap 如何按按键定位 ,本文分析命令本身。参考代码为 prosemirror-commands 的 52a84a8,整个包只有 ,约八百行。文件包含删除与光标命令、结构命令、参数化命令工厂(wrapIn、setBlockType、toggleMark、autoJoin…
本文开始分析扩展包。上一篇的最小编辑器已经处理输入、删除和加粗;加粗由按钮直接派发命令,快捷键由官方扩展处理。第一个包是 prosemirror-keymap,参考代码为 d60e24…
view 阶段覆盖了从 EditorView 总览到浏览器差异补丁的十二篇机制。本文只使用 prosemirror-model、prosemirror-transform、prosemirror-state、prosemirror-view 四个核心包实现一个最小编辑器,不引入 keymap、commands…
上一篇的坐标换算已经使用了 、 等分支。view 的输入管线、DOMObserver、选区同步和剪贴板模块也包含类似判断。这些判断条件来自 ,探测逻辑集中定义,补丁保留在对应模块。本篇说明该文件及其相邻的 DOM 工具集 。参考代码是 prosemirror-view 的 ca4c78e。 系列目录 日期 标题 0…
上一篇讨论剪贴板。view 模块中还包括坐标换算和浏览器差异补丁,本篇讨论坐标换算。 负责文档位置与屏幕坐标的双向转换,点击定位、方向键移动、拖拽落点和选区滚动都依赖这里。参考代码是 prosemirror-view 的 ca4c78e。 文档模型里的 pos…
view 模块还包括剪贴板、坐标换算和浏览器差异补丁。本篇讨论剪贴板。选区可能从节点中间切开,直接序列化为 HTML 会丢失结构信息;粘贴的 HTML 可能来自编辑器、Word、网页或终端,结构不受当前 schema 控制。prosemirror-view…
上一篇讨论的 NodeView 和 MarkView 用于定制文档节点的渲染。占位符、协作者光标、搜索高亮和选中节点边框不属于文档内容;将它们写入文档会影响序列化结果和协作同步。ProseMirror 使用 Decoration…
上一篇讨论 composition 与 IME:组合期间,编辑器需要避开输入法正在修改的节点。本篇讨论常规渲染路径中的 定制接口。默认情况下,文档节点按 schema 的 规格渲染,DOM 由编辑器管理,ViewDesc…
上一篇讨论 state 选区与 DOM 选区的双向同步。本篇讨论中文、日文输入法的 composition 场景。从输入「nihao」到上屏「你好」期间,浏览器处于 composition 状态,候选拼音直接写入 contenteditable…
上一篇讨论内容读回:浏览器修改 DOM 后,readDOMChange 将变化转换为 transaction。选区也同时存在于两处。state.selection 用文档位置表示,anchor 和 head 是文档中的数字;DOM Selection 由浏览器持有,使用节点和偏移表示,即 anchorNode…
上一篇讨论浏览器修改 DOM 后,DOMObserver 如何将变化读回文档。事件到达时,view 也可以先主动处理。本篇讨论 的事件注册机制、keydown 从进入编辑器到生成 transaction 或被放行的处理路径,以及 对默认按键行为的过滤。参考代码是 prosemirror-view 的 ca4c78e…
前两篇讨论文档到 DOM 的方向:ViewDesc 树如何将 state.doc 渲染为 DOM,增量更新如何只重画变化部分。用户在 contenteditable 中输入、删除或用输入法上屏时,浏览器会直接修改 DOM,文档模型不会预先得到通知。view 层需要将这些 DOM 变化读回并转换为 transaction…
上一篇介绍了 中描述树的静态结构:ViewDesc 类族的职责、children 与文档树的对应以及 docViewDesc 的构建。文档变化后,DOM 需要更新以反映新文档。全量替换 innerHTML 会丢失光标和输入法组合状态,也会清空自定义 node view…
上一篇分析 EditorView 构造函数时,第 4 步调用 将当前文档渲染到 ,并将结果保存为 。 是定义在 的 ViewDesc 描述树。view 层的渲染、选区、坐标换算和 DOM 读回都依赖这棵树。本文说明其静态结构,包括类族职责、children 数组与文档树的对应、顶层 docView…
前文已分析 EditorState、Selection、Transaction 和插件系统,本篇开始分析 prosemirror-view。view 层负责将不可变的 EditorState 渲染为可编辑 DOM,并将浏览器对 DOM 的改动读回 state。这个过程的入口是 中的 EditorView…
state 阶段已介绍 EditorState 的装配与应用、Selection、Transaction,以及两篇 Plugin 系统文章。本篇通过三个插件示例验证这些接口的使用方式:字符统计使用 StateField,只读开关使用 filterTransaction,选区上报使用 view…
上一篇介绍了插件系统的静态结构:PluginSpec、Plugin、PluginKey,以及 StateField 的 init/apply 约定。这里说明其余四个字段:props、view、filterTransaction、appendTransaction。props 和 view…
上一篇说明了一次状态更新如何由旧 state 应用 transaction 得到新 state。这篇讨论插件数据的存放位置。参考代码是 prosemirror-state 的 ffad5d9,主文件 ,其中导出 PluginSpec、Plugin、PluginKey…
前两篇介绍了 EditorState 和 Selection。EditorState 中的 getter 每次访问都会创建一个 Transaction,dispatch 的事务均由此创建。本文分析 Transaction 类本身,位于 ,文件两百行出头。它继承 transform 阶段介绍的 Transform…
上一篇介绍了 EditorState 的四元组,本文展开其中的 selection 字段。参考代码是 prosemirror-state 的 ffad5d9,主文件为 ,整个文件不到 500 行,包含选区基类、三种内置选区、书签机制和一组位置查找函数。标题中的第四种选区 GapCursor…
transform 阶段的六篇文章讨论了「怎么改文档」:Step、ReplaceStep、StepMap、Mapping、structure.ts,Transform 类提供这些对象的 API。但编辑器运行时还需要保存文档以外的状态。光标位置、下一次输入使用的格式、撤销栈中的 step…
这是 transform 阶段的最后一篇。前面五篇分别讨论了 Step 抽象、ReplaceStep 和 Fitter、StepMap、Mapping,以及 structure.ts 的结构判断函数。这些组件通过 Transform 类()提供给命令层。Transform 依次应用 step…
这是 transform 阶段的第四篇。前几篇介绍了 Step 抽象、ReplaceStep 的内部实现和位置映射,分别说明一步修改如何表示、应用和换算位置。本文讨论另一组判断:当前位置能否分段、两个块能否合并、选中的块能否提升、能否包进引用块。这些判断位于 ;该文件还包含 split、join、lift、wrap…
上一篇介绍了 StepMap 如何用三元组数组描述单个 step、映射位置,并通过 recover 值记录被删除位置。recover 的消费方位于同一个文件: 的下半部分定义了 Mapping 类,用它可将零到多张 StepMap 组合起来,使位置依次经过多步修改。recover、StepMap.invert…
这是 transform 阶段的第三篇。前两篇介绍了 Step 抽象和 ReplaceStep 的实现。step 应用后文档位置会变化,旧选区、装饰和并发修改中记录的位置需要换算到新文档。ProseMirror 中有许多以位置为锚点的对象:选区使用 anchor 和 head,装饰锚定在区间上,step 的 from…
上一篇介绍了 Step 抽象和几个简单的 step 实现。这篇分析 transform 包中的 ,其中包含 ReplaceStep 与 ReplaceAroundStep;以及 中为 slice 计算放置位置的 Fitter。第 8 篇提到,model 层的 replace 要求 slice…
前十二篇已经分析整体结构与 prosemirror-model:文档树、位置编号、切片和解析。model 层的约束是文档不可变,Node 不提供原地修改方法。prosemirror-transform 将键入、粘贴、加粗等操作表示为一个或多个 Step,并依次应用到旧文档以生成新文档。本篇分析 Step…
本篇是 model 阶段的第九篇。此前已分析 Node 与 Fragment 的结构、Mark、Schema 与内容表达式、ResolvedPos、Slice 与 replace、DOM 序列化与解析以及 diff。 和 中还有三类常用方法:遍历用的 nodesBetween 一族、校验用的 canReplace…
前十篇已经覆盖 model 层的文档存储(Node、Fragment、Mark)、结构约束(Schema)、位置解析(ResolvedPos)、Slice,以及 DOM 的序列化和解析。本篇分析 与 :两个文件合计不到 70 行,用于定位两份文档的差异区间。参考代码是 prosemirror-model…
上一篇分析了 toDOM 如何将文档序列化为 DOM 和 HTML。本篇分析反方向的 :外部 DOM,例如剪贴板 HTML、服务端渲染的初始内容或其他编辑器导出的片段,如何按 schema 的 parseDOM 规则解析回文档。DOMParser 负责规则的存储与匹配,ParseContext…
前几篇分析了 model 层的存储和修改:Node 与 Fragment 如何存储文档,Mark 如何挂在文本上,Schema 如何约束结构,位置如何解析为路径,以及 Slice 如何切出和插回内容。本篇转向模型输出,分析文档如何转换为浏览器 DOM 和可复制的 HTML。范围是 ,包含 DOMSerializer…
上一篇说明了 ResolvedPos 如何将数字位置解析为带深度的路径。本篇分析 与 的 copy、cut、slice、replace:从文档切出内容会得到什么对象,插回内容时 ProseMirror 如何保证结果仍符合 schema。这些操作是复制、粘贴、删除和替换的共同底层,transform…
前面三篇分析了 model 的静态结构:Node 与 Fragment 组成的树、挂在内联节点上的 Mark,以及约束树形状的 Schema。编辑器使用单个数字表示光标和选区端点,state 的 selection 与 transform 的 step…
前两篇把文档树的存储看完了:Node 记类型、属性和内容,Fragment 缓存子节点和尺寸,Mark 挂在节点上不进树。这些对象都带着 type 字段指向 NodeType 或 MarkType,但类型对象从哪来、「这个节点里允许放什么」由谁回答,还没展开。这篇读 和 两个文件:前者定义 Schema…
上一篇把 Node 与 Fragment 看完了,Node 的四个构造参数里 type、attrs、content 都讲过,剩下 marks 没有展开。粗体、斜体、链接这类内联格式就存在这个字段里,这一篇专门看它:Mark 的数据结构,mark 集合的增删和比较,schema 层面的 excludes…
上一篇在控制台里查看了文档的 JSON 和一次输入产生的 Transaction。本篇分析 model 包中的 Node 与 Fragment:文档树由这两种对象组成,Mark 挂在节点上;位置编号、Slice 和 diff 都依赖它们的字段约定。参考代码是 prosemirror-model 的 6264de…
前两篇介绍了阅读 ProseMirror 的原因和 22 个包的分工。本文先运行一个编辑器示例,在控制台检查文档 JSON 与一次输入产生的 Transaction。后续阅读 model 和 state 源码时,可以将字段与这些运行结果对应。参考代码版本:prosemirror-example-setup 的 b…
上一篇讨论了 contenteditable 的限制和 ProseMirror 的应对方式。阅读源码前,先确认代码的位置。ProseMirror 的核心和扩展分成 22 个独立包,本地工作区根目录下有 22 个 prosemirror-* 目录;每个目录各有一个 git 仓库和一份 package.json…
手头的项目需要一个富文本编辑器,需求包括表格、自定义节点和撤销功能。我先在 contenteditable 上直接实现了一版,随后发现同样的操作在不同浏览器中会产出不同的 DOM;从 DOM 读回内容时也要处理许多边界情况,一个 bug 的修复还会影响其他场景。问题在于,我把 DOM…