vimrcの話をしよう
あれは今から36万…いや、1万4千年前だったか。まぁいい。
私にとってvimrcを書いたのはつい昨日の出来事だが、
来年の私にとっては多分、明日の出来事だ。
vimrcには無数の書き方があるから、どう書けば良いのか…
そんなvimrcで大丈夫か?
2022年12月の僕のvimrcの特徴は下記の通り
- LSPを使った非同期自動補完
- Coc等は敢えて使わず、最良の未来を思い、自由に選択していけ
- 20ms程度で起動するし起動後もヌルヌル
- vimでもneovimでも動く
- pythonのLSPに対する拡張
- vimrc内部に目次をつけて管理する
- インストールは一部を除いて自動でしてくれる
パクった設定は多いが…こんなリポジトリは誰も見ないから大丈夫だ、問題ない。多分。
Cocのサポートが心配なのかい?
いいんじゃないかな、Cocもよくやってくれるしね。
しかし、Cocはおま環に遭遇して私のvimrcは一度爆死したのだ…しかし、神は言っている、ここで死ぬ定めではないと。
後で知ったけれど、暗黒美無王はCocやYouCompleteMeによる弊害を下記の記事でこう指摘している。
そのプラグインの世界だけで完結する作業ならば極上の快適さが約束されますが、自由に設定し他のプラグインを組み合わせようとすると容易に破綻します。
つまり、僕のように半端に非公開のプラグインもどきを作ったりする素人にはむしろ難しいことがあるようだ。人が持つ唯一絶対の力、それは自らの意志で進むべき道を選択することだ。
一番いいプラグインを頼む
正直、一番いいプラグインなんて分かりません。代替は多分もっと沢山あります。
今回は下記を組み合わせる方法をとりましたが、最良の未来を思い、自由に選択していけ。
名前 | 機能 | 代替になるもの? |
---|---|---|
vim-lsp | vimでLSPを使うためのプラグイン | neovimの新しい機能?, Coc |
vim-lsp-settings | vim-lspの設定を簡単にする | Coc |
ddc | 自動補完を行う | asyncomplete?, Coc |
ddc-vim-lsp | ddcとvim-lspを組み合わせる | ddc-nvim-lsp, Coc |
プラグイン管理ソフトは速度性能を求めるためにdeinを導入します。
さらに、vimrcの運用をスムーズにするために、目次をつけます。
githubでdotfileとして使うとしても、プライベートリポジトリはサクッとワンライナーで落とせないです。サクッとしたいこともあるから公開設定は必要。内容が理解できなかったらいけないので、この記事も必要。詰めた設定は手元のクローンでブランチを作りたいです。それぞれの環境が違うこともあるので、基本のvimrcと追加のvimrcを分けたいです。
当然ですが、ファイル分けすぎると性能が落ちます。
vim本体
vimの種類でもそれなりに変わります。vimにはtiny, normal, huge, fullがありますので色々選べるのですが、Vim scriptをまともに動かすためにはnormal以上が必要です。
vimとneovimが有力というのは周知の事実ですが、このneovimでもappimage版はどこでも動く代わりに起動とかが遅いです。最良の未来を思い、自由に選択していけ。
考えたくないけれど、どちらかがお亡くなりになる可能性も微レ存です。だから、両方で動く事が必要です。
依存ソフト
まずは入れようと思っている自動補完プラグインのddc.vimがDeno依存なので、denoを入れます。界隈にはDeno依存のプラグインがそれなりにあるようです。どうせDenoをいれるならということで、自作スクリプトもDeno製に決定。
以下、公式サイトからの引用。
インストール
Unix
curl -fsSL https://deno.land/x/install/install.sh | sh
WindowsPowershell
irm https://deno.land/install.ps1 | iex
scoop
scoop install deno
chocolatey
choco install deno
homebrew
brew install deno
cargo
cargo install deno --locked
プラグイン管理ソフトDein
色々なプラグイン管理ソフトがありますが、最良の未来を思い、自由に選択していけ。
今回選んだのはスピードの速いDein。多機能で高性能なので、使い心地はやや玄人向け?な感じです。一度設定してしまえばQOLが上がります。
他にはvimplugとかもいいですよね。
ちなみに、下記に依存しているのでそれらも入れることになります。インストーラーが用意されているので、それほど意識することはありません。
- roxma/nvim-yarp
- roxma/vim-hug-neovim-rpc
以下、リポジトリからの引用。
インストール
wget
sh -c "$(wget -O- https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh)"
curl
sh -c "$(curl -fsSL https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh)"
powershell
Invoke-WebRequest https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.ps1 -OutFile installer.ps1
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
./installer.ps1 ~/.cache/dein
プラグインを入れる時の注意
プラグインを入れるにあたり、出来る限り遅延読み込みをします。遅延読み込みをすることにより、起動が速くなります。
例えばDeinであれば下記のようにします。
call dein#add('prabirshrestha/vim-lsp', {'lazy': 1, 'on_event': 'VimEnter'})
VimEnterはvimの起動処理が終わった後という意味です。細かいプラグインは後で読めばいいので、さっさと起動してコードを表示してもらいましょう。
このVimEnterとかは一覧があります。下記で見られます。
help event
自分のVim scriptがあるなら、下記で発火のタイミングを指定できます。
autocommand VimEnter * ほげほげコマンド
だいたいこんな感じ。このような設定を行うことにより、起動時の体感速度は圧倒的になります。
…と言っても、素のvim(vim -u NONE)に比べると10倍以上遅いですが。
LSP
Language server protocolの略です。外部のソフトがvimで書いている事について色々してくれるやつですね。
vimでやる場合、vim-lspがあるのですが、vim-lspはvimとLSPを繋ぐだけです。最良のLSPを思い、自由に選択していけ…と言いたいところですが、これの面倒臭さは異常です。
vim-lsp-settingsを使うと、コマンド一撃でインストールしてくれます。
具体的には、下記のコマンドを入力するとエディタで開いている言語のLSPがインストールされます。
LspInstallServer
さて、vim-lsp-settingsは楽ちんではあるのですが、pythonを使っていて困りました。Lsp自体にパッケージが入っていないので、補完が丁寧じゃないのです。そこで、下記のスクリプトを書きました。
function! s:lsp_python_pip(module)
echo 'Installing '.a:module
let loc = g:lsp_settings#global_settings_dir().'/servers/'
let command = '/venv/bin/pip'
let names = ['pyls-all', 'pyls', 'pylsp-all']
for name in names
let code = loc.name.command
echo '> '.code
echo system(code)
endfor
echo 'Ended'
endfunction
command! -nargs=1 LspPip call s:lsp_python_pip("<args>")
これで、下記のようにすると補完がより強力になります。
LspPip install pandas
もちろん、これは単に仮想環境のpipを呼んでいるだけなので、uninstallとかも出来ます。
自動補完
IDEっぽいのを目指すので自動補完を入れます。自動補完はバックグラウンドでLSPで動かすので、当然体感速度上がります。
今回は自動補完プラグインとしてddcを採用しました。ddcには関連するプラグインと設定が非常に多いです。最良の未来を思い、自由に選択していけ。
ただ、一応答えになる雛形でもないと僕のような素人には難しいんですよね。というわけで取り敢えず下記を使ってみます。
名前 | 機能 | 備考 |
---|---|---|
tani/ddc-fuzzy | ddcでfuzzyな並べ替えをするやつ | |
Shougo/ddc-converter_remove_overlap | 補完の重複をなくすやつ | |
vim-denops/denops.vim | ddcを動かす為にDenoを使うやつ | 必須 |
Shougo/ddc-ui-native | popupメニューをddcと組み合わせるやつ | 必須 |
Shougo/ddc-around | カーソル周囲の単語で補完するやつ | |
shun/ddc-vim-lsp | ddcとLSPを組み合わせるやつ | LSPには必須 |
今回はこうしたけど、下記もあります。
名前 | 機能 |
---|---|
Shougo/ddc-sorter_rank | 書きかけから補完結果を並べ替えるやつ |
Shougo/ddc-matcher_head | 補完結果を名前順に並べ替えるやつ |
Shougo/pum.vim | コマンドラインモードで補完をするやつ |
確かに小さいのを組み合わせるという良さはあるのでしょうが、一寸多いですね。ここはファイルを分けたうえで目次を作って対策をします。
vimのq:で出てくるコマンドラインモードでも同じスタイルで補完して欲しければShougo/pum.vimを使うらしい。Shougo/ddc-ui-nativeはどうあっても必要らしい。
神は言っている、全てのvimrcを把握せよと
基本、Dotfilesになります。だから、Gitを使って管理すると幸せになれます。設定をふっとばしても元に戻せますしね。そして、複数のファイルに分散させて管理するのがよろしいかと。
だけど、それだけじゃなくて僕は目次が欲しかったんです。
vimrcはどんどん散らかります。そこで、僕はvimrcに目次をつけるスクリプトを書きました。vimはtagsというファイルを元にハイパーリンク的な挙動が出来ることは基本ですが…これを構築します。
どんなやり方でも良かったけれど、僕は「"# 」から始まる行を捉えるように書いてみました。まぁ、すごい適当です。見出しにアンダーバーを使わなきゃいけないという制約はあります。
"# Plugin_Dein
下記はdenoで書いたものです。
ハイパーリンク的な物を作るスクリプト
import { parse } from "https://deno.land/std@0.167.0/flags/mod.ts";
const args: object = parse(Deno.args)
const makeTag = async (fname: string, detectFunc: Function, writeFunc: Function) =>{
const text: string = await Deno.readTextFile(fname);
const lines = text.split('\n').filter(detectFunc)
const codes = lines.map(writeFunc(fname))
return codes
}
const isTypeofExt = (ext: string) => (fname: string) => fname.slice(fname.length - ext.length) === ext
const makeFnames = async (dirname: string, isFileOf: Function) => {
let result: string[] = []
for await (const dirEntry of Deno.readDir(dirname))
if (dirEntry.isFile && isFileOf(dirEntry.name))
result.push(dirname + dirEntry.name)
else if(dirEntry.isDirectory)
result = result.concat(await makeFnames(dirname + dirEntry.name + '/', isFileOf))
return result
}
const makeTags = async (detector, writer) => {
let result: string[] = []
for (const fname of await makeFnames(args._[0], isTypeofExt('.vim')))
for (const tag of await makeTag(fname, detector, writer))
result.push(tag)
return result.sort()
}
const detector = (text: string) => text.includes('"# ', 0)
const tocWriter = (fname: string) => (text: string) =>{
const path = fname.split('/')
const label = path[path.length - 1].split('.')[0].replace('-', '_')
const capitalLabel = label[0].toUpperCase() + label.slice(1)
return ('" ' + capitalLabel + '_' + text.slice(3) + ': in ' + fname)
}
const tagWriter = (fname: string) => (text: string) => {
const path = fname.split('/')
const label = path[path.length - 1].split('.')[0].replace('-', '_')
const capitalLabel = label[0].toUpperCase() + label.slice(1)
return capitalLabel + '_' + text.slice(3) + '\t' + fname + '\t/^' + text + '/'
}
if (args.tag === true){
console.log('!_TAG_FILE_SORTED 1')
let tags = await makeTags(detector, tagWriter)
for (const tag of tags.sort()) console.log(tag)
}
else if (args.toc === true){
console.log('"========================================')
console.log('"# Table_of_contents')
console.log('"========================================')
for (const tag of await makeTags(detector, tocWriter))
console.log(tag)
}
```</details>
<details><summary>これを呼ぶvimscript</summary>
```vim
function! MakeVimTOF()
call system('deno run --allow-read '.g:vimrc_dir.'/make_tags.ts'.' '.g:vimrc_dir.'/ --toc' .' > '.g:vimrc_dir.'/tof.vim')
call system('deno run --allow-read '.g:vimrc_dir.'/make_tags.ts'.' '.g:vimrc_dir.'/ --tag' .' > '.g:vimrc_dir.'/tags')
endfunction
これによって目次が出来ますが、この目次はvimrcとして読み込みませんので速度に影響はないはずです。vimrcにこんな風に書いておけばそこが目次へのリンクになります。
" Table_of_contents:
vimrcが冗長だと、色々ダルくなっていくので、なんとか工夫したい所です。
あぁ、やっぱり今回も駄目だったよ
下手くそが書いたvimrcは言うことを聞かないからな。