# 虚拟文本 Markdown 渲染解决方案 ## 问题分析 当前虚拟文本(灰色字)无法正确渲染 Markdown 和换行符,根本原因是: 1. **纯文本插入**:`insertGhostText` 使用 `tr.insertText()` 直接插入纯文本 2. **绕过解析器**:文本未经过 Milkdown 的 Markdown 解析流程 3. **节点结构错误**:`\n` 字符被当作普通字符,而非创建新段落节点 ## 解决方案架构 ```mermaid flowchart TB subgraph 当前流程 A1[LLM 返回 Markdown] --> B1[insertText 直接插入] B1 --> C1[添加 copilot_ghost mark] C1 --> D1[显示为灰色纯文本] end subgraph 新流程 A2[LLM 返回 Markdown] --> B2[调用 parserCtx 解析] B2 --> C2[生成 ProseMirror 节点] C2 --> D2[为所有节点添加 ghost 属性] D2 --> E2[插入到文档] E2 --> F2[显示为格式化灰色文本] end style D1 fill:#f99 style F2 fill:#9f9 ``` ## 技术方案 ### 方案一:使用 Milkdown Parser 解析(推荐) **优点**: - 完整支持 Markdown 语法 - 与编辑器行为一致 - 自动处理换行 **实现步骤**: 1. 获取 `parserCtx` 从 Milkdown 上下文 2. 使用 parser 将 Markdown 解析为 ProseMirror Fragment 3. 遍历所有节点,添加 `copilot_ghost` mark 4. 使用 `tr.replaceWith()` 插入节点 ### 方案二:使用 Decoration API(备选) **优点**: - 不修改实际文档内容 - 更轻量级 **缺点**: - 实现复杂 - 可能与某些功能冲突 ## 详细实现计划 ### 步骤 1:修改 copilotPlugin.ts 需要修改以下部分: ```typescript // 新增导入 import { parserCtx } from '@milkdown/kit/core' // 修改 insertGhostText 函数 async function insertGhostText(view: EditorView, suggestion: string, from: number) { if (!currentCtx || !suggestion) return const schema = view.state.schema const markType = schema.marks.copilot_ghost if (!markType) return // 使用 parser 解析 Markdown const parser = currentCtx.get(parserCtx) const doc = await parser(suggestion) if (!doc) return // 为所有文本节点添加 ghost mark const ghostDoc = doc.descendants((node, pos) => { if (node.isText) { // 添加 mark } }) // 插入节点 const tr = view.state.tr tr.replaceWith(from, from, ghostDoc.content) tr.setMeta(COPILOT_PLUGIN_KEY, { from, to: from + doc.content.size, suggestion }) view.dispatch(tr) } ``` ### 步骤 2:处理换行符 换行符处理策略: | 换行类型 | 处理方式 | |---------|---------| | 单个 `\n` | 创建 `hard_break` 节点 | | 双个 `\n\n` | 创建新段落节点 | | 列表项换行 | 创建新列表项节点 | ### 步骤 3:样式处理 需要修改 CSS 以支持格式化的虚拟文本: ```css .copilot-ghost-text { color: #999; opacity: 0.6; pointer-events: none; } /* 虚拟文本内的格式化元素 */ .copilot-ghost-text strong, .copilot-ghost-text em, .copilot-ghost-text code { opacity: inherit; } ``` ### 步骤 4:状态管理 需要跟踪虚拟节点的范围,以便: - Tab 键接受时正确移除 mark - 用户输入时正确清除虚拟内容 - 导出时正确处理虚拟文本 ## 文件修改清单 | 文件 | 修改内容 | |------|---------| | `src/plugins/copilotPlugin.ts` | 重构 insertGhostText,添加解析逻辑 | | `src/components/MilkdownEditor.vue` | 更新 CSS 样式 | | `src/plugins/types.ts` | 可能需要更新类型定义 | ## 风险与注意事项 1. **性能考虑**:解析 Markdown 可能有延迟,需要考虑用户体验 2. **嵌套处理**:复杂的 Markdown 结构(如嵌套列表)需要特殊处理 3. **撤销/重做**:确保虚拟文本的接受/拒绝正确处理 undo stack 4. **光标位置**:插入多段落内容后光标位置需要正确设置 ## 验收标准 - [ ] Markdown 语法正确渲染(粗体、斜体、代码等) - [ ] 换行符正确转换为段落 - [ ] Tab 键接受功能正常 - [ ] Escape 键拒绝功能正常 - [ ] 导出时虚拟文本正确处理 - [ ] 性能无明显下降