VimでのSchemeプログラミング

LispSchemeがとっつきにくく感じられる理由の一つに、プログラミング支援機能がまったくない状態だとすごくプログラムが書きにくいことがあるんじゃないかと思う。インタプリタを立ち上げてみてもカッコの対応表示機能がないとちょっとした式の入力も苦労する。rlwrapを使えばカッコ対応表示は解決するけど、Lisp向けのオプション設定がいくらか必要になる(→「改めてGaucheとrlwrapの連携について」)。ウェブブラウザに入力することになったらエディタで書いてコピペするのが一番現実的なやり方になってしまう。
エディタでプログラムを書くにしても、変数名に使える文字とかインデントの仕方などLisp独特の癖があるので、エディタの提供するプログラミング支援機能が(素の設定だと)うまく動かないといった苦労が出てくる。たぶんそうした苦労や敷居の高さの多くはEmacsを使えばなくなるのだろうけど、人によってはEmacsを選択することが新たな敷居になる。そしてEmacs以外でLispSchemeを書こうとすると一気に情報が少なくなる。
Vimは比較的情報が多い気がするけど、それでもEmacsに比べるとかなり少ない。なのでVimLispSchemeを書くためのメモのまとめ。

Vimの設定の仕方

基本的に設定は「.vimrc」ファイルに書いておくか、exモードで直接設定する。特定の種類のファイルに対してだけ設定したい場合は、

autocmd FileType ファイルのタイプ 実行したい内容
" または「au FileType ファイルのタイプ 実行したい内容」

と書く。ファイルタイプは拡張子で決まるけど

set filetype=scheme  " または「set ft=scheme」

のようにして明示的に与えることもできる。
Vimプラグインは「~/.vim/」以下の適切なディレクトリ(たいていpluginディレクトリ+α)におけば、機能を利用できる。

最小限の設定 (カッコの対応表示、インデント)

(追記) 最小限の設定をするには「.vimrc」に

filetype plugin indent on

と書く。これでファイルタイプがlispschemeのときは、「lisp」オプションや後述する「iskeyword」の値、「autoindent」をセットしてくれる。これらのオプションの機能説明は以下でされる(追記終わり)
LispSchemeのプログラムを書く場合、「showmatch」「lisp」をセットする(Vimのバージョンによってはshowmatchは不要)。vimを-lオプション付きで起動した場合「showmatch」と「lisp」はあらかじめセットされる。
lisp」オプションをセットすると、インデントやカッコの対応付けがLispSchemeに(ある程度)対応した設定になる。例えば行コメントや文字列中のカッコは、カッコの対応付けから外される。

;; 「%」コマンドを使うと、最初の「(」から最後の「)」にジャンプする。
(list 1 2 3 "(" 4  ;  ( 
                       5 6)
                       ;; インデントは多少おかしい
カッコの対応表示

バージョンによっては設定しなくても初めからカッコの対応表示がおこなわれる。設定がいる場合、showmatchオプション(省略名 sm)を設定する。

set showmatch     " または「set sm」

とする。
カーソルが対応するカッコへ飛ぶのをやめたい場合は、さらに

set matchtime=0

も追加する(与えた数字×1/10 秒間、対応するカッコにカーソル表示が飛ぶ)。
(※ ファイルタイプがlisp

let lisp_rainbow=1

とするとカラフルなカッコ対応表示になるのだけど、ファイルタイプschemeでは使えないみたい)

S式コメントについての注意

Vimのカッコ対応付けはS式コメントに対応していない。そのためS式コメントでコメントアウトすると、インデントや「%」コマンドが正しく動作しなくなる(後述するシンタックスハイライトがS式コメントに対応していると、一見VimがS式コメントを認識しているように見えるのでさらに混乱する)。

                  ;; 行コメントだと解釈される
(list 1 2 3 4 (- #;(+ 5
                 6))
7 8 9)

コメントアウトにはブロックコメントを使った方がいいと思う。

(list 1 2 3 4 (- #|(+ 5
                      6)|#)
      7 8 9)

面倒ならブロックコメント化をキーマッピングしておくこともできる。

" ,c 対応するカッコまでをブロックコメントでコメントアウト
" ,C 「,c」のブロックコメントを解除
autocmd FileType scheme nnoremap <buffer>
\ ,c vab%<ESC>i#\|<ESC>%a\|#<ESC>``l
autocmd FileType scheme nnoremap <buffer> <silent>
\ ,C ?#\|<ESC>/(<ESC>vab<ESC>/\|#<ESC>xx`<?#\|<ESC>xx

しかし実際にはブロックコメントも認識していないので、ブロックコメント内でカッコの対応がズレているとうまく動作しなくなる。

