file_regex 用于 Sublime Text 3 中的 Cargo 构建

file_regex for Cargo builds in Sublime Text 3

问题

在 Sublime Text 3 构建系统中捕获 Cargo / Rustc 错误消息的正确 file_regex 是什么?我特别询问了相对较新的 cargo/rustc 版本(似乎旧版本使用了一种更容易解析的单行错误输出格式)。

例子

cargo build 在损坏的 hello-world 项目上的示例输出(默认代码由 cargo new --bin broken 自动生成,删除了一个双引号以创建一条错误消息):

error: unterminated double quote string
 --> src/main.rs:2:14
  |
2 |       println!("Hello, world!);
  |  ______________^
3 | | }
  | |__^

error: aborting due to previous error

error: Could not compile `broken`.

相对文件路径为src/main.rs,行列为2:14,我个人更希望直接在行间看到的错误信息为

unterminated double quote string

第一次解决方案尝试

这种方法有效,但它没有正确捕获错误消息,因为它们位于单独的一行上,带有文件路径和行号的行之前:

    {
        "name": "cargo build",
        "shell_cmd": "cargo build",
        "working_dir": 
          "/home/you/path/to/cargo/project/with/the/toml",
        "file_regex": 
          "^ *--> *([a-zA-Z0-9_/.]+):([0-9]+):([0-9]+)()$"
    },

第二次解决方案尝试

这个funny regex here that uses this awesome lookahead-with-matching-groups-trick:

error(?:\[E\d+\])?: (?=.+\n +--> +([a-zA-Z0-9_\/.-]+):([\d]+):([\d]+))(.+)

本来可以解决问题,但是 Sublime doesn't seem to work with multiline regexes.


答案的 TL;DR 版本是,不幸的是,我认为让 Sublime 识别您问题中的错误输出的唯一方法是使工具以不同的格式生成输出(直接或通过一些位于中间的过滤器应用程序)或通过为您的构建创建可以识别此错误输出的自定义构建目标(但它仅适用于内联错误,请参见下文)。

背景

Sublime Text 中的构建系统有两个正则表达式,可用于尝试匹配错误 output/build 结果,file_regexline_regex。两者都应用于输出面板中的构建结果,以获得构建结果列表。

file_regex 是用于匹配构建结果的 "general purpose" 正则表达式,应该包含 2 到 4 个捕获适当数据的捕获组。捕获组(按顺序使用)是 filenamelinecolumnmessage

line_regex 用于错误位置信息与错误 message/location 位于不同行的情况。这里的捕获顺序为 linecolumnmessage,您可以使用其中的 1 到 3 个。

在这两种情况下,捕获都是按照上面给出的顺序使用的,这可能需要有空的捕获组来确保事情按预期排列。

在正常使用中,您只需使用 file_regex 即可,它会为您捕获结果。如果你也使用 line_regex,那么 Sublime 会在内部匹配 line_regex,然后如果它找到一个匹配项,它会向后查看匹配 file_regex 的第一行的结果输出,并且将捕获的结果组合在一起。

本质上这意味着对 Sublime 可以捕获的结果类型有一些限制;文件名必须出现在有关错误的其他信息之前才能被捕获。

按摩输出

在您上面的示例中,首先显示错误,然后显示错误位置,因此本质上无法将两个正则表达式匹配项正确关联在一起。

解决此问题的最常见方法是修改工具的输出,以便将其重新处理为 Sublime 可以使用上述正则表达式检测的格式。

例如,您可能会更改您的构建,以便它执行您的命令并将结果通过管道传输到另一个 shell 脚本或程序,这些脚本或程序可以在信息通过时即时更改信息。另一个示例是更改您的构建,以便它调用一个 script/batch 文件来执行该工具并在内部进行输出更改,以便最终输出符合 Sublime 的预期。

自定义构建目标

虽然没有办法使用正则表达式系统来完全匹配你的构建输出,但如果你主要对显示的内联构建错误感兴趣,那么如果你想用一点 Sublime 弄脏你的手,还有一些办法插件开发。在这种情况下,您需要了解一些 Python。有关于 custom build targets as well as the Sublime API 的文档可用。

在内部,当你 运行 构建时,Sublime 从 sublime-build 文件中收集信息,扩展其中的任何变量,然后调用 exec 内部命令来实际执行提供 sublime-build 中的键作为参数的构建(一些,例如 selector 没有提供,因为 Sublime 会为你处理),并且是 exec 命令设置 file_regexline_regex 设置到输出缓冲区。

