Private
Public Access
1
0
Files
u-desk/docs/01-技术文档/CodeMirror/CodeMirror-6-编辑器文档.md

688 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CodeMirror 6 编辑器文档
> **项目**: U-Desk
> **组件**: CodeEditor.vue
> **更新日期**: 2026-02-05
> **维护者**: 开发团队
---
## 📚 目录
- [简介](#简介)
- [版本信息](#版本信息)
- [核心架构](#核心架构)
- [主题系统](#主题系统)
- [语言支持](#语言支持)
- [API 参考](#api-参考)
- [最佳实践](#最佳实践)
- [常见问题](#常见问题)
- [升级指南](#升级指南)
- [参考资料](#参考资料)
---
## 简介
### 什么是 CodeMirror 6
CodeMirror 6 是一个**基于 TypeScript 重写的现代代码编辑器**,采用模块化架构,提供:
- 🚀 **高性能**: 比 v5 快 40%,内存少 35%
- 📦 **模块化**: 只加载需要的功能
- 🎨 **可定制**: 灵活的主题和扩展系统
- 🔍 **准确**: 基于 Lezer 的语法解析
- 💪 **类型安全**: 完整的 TypeScript 支持
### 为什么选择 CodeMirror 6
| 特性 | CodeMirror 6 | Monaco (VS Code) | Ace |
|------|--------------|------------------|-----|
| 包体积 | ~50KB (gzip) | ~2MB | ~300KB |
| TypeScript | ✅ 原生支持 | ✅ 支持 | ⚠️ 部分 |
| 模块化 | ✅ 高度模块化 | ❌ 单体 | ⚠️ 中等 |
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 移动端 | ✅ 良好支持 | ⚠️ 一般 | ⚠️ 一般 |
---
## 版本信息
### 当前使用的版本
```json
{
"@codemirror/view": "6.39.8",
"@codemirror/state": "6.5.3",
"@codemirror/language": "6.12.1",
"@codemirror/commands": "6.10.1",
"@codemirror/highlight": "0.19.8",
"@codemirror/theme-one-dark": "6.1.3",
"@codemirror/lang-javascript": "6.2.4",
"@codemirror/lang-python": "6.2.1",
"@codemirror/lang-go": "6.0.1",
"@codemirror/lang-json": "6.0.2",
"@codemirror/legacy-modes": "6.5.2"
}
```
### 最新版本2026-02
- **@codemirror/view**: 6.39.12 (2026-01-30)
- **@codemirror/state**: 6.5.3 (当前版本)
- **@codemirror/language**: 6.12.1 (当前版本)
> 注:我们的版本略旧但稳定,建议在下次迭代时更新
---
## 核心架构
### 包结构
```
@codemirror/
├── view/ # 编辑器视图和 DOM 交互
├── state/ # 编辑器状态和事务
├── language/ # 语言支持和高亮
├── commands/ # 内置命令
├── search/ # 搜索和替换
├── autocomplete/ # 自动补全
├── lint/ # 代码检查
├── lang-*/ # 语言包
└── legacy-modes/ # 旧版语言模式
```
### 核心概念
#### 1. EditorState状态
编辑器的不可变状态,包含文档内容:
```javascript
import { EditorState } from '@codemirror/state'
const state = EditorState.create({
doc: 'console.log("Hello, World!")',
extensions: [/* ... */]
})
```
#### 2. EditorView视图
编辑器的 UI 表示:
```javascript
import { EditorView } from '@codemirror/view'
const view = new EditorView({
state: state,
parent: document.body
})
```
#### 3. Extensions扩展
配置编辑器功能的核心机制:
```javascript
const extensions = [
lineNumbers(), // 显示行号
highlightActiveLine(), // 高亮当前行
history(), // 撤销/重做
keymap.of(defaultKeymap) // 键盘映射
]
```
---
## 主题系统
### 主题构成
CodeMirror 6 的主题由两部分组成:
1. **基础样式** (`EditorView.theme`) - UI 元素样式
2. **高亮样式** (`HighlightStyle.define`) - 语法高亮颜色
### 暗色主题One Dark
```javascript
import { oneDark } from '@codemirror/theme-one-dark'
// 直接使用
extensions.push(oneDark)
```
### 亮色主题(自定义)
```javascript
import { HighlightStyle } from '@codemirror/language'
import { tags } from '@lezer/highlight'
// 1. 定义语法高亮样式
const lightHighlightStyle = HighlightStyle.define([
{ tag: tags.keyword, color: '#d73a49', fontWeight: 'bold' },
{ tag: tags.string, color: '#032f62' },
{ tag: tags.number, color: '#005cc5' },
{ tag: tags.comment, color: '#6a737d', fontStyle: 'italic' },
{ tag: tags.function(tags.variableName), color: '#6f42c1' },
{ tag: tags.className, color: '#22863a' },
{ tag: tags.propertyName, color: '#e36209' },
{ tag: tags.variableName, color: '#005cc5' }
])
// 2. 定义基础主题样式
const lightTheme = EditorView.theme({
'&': { backgroundColor: '#ffffff' },
'.cm-gutters': { backgroundColor: '#f7f7f7', color: '#999' },
'.cm-line': { caretColor: '#000' },
'.cm-selection': { backgroundColor: '#d9d9d9' }
})
// 3. 应用主题
extensions.push(lightTheme, lightHighlightStyle)
```
### 主题切换
```javascript
import { Compartment } from '@codemirror/state'
// 创建主题隔离区
const themeCompartment = new Compartment()
// 初始化
const view = new EditorView({
extensions: [
themeCompartment.of(oneDark) // 初始主题
]
})
// 切换主题
function switchTheme(isDark) {
view.dispatch({
effects: themeCompartment.reconfigure(
isDark ? oneDark : lightTheme
)
})
}
```
### 可用的标签Tags
```javascript
import { tags } from '@lezer/highlight'
// 基础标签
tags.keyword // 关键字 (const, var, function)
tags.string // 字符串
tags.number // 数字
tags.comment // 注释
tags.variableName // 变量名
tags.function // 函数
tags.className // 类名
tags.propertyName // 属性名
tags.operator // 操作符
tags.tagName // HTML/XML 标签
tags.attributeName // 属性名
tags.bool // 布尔值
tags.null // null 值
// 组合标签
tags.function(tags.variableName) // 函数调用的变量名
tags.definition(tags.name) // 定义时的名称
```
---
## 语言支持
### 现代语言包
支持 30+ 编程语言,动态加载:
```javascript
// JavaScript/TypeScript
import { javascript } from '@codemirror/lang-javascript'
javascript({ jsx: true, typescript: true })
// Python
import { python } from '@codemirror/lang-python'
python()
// Go
import { go } from '@codemirror/lang-go'
go()
// JSON
import { json } from '@codemirror/lang-json'
json()
// Markdown
import { markdown } from '@codemirror/lang-markdown'
markdown({ codeLanguages: languages })
```
### Legacy 语言包
通过 StreamLanguage 包装旧模式:
```javascript
import { StreamLanguage } from '@codemirror/language'
import { ruby } from '@codemirror/legacy-modes/mode/ruby'
StreamLanguage.define(ruby)
```
### 文件扩展名映射
```javascript
const langMap = {
// JavaScript/TypeScript
'js': 'javascript', 'jsx': 'javascript',
'ts': 'typescript', 'tsx': 'typescript',
// 样式
'css': 'css', 'scss': 'css', 'less': 'css',
// 数据
'json': 'json', 'yaml': 'yaml', 'xml': 'xml',
// 脚本
'py': 'python', 'rb': 'ruby', 'sh': 'shell',
// 编译型
'go': 'go', 'rs': 'rust', 'cpp': 'cpp'
}
```
### 动态加载语言
我们的实现使用缓存和动态导入:
```javascript
// 1. 语言缓存
const languageCache = new Map()
// 2. 动态导入
export async function loadLanguageExtension(language) {
if (languageCache.has(language)) {
return languageCache.get(language)
}
try {
// 动态导入语言包
const mod = await import(`@codemirror/lang-${language}`)
const extension = mod[language]()
languageCache.set(language, extension)
return extension
} catch (error) {
console.error(`加载语言包失败: ${language}`, error)
return null
}
}
```
---
## API 参考
### 核心属性
```javascript
const props = {
modelValue: String, // 编辑器内容 (v-model)
fileExtension: String // 文件扩展名 (如 'js', 'py')
}
```
### 核心事件
```javascript
const emit = {
'update:modelValue': String // 内容变化时触发
}
```
### 主要方法
#### createEditor(docContent)
创建编辑器实例:
```javascript
const createEditor = async (docContent = '') => {
const extensions = await createExtensions()
const state = EditorState.create({
doc: docContent,
extensions
})
view = new EditorView({ state, parent: editorContainer.value })
}
```
#### recreateEditor()
重建编辑器(切换主题/语言时):
```javascript
const recreateEditor = async () => {
if (!view) return
const currentDoc = view.state.doc.toString()
view.destroy()
await createEditor(currentDoc)
}
```
#### createExtensions()
构建扩展配置:
```javascript
const createExtensions = async () => {
const extensions = [
// 基础功能
lineNumbers(),
highlightActiveLineGutter(),
history(),
keymap.of(defaultKeymap),
bracketMatching(),
// 事件监听
EditorView.updateListener.of((update) => {
if (update.docChanged) {
emit('update:modelValue', update.state.doc.toString())
}
}),
// 自定义样式
EditorView.theme({ /* ... */ })
]
// 主题
if (themeStore.isDark) {
extensions.push(oneDark)
} else {
extensions.push(lightTheme, lightHighlightStyle)
}
// 语言支持
const language = getLanguageFromExtension(props.fileExtension)
if (language !== 'text') {
const langExtension = await loadLanguageExtension(language)
if (langExtension) {
extensions.push(langExtension)
}
}
return extensions
}
```
---
## 最佳实践
### 1. 使用 Compartment 动态切换
**不好的做法**:重建整个编辑器
```javascript
// 每次切换都重建,性能差
watch(language, () => {
view.destroy()
view = new EditorView({ /* ... */ })
})
```
**推荐做法**:使用 Compartment
```javascript
const languageCompartment = new Compartment()
watch(language, async (newLang) => {
const lang = await loadLanguageExtension(newLang)
view.dispatch({
effects: languageCompartment.reconfigure(lang)
})
})
```
### 2. 异步加载语言包
```javascript
// 预加载常用语言
export async function preloadCommonLanguages() {
await Promise.all([
'javascript',
'json',
'markdown',
'python',
'sql'
].map(loadLanguageExtension))
}
// 在应用启动时调用
onMounted(() => {
preloadCommonLanguages()
})
```
### 3. 防抖更新
```javascript
import { debounce } from 'lodash-es'
const debouncedUpdate = debounce((value) => {
emit('update:modelValue', value)
}, 300)
EditorView.updateListener.of((update) => {
if (update.docChanged) {
debouncedUpdate(update.state.doc.toString())
}
})
```
### 4. 内存管理
```javascript
onBeforeUnmount(() => {
// 务必销毁编辑器
view?.destroy()
view = null
})
```
### 5. 主题持久化
```javascript
// 从 localStorage 读取
const savedTheme = localStorage.getItem('editor-theme') || 'dark'
// 保存主题变化
watch(theme, (newTheme) => {
localStorage.setItem('editor-theme', newTheme)
})
```
---
## 常见问题
### Q1: 语法高亮不显示?
**可能原因**
1. 语言扩展未正确加载
2. 主题样式未配置
3. 文件扩展名映射错误
**解决方案**
```javascript
// 检查语言是否加载
console.log('Language:', language, 'Extension:', langExtension)
// 确保主题包含高亮样式
extensions.push(lightHighlightStyle)
```
### Q2: 切换主题时编辑器闪烁?
**原因**:重建整个编辑器导致
**解决方案**:使用 Compartment
```javascript
const themeCompartment = new Compartment()
view.dispatch({
effects: themeCompartment.reconfigure(newTheme)
})
```
### Q3: 大文件性能差?
**优化方案**
```javascript
// 虚拟滚动已内置,但可以调整
const virtualScroll = new Compartment()
extensions.push(
virtualScroll.of({
// 调整渲染窗口
viewportMargin: 1000
})
)
```
### Q4: 如何添加自定义语言?
```javascript
// 1. 使用 Lezer 定义语法
import { parser } from '@lezer/generator'
// 2. 创建语言包
import { LanguageSupport } from '@codemirror/language'
const myLanguage = new LanguageSupport(parser)
// 3. 使用
extensions.push(myLanguage)
```
---
## 升级指南
### 从 v5 升级到 v6
主要变化:
| v5 | v6 |
|----|----|
| `CodeMirror(document)` | `new EditorView({ state })` |
| `{line, ch}` 位置 | 数字偏移量 |
| `getValue()` / `setValue()` | `state.doc.toString()` / `dispatch()` |
| `setOption()` | 使用 `Compartment.reconfigure()` |
完整迁移指南https://codemirror.net/docs/migration/
### 升级步骤
1. **更新依赖**
```bash
npm install @codemirror/view@latest @codemirror/state@latest
```
2. **调整 API 调用**
```javascript
// v5
editor.setValue('new content')
// v6
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: 'new content'
}
})
```
3. **更新主题系统**
```javascript
// v5: 使用 CSS 类
editor.setOption('theme', 'my-theme')
// v6: 使用扩展
extensions.push(EditorView.theme({ /* ... */ }))
```
---
## 参考资料
### 官方文档
- [📖 CodeMirror 文档首页](https://codemirror.net/docs/)
- [📚 参考手册](https://codemirror.net/docs/ref/)
- [🎨 示例:样式定制](https://codemirror.net/examples/styling/)
- [⚙️ 示例:配置](https://codemirror.net/examples/config/)
- [📝 变更日志](https://www.codemirror.net/docs/changelog/)
### 社区资源
- [CodeMirror 6 快速入门](https://discuss.codemirror.net/t/codemirror-6-quickstart-and-learn-by-examples/5375)
- [构建代码编辑器教程](https://davidmyers.dev/blog/how-to-build-a-code-editor-with-codemirror-6-and-typescript/introduction)
- [Material UI 集成](https://www.bayanbennett.com/posts/styling-codemirror-v6-with-material-ui-devlog-005/)
- [中文入门教程](https://segmentfault.com/a/1190000043463221)
### 相关包
- [@codemirror/language-data](https://github.com/codemirror/language-data) - 文件类型检测
- [@uiw/react-codemirror](https://www.npmjs.com/package/@uiw/react-codemirror) - React 封装
### 论坛讨论
- [优雅支持多种语言](https://discuss.codemirror.net/t/elegant-way-to-support-a-ton-of-languages/3600)
- [动态加载语法高亮](https://codemirror.net/docs/ref/#lang.StreamLanguage)
- [主题系统设计讨论](https://discuss.codemirror.net/t/styling-and-theming-design-discussion/2958)
---
## 维护日志
### 2026-02-05
- ✅ 修复亮色主题语法高亮问题
- ✅ 添加自定义亮色主题支持
- ✅ 创建完整的技术文档
### 未来计划
- [ ] 升级到最新版本6.39.12
- [ ] 添加更多主题选项
- [ ] 支持自定义快捷键
- [ ] 添加代码折叠功能
- [ ] 集成 LSP语言服务器协议
- [ ] 性能优化(大文件处理)
---
## 相关文件
```
frontend/src/
├── components/
│ └── CodeEditor.vue # 主编辑器组件
├── utils/
│ └── codeMirrorLoader.js # 语言包动态加载
└── stores/
└── theme.js # 主题状态管理
```
---
**文档维护**: 开发团队
**最后更新**: 2026-02-05
**版本**: 1.0.0