Vim 下使用 Slimv(類似Slime) 輕鬆除錯 Common Lisp

自由布魯斯發表於2016-06-01

Vim 下使用 Slimv(類似Slime) 輕鬆除錯 Common Lisp

目錄

  • 前情回顧
  • 安裝slimv
  • 資料配置
  • 預設快捷鍵
  • 實戰演練

前情回顧

在上一篇文章想法驗證:超輕量級全功能純文字介面 REPL 類語言 IDE: Vim+Tmux+Slimv中我們對於在 tmux 中新建視窗執行 swank 服務端的想法經過了手工驗證, 證明了我們的想法是可行的, 本文則完成實際的配置, 把所有的配置資訊都寫入到配置檔案中.

安裝slimv

如果你看過上一篇文章並且照著做了, 那你的 slimv 已經安裝好了. 如果沒有安裝的話可以按照下面的操作來安裝:

我們繼續通過 pathogen 來管理 slimv 外掛, 也就是說, 只要進入 .vim/bundle/ 目錄下, 把 slimvgit 克隆進去, 暫時不做配置, 因為我們要手動進行試驗, 安裝命令如下:

cd ~/.vim/bundle/
git clone 

安裝就這麼簡單, 如果對於 pathogen 的安裝有不清楚的地方可以看看本系列第一篇文章裡的描述超輕量級純文字介面 REPL 類語言 IDE.

接下來就是資料配置了.

資料配置

Linux 和 OSX 平臺下的配置

主要是對 vim 的配置, 因為我們要從vim 中啟動 slimvswank, 只要在你原來的 .vimrc 配置檔案中加入如下內容即可:

"Set mapleader 
let mapleader = ","

" slimv for clisp
let g:slimv_swank_cmd = '! tmux new-window -d -n REPL-CLISP "clisp -i ~/.vim/bundle/slimv/slime/start-swank.lisp"'

上面的配置呼叫了 clisp, 以下為 sbclccl 還有 ecl 的配置命令:

" slimv for sbcl 
let g:slimv_swank_cmd = '! tmux new-window -d -n REPL-SBCL "sbcl --load ~/.vim/bundle/slimv/slime/start-swank.lisp"'

" slimv for ccl
let g:slimv_swank_cmd = '! tmux new-window -d -n REPL-CCL "ccl -l ~/.vim/bundle/slimv/slime/start-swank.lisp"'

" slimv for ecl
let g:slimv_swank_cmd = '! tmux new-window -d -n REPL-ECL "ecl -load ~/.vim/bundle/slimv/slime/start-swank.lisp"'

Win32 平臺下的配置

Win32 下不使用 tmux, 直接按照官方配置文件的配置方法配置就可以了, 需要正確指定兩個目錄, 一個是 Common Lisp 安裝目錄, 一個是 slimv 安裝目錄

  • Common Lisp 安裝目錄: c:/Program Files/Lisp Cabinet/bin/ccl/
  • Slimv 安裝目錄: c:/Program Files/Lisp Cabinet/site/lisp/slime

配置命令如下:

let g:slimv_swank_cmd = '!start "c:/Program Files/Lisp Cabinet/bin/ccl/wx86cl.exe" -l "c:/Program Files/Lisp Cabinet/site/lisp/slime/start-swank.lisp"'

還有一個好訊息就是我們對 slimv 的資料配置跟原來的 vim-slime 的配置不衝突, 所以這兩個外掛的功能你可以同時使用, 只要通過不同的快捷鍵呼叫即可.

預設快捷鍵

官網的文件裡給出瞭如下快捷鍵:


                                            *slimv-keyboard*

有兩套快捷鍵可供選擇, 預設快捷鍵繫結第一套, 設定變數為:

g:slimv_keybindings=1

第二套設定變數為:

g:slimv_keybindings=2

注意前導鍵 預設設定為逗號(,), 當然你可以改為其他鍵, 設定全域性變數為:

g:slimv_leader

在圖形介面下每個選單項都會列出對應的快捷鍵, 不過我們是文字介面, 就沒有這個福利了.

