SublimeText构建系统简介及若干配置示例

有兄弟来问关于\(Sublime\ Text\)构建系统配置的问题,本文进行一些整理并给出一些例子

\(Sublime\ Text\)构建系统简介

\(Sublime\ Text\)提供构建系统(\(Build\ Systems\))以允许用户运行外部程序,构建系统通过\(json\)的形式指定并保存在\(sublime-build\)文件中,功能包括:根据文件类型自动选择构建系统、记住上次使用的构建系统、构建系统结果的导航、取消执行构建

\(Sublime\ Text\)中,用户新增构建系统的方式为工具-构建系统-新建构建系统,新建的构建系统位于:

  • 安装目录下Data/Packages/User中(\(Windows\)下)
  • 用户文件夹下.config/sublime-text-3/Packages/User中(\(Linux\)下)

下文主要整理一些关键/常用/官方文档没有详细涉及的内容,\(Sublime\ Text\)构建系统的完整介绍可参考官方文档

修改\(Sublime\ Text\)内置构建系统

\(Sublime\ Text\)内置了一些常见语言的构建系统,内置的构建系统使用\(Sublime\ Text\)提供的控制台,其不同于\(VSCode\),无法处理用户输入,可能的解决方包括

  • 使用\(Sublime\ REPL\)插件,其性能较差,个人不是很推荐
  • 修改内置构建系统,改为使用其它终端,例如\(cmd\)、\(gnome-terminal\)、\(kconsole\)等

\(Sublime\ Text\)内置的构建系统位于\(Sublime\ Text\)安装目录下\(Packages\)目录中的\(sublime-package\)文件内,\(sublime-package\)文件实质上是压缩文件,用解压软件打开即可修改其中的\(sublime-build\)文件,这样就无需通过新建一个构建系统的方式“曲线救国”

需要注意的是\(C\)和\(C++\)的配置均位于\(C++.sublime-package\)中,此外如果一个\(sublime-package\)文件中没有对应的\(sublime-build\)文件,向其中添加\(sublime-build\)文件后,该\(sublime-build\)文件对应的构建系统可以正常加载

个人建议长期使用的构建系统以内置构建系统的方式存储,临时使用的构建系统使用工具-构建系统-新建构建系统的方式添加

\(sublime-build\)部分字段解释

  • cmd字段
    • shell_cmd字段为空时需要,会被shell_cmd字段覆盖
    • cmd字段在\(Sublime\ Text\)中的执行方式是subprocess.Popen(cmd)
  • shell_cmd字段
    • cmd字段为空时需要,会覆盖cmd字段
    • cmd字段在\(Sublime\ Text\)中的执行方式是subprocess.Popen(shell_cmd, shell=True)
  • shell字段
    • shell_cmd字段为空时有效,用来影响cmd字段
    • 设置此字段时,cmd字段中的命令将通过\(Shell\)运行,似乎可以认为cmd + shell = shell_cmd
  • file_regex字段
  • selector字段
    • 设置了工具-构建系统-自动之后,根据此字段选择使用的构建系统
    • 需要注意的是,这个字段对应的不是文件扩展名,而是视图-语法中的语法名
  • variants字段
    • 用于在一个\(sublime-build\)中设置多个构建选项,可以通过工具-用...构建选择
    • 非首次使用时,工具-构建会使用上一次使用的构建选项,无需每次构建时指定构建选项

\(C\)配置及测试

文件名、行号/行内偏移、错误信息都可以抓取到

  • \(sublime-build\)配置(以\(Windows\)为例)

    {
        "encoding": "utf-8",
        "working_dir": "$file_path",
        "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
        "selector": "source.c",
        "variants": [
            {
                "name": "Run",
                "shell_cmd": "gcc -Wall -std=c99  $file -o $file_base_name && $file_path/$file_base_name"
            },
            {
                "name": "RunInCmd",
                "shell_cmd": "gcc -Wall -std=c99  $file -o $file_base_name && start cmd /c \"$file_path/$file_base_name & pause\""
            }
        ]
    }
    
  • \(HelloWorld\)测试代码

    #include <stdio.h>
    
    int main() {
        puts("Hello World");
        return 0;
    }
    
  • \(A\ +\ B\ Problem\)测试代码

    #include <stdio.h>
    
    int main() {
        int a, b;
        scanf("%d%d", &a, &b);
        printf("%d\n", a + b);
        return 0;
    }
    
  • 出错测试代码

    int main() {
        1 + 2
        return 0;
    }
    

