Keel GitHub

CodeEditor

在线示例:这是真实的 Keel 程序编译成 WebAssembly,可以直接操作。首次加载约 10 MB。全屏打开

代码编辑器:行号、语法高亮、多光标和列选择、查找替换、代码折叠、括号配对、撤销重做、剪贴板、输入法。只布局可见行,行按块存储:20 万行的文件每帧约 2 毫秒(BenchmarkCodeEditorLargeFileFrame),TestCodeEditorLargeFileFrames 检查在其中编辑和翻页。

ed := kit.CodeEditor(src).Language("go").Name("main.go").Height(400).
    OnChange(save).
    OnComplete(func(line, col int, prefix string) []kit.CodeCompletion { return lsp.Complete(line, col) }).
    OnHover(func(line, col int) string { return lsp.Hover(line, col) }).
    OnDefinition(func(line, col int) { ed.SetCursor(lsp.Definition(line, col)) })
ed.SetDiagnostics(lsp.Diagnostics())

不内置语言服务器协议:应用把语言服务器的诊断、补全、悬停、跳转定义接到 SetDiagnostics、OnComplete、OnHover、OnDefinition。

高亮

chroma 按 Language 的名字选择语法。编辑后立即重新高亮改动附近的几十行,再在后台重算整个文件;后台结果回来前,其余行保持原有颜色。高亮同时标出字符串和注释,括号配对据此判断位置。没有用 Tree-sitter:它在 Go 里需要 cgo,会破坏浏览器版和交叉编译。

键盘

操作 macOS Windows / Linux
按词移动、删除 Option+←/→、Option+⌫ Ctrl+←/→、Ctrl+⌫
行首、行尾 ⌘+←/→、Home/End(Home 在首个非空白列和第 0 列间切换) Home/End
文件首尾 ⌘+↑/↓、⌘+Home/End Ctrl+Home/End
上下加光标 ⌘+Option+↑/↓ 或 Option+Shift+↑/↓ Ctrl+Alt+↑/↓ 或 Alt+Shift+↑/↓
选中下一处相同文字 ⌘+D Ctrl+D
只留主光标 Esc Esc
查找、查找替换 ⌘+F、⌘+Option+F Ctrl+F、Ctrl+H
下一个、上一个匹配 ⌘+G、⌘+Shift+G、F3、Shift+F3 Ctrl+G、Ctrl+Shift+G、F3、Shift+F3
折叠、展开所在区域 ⌘+Option+[、⌘+Option+] Ctrl+Alt+[、Ctrl+Alt+]
跳转定义 F12 F12
补全 Ctrl+Space Ctrl+Space
撤销、重做 ⌘+Z、⌘+Shift+Z Ctrl+Z、Ctrl+Shift+Z