从这里开始,Sublime 核心直接使用应用的设置来对构建结果执行导航,例如通过单击结果打开文件,或使用导航命令转到下一个和上一个错误.

但是,exec 命令负责使用相同的结果向您显示内联构建错误,因此仍然可以使内联错误消息正常工作,尽管结果导航只能将您带到文件中的位置。

您可以在 sublime-build 文件中提供的键之一是 target,它指定应该执行构建的命令;如果未给出,则默认为 exec

通过创建自己的模仿 exec 功能的自定义命令并在 sublime-buildtarget 指令中使用它,您可以挂接到构建过程以捕获适当的数据.

exec命令存储在Default/exec.py中,您可以使用命令面板中的View Package File命令查看。

作为一个最小的例子,下面的插件定义了一个名为 cargo_exec 的新命令,它完全模仿了 exec 命令的作用。 self.output_view.find_all_results_with_text() 的调用是 API 调用,它使 Sublime 核心 return 来自构建输出视图的所有错误信息,用于设置用于内联构建错误的幻像.

通过修改该代码来检查缓冲区的内容并使用您对错误外观的自定义知识,核心 exec 命令中的其余代码将为您显示内联错误。

import sublime
import sublime_plugin

from Default.exec import ExecCommand

# Subclass the exec command to hook into the output processing.
class CargoExecCommand(ExecCommand):
    def run(self, **kwargs):
        # If we are being told to kill a running build, kill it right away
        # and leave.
        if kwargs.get("kill", False):
            return super().run(kill=True)

        # Use our super class to execute the build from this point.
        super().run(**kwargs)

    # override the super class method so we can handle output as it
    # arrives in the output panel.
    def service_text_queue(self):
        is_empty = False
        with self.text_queue_lock:
            if len(self.text_queue) == 0:
                # this can happen if a new build was started, which will clear
                # the text_queue
                return

            characters = self.text_queue.popleft()
            is_empty = (len(self.text_queue) == 0)

        self.output_view.run_command(
            'append',
            {'characters': characters, 'force': True, 'scroll_to_end': True})

        if self.show_errors_inline and characters.find('\n') >= 0:
            errs = self.output_view.find_all_results_with_text()
            errs_by_file = {}
            for file, line, column, text in errs:
                if file not in errs_by_file:
                    errs_by_file[file] = []
                errs_by_file[file].append((line, column, text))
            self.errs_by_file = errs_by_file

            self.update_phantoms()

        if not is_empty:
            sublime.set_timeout(self.service_text_queue, 1)


# Use the latest build results to add inline errors to newly opened files.
class CargoExecEventListener(sublime_plugin.EventListener):
    def on_load(self, view):
        w = view.window()
        if w is not None:
            w.run_command('cargo_exec', {'update_phantoms_only': True})

要将其用作自定义构建目标,您需要向 sublime-build 文件添加几个额外的键:

// Specify a custom build target to execute this build, and specify what
// argument to the command will cause it to cancel a build that is currently
// running.
"target": "cargo_exec",
"cancel": {"kill": true},

这个 AWK 脚本将错误消息与行号结合起来,并按照 Sublime 想要的顺序排列:

awk 'BEGIN { errmsg="" } /error(\[E[0-9]+\])?:.*/ {errmsg=[=10=]; next} /\ *-->\ *.*/ { printf "%s::::%s\n", [=10=], errmsg; next} {print [=10=]}'

输出与 following regex:

简单匹配
error(?:\[E\d+\])?: (?=.+\n +--> +([a-zA-Z0-9_\/.-]+):([\d]+):([\d]+))(.+)

您可以在 shell_cmd 中使用 | 和 AWK 脚本,因此此构建配置会捕获所有错误消息并将它们显示在正确的行号处:

{
    "name": "cargo build",
    "working_dir": "/wherever/your/project",
    "shell_cmd": "cargo build 2>&1 | awk 'BEGIN { errmsg=\"\" } /error(\[E[0-9]+\])?:.*/ {errmsg=\[=12=]; next} /\ *-->\ *.*/ { printf \"%s::::%s\n\", \[=12=], errmsg; next} {print \[=12=]}'",
    "file_regex": " +--> +([a-zA-Z_\/.-]+):(\d+):(\d+)::::(.*)"
}

这也以同样的方式对待 warnings:

"shell_cmd": "cargo build 2>&1 | awk 'BEGIN { errmsg=\"\" } /(error|warning)(\[E[0-9]+\])?:.*/ {errmsg=\[=13=]; next} /\ *-->\ *.*/ { printf \"%s::::%s\n\", \[=13=], errmsg; next} {print \[=13=]}'",