(list 1 2 3 4 (- #|(+ 5  (      ; ブロックコメント内の余分なカッコ
                          6)|#)
                 7 8 9)
インデント

改行時に自動でインデントさせたい場合は、

set autoindent    " または「set ai」

とする。ただし「lisp」オプションがセットされていないとLisp向けのインデントにならない。Schemeファイルのときだけオートインデントにしたければ、

autocmd FileType scheme setlocal autoindent

とする。
また「expandtab」オプション(省略名 et)をセットするとタブ入力やオートインデントのタブが全てスペースでおこなわれるようになる。
インデント用のコマンドは次のようになっている。

キー入力 効果
== その行をインデント
=移動コマンド 現在行から移動先までをインデント

例えばカッコの上で「=%」と入力すると、対応するカッコまでインデントされる。
また次のようにキーマップを設定すると、挿入モード時にタブを打つと現在行のインデントを整えてくれる。

" (挿入モード時)<Tab>  現在行のインデントを整える
autocmd FileType scheme inoremap <buffer> <Tab> <C-o>==

(Ctrl-oは一時的に挿入モードを抜けコマンドを一個実行すると挿入モードに戻る。「inoremap」は、たぶん Insert mode, NO REcursive, MAPの略。再帰展開されないimap。inoremapについて詳しくは→Vim documentation: map)
lispモードでのインデントは基本的に

(procedure
  arg1
  arg2
  arg3)
(procedure arg1
           arg2
           arg3)

のようになる。ただし「lispwords」オプションにセットされている手続き・マクロについては

(procedure arg1
  arg2
  arg3)

とインデントされる。lispwordsの値は好みに合わせて追加・除去すればいい。
lispwordsにはあらかじめいくつかの単語が定義されている。syntax機能を使っているなら、シンタックス定義ファイルの内容に基づいてさらに追加される。何が登録されているかを見るには、

set lispwords?

とする。
「with-」「call-with-」「run-with-」系の関数のうち多くのもの(2つ以上の引数を取り、後ろの引数に手続きオブジェクトが来るもの)はlispwordsに追加すると見やすいかも。
例えば、「call-with-input-file」がlispwordsに登録されていない場合、

(call-with-input-file "file1"
                      (lambda (p)
                        (port->string p)))

のようにインデントされる。インデントが深すぎると感じる場合は、改行を入れると

(call-with-input-file
  "file1"
  (lambda (p)           
    (port->string p)))

のようにインデントされる。これを

set lispwords+=call-with-input-file   " 追加

としてやれば、次のようにインデントされる。

(call-with-input-file "file1"
  (lambda (p)
    (port->string p)))

複数の単語を追加するには「set lispwords+=…」を繰り返してもいいし、単語を「,」で区切って一度に追加してもいい。

set lispwords+=call-with-input-file,call-with-output-file   " 複数の単語を追加

また「if」は初めから登録されているはずだけど、

set lispwords-=if                     " 除去

としてやると、次のようにインデントされる。

(if (< 1 2)
    3
    4)

次のようにインデントさせる方法があるのかは不明。

(call-with-input-file
    "file1"
  (lambda (p)
    (port->string p)))

補完

単語を構成する文字の決定: iskeywordオプション

