Vim for Scheme

最近开始阅读Structure and Interpretation of Computer Programs(SICP)这一本书,接触到Scheme这一门Lisp家族的语言。Lisp语言的编程很多时候都采用了Read-Eval-Print Loop(REPL)这一种编程方式。REPL其实就是我们平常所见到的交互式编程环境,譬如说python也是可以采用REPL的,在Linux的shell里面输入python,我们就可以进入一个python的REPL编程环境。但是如果能在vim中REPL就更好了。

> (+ 1 1)
2

Why vim

一般来说,Scheme的许多实现都带有一个解释器来支持REPL,但是Scheme并不是一门脚本语言,它是需要编译的,我们还是希望能够把源码放在一个文件里面(对于python这样的脚本语言也是这样的)。于是我想有一个开发环境来支持REPL这样的操作。DrRacket是不错的选择,但是它只能支持Racket,如果能把vim改造一下,变成一个支持REPL的IDE就再好不过了。

再说,如果能在vim里面写scheme,那么就可以使用各种插件,什么自动补全、状态栏等等,还有vim的各种操作。所以说vim应该能够成为一个比较好的开发平台。

vim plugins

for Lisp

首先就是,vim上的支持Lisp家族语言的插件相对比较少,好像emacs支持得比较好。但是vim上总会有解决方案的。

通过一段时间搜索,找到了两个,slimv和vim-slime。

slimv是试图把emacs里面的SLIME移植到vim上,我尝试使用了一下,但是好像没有配置成功(可能是不支持我使用的ChezScheme)。

vim-slime也是受SLIME启发,但是它只实现简单的文本传输的功能。具体来说,就是它会把vim的buffer里面特定部分的文本发送到解释器那里,然后接下来的事情就是由解释器来完成。这个插件的好处在于可塑性非常强,只要是个能打字的解释器就能用。而且现在它支持GNU Screen、tmux、vim terminal、neovim terminal,也就是说可以把解释器和vim放在同一个终端里面,并排着,使用起来特别好。

vim-slime

vim-slime的安装方法以及各种文档可以在github上找到。其实就是复制两个文件到vim的运行环境就可以了。

Arch Linux用户可以用我打的包vim-slime

配置也十分简单。vim-slime默认发送文本到GNU Screen,而其它的话就要在vimrc里面配置一下。我选择使用vim的内置终端(vim8的新特性)来运行解释器。这时候只要在vimrc加上

"vim-slime
let g:slime_target = "vimterminal"

就算是配置完成了。每次运行时vim-slime还会有提示,按提示输入相应的参数就可以了。

配置完后,按下<c-c><c-c>(按两下Ctrl+C),就可以将一个段落的代码发送到解释器去了。当然还可以选中一些文本,然后<c-c><c-c>,就可以只发送选中的文本(这样不用每一次都让解释器从头跑一遍整段代码)。

下面是配置好后的vim

vim-slime auto start

但是,每一次我们要使用REPL的时候,我们都要在vim-slime的菜单里面输入我们要运行的解释器,这样还不够方便。最好当然是一打开Scheme的源文件,整个REPL的开发环境就已经出现了,不需要再手动去输入什么东西。于是我就开始加一些vim script,让整个过程自动化。最终得到的脚本是这样的

function SchemeStart()
    "only if the buffer is a scheme file and no terminal exists
    if &filetype == "scheme" && len(term_list()) == 0
        "only start once
        autocmd! SchemeREPL *
        augroup! SchemeREPL

        "get original buffer number
        let original_bufnr = win_getid()
        "start the terminal, with scheme running
        let term_bufnr = term_start('scheme', {"term_finish": "close"})
        "get back to the original buffer
        call win_gotoid(original_bufnr)

        "no vim-slime menus
        if !exists("b:slime_config")
               let b:slime_config = {"bufnr": ""}
        endif
        let b:slime_config["bufnr"] = term_bufnr
    endif
endfunction

function SchemeSetUp()
    "everytime a new buffer is read, try to start REPL
    augroup SchemeREPL
        autocmd BufRead * :call SchemeStart()
    augroup END
    "the time when vim is ready
    call SchemeStart()
endfunction

augroup SchemeREPL
    "when vim is ready, start the script
    autocmd VimEnter * :call SchemeSetUp()
augroup END

我用了一些autocmd来在vim加载完成后再执行相应的脚本,同时也能实现在打开vim后,在vim里再打开Scheme文件的时候自动打开REPL环境。脚本会自动打开终端并运行Scheme解释器,之后通过设置vim-slime的一个变量,来绕过vim-slime的输入菜单。

至此,我的Vim for Scheme就算是配置完全了。

more about REPL

正如前面所说,vim-slime只是简单的把文本发送到一个解释器,这就意味着这个插件不仅可以用在Scheme上面,也可以用在其他地方,譬如说shell脚本、python、SQL等等,都可以用到这个插件,操作方法和上述的基本一模一样,只要把终端里面打开的解释器换成所需要的(如sh,python,mysql)即可。

buffer centric workflow

其实上面配置的这个REPL环境是用了所谓buffer centric workflow,编写代码的动作都是在缓存(vim)里面完成的,而只有在执行求值的时候代码才会被发送到解释器。这样一个workflow的好处在于,能够利用解释器来验证代码的正确性,即时的看到代码的运行结果,同时也能方便的将写好的代码保存到硬盘上。

more vim plugins

下面是一些vim插件推荐

  • vim-airline
  • SuperTab