最近开始玩Sublime Text,目前还在Evaluate阶段,逐步尝试用它替换掉原来Vim。期间实时语法检查用到了Sublime Linter这个插件,感觉还是蛮不错的。但是用于用了RVM管理Ruby版本,因此不能让Sublime Linter使用系统级的Ruby了。对此其实Sublime Linter是做了准备的,在它的README中就有这样一句:

If you are using rvm or rbenv, you will probably have to specify the full path to the ruby you are using in the "sublimelinter_executable_map" setting. See "Configuring" below for more info.

一般是要求修改sublimelinter_executable_map使之为$HOME/.rvm/bin/rvm-auto-ruby,这么做得话,显然就可以用RVM默认的Ruby版本了。但是这样对我而言还是不够的,由于工作的原因,我不同的项目都使用不同的ruby版本,而默认则是Ruby 1.8.7,当我在编写Ruby 1.9.3的代码时,老是按照Ruby 1.8.7来做语法检查,显然会出现大把大把的错误。(这个不是SublimeLinter的工作不到位,要知道以前用Vim的时候根本没得选。。)

对我这种Case而言,最好的方法是根据文件所在项目目录里的.rvmrc文件来判断这个文件该使用哪个Ruby版本。为此我写了一个shell脚本(起名叫rvm_use):

#!/usr/bin/env bash

[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"
for i in "${@:2}"; do
    if [[ ! "$i" =~ ^- ]]; then
        cd `dirname $i`
        break
    fi
done
$@

这个脚本接受一行完整的Shell命令,比如"ruby -wc ...",然后从第二个参数开始(第一个参数往往是ruby或是rspec这样的可执行文件),如果检测到第一个首字母不是 '-' 的参数,认为这个参数就是项目文件所在位置,用cd命令进入到这个文件所在的目录(rvm已经hack了cd命令,只要进入的目录有.rvmrc就会切换到它指定的Ruby版本),然后执行用户送入的命令。

刚才这个脚本已经适用于Sublime Build之类同样涉及到Ruby的功能,但是要给SublimeLinter用的话,还要额外加一个脚本,这是因为SublimeLinter认为解释器命令一定只有一个参数,如果我把解释器设为 'rvm_use ruby' 的话将会出错。因此额外增加一个rvm_use_ruby脚本:

#!/usr/bin/env bash

`dirname $0`/rvm_use ruby $@

让Sublime Linter使用这个脚本就行了。

本来以为这么做就足够了,但是后来发现依然不够。调试发现Sublime Linter依然没有使用到正确的Ruby版本,阅读插件的Python源码发现(感谢不久前的Python Training,多少掌握了一些Python),Sublime Linter为了尽可能实现实时检测,提供了三种检查策略,通过文件检查,通过临时文件检查,通过STDIN检查。对于C语言这样比较死板的语言来说,文件必须先被保存,然后让检查工具去检查这个被保存的文件的语法才能得到正确的结果。Java则稍微高级一点,无需等到保存,而是在检查前将当前还未保存的文件偷偷写入到一个临时文件,然后检查那个临时文件的语法。至于Ruby由于是脚本语言,支持直接用STDIN将代码输入进去,这样同样无需保存文件即可实现实时检查。但是这个做法显然就无法应用我之前的办法了。因为rvm_use命令参数中将没有一个文件名,也就无从知晓应该用的Ruby版本了。

对于这种情况,一种可行的办法是修改SublimeLinter/sublimelinter/modules/ruby.py,修改它的CONFIG中的"input_method"参数(抱歉,没找到针对User目录的修改方法,只能改源文件了),但是这并不是一种理想的办法。因为这样就彻底失去了实时检查的功能,我并不想做这样的妥协。因此既然要改SublimeLinter/sublimelinter/modules/ruby.py文件,何不改的再彻底点,针对Ruby彻底重写executable_check方法?修改后的文件是这样的:(再次感谢Python Training)

# -*- coding: utf-8 -*-
# ruby.py - sublimelint package for checking ruby files

import re
import os
import subprocess

from base_linter import BaseLinter

CONFIG = {
    'language': 'Ruby',
    'executable': 'ruby',
    'lint_args': '-wc'
}


class Linter(BaseLinter):
    def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
        for line in errors.splitlines():
            match = re.match(r'^.+:(?P<line>\d+):\s+(?P<error>.+)', line)

            if match:
                error, line = match.group('error'), match.group('line')
                self.add_message(int(line), lines, error, errorMessages)

    def executable_check(self, view, code, filename):
        args = [self.executable]
        args.extend(self._get_lint_args(view, code, filename))
        dirname = os.path.dirname(filename)

        process = subprocess.Popen(['bash', '-c',
                               'source $HOME/.rvm/scripts/rvm && \
                                cd ' + dirname + ' && '\
                                + '  '.join(args)],
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT,
                               startupinfo=self.get_startupinfo())
        process.stdin.write(code)
        result = process.communicate()[0]

        return result.strip()

这里用bash -c启动一个独立环境,在这个环境里执行source命令后用被rvm hack过的cd命令进入项目文件所在文件夹,然后再执行检查命令。这样就可以完美解决问题了。

注意Sublime的配置文件一定要被git托管,否则一旦源码因为升级而还原的话所有修改就都丢失了。