Vimには単語の補完機能がある。ただしLispSchemeで補完を使う場合は、単語を構成する文字を指定しなおす必要がある(多くの言語で単語から除外される文字の多くが、LispSchemeでは単語の一部として使えるので(「#$%&*+-./<=>?@^~」など)。

単語文字はiskeywordオプション(省略名isk)の値で決まる。lispオプションが有効のときはハイフン「-」が常に単語構成文字扱いになるけどiskeywordの値自体は変化しない。明示的に追加しておいた方がたぶんいい(もしファイルタイププラグイン設定がオンになっていれば自動的にLisp向けになるのでiskeywordの設定は必要ない)。
デフォルトの値は次のようになっている(環境によって異なるのかもしれない)。

@,48-57,_,192-255
" @ 全アルファベットを表す
" 48-57 [0-9]
" _ アンダーバー
" 192-255 (ヨーロッパ系言語の)アクセント付き文字

これは次のように設定しておけばいい。

autocmd FileType scheme setlocal iskeyword=@,33,35-38,42-43,45-58,60-64,94,_,126
" 33 !
" 35-38 #$%&
" 42-43 *+
" 45-58 -./[0-9]:
" 60-64 <=>?@
" 94 ^
" 126 ~
補完単語ファイルの指定

何を参照して補完するかはcompleteオプション(省略名cpt)の値で決まる。参照先はあらかじめいくつか設定されている(カレントバッファ+α)。
ファイルを指定する場合は「kファイル名」という形式でおこなう。k形式の指定は複数個可。またファイル名はパターンでもいい。k形式以外の説明は省略。

" 以下は「set cpt=……」でもよい
set complete+=k~/foo_file  " ホームディレクトリのfoo_fileを補完語ファイルに指定
set complete+=k~/dict/*    " dictディレクトリのすべてのファイルを指定
set complete+=k            " kだけの場合、dictionaryに設定されたファイルを表す
set complete+=k適当なディレクトリ/lib/**/*.scm  " lib以下の全ての階層を調べる

最後の例のようにライブラリファイルの置かれたディレクトリを指定すれば多くの関数・マクロの名前を補完対象にできる。ただし(ライブラリプログラム内で使われている)必要ない単語もたくさん補完候補になってしまう。
またVim用の補完単語ファイルはrlwrapコマンドと共用することもできる(Gauche用の補完単語リストの作り方→「改めてGaucheとrlwrapの連携について」補完語リストの作成)。あるいはモジュールごとに補完語ファイルを作っておけば、補完する時にどのモジュールの語なのか判断することができる(後述する)。
辞書補完(補完のキー操作が異なる)用のファイルはdictionary(省略名 dict)で指定する。

set dictionary+=/usr/share/dict/words

好みやファイルタイプによってcompleteとdictionaryの設定を変えるとよい。例えば通常はcompleteに英単語ファイルを設定し、プログラムを書くときはcompleteに(ファイルタイプに合わせた)関数名・キーワードファイルを設定するとか。例えばこんな風に。

set dictionary+=/usr/share/dict/words
set complete+=k   "dictionaryに登録されているファイルをcompleteに追加
autocmd FileType scheme setlocal complete-=k
autocmd FileType scheme setlocal complete+=k~/.gosh_completions
autocmd FileType scheme setlocal complete+=k/usr/share/dict/words

補完のキー操作は次のようになっている。
(complete補完は前操作なしで<C-n><C-p>で始まる)

(挿入モードで) 効果
<C-n> 次の補完候補
<C-p> 前の補完候補
<C-y> 補完確定(<C-y>を押さなくても文字を入力すれば補完は確定する)
<C-e> 補完をやめる
<C-x><C-n> 現在ファイル内に出てきている単語で補間する
<C-x><C-p> 現在ファイル内に出てきている単語で補間する
<C-x><C-f> ファイル名補完
辞書補完 (dictionaryに登録されたファイル内の単語から補間)
バッファ内の単語による補完
モジュール・ライブラリごとに補完語ファイルを用意する

補完候補が表示されるとき、それがどのファイルに含まれていたものなのかも一緒に表示される。なのでモジュールやライブラリごとに補完語のファイルを作っておけば、補完語がどのモジュールのものなのかが判るようになる。

「.vimrc」の設定は例えばこんな感じになる。

" 補完ファイルの置き場所は好みで。ディレクトリ名は短めの方がいいかもしれない
autocmd FileType scheme setlocal complete+=k~/.gosh_idx/*

Gaucheの場合は例えば次のようなプログラムでファイルを作成できる。

;;;; Gaucheのモジュールごとに補完語ファイルを作成する。
;;;; この例では、gosh_idxディレクトリ以下に作成するので、
;;;; あとはディレクトリを好きな場所に(好きな名前で)移動させる。
(use srfi-1)
(use srfi-13)   ; string-titlecase
(use file.util)

(define (main args)
  (define index-dir "gosh_idx")
  (define predefined-modules '(null scheme gauche))
  (define (names-to-file mod-name symbols)
    (call-with-output-file
      (build-path index-dir mod-name)
      (lambda (to-port)
        (dolist (str (sort (map symbol->string symbols)))
          (format to-port "~a\n" str)))))

  (load-all-modules)
  (make-directory* index-dir)
  (dolist (module (sort-by (all-modules) x->string))
    (let* ((mod-name (symbol->string (module-name module)))
           ; srfiが候補の先に来るようにファイル名の頭を大文字にする
           (mod-name (if (#/^srfi-/ mod-name)
                       (string-titlecase mod-name)
                       mod-name)))
      (names-to-file mod-name (module-exports module))))

  (names-to-file
    "Predefined"
    (append-map
      (lambda (module)
        (hash-table-keys (module-table (find-module module))))
      predefined-modules)))

(define (load-all-modules)
  ;; 読むとエラーになるファイルを排除する。
  ;; 不必要なファイルをいろいろ読み込んでいるので、本当はそれらも排除したほうがいい。
  (define excluded-files '("gauche/matrix.scm" "os/windows.scm" "srfi/"))
  (define obsoleted-modules
    '(gauche.let-opt gauche.validator gauche.singleton))
  (dolist (pattern (list "*/*" "srfi-*" "gauche/mop/*"))
    (library-for-each
      pattern
      (lambda (_ path)
        (unless (or (any (cut string-scan path <>) excluded-files)
                    (any (cut library-has-module? path <>) obsoleted-modules))
          (guard (exc (else 'nop))  ; モジュールをロードしたときのエラーは無視する
            (load path))))
      :paths (filter absolute-path? *load-path*)
      :allow-duplicates? #t)))
autocomplpopプラグイン

キー操作なしで自動で補完候補が表示されるようにするautocomplpopというプラグインがある(http://www.vim.org/scripts/script.php?script_id=1879https://github.com/vim-scripts/AutoComplPop)。プラグインを利用するには、「~/.vim/」ディレクトリでアーカイブを展開すればいい。
必要そうな設定をいくつか示す。

" completeオプションの設定をautocomplpopに反映させる
autocmd FileType * :let g:acp_completeOption = &complete

" 何文字以上の単語について自動補完するか(デフォルトは2)
" let g:acp_behaviorKeywordLength = 2

" <F2>キーで機能をON/OFFさせる。もしかしてうまく動かないかも
inoremap <silent> <expr> <F2>
\ (exists('#AcpGlobalAutoCommand#InsertEnter')) ?
\ "\<C-o>:AutoComplPopDisable\<CR>" :
\ "\<C-o>:AutoComplPopEnable\<CR>"

" 上の設定がうまくいかないときの別案
"inoremap <silent> <expr> <F2> "\<C-o>:AutoComplPopDisable\<CR>"
"inoremap <silent> <expr> <F3> "\<C-o>:AutoComplPopEnable\<CR>"

autocomplpop.vimの動作で変更した方がいいかもしれないこと その1:

" 候補のポップアップ中にエンターを入力したときに、改行されるようにする。
inoremap <expr> <CR> (pumvisible()) ? "\<C-y>\<CR>" : "\<CR>"

その2: 補完の第1候補が自動的に選ばれた状態になるのをやめる。autoload/acp.vimを直接書き換える。ver.2.14.1の場合:

      " a command to restore to original text and select the first match
      return (s:behavsCurrent[s:iBehavs].command =~# "\<C-p>" ? "\<C-n>\<Up>"
            \                                                 : "\<C-p>\<Down>")

      " a command to restore to original text and select the first match
      return (s:behavsCurrent[s:iBehavs].command =~# "\<C-p>" ? "\<C-n>"
            \                                                 : "\<C-p>")

シンタックスハイライト

シンタックスハイライトを有効にするには次のようにする。

syntax enable

定義ファイルが読まれる順番は、

set runtimepath?

で確認できる。ファイルタイプが「scheme」なら

~/.vim/syntax/scheme.vim
$VIMRUNTIME/syntax/scheme.vim    # Vimにあらかじめ入っているファイル
~/.vim/after/syntax/scheme.vim   # 自分用の追加設定

が順番に読まれる(標準的な構成にしたがって書かれたシンタックスファイルは最初に読んだファイル以外は定義を読み込まないようにしている)。
シンタックス用のファイルの書き方は調べていないので、説明はなし(Gauche向けのシンタックスファイルがhttp://legacy.e.tir.jp/wiliki?vim%3Ascheme.vimにあるので、ここを参考にする)。
あとは追加の設定を「~/.vim/after/syntax/scheme.vim」に書く。lispwordsの設定は「.vimrc」よりもここに書くのがいいかもしれない。

開発しているプログラムの実行

単純な方法

編集しているプログラムをGaucheで実行:

:!gosh %

対話環境は使えないけど、情報の出力にはデバッグプリントのためのリーダーマクロ「#?=」が利用できる。
Gaucheで実行し、そのままread-eval-printループに入る:

:!gosh -l %:p

「.vimrc」でキーマッピングするなら、こんな感じ。

" 編集ファイルをセーブしてGaucheで実行する。
autocmd FileType scheme nnoremap <buffer> ,t :w \| !gosh %<CR>

" 編集ファイルをセーブ・実行してread-eval-printループに入る
" ※「:p」はファイル名を絶対パスに展開する。
" goshのみを使う版:
" autocmd FileType scheme nnoremap <buffer> ,u :w \| !gosh -l %:p<CR>
"
" rlwrapを利用:
autocmd FileType scheme nnoremap <buffer>
\ ,u :w \| !rlwrap -c -q '"' -b "'"'(){}[]".,;@\#\|\`' -m gosh -l %:p<CR>
非同期にプログラムを実行

非同期実行についての特別な機能を導入しなくても、例えば次のようなことができる。
まずUnixのtailコマンドにはファイルに追加されていく内容を監視する機能があるので、別のシェルをどこかで立ち上げて適当なファイルを監視させる。

tail -F gosh_output        # またはfオプションで

あるいは名前付きパイプを作ってそれを監視する。

mkfifo gosh_output
tail -F gosh_output

そしてVim側でコマンドを実行するときに、(1)出力をそのファイルにリダイレクトさせて(2)コマンドをバックグラウンド実行させる。

:!gosh %>>gosh_output 2>&1 &

注意: このやり方で停止しないプログラムを実行すると、ファイルに書き込み続けたまま、プログラムが止まっていないことに気づかず放置してしまう可能性がある。

キーマッピングの例:

autocmd FileType scheme nnoremap <buffer>
\ ,v :!gosh %>>gosh_output 2>&1 &<CR><CR>
quickrunプラグイン

編集中のファイルを実行して、その結果を別のウィンドウに出力するquickrunというプラグインがある(http://www.vim.org/scripts/script.php?script_id=2419)。
「\r」で、プログラムを実行する別のウィンドウに表示(正確には「\」はに設定されているキー)。ウィンドウは「q」で消せる(Vimの標準のウィンドウ操作も使える)。ファイルにセーブしなくてもバッファの内容を実行してくれる。
標準設定では、ファイルタイプがschemeの時は「gosh」または「mzscheme」を(可能ならば)実行するようになっている。処理系を変える場合は次のように書く。例えばguileにするなら次のようになる。

let g:quickrun_config = {}    " 初期化
let g:quickrun_config['scheme'] = { 'scheme': { 'command': 'guile'}}
" または「let g:quickrun_config = { 'scheme': { 'command': 'guile'}}」

テキストの折り畳み

内部関数が大きくなったときなどに使うかもしれない機能。

折り畳み位置を設定すれば、例えば次のようなプログラム

(define (foo x)
  (define (bar x)
    (+ x
       3
       4))
  (bar x))

の一部を折り畳んで表示させることができる。

(define (foo x)
+---  4 行:(define (bar x)-----------------------------------------
  (bar x))

折り畳みは入れ子にすることもできる。
同じ場所を繰り返し折り畳み位置に指定することもできて、例えば次のような文章の場合「深さ2のテキスト」の部分を2回折り畳んで管理するという使い方ができる。ただし判りにくくなる場合もある。

深さ0のテキスト
深さ0のテキスト
    深さ1のテキスト
    深さ1のテキスト
深さ0のテキスト
深さ0のテキスト
        深さ2のテキスト
        深さ2のテキスト
深さ0のテキスト
深さ0のテキスト

foldcolumnオプション(省略名 fdc)に値を設定すると、その行数分だけ折り畳み情報が表示される(デフォルトの値は0)。

set foldcolumn=4

とすると、 次のような表示になる。

-   深さ0のテキスト
|   深さ0のテキスト
|-      深さ1のテキスト
||      深さ1のテキスト
|   深さ0のテキスト
|   深さ0のテキスト
|--         深さ2のテキスト
|||         深さ2のテキスト
|   深さ0のテキスト
|   深さ0のテキスト
折り畳みの主なキー操作
入力キー 効果
zf移動コマンド 指定した範囲を折り畳む
ビジュアルzf ビジュアルモードで指定して折り畳む zF 与えた行数分を折り畳む
zd 折り畳みを削除 zD 入れ子も全て削除
za 折り畳みの開け閉め zA 入れ子も全て開け閉めする
zo 折り畳みを開ける zO 入れ子も全て開ける
zc 折り畳みを閉じる zC 入れ子も全て閉じる
zr 浅いレベルから1段開ける zR 全部開く
zm 深いレベルから1段閉じる zM 全部閉じる
zv 現在行が折り畳みでなくなるまで開く
zi 折り畳みの有効・禁止の切り替え
zE 全ての折り畳みを削除

「zr」「zR」「zm」「zM」はfoldlevelオプション(省略名 fdl)の値を増減している。「r」は減少、「m」は増加(「set foldlevel?」でチェックしてみると判る)。折り畳み領域の深さとfoldlevelの値を比較して領域の開閉をおこなう。foldcloseオプション(省略名 fcl)の値をallにしておくと、foldlevelより深い領域から出ると、その領域は自動的に閉じられるようになる。

" foldlevelの値より深いレベルの折り畳み領域から出ると閉じられる
set foldclose=all

あとはLisp用に次のようなキーマッピングをしておくといいかもしれない。

" キー操作の追加
" zp 対応するカッコまでを折り畳み範囲に設定
" zP (2行以上にまたがる)定義全てを折り畳み範囲に設定
autocmd FileType scheme nnoremap <buffer> zp zfab
autocmd FileType scheme nnoremap <buffer> <silent>
\ zP zE:g/ *(define/normal zf%zo<CR><C-o>zR

まとめ

「.vimrc」のサンプル
" ファイル形式別のプラグイン・インデントを有効にする
filetype plugin indent on
" シンタックスハイライトを有効にする
syntax enable

" 補完語ファイルの設定
set dictionary&
" 辞書補完用のファイルは環境依存。追加も可能
set dictionary+=/usr/share/dict/words
set complete&
set complete+=k

" ----------------------------------------
" Schemeのための設定

" ファイル形式別プラグイン・インデントが有効の場合、この設定は不要
" autocmd FileType scheme setlocal autoindent lisp
" autocmd FileType scheme setlocal iskeyword=@,33,35-38,42-43,45-58,60-64,94,_,126
" 33 !
" 35-38 #$%&
" 42-43 *+
" 45-58 -./[0-9]:
" 60-64 <=>?@
" 94 ^
" 126 ~

" タブ文字を使わずスペースでインデントさせる
autocmd FileType scheme setlocal expandtab shiftwidth=2


" カーソルをよく見失う場合
" autocmd FileType scheme setlocal cursorline
" autocmd FileType scheme setlocal cursorcolumn

" 補完語ファイルの設定
" 英単語をいったん補完語から外す
autocmd FileType scheme setlocal complete-=k

" Gauche用補完語ファイルの設定 ファイル名は適宜変更する
" autocmd FileType scheme setlocal complete+=k~/.gosh_completions

" モジュールごとに補完語ファイルを作った場合の例
autocmd FileType scheme setlocal complete+=k~/.gosh_idx/*

" 英単語を、プログラム用の補間語のあとに候補にする
autocmd FileType scheme setlocal complete+=k/usr/share/dict/words

" キー設定追加:
" ,b  ひとつ外側の「(」に移動
autocmd FileType scheme nnoremap <buffer> <silent> ,b hvab<ESC>`<

" ,c 対応するカッコまでをブロックコメントでコメントアウト
" ,C 「,c」のブロックコメントを解除
autocmd FileType scheme nnoremap <buffer>
\ ,c vab%<ESC>i#\|<ESC>%a\|#<ESC>``l
autocmd FileType scheme nnoremap <buffer> <silent>
\ ,C ?#\|<ESC>/(<ESC>vab<ESC>/\|#<ESC>xx`<?#\|<ESC>xx

" ,s  対応するカッコまでの内容とレジスタ内の内容を入れ替える
autocmd FileType scheme nnoremap ,s vabp=ab

" (挿入モード)<Tab>  現在行のインデントを整える
autocmd FileType scheme inoremap <buffer> <Tab> <C-o>==
" (Ctrl-oは一時的に挿入モードを抜け、コマンドを一個実行すると挿入モードに戻る)

" Vim内からGaucheを実行する
" キー設定
" ,t  編集ファイルをgoshで実行する
autocmd FileType scheme nnoremap <buffer> ,t :w \| !gosh %<CR>

" ,u  編集ファイルを実行してread-eval-printループに入る
" goshのみを使う版:
" autocmd FileType scheme nnoremap <buffer> ,u :w \| !gosh -l %:p<CR>
"
" rlwrapを利用:
autocmd FileType scheme nnoremap <buffer>
\ ,u :w \| !rlwrap -c -q '"' -b "'"'(){}[]".,;@\#\|\`' -m gosh -l %:p<CR>
" 「:p」はファイル名を絶対パスに展開する。

" ,v 編集ファイルの実行結果をoutput.logファイルに非同期に書き込む。
"    別のシェルから「tail -F output.log」で監視しておく
autocmd FileType scheme nnoremap <buffer>
\ ,v :w \| !gosh %>>output.log 2>&1 &<CR><CR>


" 折り畳み機能についての設定 
" 折り畳み範囲の表示幅
autocmd FileType scheme setlocal foldcolumn=1
" foldlevelの値より深いレベルの折り畳み領域から出ると閉じられる
autocmd FileType scheme setlocal foldclose=all

" 折り畳みについてのキー設定の追加
" zp  対応するカッコまでを折り畳み範囲に設定
" zP  (2行以上にまたがる)定義全てを折り畳み範囲に設定
autocmd FileType scheme nnoremap <buffer> zp zfab
autocmd FileType scheme nnoremap <buffer> <silent>
\ zP zE:g/ *(define/normal zf%zo<CR><C-o>zR

" ----------------------------------------
" autocomplpopプラグイン用の設定
" completeオプションの設定を反映させる
autocmd FileType * :let g:acp_completeOption = &complete

" 何文字以上の単語について自動補完するか(デフォルトは2)
" let g:acp_behaviorKeywordLength = 2

" <F2>キーで機能をON/OFFさせる。もしかしてうまく動かないかも
inoremap <silent> <expr> <F2>
\ (exists('#AcpGlobalAutoCommand#InsertEnter')) ?
\ "\<C-o>:AutoComplPopDisable\<CR>" :
\ "\<C-o>:AutoComplPopEnable\<CR>"

" 上の設定がうまくいかないときの別案
"inoremap <silent> <expr> <F2> "\<C-o>:AutoComplPopDisable\<CR>"
"inoremap <silent> <expr> <F3> "\<C-o>:AutoComplPopEnable\<CR>"


" autocomplpopの挙動を変更
" 候補のポップアップ中にエンターを入力したときに、改行されるようにする。
inoremap <expr> <CR> (pumvisible()) ? "\<C-y>\<CR>" : "\<CR>"

コマンド集

カッコ

S式単位での操作はできない(または難しい)ようなので、カッコの対応を利用する。

入力 効果
% (カッコを前方方向に探してそこから)対応するカッコにジャンプ
[( 左方向で対応していない「(」にジャンプ
]) 右方向で対応していない「)」にジャンプ

「c」「d」「y」「=」「zf」「v」のように移動で範囲を指定するコマンド(とビジュアルモード)は、通常の移動コマンド以外に「a」「i」から始まる指定がおこなえる(テキストオブジェクト)。

入力 範囲指定 入力 範囲指定
aw 単語とそれを囲む空白 iw 単語
ab カッコで囲まれた範囲(カッコを含む) ib カッコで囲まれた範囲(カッコを含まない)
a" 「"」で囲まれた範囲(「"」を含む) i" 「"」で囲まれた範囲(「"」を含まない)

これ以外にも「a〜」「i〜」で色々な指定ができる。S式を表すテキストオブジェクトがあればそれが一番良いのだけど。

「%」を使った範囲指定はカーソルがカッコの上にない場合、たぶん望んでいる結果とは違うものになる。しかしカッコに囲まれた範囲の指定に常に「ab」を使うと今度は打鍵数が増える。
また「ab」による指定でも問題が発生する状況がある。挙動から判断すると、「ab」や「ib」はまず左方向にある開いた「(」を探している。しかしこのとき文字列リテラルに現れているカッコも対応に含めている。そのため文字列リテラルの中でカッコのバランスが崩れていると、LispSchemeの文法から予想されるものとは違った結果になる。「vab」と打ち込んでみれば、どう選択されるのか見ることができる。

入力 効果 入力 効果
y% 対応するカッコまでコピー yab カッコに囲まれた範囲をコピー
d% 対応するカッコまで削除 dab カッコに囲まれた範囲を削除
c% 対応するカッコまでを変更 cab カッコに囲まれた範囲を変更
v% 対応するカッコまでを選択 vab カッコに囲まれた範囲を選択
(abを繰り返すごとに、選択範囲が広がっていく)
v%p カッコまでとレジスタの内容の交換(※) vabp 囲まれた範囲とレジスタの内容の交換(※)

(※ビジュアルモードで「p」「P」を使うと、レジスタの内容をペーストして、代わりに選択範囲の内容がレジスタに保存される)

" キー操作設定
" ,s  対応するカッコまでの内容とレジスタ内の内容を入れ替える
autocmd FileType scheme nnoremap ,s vabp=ab

" ,b  ひとつ外側の「(」に移動
autocmd FileType scheme nnoremap <buffer> <silent> ,b hvab<ESC>`<

" ,c 対応するカッコまでをブロックコメントでコメントアウト
" ,C 「,c」のブロックコメントを解除
autocmd FileType scheme nnoremap <buffer>
\ ,c vab%<ESC>i#\|<ESC>%a\|#<ESC>``l
autocmd FileType scheme nnoremap <buffer> <silent>
\ ,C ?#\|<ESC>/(<ESC>vab<ESC>/\|#<ESC>xx`<?#\|<ESC>xx
インデント

注意するオプション→ lisp autoindent(省略名 ai)

キー入力 効果
== その行をインデント
=移動コマンド 現在地から移動先までをインデント
" キー操作設定
" (挿入モード時)<Tab> 現在行のインデントを整える
autocmd FileType scheme inoremap <buffer> <Tab> <C-o>==
補完

注意するオプション→ iskeyword(省略名 isk) complete(省略名 cpt) dictionary(省略名 dict)

" 補完語ファイルの設定
set dictionary&
" 辞書補完用のファイルは環境依存。追加も可能
set dictionary+=/usr/share/dict/words
set complete&
set complete+=k

" 単語構成文字のScheme向け設定の例
autocmd FileType scheme setlocal iskeyword=@,33,35-38,42-43,45-58,60-64,94,_,126
" プログラムを書くとき英単語を補完語から外す
autocmd FileType scheme setlocal complete-=k
" 補完単語のファイルは自前で用意 (ファイル名は適宜変更する)
autocmd FileType scheme setlocal complete+=k~/.gosh_complations
(挿入モードで) 効果
C-n 次の補完候補
C-p 前の補完候補
C-y 補完確定(C-yを押さなくても何か文字を入力すれば補完は確定する)
C-e 補完をやめる
C-x C-f ファイル名補完
C-x C-k 辞書補完
C-x バッファ内の単語による補完
ウィンドウ

コマンドラインから
複数ウィンドウで開くvim -o {file1} {file2} ...vim -O {file1} {file2} ...
ウィンドウを分割/開く
ウィンドウを二分割
(新規ウィンドウの高さ(幅)をNで)
C-w s
C-w v
C-w C-v
:[N]sp[lit]:[N]vs[plit]
新規ウィンドウでファイルを開く:[N]new {file} :[N]vne[w] {file}
:[N]sp[lit] {file}:[N]vs[plit] {file}
新規ウィンドウで空ファイルを開くC-w n
C-w C-n
:[N]new:[N]vne[w]
ウィンドウを終了/閉じる
カレントウィンドウを終了(quit)C-w q
C-w C-q
:q[uit]
カレントウィンドウを閉じる(close)C-w c
:clo[se]
カレントウィンドウ以外を閉じる(only)C-w o
C-w C-o
:on[ly]
全てのバッファへの操作
各バッファにウィンドウを一つ割り当て:ba[ll]
全てのバッファを書き込み:wa[ll]
全てのバッファを終了:qa[ll]
全てのバッファを書き込んで終了:wqa[ll]
その他のバッファ操作
バッファリストを見る

(!を付けると削除したバッファも表示)
%現在バッファaアクティブ+変更あり
#前バッファh隠れ
その他非アクティブ
:files[!]
:buffers[!]
:ls[!]
カレントウィンドウ新規ウィンドウ
N番バッファを表示:Nb[uffer]:Nsb[uffer]
:b[uffer] N:sb[uffer] N
次のバッファを表示(「N個先」の指定も可能):bn[ext]:sbn[ext]
前のバッファを表示(「N個前」の指定も可能):bN[ext]:sbN[ext]
:bp[revious]:sbp[revious]
最初のバッファを表示:br[ewind]:sbr[ewind]
:bf[irst]:sbf[irst]
最後のバッファを表示:bl[ast]:sbl[ast]
N番バッファをリストから削除:Nbd[elete]
:bd[elete] N
ウィンドウ間の移動
別のウィンドウへ(順方向)C-w C-w
C-w w
別のウィンドウへ(逆方向)C-w W
別のウィンドウへ
C-w ←C-w ↓C-w ↑C-w →
C-w hC-w jC-w kC-w l
C-w C-hC-w C-jC-w C-kC-w C-l
一番左上のウィンドウへC-w t
C-w C-t
一番右下のウィンドウへC-w b
C-w C-b
ウィンドウの大きさを変更
カレントウィンドウを大きくC-w +C-w >
:res[ize] +N:vertical res[ize] +N
カレントウィンドウを小さくC-w -C-w <
:res[ize] -N:vertical res[ize] -N
カレントウィンドウの大きさをNに:res[ize] N:vertical res[ize] N
ウィンドウの大きさをそろえるC-w =
ウィンドウの位置の入れ替え
次位置のウィンドウと入れ替えC-w x
C-w C-x
ウィンドウ位置を回転(順方向)C-w r
C-w C-r
ウィンドウ位置を回転(逆方向)C-w R
カレントウィンドウを端へ移す
C-w HC-w JC-w KC-w L

折り畳み
入力キー 効果
zf移動コマンド 指定した範囲を折り畳む
ビジュアルzf ビジュアルモードで指定して折り畳む zF 与えた行数分を折り畳む
zd 折り畳みを削除 zD 入れ子も全て削除
za 折り畳みの開け閉め zA 入れ子も全て開け閉めする
zo 折り畳みを開ける zO 入れ子も全て開ける
zc 折り畳みを開ける zC 入れ子も全て閉じる
zr 浅いレベルから1段開ける zR 全部開く
zm 深いレベルから1段閉じる zM 全部閉じる
zv 現在行が折り畳みでなくなるまで開く
zi 折り畳みの有効・禁止の切り替え
zE 全ての折り畳みを削除
" キー操作設定
" zp  対応するカッコまでを折り畳み範囲に設定
" zP  (2行以上にまたがる)定義全てを折り畳み範囲に設定
autocmd FileType scheme nnoremap <buffer> zp zfab
autocmd FileType scheme nnoremap <buffer> <silent>
\ zP zE:g/ *(define/normal zf%zo<CR><C-o>zR