DirectSurface UIDirectSurface UI
开始使用
组件/可视化和文档组件

可视化和文档组件 / COMPONENT

CodeEditor

RenderCodeEditor 是基于 PlainTextEditor 大文本编辑器 的代码编辑器外壳。它复用 PlainTextEditor 的文本模型、光标、选区、滚动、行号、IME 和虚拟绘制能力,并增加通用语言服务接入点。

文档 READY示例 1
PUBLIC APIRenderCodeEditor

FUNCTION EXPLORER

可运行示例与完整源码。

这里始终保留组件的主运行入口;文档中的示例用于补充具体功能说明。

LIVE EXAMPLE CODE EDITOR
全屏
正在启动 DirectSurface UI 运行时…

CodeEditor 代码编辑器

通用布局能力RenderCodeEditor 实例统一支持 widthheight、min/max、margin 和槽位对齐;编辑器内容 padding、行高和 gutter 是内部编辑规格。详见组件通用布局属性

RenderCodeEditor 是基于 PlainTextEditor 大文本编辑器 的代码编辑器外壳。它复用 PlainTextEditor 的文本模型、光标、选区、滚动、行号、IME 和虚拟绘制能力,并增加通用语言服务接入点。

核心原则:

CodeEditor 管输入、选择、绘制和应用 edit。
LanguageAdapter 管语言语义、诊断、语义 token、补全、hover 和格式化。

CodeEditor 不内置业务语言语义,也不硬编码字段、关键字或操作符。业务 DSL 应通过 CodeEditorLanguageAdapter 接入。

LIVE CANVAS可运行组件示例
ON-DEMAND RUNTIME交互式示例将在进入视区时启动避免文档首屏同时初始化多个 Canvas Runtime

何时使用

  • 需要在业务系统内编辑 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 补全触发来源,manualauto
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 文本模型约定:linecolumn 都是 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

  • keyword
  • variable
  • field
  • type
  • operator
  • action
  • dataset
  • rule
  • string
  • number
  • quantity
  • punctuation

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 ?? rangefindReferences() 只返回位置列表,不会改变编辑器选区,业务页面可以把结果放入侧栏、底部面板或命令面板。

重命名和智能选区:

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 是否设置语言适配器。

相关组件