\(C++\)配置及测试

文件名、行号/行内偏移、错误信息都可以抓取到

  • \(sublime-build\)配置(以\(Windows\)为例)

    {
        "encoding": "utf-8",
        "working_dir": "$file_path",
        "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
        "selector": "source.c++",
        "variants": [
            {
                "name": "Run",
                "shell_cmd": "g++ -Wall -std=c++0x  $file -o $file_base_name && $file_path/$file_base_name"
            },
            {
                "name": "RunInCmd",
                "shell_cmd": "g++ -Wall -std=c++0x  $file -o $file_base_name && start cmd /c \"$file_path/$file_base_name & pause\""
            }
        ]
    }
    
  • \(HelloWorld\)测试代码

    #include <iostream>
    using namespace std;
    
    int main() {
        cout << "Hello World" << endl;
        return 0;
    }
    
  • \(A\ +\ B\ Problem\)测试代码

    #include <iostream>
    using namespace std;
    
    int main() {
        int a, b;
        cin >> a >> b;
        cout << a + b << endl;
        return 0;
    }
    
  • 出错测试代码

    int main() {
        1 + 2
        return 0;
    }
    

\(Go\)配置及测试

文件名、行号/行内偏移、错误信息都可以抓取到

\(Go\)支持解释执行和构建执行,此处选择构建执行,以获取报错信息

  • \(sublime-build\)配置(以\(Windows\)为例)

    {
        "encoding": "utf-8",
        "working_dir": "$file_path",
        "file_regex": "^([^#][^#][^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
        "selector": "source.go",
        "variants": [
            {
                "name": "Run",
                "shell_cmd": "go build $file && $file_path/$file_base_name"
            },
            {
                "name": "RunInCmd",
                "shell_cmd": "go build $file && start cmd /c \"$file_path/$file_base_name & pause\""
            }
        ]
    }
    
  • \(HelloWorld\)测试代码

    package main
    
    import "fmt"
    
    func main() {
        fmt.Println("Hello World")
    }
    
  • \(A\ +\ B\ Problem\)测试代码

    package main
    
    import "fmt"
    
    func main() {
        var a, b int
        fmt.Scanf("%d%d", &a, &b)
        fmt.Println(a+b)
    }
    
  • 出错测试代码

    package main
    
    func main() {
        1 +
    }
    

\(Java\)配置及测试

文件名、行号、错误信息都可以抓取到,但由于行内偏移是通过^指示的形式给出,因此无法抓取到

此外一个坑点是需要使用cp936编码解决乱码问题

  • \(sublime-build\)配置(以\(Windows\)为例)

    {
        "encoding": "cp936",
        "working_dir": "$file_path",
        "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
        "selector": "source.java",
        "variants": [
            {
                "name": "Run",
                "shell_cmd": "javac $file -encoding utf-8 && java $file_base_name"
            },
            {
                "name": "RunInCmd",
                "shell_cmd": "javac $file -encoding utf-8 && start cmd /c \"java $file_base_name & pause\""
            }
        ]
    }
    
  • \(HelloWorld\)测试代码

    public class Java_HelloWorld {
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    
  • \(A\ +\ B\ Problem\)测试代码

    import java.util.Scanner;
    
    public class Java_A_PLUS_B {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(system.in);
            System.out.println(scanner.nextInt() + scanner.nextInt());
        }
    }
    
  • 出错测试代码

    public class Java_Error {
        public static void main(String[] args) {
            1 + 2
        }
    }
    

\(JavaScript\)配置及测试

