语言扩展
Zed 中的语言支持包含几个组件
- 语言元数据和配置
- 语法
- 查询
- 语言服务器
语言元数据
Zed 支持的每种语言都必须在扩展的 languages 目录中的子目录中定义。
该子目录必须包含一个名为 config.toml 的文件,其结构如下:
name = "My Language"
grammar = "my-language"
path_suffixes = ["myl"]
line_comments = ["# "]
name(必需) 是将在“选择语言”下拉菜单中显示的易读名称。grammar(必需) 是语法名称。语法是单独注册的,如下所述。path_suffixes是应与此语言关联的文件后缀数组。与设置中的file_types不同,此项不支持 glob 模式。line_comments是用于识别语言中行注释的字符串数组。这用于editor::ToggleComments键绑定:cmd-/|ctrl-/ 用于切换代码行。tab_size定义此语言使用的缩进/制表符大小 (默认值为4)。hard_tabs是用制表符 (true) 还是空格 (false,默认值) 缩进。first_line_pattern是一个正则表达式,除了path_suffixes(如上) 或设置中的file_types之外,还可以用于匹配应使用此语言的文件。例如,Zed 通过匹配脚本第一行中的 shebang 行来识别 Shell 脚本。debuggers是用于识别语言中调试器的字符串数组。启动调试器的New Process Modal时,Zed 将按照此数组中条目的顺序排列可用的调试器。
语法
Zed 使用 Tree-sitter 解析库来提供内置的特定于语言的功能。有许多语言的语法可用,您还可以开发自己的语法。Zed 功能的不断增长的列表是使用 Tree-sitter 查询通过语法树上的模式匹配构建的。如上所述,扩展中定义的每种语言都必须指定用于解析的 Tree-sitter 语法的名称。然后,这些语法会在扩展的 extension.toml 文件中单独注册,如下所示:
[grammars.gleam]
repository = "https://github.com/gleam-lang/tree-sitter-gleam"
rev = "58b7cac8fc14c92b0677c542610d8738c373fa81"
repository 字段必须指定应从中加载 Tree-sitter 语法的存储库,而 rev 字段必须包含要使用的 Git 修订版本,例如 Git 提交的 SHA。如果您正在本地开发扩展并希望从本地文件系统加载语法,则可以使用 file:// URL 作为 repository。一个扩展可以通过引用多个 Tree-sitter 存储库来提供多个语法。
Tree-sitter 查询
Zed 使用 Tree-sitter 查询语言生成的语法树来实现以下功能:
- 语法高亮
- 括号匹配
- 代码大纲/结构
- 自动缩进
- 代码注入
- 语法覆盖
- 文本修订
- 可运行代码检测
- 选择类、函数等
以下部分将详细阐述 Tree-sitter 查询如何在 Zed 中实现这些功能,并以 JSON 语法作为指导示例。
语法高亮
在 Tree-sitter 中,highlights.scm 文件定义了特定语法的语法高亮规则。
以下是 JSON 的 highlights.scm 文件中的一个示例:
(string) @string
(pair
key: (string) @property.json_key)
(number) @number
此查询将字符串、对象键和数字标记为高亮显示。以下是主题支持的捕获的完整列表:
| 捕获 | 描述 |
|---|---|
| @attribute | 捕获属性 |
| @boolean | 捕获布尔值 |
| @comment | 捕获注释 |
| @comment.doc | 捕获文档注释 |
| @constant | 捕获常量 |
| @constructor | 捕获构造函数 |
| @embedded | 捕获嵌入内容 |
| @emphasis | 捕获强调文本 |
| @emphasis.strong | 捕获强烈强调文本 |
| @enum | 捕获枚举 |
| @function | 捕获函数 |
| @hint | 捕获提示 |
| @keyword | 捕获关键字 |
| @label | 捕获标签 |
| @link_text | 捕获链接文本 |
| @link_uri | 捕获链接 URI |
| @number | 捕获数值 |
| @operator | 捕获运算符 |
| @predictive | 捕获预测文本 |
| @preproc | 捕获预处理器指令 |
| @primary | 捕获主要元素 |
| @property | 捕获属性 |
| @punctuation | 捕获标点符号 |
| @punctuation.bracket | 捕获括号 |
| @punctuation.delimiter | 捕获分隔符 |
| @punctuation.list_marker | 捕获列表标记 |
| @punctuation.special | 捕获特殊标点符号 |
| @string | 捕获字符串文字 |
| @string.escape | 捕获字符串中的转义字符 |
| @string.regex | 捕获正则表达式 |
| @string.special | 捕获特殊字符串 |
| @string.special.symbol | 捕获特殊符号 |
| @tag | 捕获标签 |
| @tag.doctype | 捕获文档类型 (例如,在 HTML 中) |
| @text.literal | 捕获文字文本 |
| @title | 捕获标题 |
| @type | 捕获类型 |
| @variable | 捕获变量 |
| @variable.special | 捕获特殊变量 |
| @variant | 捕获变体 |
括号匹配
brackets.scm 文件定义匹配括号。
以下是 JSON 的 brackets.scm 文件中的一个示例:
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)
此查询识别开括号、大括号和引号。
| 捕获 | 描述 |
|---|---|
| @open | 捕获开括号、大括号和引号 |
| @close | 捕获闭括号、大括号和引号 |
代码大纲/结构
outline.scm 文件定义代码大纲的结构。
以下是 JSON 的 outline.scm 文件中的一个示例:
(pair
key: (string (string_content) @name)) @item
此查询捕获对象键以用于大纲结构。
| 捕获 | 描述 |
|---|---|
| @name | 捕获对象键的内容 |
| @item | 捕获整个键值对 |
| @context | 捕获为大纲项提供上下文的元素 |
| @context.extra | 捕获大纲项的额外上下文信息 |
| @annotation | 捕获注释大纲项的节点 (文档注释、属性、装饰器)1 |
这些注释由助手在生成代码修改步骤时使用。
自动缩进
indents.scm 文件定义缩进规则。
以下是 JSON 的 indents.scm 文件中的一个示例:
(array "]" @end) @indent
(object "}" @end) @indent
此查询标记数组和对象的末尾以进行缩进。
| 捕获 | 描述 |
|---|---|
| @end | 捕获闭括号和大括号 |
| @indent | 捕获整个数组和对象以进行缩进 |
代码注入
injections.scm 文件定义了将一种语言嵌入到另一种语言中的规则,例如 Markdown 中的代码块或 Python 字符串中的 SQL 查询。
以下是 Markdown 的 injections.scm 文件中的一个示例:
(fenced_code_block
(info_string
(language) @injection.language)
(code_fence_content) @injection.content)
((inline) @content
(#set! injection.language "markdown-inline"))
此查询识别围栏代码块,捕获信息字符串中指定的语言和块内的内容。它还捕获内联内容并将其语言设置为“markdown-inline”。
| 捕获 | 描述 |
|---|---|
| @injection.language | 捕获代码块的语言标识符 |
| @injection.content | 捕获要作为不同语言处理的内容 |
请注意,我们不能在此处使用 JSON 作为示例,因为它不支持语言注入。
语法覆盖
overrides.scm 文件定义了语法 _作用域_,可用于在特定语言结构中覆盖某些编辑器设置。
例如,有一个名为 word_characters 的特定语言设置,用于控制哪些非字母字符被视为单词的一部分,例如双击选择变量时。在 JavaScript 中,“$”和“#”被视为单词字符。
还有一个名为 completion_query_characters 的特定语言设置,用于控制哪些字符触发自动完成建议。在 JavaScript 中,当光标位于 _字符串_ 内时,“-”应被视为完成查询字符。为此,JavaScript 的 overrides.scm 文件包含以下模式:
[
(string)
(template_string)
] @string
JavaScript 的 config.toml 包含此设置:
word_characters = ["#", "$"]
[overrides.string]
completion_query_characters = ["-"]
您还可以在特定作用域中禁用某些自动关闭括号。例如,为了防止在字符串中自动关闭 ',您可以在 JavaScript 的 config.toml 中放置以下内容:
brackets = [
{ start = "'", end = "'", close = true, newline = false, not_in = ["string"] },
# other pairs...
]
范围包含
默认情况下,overrides.scm 中定义的范围是_排他_的。因此在上述情况下,如果光标位于分隔字符串的引号_外部_,则 string 作用域将不会生效。有时,您可能希望使范围_包含_。您可以通过在查询中的捕获名称后添加 .inclusive 后缀来实现此目的。
例如,在 JavaScript 中,我们还禁用了注释中单引号的自动关闭。并且注释作用域必须一直延伸到行注释后的换行符。为了实现这一点,JavaScript 的 overrides.scm 包含以下模式:
(comment) @comment.inclusive
文本对象
textobjects.scm 文件定义了通过文本对象导航的规则。此功能在 Zed v0.165 中添加,目前仅在 Vim 模式下使用。
Vim 提供了两个粒度级别的文件导航。使用 [] 等逐节导航,以及使用 ]m 等逐方法导航。即使不支持函数和类的语言,通过定义类似的概念也能很好地工作。例如,CSS 将规则集定义为方法,将媒体查询定义为类。
对于具有闭包的语言,这些通常不应在 Zed 中算作函数。但这只是尽力而为,因为像 JavaScript 这样的语言在语法上没有区分闭包和顶层函数声明。
对于具有像 C 这样的声明的语言,提供匹配 @class.around 或 @function.around 的查询。如果没有内部,if 和 ic 文本对象将默认为这些。
如果您不确定在 textobjects.scm 中放置什么,nvim-treesitter-textobjects 和 Helix 编辑器都有许多语言的查询。您可以参考 Zed 内置语言以了解如何调整这些查询。
| 捕获 | 描述 | Vim 模式 |
|---|---|---|
| @function.around | 整个函数定义或文件中的等效小节。 | [m, ]m, [M,]M 动作。af 文本对象 |
| @function.inside | 函数体 (大括号内的内容)。 | if 文本对象 |
| @class.around | 整个类定义或文件中的等效大节。 | [[, ]], [], ][ 动作。ac 文本对象 |
| @class.inside | 类定义的内容。 | ic 文本对象 |
| @comment.around | 整个注释 (例如,所有相邻的行注释或块注释) | gc 文本对象 |
| @comment.inside | 注释内容 | igc 文本对象 (很少支持) |
例如
; include only the content of the method in the function
(method_definition
body: (_
"{"
(_)* @function.inside
"}")) @function.around
; match function.around for declarations with no body
(function_signature_item) @function.around
; join all adjacent comments into one
(comment)+ @comment.around
文本修订
redactions.scm 文件定义了文本修订规则。在协作和共享屏幕时,它确保某些语法节点以修订模式呈现,以避免信息泄漏。
以下是 JSON 的 redactions.scm 文件中的一个示例:
(pair value: (number) @redact)
(pair value: (string) @redact)
(array (number) @redact)
(array (string) @redact)
此查询将键值对和数组中的数字和字符串值标记为修订。
| 捕获 | 描述 |
|---|---|
| @redact | 捕获要修订的值 |
可运行代码检测
runnables.scm 文件定义了检测可运行代码的规则。
以下是 JSON 的 runnables.scm 文件中的一个示例:
(
(document
(object
(pair
key: (string
(string_content) @_name
(#eq? @_name "scripts")
)
value: (object
(pair
key: (string (string_content) @run @script)
)
)
)
)
)
(#set! tag package-script)
(#set! tag composer-script)
)
此查询检测 package.json 和 composer.json 文件中的可运行脚本。
@run 捕获指定运行按钮应在编辑器中显示的位置。其他捕获 (除带下划线前缀的捕获外) 在运行代码时以 ZED_CUSTOM_$(capture_name) 为前缀作为环境变量公开。
| 捕获 | 描述 |
|---|---|
| @_name | 捕获“scripts”键 |
| @run | 捕获脚本名称 |
| @script | 也捕获脚本名称 (用于不同目的) |
语言服务器
Zed 使用 语言服务器协议 提供高级语言支持。
一个扩展可以提供任意数量的语言服务器。要从您的扩展提供语言服务器,请在 extension.toml 中添加一个条目,其中包含您的语言服务器的名称及其适用的语言。languages 列表中的条目必须与该语言的 config.toml 文件中的 name 字段匹配。
[language_servers.my-language-server]
name = "My Language LSP"
languages = ["My Language"]
然后,在您的扩展的 Rust 代码中,实现扩展的 language_server_command 方法。
#![allow(unused)] fn main() { impl zed::Extension for MyExtension { fn language_server_command( &mut self, language_server_id: &LanguageServerId, worktree: &zed::Worktree, ) -> Result<zed::Command> { Ok(zed::Command { command: get_path_to_language_server_executable()?, args: get_args_for_language_server()?, env: get_env_for_language_server()?, }) } } }
您可以使用 Extension trait 中的几个可选方法来自定义语言服务器的处理。例如,您可以使用 label_for_completion 方法控制完成的样式。有关方法的完整列表,请参阅 Zed 扩展 API 的 API 文档。
多语言支持
如果您的语言服务器支持其他语言,您可以使用 language_ids 将 Zed languages 映射到所需的 LSP 特定 languageId 标识符。
[language-servers.my-language-server]
name = "Whatever LSP"
languages = ["JavaScript", "HTML", "CSS"]
[language-servers.my-language-server.language_ids]
"JavaScript" = "javascript"
"TSX" = "typescriptreact"
"HTML" = "html"
"CSS" = "css"