vim 定義了快捷鍵序列的超時值, 如果你覺得自己手速慢, 來不及在規定的超時時間內輸完長長的快捷鍵序列, 那麼你也可以自己設定一下相關的超時時間, 直接設定 vim 裡對應的這幾個引數好了:

timeout
ttimeout
timeoutlen
ttimeoutlen

下面就是具體的快捷鍵了, Set#1 代表第一套, Set#2 代表第二套, Command 代表該快捷鍵對應的命令.

Set#1   Set#2    Command
---------------------------------------------------
,,      ,,       Slimv 選單

編輯命令 (Insert 模式):
<C-X>0           關閉形式
<Tab>            自動補全輸入的符號
<Space>          函式引數列表

編輯命令 (Normal 模式):
,)      ,tc      關閉形式
,(      ,(t      括號自動成對開關

求值命令:
["x],d  ["x],ed      求值 Defun (當前頂層) [放到暫存器 register x]
["x],e  ["x],ee      求值當前表示式 (當前子形式) [放到暫存器 reg. x]
["x],r  ["x],er      求值區域 (visual 選擇) [或者來自 register x 的文字]
,b      ,eb      求值緩衝區內所有內容
,v      ,ei      互動求值 (evaluates in frame when in SLDB)
,u      ,eu      未定義函式求值

除錯命令:
,1      ,m1      Macroexpand-1
,m      ,ma      Macroexpand All
,t      ,dt      Toggle Trace
,T      ,du      Untrace All
,B      ,db      Set Breakpoint
,l      ,dd      Disassemble
,i      ,di      Inspect (inspects in frame when in SLDB)
,a      ,da      Abort
,q      ,dq      Quit to Toplevel
,n      ,dc      Continue
,H      ,dl      List Threads
,K      ,dk      Kill Thread
,G      ,dg      Debug Thread

編譯命令:
,D      ,cd      Compile Defun
,L      ,cl      Compile and Load File
,F      ,cf      Compile File
["x],R  ["x],cr      Compile Region [or text from register x]

交叉引用命令:
,xc     ,xc      Who Calls
,xr     ,xr      Who References
,xs     ,xs      Who Sets
,xb     ,xb      Who Binds
,xm     ,xm      Who Macroexpands
,xp     ,xp      Who Specializes
,xl     ,xl      List Callers
,xe     ,xe      List Callees

效能測量命令:
,p      ,pp      Toggle Profile
,B      ,pb      Profile by Substring
,U      ,pa      Unprofile All
,?      ,ps      Show Profiled
,o      ,pr      Profile Report
,x      ,px      Profile Reset

文件命令:
,s      ,ds      Describe Symbol
,A      ,da      Apropos
,h      ,dh      Hyperspec
,]      ,dt      Generate Tags

Repl 命令:
,c      ,rc      Connect to Server
,y      ,ri      Interrupt Lisp Process

Set#1   Set#2    Command
---------------------------------------------------
,\      ,\       REPL 選單 (獨立選單, 僅在 REPL 緩衝區有效)

REPL 選單命令:
,.      ,rs      Send Input
,/      ,ro      Close and Send Input
,g      ,rp      Set Package
<C-C>   <C-C>    Interrupt Lisp Process
,<Up>   ,rp      Previous Input
,<Down> ,rn      Next Input
,-      ,-       Clear REPL

這些命令確實豐富, 基本上除錯程式是夠用了, 實在不夠用的話也可以在 REPL 區自己手動輸入相關的除錯命令, 稍微麻煩一些而已.

剩下的部分我們用一個例項研演示一下這個超輕量級全功能純文字介面的 REPL 開發環境有哪些功能, 主要參考自 slimv 的官方教程一, 二, 三:

Slimv Tutorial - Part One
Slimv Tutorial - Part Two
Slimv Tutorial - Part Three

實戰演練

REPL 區基本操作

首先要說說 paredit 這個外掛的效果, 它預設是開啟的, 最簡單就是自動為你補全括號, 比如你輸入一個左括號 (, 它會自動補全一個右括號 ), 控制開關在這裡, .vimrc 中增加:

let g:paredit_mode=0

它還有個功能叫 electric return, 當你輸入回車時, 會插入新行, 控制開關如下:

let g:paredit_electric_return=0

其次是 Common Lisp 中的幾個快捷鍵, 在 REPL 區使用:

*, **, ***:

可以得到 REPL 區上一次物件求值結果

+, ++, +++:

可以得到 REPL 區上一次求值的形式(表示式)

效果如下:

90 CL-USER> (+ 123 345)
91 468
92 CL-USER> *
93 468
94 CL-USER> **
95 468
96 CL-USER> ***
97 468
98 CL-USER> +
99 ***
100 CL-USER> ++
101 ***
102 CL-USER> (+ 123 345)
103 468
104 CL-USER> +
105 (+ 123 345)
106 CL-USER> ++
107 (+ 123 345)
108 CL-USER>
REPL  =============        

另外也支援歷史命令檢視: 可以在 Insert 模式下使用上下鍵

編輯原始檔

開啟彩虹括號

先在 ~/.vimrc 配置檔案中開啟 rainbow-parentheses, 用下面這條語句:

let g:lisp_rainbow=1

這樣你在編輯 lisp 檔案時就會發現你的不同層次的括號對會呈現不同的顏色, 同一層次的括號對會使用相同的顏色, 效果是這樣:

開始編輯程式碼

vim 建立一個新檔案, 命令如下:

vi ~/code-staff/morse.lisp

下面這是一個典型的顯示介面, 上半部分是 REPL 區, 下半部分是編輯區:

  3 CL-USER>
~                                                                                                                                               
~                                                                                                                                               
~ 
REPL                                                                                                                                            
  1 (defpackage :morse
  2   (:use :common-lisp)
  3   )
  4 
  5 (in-package :)
~        
~
~/code-staff/morse.lisp [+]
(in-package PACKAGE-NAME)
[0] 1:bash  2:vim* 3:bash  4:bash  5:bash- 6:REPL-CLISP        "Air.local" 19:45 31- 8-15

最下面一行[0] 1:bash 2:vim* 3:bash 4:bash 5:bash- 6:REPL-CLISP "Air.local" 19:45 31- 8-15tmux 的顯示資訊;
倒數第二行 (in-package PACKAGE-NAME) 是把游標放在編輯區的 in-package 上時 vim 給出的函式引數資訊提示;
倒數第三行 ~/code-staff/morse.lisp [+]vim 狀態列顯示的編輯區資訊.

截圖如下:

文件命令

把游標移動到 defpackage 上, 進入命令模式:

  • 輸入 ,s, 就可以檢視 defpackage 的詳細的資訊;

  • 輸入 ,h 開啟預設瀏覽器, 檢視 HyperSpec 中對 defpackage 的定義;

  • 輸入 ,AREPL 區呼叫 (apropos "defpackage");

  • 輸入 ,] 增加標籤(需要事先安裝好 ctags 外掛)