\(JavaScript\)是解释型语言,错误信息将在执行时产生,因此没有必要配置file_regex字段

  • \(sublime-build\)配置(以\(Windows\)为例)

    {
        "encoding": "utf-8",
        "working_dir": "$file_path",
        "selector": "source.js",
        "variants": [
            {
                "name": "Run",
                "shell_cmd": "node $file"
            },
            {
                "name": "RunInCmd",
                "shell_cmd": "start cmd /c \"node $file & pause\""
            }
        ]
    }
    
  • \(HelloWorld\)测试代码

    console.log('Hello World')
    
  • \(A\ +\ B\ Problem\)测试代码

    const readline = require('readline')
    
    function read() {
        return new Promise((resolve) => {
            const reader = readline.createInterface({
                input: process.stdin,
                output: process.stdout
            })
            reader.question('', (answer) => {
                reader.close()
                resolve(answer.trim())
            })
        })
    }
    
    read().then((line) => console.log(line.split(' ').map(x => parseInt(x)).reduce((a, b) => a + b, 0)))
    
  • 出错测试代码

    alert(1 + 2)
    

\(Python\)配置及测试

\(Python\)是解释型语言,错误信息将在执行时产生,因此没有必要配置file_regex字段

  • \(sublime-build\)配置(以\(Windows\)为例)

    {
        "encoding": "utf-8",
        "working_dir": "$file_path",
        "selector": "source.python",
        "variants": [
            {
                "name": "Run",
                "shell_cmd": "python -u $file"
            },
            {
                "name": "RunInCmd",
                "shell_cmd": "start cmd /c \"python -u $file & pause\""
            }
        ]
    }
    
  • \(HelloWorld\)测试代码

    print('Hello World')
    
  • \(A\ +\ B\ Problem\)测试代码

    a, b = input().split()
    print(int(a) + int(b))
    
  • 出错测试代码

    print('' + 1)
    

\(Scala\)配置及测试

文件名、行号/行内偏移、错误类型都可以抓取到,但更详细的错误信息和其它信息并不在同一行,因此无法抓取,此时\(Sublime\ Text\)将匹配file_regex的整行作为错误信息

\(Scala\)支持直接解释执行和构建后解释执行,此处选择构建后解释执行,以获取报错信息

此外一个坑点是需要使用-color避免抓取到用于控制颜色的乱码

  • \(sublime-build\)配置(以\(Windows\)为例)

    {
        "encoding": "utf-8",
        "working_dir": "$file_path",
        "file_regex": "^.*? (..[^:]*):([0-9]+):?([0-9]+)? .*$",
        "selector": "source.scala",
        "variants": [
            {
                "name": "Run",
                "shell_cmd": "scalac -explain -explain-types -color never $file && scala $file_base_name"
            },
            {
                "name": "RunInCmd",
                "shell_cmd": "scalac -explain -explain-types -color never $file && start cmd /c \"scala $file_base_name & pause\""
            }
        ]
    }
    
  • \(HelloWorld\)测试代码

    object Scala_HelloWorld extends App {
        println("Hello World")
    }
    
  • \(A\ +\ B\ Problem\)测试代码

    object Scala_A_PLUS_B extends App {
        println(scala.io.StdIn.readLine().split(" ").map(_.toInt).sum)
    }
    
  • 出错测试代码

    object Scala_Error extends App {
        1 + solve()
    }
    

在\(Linux\)下完成配置

只需要调整shell_cmd字段,使用相应的终端,并指定终端启动后需要自动执行的命令即可

以\(Python\)为例,在使用gnome-terminal(为\(Gnome\)桌面系统认)时,命令行可以是:

gnome-terminal -- bash -c \"if [ -f '${file_path}/${file_base_name}' ]; then rm '${file_path}/${file_base_name}'; fi; python3 -u '${file}'; read -p 'Process Exit, Press any key to quit...'\"

以\(Python\)为例,在使用konsole(为\(KDE\)桌面系统认)时,命令行可以是:

konsole -e bash -c \"if [ -f '${file_path}/${file_base_name}' ]; then rm '${file_path}/${file_base_name}'; fi; python3 -u '${file}'; read -p 'Process Exit, Press any key to quit...'\"

相关文章

Centos系统之Shell编程基础知识
从Export理解Shell环境和变量生存期
linux shell数组变量、类型及规则
Centos编程Shell基本工作原理方案
Centos操作系统编程之Shell 问答录
rsync-linux备份脚本