Enter 保持缩进,在 { ( [ 后多缩进一级(Python 另识别 :),在一对括号中间时把右括号放到下一行。Tab/Shift+Tab 缩进和反缩进所选行。没有选区时复制、剪切整行。粘贴的行数和光标数相同时,每个光标各得一行。先按 Esc 再按 Tab 可以离开编辑器。

鼠标

单击定位,拖动选择,双击选词,三击选行,点行号选整行(折叠时选整个区域)。Option/Alt+单击加光标,Option/Alt+Shift 拖动选一列。按住 ⌘/Ctrl 时指针下的标识符带下划线,单击跳转定义。行号左边的箭头折叠、展开。

选项与 API

  • 括号配对 AutoClose(bool),默认开:输入 ( [ { " ' ``` 自动补右侧;在右侧字符前输入同一个右括号时跳过它;退格删除空括号对;有选区时用括号包住选区;在空白行输入右括号时缩进退回到对应的左括号。字符串和注释里不配对括号,单词后的 ' 不配对。
  • 折叠按缩进:一行和它后面缩进更深的行组成一个区域,回到同级缩进的右括号行保持显示。Fold/Unfold/FoldAll/UnfoldAll/Folded;光标移进折叠区会自动展开;在区域上方编辑时折叠跟着移动。
  • TabSize(n, hard) 设置制表位宽度和 Tab 插入制表符还是空格;默认宽 4,按文件现有缩进决定。ShowWhitespace(bool) 用点和箭头标出空格和制表符。
  • 查找面板:区分大小写、全字匹配、正则表达式(替换里可以用 $1),匹配项在文中和滚动条旁标出,最多统计 10000 处。全部替换是一次撤销。OpenSearch(replace)、CloseSearch、SearchMatches、Searchable(bool);只读编辑器只能查找。
  • Value/SetValue、Cursor/SetCursor、Cursors、Selection、Lines、SetReadOnly、SetDisabled、Focus、Fill。多光标编辑整体算一次撤销。

Agent:编辑器角色 textbox,名字是 Name,值是全文(超过 2000 行时是行数);补全项是独立的 option;悬停提示是 tooltip;查找面板角色 search,里面的输入框和按钮单独列出;折叠箭头是按钮,名字如"折叠 6"。

验证:go run ./examples/components -section code_editor。

自定义搜索会话

SetSearchQuery(query, CodeSearchOptions{MatchCase, WholeWord, Regex}) 启动搜索并绘制匹配,不打开内置面板、不抢焦点;Searchable(false) 只关闭内置入口,仍可使用应用搜索栏。SearchSession 返回查询、选项、面板状态、InvalidPattern、Truncated、Current 和 Matches 的副本;Current 从 0 开始,没有恰好选中的匹配时为 -1。

NextSearchMatch、PreviousSearchMatch 循环跳转,SelectSearchMatch(index) 跳到指定结果并展开折叠。ReplaceCurrentSearchMatch(text) 只替换恰好选中的匹配,ReplaceAllSearchMatches(text) 返回整份文档的替换数,一次操作对应一次撤销和一次 OnChange。只读或自身禁用时这两种替换返回 false/0。CloseSearch 结束高亮;只有关闭内置面板时才把焦点交回编辑区。

匹配使用逐行引擎,不支持跨行或零长度匹配。列表最多保留 10,000 条,多出的结果使 Truncated 为 true;全部替换仍遍历全部匹配。正则替换支持 Go regexp 的 $1/${name} 展开。大文档搜索和全部替换同步执行,应用应合并高频输入;此接口不是后台 LSP 搜索。

跟踪装饰集合

marks := ed.Decorations(
    kit.CodeDecoration{Range: kit.CodeRange{Line: 2, Col: 0, EndLine: 2, EndCol: 8}, Style: kit.CodeDecorationFill},
    kit.CodeDecoration{Range: kit.CodeRange{Line: 4, Col: 0, EndLine: 4, EndCol: 6}, Style: kit.CodeDecorationFrame},
)
marks.Append(other)
tracked := marks.Get()
marks.Clear()   // 仍可复用
marks.Dispose() // 此后该集合的操作不再生效

CodeRange 使用从 0 开始的行号和 rune 列,半开区间;不同于上游的 UTF-8 字节偏移。每个集合独立,支持 Frame、Fill、Text(前景色)、Underline;Color 为 nil 时跟随 CodeText,填充使用低透明度。颜色与返回结果均复制,丢弃句柄不会移除装饰;保留句柄也会保留编辑器引用,用 Dispose 释放。

插入在两端不扩大范围,内部插入扩大范围;替换将内部锚点收敛到新范围,整段删除移除条目。撤销/重做同样变换当前位置,已删除的条目不会复活;应从语义数据重建需要恢复的装饰。SetValue 根据最长共同前缀/后缀变换中间改动,仍清空编辑历史。

可见行通过区间索引查询,折叠内容不绘制;更新/追加重建排序,编辑按已有顺序线性维护索引。填充在边框/下划线下,随后绘制选择与文字;同类后创建的集合和后追加的条目覆盖先前样式。装饰不预留空间、不截获输入。几何装饰按可见逻辑行分段绘制,空行没有字形区间时不画;没有连续跨行轮廓或软换行投影。Text 装饰改变颜色,不改变字重或字体度量。

语言编辑规则

err := kit.SetCodeLanguageRules("template", kit.CodeLanguageRules{
    Brackets: []kit.CodePair{{Open: "{{", Close: "}}"}},
    AutoClosingPairs: []kit.CodePair{{Open: "{{", Close: "}}", NotIn: []kit.CodeSyntaxContext{kit.CodeSyntaxString, kit.CodeSyntaxComment}}},
    AutoCloseBefore: ";,}",
    Increase: `\{\{\s*$`, Decrease: `^\s*\}\}`,
})
ed.Language("template").AutoClose(true).SmartIndent(true)

SetCodeLanguageRules 按语言注册,可在同一事件里替换,下一次编辑立即使用。Chroma 能识别的名称归到其语言名,未知名称保留大小写;ClearCodeLanguageRules 恢复默认。SetEditingRules(&rules) 安装实例覆盖,nil 恢复注册表;非法正则、空/跨行/超过 64 rune 的分隔符拒绝整次配置,保留旧规则。

Brackets 控制 Enter 的结构缩进;AutoClosingPairs 为 nil 时使用 Brackets,非 nil 空切片关闭自动配对。输入支持多字符配对、跨越已有结束串、单行选区包裹和空配对 Backspace。AutoCloseBefore 限制后继字符,空白和行末始终允许。NotIn 默认用 Chroma 的 code/string/comment 分类,SyntaxContext 可由应用替换;未知语言没有语法分类时视为 Code。高亮继续由 Chroma 提供,不是 Tree-sitter。

Increase/Decrease 分别在 Enter 前后文本上匹配,不格式化现有行或粘贴。未提供规则时按结构括号缩进,默认 Python 另识别行尾冒号。AutoClose 和 SmartIndent 独立;关闭 SmartIndent 仍复制当前行前导空白,但不增加/拆分缩进。语言切换不重置这两个偏好。