CodeEditor 代码编辑器
通用布局能力:
RenderCodeEditor实例统一支持width、height、min/max、margin和槽位对齐;编辑器内容 padding、行高和 gutter 是内部编辑规格。详见组件通用布局属性。
RenderCodeEditor 是基于 PlainTextEditor 大文本编辑器 的代码编辑器外壳。它复用 PlainTextEditor 的文本模型、光标、选区、滚动、行号、IME 和虚拟绘制能力,并增加通用语言服务接入点。
核心原则:
CodeEditor 管输入、选择、绘制和应用 edit。
LanguageAdapter 管语言语义、诊断、语义 token、补全、hover 和格式化。
CodeEditor 不内置业务语言语义,也不硬编码字段、关键字或操作符。业务 DSL 应通过 CodeEditorLanguageAdapter 接入。
何时使用
- 需要在业务系统内编辑 DSL、脚本、表达式、配置或规则。
- 需要展示诊断波浪线、语义高亮、格式化和后续补全/hover。
- 需要和 DirectSurface UI 的主题、焦点、Overlay、DevTools、布局检查保持一致。
- 已有独立语言服务,不希望编辑器直接依赖 parser/semantic/compiler 内部实现。
何时不要使用
- 只编辑普通备注,使用 TextArea。
- 只展示日志或大文本,使用 PlainTextEditor。
- 需要富文本、表格、图片、病历数据元,使用专业业务编辑器。
API 总览
import { RenderCodeEditor, type CodeEditorLanguageAdapter, type CodeEditorDiagnostic, type CodeEditorSemanticToken, type CodeEditorTextEdit, type CodeEditorCompletionActivation, type CodeEditorCompletionItem, type CodeEditorHover, type CodeEditorCodeAction, type CodeEditorContextMenuRequest, type CodeEditorLocation, type CodeEditorReference, type CodeEditorSelectionRange,} from 'ds-ui'
| API | 类型 | 用途 |
|---|---|---|
RenderCodeEditor |
class | 通用代码编辑器 render object。 |
CodeEditorLanguageAdapter |
interface | 语言服务适配器。 |
CodeEditorDiagnostic |
type | 诊断信息。 |
CodeEditorSemanticToken |
type | 语义高亮 token。 |
CodeEditorTextEdit |
type | 文本编辑。 |
CodeEditorCompletionActivation |
type | 补全触发来源,manual 或 auto。 |
CodeEditorCompletionItem |
type | 补全项。 |
CodeEditorHover |
type | Hover 信息。 |
CodeEditorCodeAction |
type | 快速修复或源码操作。 |
CodeEditorContextMenuRequest |
type | 右键菜单请求上下文。 |
CodeEditorLocation |
type | 跳转定义位置。 |
CodeEditorReference |
type | 引用位置。 |
CodeEditorSelectionRange |
type | 智能选区候选。 |
MaybePromise |
type | 同步或异步返回值。 |
最小示例
const editor = new RenderCodeEditor({ value: '规则 儿童禁用\n如果 患者.年龄 小于 18', languageAdapter: { getDiagnostics: (source: string) => validateRule(source), getSemanticTokens: (source: string) => tokenizeRule(source), formatDocument: (source: string) => formatRule(source), },})
LanguageAdapter
CodeEditorLanguageAdapter 的 position/range 使用 DirectSurface UI 文本模型约定:line 和 column 都是 0-based,range.end 是右开区间。
interface CodeEditorLanguageAdapter<TContext = unknown> { getDiagnostics?(source: string, context: TContext | undefined): MaybePromise<CodeEditorDiagnostic[]> getSemanticTokens?(source: string, context: TContext | undefined): MaybePromise<CodeEditorSemanticToken[]> getCompletions?(source: string, position: TextPosition, context: TContext | undefined): MaybePromise<CodeEditorCompletionItem[]> getHover?(source: string, position: TextPosition, context: TContext | undefined): MaybePromise<CodeEditorHover | null> formatDocument?(source: string, context: TContext | undefined): MaybePromise<string | CodeEditorTextEdit[]> getCodeActions?(source: string, diagnostic: CodeEditorDiagnostic | undefined, context: TContext | undefined): MaybePromise<CodeEditorCodeAction[]> getDefinition?(source: string, position: TextPosition, context: TContext | undefined): MaybePromise<CodeEditorLocation | null> getReferences?(source: string, position: TextPosition, context: TContext | undefined): MaybePromise<CodeEditorReference[]> getRenameEdits?(source: string, position: TextPosition, newName: string, context: TContext | undefined): MaybePromise<CodeEditorTextEdit[]> getSelectionRanges?(source: string, position: TextPosition, context: TContext | undefined): MaybePromise<CodeEditorSelectionRange[]>}
当前版本已经接入:
getDiagnostics():显示错误、警告、提示装饰。getSemanticTokens():作为外部 line token provider 驱动语义高亮。getCompletions():打开补全列表、键盘选择并应用补全。getHover():显示 hover tooltip。formatDocument():可返回完整格式化文本或 edit 列表。getCodeActions():返回快速修复或源码操作,业务可通过按钮、命令或诊断列表触发。getDefinition():返回当前位置的定义位置,Ctrl/Meta点击也会触发。getReferences():返回当前位置符号的引用列表。getRenameEdits():返回重命名需要应用的 edit 列表。getSelectionRanges():返回从小到大的结构化选区候选。
构造参数
RenderCodeEditorOptions 继承 RenderPlainTextEditorOptions,额外支持:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
languageAdapter |
CodeEditorLanguageAdapter |
undefined |
语言服务适配器。 |
languageContext |
unknown |
undefined |
传给语言服务的业务上下文。 |
diagnosticsDebounceMs |
number |
300 |
文本变化后的诊断防抖时间。 |
completionTriggerCharacters |
string[] |
['.', ' ', '\n'] |
自动触发补全的字符。 |
hoverDelayMs |
number |
300 |
鼠标停留后触发 hover 的延迟。 |
onDiagnosticsChange |
(diagnostics) => void |
undefined |
诊断更新回调。 |
onSemanticTokensChange |
(tokens) => void |
undefined |
语义 token 更新回调。 |
onCompletionsChange |
(items) => void |
undefined |
补全列表更新回调。 |
onHoverChange |
(hover) => void |
undefined |
Hover 信息更新回调。 |
onContextMenuRequest |
(request) => void |
undefined |
编辑器右键菜单请求回调,业务可接入 AppOverlay 或 Shell 菜单。 |
诊断
诊断使用 0-based range:
interface CodeEditorDiagnostic { range: TextRange message: string severity?: 'error' | 'warning' | 'info' source?: string code?: string}
CodeEditor 会把诊断转换为 PlainTextEditor decoration:
error:错误下划线。warning:警告下划线。info:提示下划线。
业务可通过 editor.diagnostics 获取当前诊断副本,也可以调用:
await editor.validateNow()editor.setDiagnostics(diagnostics)
异步诊断会绑定内部版本号。旧请求返回时,如果文本已更新,结果会被忽略。
语义高亮
语义 token 使用同样的 0-based range:
interface CodeEditorSemanticToken { range: TextRange type: string modifiers?: string[]}
常用 type:
keywordvariablefieldtypeoperatoractiondatasetrulestringnumberquantitypunctuation
CodeEditor 会将这些 token 转换为 PlainTextEditor 的行级 token provider。没有语义 token 的行会退回到 PlainTextEditor 自身 tokenizer。
补全
补全项由 getCompletions() 返回:
interface CodeEditorCompletionItem { label: string kind?: CodeEditorCompletionKind insertText?: string detail?: string documentation?: string range?: TextRange sortText?: string commitCharacters?: string[]}
触发方式:
- 调用
editor.triggerCompletion()手动触发。 - 输入
completionTriggerCharacters中的字符后自动触发。 - 默认触发字符是
.、空格和换行。 Ctrl+Space/Meta+Space触发补全。
键盘交互:
ArrowDown/ArrowUp切换选中项。Enter/Tab应用选中项。- 自动触发的补全如果用户还没有用方向键调整选中项,
Enter会关闭补全并继续交给编辑器处理,避免普通换行误接受第一项。 Escape关闭补全。- 输入当前项
commitCharacters中的字符,会应用补全并同时插入该字符。
应用规则:
- 如果 completion item 提供
range,替换该 range。 - 如果没有
range,在当前光标处插入insertText || label。 - 补全结果绑定当前 source version,旧异步结果不会覆盖新文本。
常用 API:
await editor.triggerCompletion()await editor.triggerCompletion(undefined, { activation: 'auto' })editor.moveCompletionSelection(1)editor.acceptSelectedCompletion()editor.closeCompletion()
Hover
Hover 信息由 getHover() 返回:
interface CodeEditorHover { range: TextRange label: string detail?: string type?: string documentation?: string}
触发方式:
- 调用
editor.triggerHover(position)手动触发。 - 鼠标停留在文本位置超过
hoverDelayMs后自动触发。 - 文本变化、点击编辑器、鼠标离开编辑器时会关闭 hover。
- 补全列表打开时,hover tooltip 会自动隐藏,避免两个浮层互相遮挡。
常用 API:
await editor.triggerHover({ line: 0, column: 4 })editor.closeHover()editor.hoverInfo
Hover 结果同样绑定当前 source version,旧异步结果不会覆盖新文本。
格式化和 Edit
格式化可以返回完整文本:
formatDocument: (source: string) => formatDsl(source)
也可以返回 edit 列表:
formatDocument: (source: string) => [ { range: { start: { line: 0, column: 0 }, end: { line: 0, column: 2 } }, newText: '规则', },]
CodeEditor 应用多个 edit 时会从后往前执行,避免前一个 edit 改变后续 range。
await editor.formatDocument()editor.applyTextEdits(edits)
快速修复、导航和重构
快速修复由 getCodeActions() 返回:
interface CodeEditorCodeAction { title: string kind: 'quickfix' | 'source.format' | string edits: CodeEditorTextEdit[] diagnostics?: CodeEditorDiagnostic[]}
常用 API:
const actions = await editor.triggerCodeActions(editor.diagnostics[0])editor.applyCodeAction(actions[0])
定义和引用:
interface CodeEditorLocation { name: string kind?: string range: TextRange selectionRange?: TextRange} interface CodeEditorReference { name: string kind?: string range: TextRange}
常用 API:
await editor.goToDefinition()const references = await editor.findReferences()
goToDefinition() 会自动跳转到返回的 selectionRange ?? range。findReferences() 只返回位置列表,不会改变编辑器选区,业务页面可以把结果放入侧栏、底部面板或命令面板。
重命名和智能选区:
await editor.renameSymbol('新名称')await editor.expandSelection()
renameSymbol() 会应用 language adapter 返回的 edits。expandSelection() 会从 getSelectionRanges() 返回值中选择比当前选区更大的下一个 range,并把它设置为当前选区。selectSelectionRangeAt() 用于双击场景,会选择包含当前位置的最小 selection range。
快捷键和鼠标交互:
Shift+Alt+F格式化。Shift+Alt+ArrowRight扩大选区。Alt+Enter触发 code actions。Ctrl/Meta点击触发跳转定义。- 双击优先使用
getSelectionRanges()选择语义 token,没有语言服务结果时保留 PlainTextEditor 的默认 token 选择。 - 右键会触发
onContextMenuRequest,回调里包含右键位置、文本位置、命中的诊断和已加载的 code actions;编辑器不直接持有业务 overlay。
和 PlainTextEditor 的关系
RenderCodeEditor 继承 RenderPlainTextEditor。PlainTextEditor 新增了两个通用扩展点:
editor.setLineTokenProvider(provider)editor.setDecorations(decorations)
这些扩展点不依赖 CodeEditor,也可以被日志查看器、配置编辑器或自定义脚本编辑器复用。
业务 DSL 接入建议
业务 DSL 应提供薄适配层,把自身 Language Service 的位置模型转换为 DirectSurface UI 的 0-based 模型:
const adapter: CodeEditorLanguageAdapter<DslContext> = { getDiagnostics(source, context) { return getDslDiagnostics(source, { context }).map(fromDslDiagnostic) }, getSemanticTokens(source, context) { return getDslSemanticTokens(source, { context }).map(fromDslSemanticToken) }, formatDocument(source) { return formatDsl(source) },}
编辑器层不应直接引用 DSL 的 lexer、parser、checker、compiler 内部源码。
状态和调试
debugState() 在 PlainTextEditor 的基础上增加:
| 字段 | 说明 |
|---|---|
languageFeatureVersion |
当前语言服务调度版本。 |
diagnosticsCount |
当前诊断数量。 |
semanticTokenCount |
当前语义 token 数量。 |
completionCount |
当前补全项数量。 |
completionVisible |
补全列表是否打开。 |
selectedCompletionLabel |
当前选中的补全项 label。 |
hoverVisible |
Hover tooltip 是否打开。 |
hoverLabel |
当前 hover 的标题。 |
diagnosticsDebounceMs |
诊断防抖时间。 |
hoverDelayMs |
Hover 延迟时间。 |
hasLanguageAdapter |
是否设置语言适配器。 |