執行 ,A 的結果:

 10 CL-USER> (apropos "defpackage")
 11 DEFPACKAGE                                 macro
 12 COMMON-LISP::DEFPACKAGE-MODERNIZE
 13 COMMON-LISP::DEFPACKAGE-RECORD-SYMNAME
 14 ; No value
 15 CL-USER>

執行 ,s 的結果:

~/code-staff/morse.lisp [+]                                                                                                                     
DEFPACKAGE is the symbol DEFPACKAGE, lies in #<PACKAGE COMMON-LISP>, is accessible in 19 packages CLOS, COMMON-LISP, COMMON-LISP-USER, EXPORTING, EXT, POSIX, PXREF, REGEXP, SCREEN, SWANK, SWANK-LOADER, SWANK-MONITOR, SWANK-REPL, SWANK/BACKEND, SWANK/CLISP, SWANK/GRAY,SWANK/MATCH, SWANK/RPC, SYSTEM, names a macro, has 1 property SYSTEM::DOCHTTP/1.1 404 Not FoundHTTP/1.1 200 OK
.
ANSI-CL Documentation is at "http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/mac_defpackage.html"HTTP/1.1 301 Moved PermanentlyHTTP/1.1 200 OK

CLISP Documentation is at "http://clisp.cons.org/impnotes/pack-intro.html#defpack" For more information, evaluate (SYMBOL-PLIST 'DEFPACKAGE).

 #<PACKAGE COMMON-LISP> is the package named COMMON-LISP. It has 2 nicknames LISP, CL.
 It imports the external symbols of 1 package CLOS and exports 978 symbols to 18 packages SWANK-REPL, SWANK, SWANK/RPC, SWANK/MATCH,SWANK/GRAY, SWANK/CLISP, SWANK-MONITOR, PXREF, SWANK/BACKEND, SWANK-LOADER, REGEXP, POSIX, EXPORTING, SCREEN, CLOS, COMMON-LISP-USER, EXT,SYSTEM.

 #<MACRO #<COMPILED-FUNCTION DEFPACKAGE> (&WHOLE SYSTEM::WHOLE-FORM SYSTEM::PACKNAME &REST SYSTEM::OPTIONS)> is a macro expander. Argument list: (&WHOLE SYSTEM::WHOLE-FORM SYSTEM::PACKNAME &REST SYSTEM::OPTIONS) For more information, evaluate (DISASSEMBLE (MACRO-FUNCTION 'DEFPACKAGE)).

Documentation:
SYSTEM::IMPNOTES:
"pack-intro.html#defpack"
CLHS:
"Body/mac_defpackage.html"
SYSTEM::FILE:
((SYSTEM::DEFUN/DEFMACRO #P"/opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_release_tarballs_ports_lang_clisp/clisp/work/clisp-2.49/src/defpackage.fas" 11 202))
Press ENTER or type command to continue

求值命令

現在開始除錯我們的小程式, 先求值第一個形式, 把游標放在 (defpackage ...) 形式體內任何地方, 然後輸入 ,d, 我們的這個形式體就被髮送到 REPL 並且完成求值, 這個命令會求值頂層的形式, 結果如下:

 15 CL-USER> (defpackage :morse
 16   (:use :common-lisp)
 17   )
 18 #<PACKAGE MORSE>
 19 CL-USER> 

現在再把游標移到 (in-package ...) 內並且輸入 ,e, 這個命令將會在 REPL 緩衝區求值當前的 S-表示式, 結果如下:

 19 CL-USER> (in-package :morse)
 20 #<PACKAGE MORSE>
 21 CL-USER>

我們可以在 REPL 輸入 (find-package :morse) 來檢查是否生效, 還可以輸入 ,g 把當前包設定為 :morse 並且進入該包, 這個改變會通過 REPL 提示符的變化(變為 morse)以及變數 *package* 反映出來:

 30 #<PACKAGE MORSE>
 31 CL-USER> 
 32 MORSE>*package* 
 33 #<PACKAGE MORSE>
 34 MORSE> 

現在讓我們加入莫爾斯碼對映函式, 在我們輸入 defparameter空格 後, 函式引數列表會出現在狀態列. 這個功能對於所有使用者定義的函式也有效, 不僅僅是那些內建函式.

  5 (in-package :morse)
  6 (defparameter )
~
~                                                                                                                                               
~/code-staff/morse.lisp [+]                                                                                                                     
(defparameter &WHOLE WHOLE-FORM SYMBOL INITIAL-VALUE &OPTIONAL DOCSTRING)
[0]  1:bash  2:vim* 3:bash  4:bash  5:bash- 6:REPL-CLISP 

現在開始填充莫爾斯對映表, 可以從網路上搜尋到, 這裡可以對程式碼進行自動縮排, 選擇好區域後按 = (貌似這裡我的表現跟教程的不太一致,先寫, 後面再找原因)

  5 (in-package :morse)
  6 (defparameter *morse-mapping*
  7   '((#\A ".-")
  8     (#\B "-...")
  9     (#\C "-.-.")
 10     (#\, "--..--")
 11     (#\? "..--..")
 12     )
 13   )
 14 
 15 (defun character-to-morse (character)
 16   (assoc character *morse-mapping* :test #'char-equal)
 17   )
 18 
~      

輸入單獨的左括號

然後我們意識到我們只需要返回值的第二部分, 所以我們需要把 cdr 放在 (assoc ...) 前面,但是因為有 paredit 的緣故,我們無法輸入一個單獨的左括號 (, 因為它會自動輸入成對的括號 (), 把游標移到最前面的左括號, 也就是這個 (assoc, 然後按下 ,w,W(在我們的環境下大寫 W 有效, 小寫無效), 它會在 S-表示式 外面用一對新括號把表示式括起來(paredit wrap), 現在我們就可以輸入 cdr 了:

 13 (defun character-to-morse (character)
 14   (cdr (assoc character *morse-mapping* :test #'char-equal)))

paredit wrap 的相反操作是 splice, 通過按下 ,s, 它會刪除掉最外層的括號, 還有一些類似的命令:

  • 輸入 ,o 切分S-表示式 Split S-expression
  • 輸入 ,J 加入S-表示式 Join S-expression
  • 輸入 ,I 提升子形式 Raise subform
  • 輸入 ,< 括號左移 Move left
  • 輸入 ,> 括號右移 Move right

編譯

現在編譯我們的程式碼, 輸入 ,D, 我們還可以編譯和載入整個程式碼原始檔:

  • ,F 編譯整個檔案
  • ,L 編譯並且載入整個檔案

結果如下:

 42 MORSE>
 43 
 44 Compilation finished. (No warnings)  [0.003131000092253089 secs]
 45 
 46 MORSE>
 47 
 48 Compilation finished. (No warnings)  [0.002271000063046813 secs]
 49 
 50 MORSE> ;; Compiling file /Users/admin/code-staff/morse.lisp ...
 51 ;; Wrote file /Users/admin/code-staff/morse.fas
 52 0 errors, 0 warnings
 53 MORSE>
 54 
 55 Compilation finished. (No warnings)  [0.03273700177669525 secs]
 56 
 57 MORSE>

自動補全

現在是時候測試一下我們的 character-to-morse 函式了, 輸入 C-w w從程式碼編輯區切換到 REPL 緩衝區, 輸入 char 接著按下 Tab 鍵, 你將會看到彈出一個可能的自動完成列表, 截圖如下:

如果你繼續輸入更多字元, 彈出選單中的選項會自動縮小範圍匹配, 截圖如下:

預設呼叫的方法是模糊補全 fuzzy completion, 因此你甚至可以輸入 ctm 再按 Tab (ctm 是 character-to-morse 的首字母)

這種補全方法在 REPL 緩衝區也不受限制, 在程式碼編輯區它以同樣的方式工作, 直到 slimv 連線到 swank 伺服器上.

順便說一句, 還有另一種 vim 的自動補全, 通過按 C-pC-n, 這種補全方式會在當前緩衝區查詢相同的單詞字首, 這種方式對於補全那些不是符號名的單詞比較有用, 比如註釋或者字串裡的某些文字.

  • 輸入 Ctrl p 向前查詢
  • 輸入 Ctrl n 向後查詢

我們選擇了正確的補全完成函式呼叫:

 98 MORSE> (character-to-morse #\a)
 99 (".-")
100 MORSE> (character-to-morse #\b)
101 ("-...")
102 MORSE> (character-to-morse #\c)
103 ("-.-.")
104 MORSE> 

我們得到的是一個列表, 裡面包含一個字串, 但是我們需要的結果是一個字串, 我們意識到我們應該用 second 來代替函式中的 cdr, 因此我們相應地修改程式碼並且按下 ,d 重新求值這個 defun

 13 (defun character-to-morse (character)
 14   (second (assoc character *morse-mapping* :test #'char-equal)))

切換回 REPL 緩衝區, 在 Insert 模式下按下 向上 箭頭來重新呼叫最後一條命令, 然後輸入回車求值:

104 MORSE> (defun character-to-morse (character)
105   (second (assoc character *morse-mapping* :test #'char-equal)))
106 CHARACTER-TO-MORSE
107 MORSE> (character-to-morse #\c)
108 "-.-."
109 MORSE> 

好極了, character-to-morse 現在返回了作為引數輸入的字元的莫爾斯程式碼串.

截圖不完整, 後面補

相關文章