denops.vimで括弧補完プラグインを書いた

4 min read読了の目安(約4200字 2

始めに

こんにちは, higashiです.
今回はdenops.vimで括弧補完プラグインを作成したので紹介していきます.

今回作ったプラグイン

dps-kakkonanというプラグインです.

元々作成していたvim-kakkonanというプラグインをdenops.vimを使って実装した形になります.

denops.vimについて

こちらの記事で作者であるlambdalisueさんが解説されています.

簡単に説明するとDenoを使ってVim/Neovimのプラグインを書くことができます.
TypeScriptで記述できるので型を使うことができる他, すでにあるDenoの資産も使えます.

dps-kakkonanの機能

括弧補完です.
普通の括弧補完です.
こちらのgifを見ていただければわかります.
Image from Gyazo

実装について

  • 括弧を補完
  • 右に閉じ括弧がある状態で閉じ括弧を入力すると右に一つカーソルがズレる
  • 開き括弧と閉じ括弧のがカーソルの両隣になる状態でバックスペースを押すと括弧がどちらも削除される

と言った基本の機能しか実装していないためそこまでコードは長くならず, TypeScript 105行で実装できました.

start(async (vim) => {
    const getLineChar = async (diff: number): Promise<string> => {
        if (typeof diff !== "number") {
            throw new Error(`'diff' attribute of 'getLineChar' in must be a number`)
        }

        const cursorStr = await vim.call('getline', '.');
        if (typeof cursorStr !== "string") {
            throw new Error(`'cursorStr' attribute of 'kakkonanCompletion' in must be a string`)
        }

        const cursorLine = await vim.call('line', '.');
        if (typeof cursorLine !== "number") {
            throw new Error(`'cursorLine' attribute of 'kakkonanCompletion' in must be a number`)
        }

        const cursorCol = await vim.call('col', '.');
        if (typeof cursorCol !== "number") {
            throw new Error(`'cursorCol' attribute of 'kakkonanCompletion' in must be a number`)
        }

        const cursorChar = cursorStr.substr(cursorCol + diff, 1);

        return cursorChar;
    }
})

このようにアロー関数で関数を登録できたのはとても書きやすくて良かったです.

また, 返り値がVim/Neovimにそのまま渡されるのでこのような関数をinoremap等でinsert modeから呼び出すと返り値がファイルに挿入されます.

async hogehogefugafuga(inputBrackets: unknown): Promise<string> {
    if (typeof inputBrackets !== "string") {
        throw new Error(`'inputBrackets' attribute of 'kakkonanCompletion' in must be a string`);
    }

    return "hogehoge" + inputBrackets;
}

この場合だとhogehogeに引数の文字列が結合されて挿入されます.
この仕様を活かして括弧補完を作成しました.

キーマップの定義も簡単で, vim.execute()の中に普段vimrcに書いているようなキーマップの設定を入れるだけです.

vim.execute(`
    inoremap <expr> ( denops#request("kakkonan", "kakkonanCompletion", ['(']) . "\<left>"
    inoremap <expr> { denops#request("kakkonan", "kakkonanCompletion", ['{']) . "\<left>"
    inoremap <expr> [ denops#request("kakkonan", "kakkonanCompletion", ['[']) . "\<left>"
    inoremap <expr> " denops#request("kakkonan", "kakkonanCompletion", ['"']) != "" ? '""' . "\<left>" : "\<right>"
    inoremap <expr> ' denops#request("kakkonan", "kakkonanCompletion", ["'"]) != "" ? "''" . "\<left>" : "\<right>"
    inoremap <expr> ${backQuote} denops#request("kakkonan", "kakkonanCompletion", ['${backQuote}']) != "" ? '${backQuote + backQuote}' . "\<left>" : "\<right>"
    inoremap <expr> ) denops#request("kakkonan", "kakkonanEscapeBrackets", [')']) == v:false ? ")" : "\<right>"
    inoremap <expr> } denops#request("kakkonan", "kakkonanEscapeBrackets", ['}']) == v:false ? "}" : "\<right>"
    inoremap <expr> ] denops#request("kakkonan", "kakkonanEscapeBrackets", [']']) == v:false ? "]" : "\<right>"
    inoremap <expr> <CR> denops#request("kakkonan", "kakkonanBackSpaceEnter", []) == v:false ? "\<CR>" : "\<CR>\<C-o>\<S-o>"
    inoremap <expr> <BS> denops#request("kakkonan", "kakkonanBackSpaceEnter", []) == v:false ? "\<BS>" : "\<BS>\<right>\<BS>"
`);

これはdps-kakkonan内で定義しているコマンドです.
このように複数行文字列を渡すことで一気にコマンドの定義が行えます.

ちなみに以下のように一つづつ定義することも可能です.

vim.execute(`inoremap <expr> ( denps#request("kakkonan", "kakkonanCompletion", ['('])`)
vim.execute(`inoremap <expr> [ denps#request("kakkonan", "kakkonanCompletion", ['['])`)

最後に

denops.vimのおかげでDenoを使ってVim plugin作成ができるようになりました.
このようにプラグイン作成の幅が広がっていくのはとても良いと思います.

これを機にTypeScriptもしっかり勉強したいです.

ここまで読んでくれた皆様ありがとうございました.
denops.vimに興味を持った方はぜひvim-jpのSlack内にある#tech-denopsにも参加してみてください.

vim-jp Slackへの参加はこちらから