编辑器本身还有 CodeMirror 语言模式(语言定义中的
ui-mode),在后端返回之前就能立即高亮。
语言定义
每种语言是languages/ 下的一个 TOML 文件(构建时嵌入;用户也可以在自己的配置目录里添加)。它声明识别规则、注释符号、tree-sitter 语法、编辑器模式,以及要启动的语言服务器和调试器:
languages/rust.toml
[[language-servers]](比如 TypeScript 同时用 vtsls 和 eslint)。ide-tools 维护着按同一 name 索引的可安装服务器注册表,把它们装进应用托管的工具目录,不需要 sudo。
语言服务器
ide-lsp 是语言服务器管理器:
- 懒启动。 打开匹配语言的文档时,按
(服务器名, 工作区根目录)启动服务器,根目录由root-markers决定。 - 文档同步。 后端持有每个打开文档的权威副本(
ide-text)。编辑器发送带基线版本号的变更集,后端应用后再转发didChange。过期的变更集以outOfSync拒绝,编辑器随即重新同步。 - 只用 IDE 类型。 所有请求都使用 IDE 坐标(UTF-16、从 0 开始),返回
ide_lsp::types中的类型,从不暴露lsp-types,无论服务器发来什么,IPC 接口都保持稳定。 - 能力。 服务器不支持的请求返回
unavailable,消息形如 “jdtls doesn’t support inline completion”。编辑器按文档、按功能分别退避(src/editor/lsp/availability.ts),一个服务器缺某项能力不会连带关掉其他功能。 - 信任。 服务器只在受信任的项目里启动,因为它们会执行项目的构建脚本。
java/ 下应用自有的扩展包:java-usages(带读/写访问区分的语义用法、配置文件导航)、kotlin-bridge(把 Kotlin 库放到 Java classpath 上)以及打过补丁的 Lombok agent,由 ide-sdk / ide-tools 嵌入。java-usages 命令缺失或失败时,查找用法会退回到标准的 LSP references。
IDE 自己的索引
ide-index 在后台为项目的文件及其符号建立索引。符号来自 tree-sitter 标签(ide-language),所以只要有内置语法的语言都能建索引,不依赖语言服务器。索引持久化在 redb 中,用 nucleo 做模糊匹配。
索引从不阻塞:查询基于已经索引的部分立即应答,索引完成时 IdeEvent::IndexState 通知 UI(即 “dumb mode” 指示)。
tree-sitter 语法
ide-language 内置 tree-sitter 语法,并为每个打开的文档维护一棵语法树:首次查询时懒解析,编辑后增量重解析,关闭时丢弃。在此之上:
- 大纲(Structure)、折叠、符号标签,以及对没有 CodeMirror 模式的语言做高亮;
ide-syntax,即结构服务:节点与表达式查找、语法错误、作用域与局部变量、标识符出现位置、结构化范围。
certain: false、exact: false、候选列表),而不是去猜。
ide-analysis(Locate Duplicates、结构化搜索与替换)和 ide-spell(标识符、注释和字符串)也基于这些语法树工作。
结果如何合并
为什么是 LSP 而不是类 PSI 模型
OpenIDEA 没有一个由 IDE 自己维护的统一代码语义模型(IntelliJ 称之为 PSI)。语义知识——类型、解析、引用——来自每种语言自己的服务器;IDE 自己的那几层(索引、tree-sitter 结构)都是语法层面的。 这在实践中意味着:- 广度来自现成的服务器。 支持一门语言,主要是写一份语言定义,再配上 IDE 能安装的服务器,而不是在 IDE 内部实现解析器和名称解析。
- 深度受限于服务器。 查找用法、重命名、重构、检查和快速修复的效果,取决于服务器对相应 LSP 请求的实现。服务器缺某个功能时,IDE 会如实返回
unavailable,不会编造结果。 - 没有跨语言的语义模型。 需要同时理解多种语言的重构,或者按解析后的类型做匹配的结构化搜索,在这套架构上做不到。路线图说明了这一范围,各语言页面列出了各自的限制。
- 按语言补齐短板。 当服务器可以扩展(如上面的 JDT LS 扩展包),或者有插件提供 provider 时,IDE 能在该语言上做得更深,而无需通用的语义模型。
- 语法兜底很保守。 tree-sitter 这一层从不猜测语义,没把握的地方都会标出来。
测试
语言冒烟测试(crates/ide-host/tests/language_smoke.rs)针对每种语言的 fixtures/ 项目、用真实的服务器跑一遍日常清单:补全、跳转到定义、查找用法、悬停、带快速修复的诊断、一个 intention、重命名、格式化、参数信息、Structure 视图、自动导入、Optimize Imports 和移动文件。见冒烟测试。