Skip to content

语言支持

Language是Sora Editor实现提供特定语言例如语法分析、自动补全和缩进等功能的接口。

单个Language实例应当只提供给一个编辑器使用,而且当一个编辑器实例被销毁或者设置新的Language实例时,旧的Language 将会自动销毁。

你可以调用CodeEditor#setEditorLanguage为编辑器设置一个新的Language。默认情况下,编辑器使用内置的EmptyLanguage 作为自己的语言,并且不进行任何分析,即语法高亮和其他语言功能均不可使用。

我们提供了一些通用的语言功能实现供您设置编程语言的分析和语法高亮显示。但请注意language-java模块只适用于简单的Java关键字的语法高亮。

使用语言模块

在使用语言模块之前,请确保已将其导入到项目中。

language-textmate

此模块使用textmate语法来标记文本并实现各种编程语言的高亮显示。 textmate在Visual Studio CodeEclipse 中也用于语法高亮。大多数情况下请使用这个模块实现语法高亮,而不是自己编写Language实现。

请按照以下步骤将textmate用于您的编辑器。

查找语言语法和配置

textmate支持非常多的语言,它的语法高亮规则由名为*.tmLanguage的PLIST文件或者名为*.tmLanguage.json的JSON文件定义。 你需要textmate规则文件(又叫语法)和可选的语言配置文件(*.language-configuration.json)为您的语言提供高亮配置。

您可以在以下位置找到这些文件:

查找主题

textmate必须和textmate主题搭配使用。你还需要从VSCode Extensions 中查找您满意的主题JSON文件。 里面有一些文件夹的命名规则是theme-*。他们就是您要找的VSCode内置的textmate主题。

准备语言注册表

textmate可以加载多种语言,所以我们需要提前准备一下languages.json。现在假设您的assets文件夹的目录结构如下:

Text
.
├─ textmate
│  ├─ java
│  │  ├─ syntaxes
│  │  │  └─ java.tmLanguage.json
│  │  └─ language-configuration.json
│  └─ kotlin
│     ├─ syntaxes
│     │  └─ Kotlin.tmLanguage
│     └─ language-configuration.json
└─ language.json

您的language.json内容如下:

JSON
{
  "languages": [
    {
      "grammar": "textmate/java/syntaxes/java.tmLanguage.json",
      "name": "java",
      "scopeName": "source.java",
      "languageConfiguration": "textmate/java/language-configuration.json"
    },
    {
      "grammar": "textmate/kotlin/syntaxes/Kotlin.tmLanguage",
      "name": "kotlin",
      "scopeName": "source.kotlin",
      "languageConfiguration": "textmate/kotlin/language-configuration.json"
    }
  ]
}

name可以自行定义,scopeName是语法文件的作用域。

对于可以嵌入其他语言的语言,例如HTML和Markdown等标记语言。请参考适用于HTML的示例应用

加载语法和主题

在编辑器中使用textmate之前,我们应该将语法和主题文件提前加载到注册表中。 无论有多少编辑器实例在使用textmate,这些步骤都应该只执行一次。

假设我们要从assets文件夹加载textmate文件。首先,我们需要添加textmate内部文件访问权限。

Kotlin
FileProviderRegistry.getInstance().addFileProvider(
    AssetsFileResolver(
        applicationContext.assets // 使用应用上下文
    )
)
Java
FileProviderRegistry.getInstance().addFileProvider(
    new AssetsFileResolver(
        getApplicationContext().getAssets() // 使用应用上下文
    )
)

然后,我们需要加载主题。下面的代码演示如何将单个主题加载到编辑器中。

Kotlin
val themeRegistry = ThemeRegistry.getInstance()
val name = "quietlight" // 主题名称
val themeAssetsPath = "textmate/$name.json"
themeRegistry.loadTheme(
    ThemeModel(
        IThemeSource.fromInputStream(
            FileProviderRegistry.getInstance().tryGetInputStream(themeAssetsPath), themeAssetsPath, null
        ),
        name
    ).apply {
        // 如果主题是适用于暗色模式的,请额外添加以下内容
        // isDark = true
    }
)
Java
var themeRegistry = ThemeRegistry.getInstance();
var name = "quietlight"; // 主题名称
var themeAssetsPath = "textmate/" + name + ".json";
var model = new ThemeModel(
        IThemeSource.fromInputStream(
                FileProviderRegistry.getInstance().tryGetInputStream(themeAssetsPath), themeAssetsPath, null
        ),
        name
);
// 如果主题是适用于暗色模式的,请额外添加以下内容
// model.setDark(true);
themeRegistry.loadTheme(model);

接下来,为textmate选择并启用一个主题。textmate将会使用其注册表来管理全局配色方案。

Kotlin
ThemeRegistry.getInstance().setTheme("您的主题名称")
Java
ThemeRegistry.getInstance().setTheme("您的主题名称");

最后,我们加载语言的语法和配置。

Kotlin
GrammarRegistry.getInstance().loadGrammars("textmate/languages.json")
Java
GrammarRegistry.getInstance().loadGrammars("textmate/languages.json");
通过Kotlin DSL语法加载

您可以使用Kotlin DSL将Language加载到语法注册表中,而不需要languages.json

示例:

Kotlin
GrammarRegistry.getInstance().loadGrammars(
    languages {
        language("java") {
            grammar = "textmate/java/syntaxes/java.tmLanguage.json"
            defaultScopeName()
            languageConfiguration = "textmate/java/language-configuration.json"
        }
        language("kotlin") {
            grammar = "textmate/kotlin/syntaxes/Kotlin.tmLanguage"
            defaultScopeName()
            languageConfiguration = "textmate/kotlin/language-configuration.json"
        }
        language("python") {
            grammar = "textmate/python/syntaxes/python.tmLanguage.json"
            defaultScopeName()
            languageConfiguration = "textmate/python/language-configuration.json"
        }
    }
)

defaultScopeName()会将scopeName设置为source.${languageName}

设置编辑器

设置编辑器的配色方案。如果TextMateColorScheme没被应用到编辑器中,则textmate的语法高亮结果的颜色是透明的。

Kotlin
editor.colorScheme = TextMateColorScheme.create(ThemeRegistry.getInstance())
Java
editor.setColorScheme(TextMateColorScheme.create(ThemeRegistry.getInstance()));

最后,设置编辑器语言。

Kotlin
val languageScopeName = "source.java" // 您目标语言的作用域名称
val language = TextMateLanguage.create(
    languageScopeName, true /* true表示启用自动补全 */
)
editor.setEditorLanguage(language)
Java
var languageScopeName = "source.java"; // 您目标语言的作用域名称
var language = TextMateLanguage.create(
        languageScopeName, true /* true表示启用自动补全 */
);
editor.setEditorLanguage(language);

恭喜!您已经完成了所有设置。尽情享受吧!

language-java

为Java语言提供基于token的高亮显示、关键字自动补全和代码块标记的支持。它还具有一些用于测试编辑器的实验性功能。

虽然它的功能仍然很简单,但它的速度要比其他复杂的语言分析快得多。

若要创建并使用它,请参考下面的代码:

Kotlin
editor.editorLanguage = JavaLanguage()
Java
editor.setEditorLanguage(new JavaLanguage());

language-treesitter

TreeSitter由Atom和现在Zed的作者们开发。 TreeSitter是一个解析器生成器工具和一个增量解析库。

使用TreeSitter,我们可以为源文件构建抽象语法树,并在编辑源文件时高效地更新该语法树并使用语法树进行准确的语法高亮显示。

我们使用android-tree-sitter调用tree-sitter的API。

在继续往下阅读前,我们强烈建议您先了解一下编辑器框架中的TextStyle

准备语言

您可以先在android-tree-sitter 中查找是否已存在您需要的语言实现。如果没有您需要的语言实现,则您必须自己构建一个适用于Android的语言实现。

此外,还需要四个scm文件来查询语法树:

    1. 高亮显示

对于适用于绝大多数语言的highlights.scm都可以在TreeSitter语言存储库中找到。 例如这个适用于Java语言。

    1. 代码块(可选)

这个仅适用于sora-editor。 请参考这个示例

    1. 符号对高亮(可选)

这个仅适用于sora-editor。 请参考这个 示例

    1. 局部变量(可选)

对于适用于大多数语言局部变量的locals.scm 可以在nvim-treesitter存储库中找到。

相关链接:

创建语言详细说明

首先,TsLanguageSpec应该由tree-sitter语言实例和scm源文本创建。您可能需要为您的locals.scm添加一个自定义的LocalsCaptureSpec

Kotlin
val spec = TsLanguageSpec(
    // 您的tree-sitter语言实例
    language = TSLanguageJava.getInstance(),
    // scm原文本
    highlightScmSource = assets.open("tree-sitter-queries/java/highlights.scm")
        .reader().readText(),
    codeBlocksScmSource = assets.open("tree-sitter-queries/java/blocks.scm")
        .reader().readText(),
    bracketsScmSource = assets.open("tree-sitter-queries/java/brackets.scm")
        .reader().readText(),
    localsScmSource = assets.open("tree-sitter-queries/java/locals.scm")
        .reader().readText(),
    localsCaptureSpec = object : LocalsCaptureSpec() {
        // 覆盖和更改任何语言详细说明的方法
    }
)

有时,您的scm文件使用外部谓词方法(客户端谓词)来更好地查询语法树。在这种情况下,请将谓词实现添加到predicates参数中。

制作Language和主题

使用您的TsLanguageSpec和主题构建器DSL语法创建一个TsLanguage

Kotlin
// 在Kotlin中轻松制作文本样式的扩展功能
import io.github.rosemoe.sora.lang.styling.textStyle

// ...
val language = TsLanguage(languageSpec, false /* useTab */) {
    // 主题构建器DSL
    // 将文本样式应用于捕获的语法节点

    // 将样式应用于单一类型的节点
    textStyle(KEYWORD, bold = true) applyTo "keyword"
    // 应用于多个节点
    textStyle(LITERAL) applyTo arrayOf("constant.builtin", "string", "number")
}

应用Language

现在,Language实例可以应用于编辑器。

Kotlin
editor.setEditorLanguage(language)

请注意,TsLanguageSpec对象不可重复使用,因为TsLanguage被销毁时它也会被关闭。

基于 LGPL-2.1 许可发布