158 lines
4.0 KiB
Markdown
158 lines
4.0 KiB
Markdown
|
|
# 虚拟文本 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 键拒绝功能正常
|
|||
|
|
- [ ] 导出时虚拟文本正确处理
|
|||
|
|
- [ ] 性能无明显下降
|