语言扩展

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 快捷键绑定
    无默认绑定
    用于切换代码行。
  • tab_size 定义用于此语言的缩进/制表符大小(默认为 4)。
  • hard_tabs 是否使用制表符 (true) 或空格 (false,默认值) 进行缩进。
  • first_line_pattern 是一个正则表达式,除了 path_suffixes(如上)或设置中的 file_types 之外,还可用于匹配应使用此语言的文件。 例如,Zed 使用此方法通过匹配脚本第一行中的 shebangs 行来识别 Shell 脚本。

语法

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。 一个扩展可以通过引用多个 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
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 匹配的查询。如果没有内部内容,ific 文本对象将默认使用这些。

如果您不确定在 textobjects.scm 中放置什么,nvim-treesitter-textobjectsHelix 编辑器都有许多语言的查询。您可以参考 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 中添加一个条目,其中包含您的语言服务器的名称以及它适用的语言。

[language_servers.my-language]
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 文档