🌕

NeovimとLua

2020/11/10に公開
2

luaでのoption設定が2021年7月時点の情報です。それ以外は、2021年2月上旬時点から更新していません。そのため、Neovim v0.5.1以上に対応していません。

HEADのBreaking Changesはこのissueにまとまっています。

NeovimのLuaプラグインを作成する場合、次のtemplateが役に立つはずです。lintとtestの設定をしているtemplate達です。

Newslettersもあります。

設定フレームワーク

Luaで書かれているNeovimプラグインを沢山発見し、理由が気になり調べた結果をまとめたものです。次の3つを記載しています。

  • NeovimとLuaの相性がいい理由
  • Luaを利用したNeovimの設定
  • Lua製のプラグインの紹介
    プラグインマネージャーやLSP,fuzzy finderなどがあります。

NeovimとLuaの関係

NeovimはLua 5.1を組み込んでおり、Luaを実行できます。そのため、Neovimのコマンドモードで lua print('Hello World') を実行すれば、 Hello world とメッセージを表示します。

NeovimとLuaが相性が良い理由

Neovimの有名なコントリビューターのTJさんはVimconfのプレゼンで次の5つといっています。

  • 簡単
    Luaの学習コストは低く、誰でもすぐ書けます。
  • Luaのサイズが小さい
    バイナリサイズ(linux用)は200KB以下です。
  • 移植性
    ISO Cで実装されているため、OS Kernel内でもLuaは実行できます。
  • 埋め込みに適している。
    Vim scriptからLuaの関数を呼び出すことができます。その逆もできます。
  • Vim scriptよりスピードが早い
    Vim scriptなら約5.5秒かかる処理をLua(LuaJIT)は約0.003秒で処理します。

Lua単体の感想

  • 配列とdict両方になるtableは慣れれば見やすい
{1, 2, 3}  -- 配列
{a = 1, b = 2, c = 3}  -- dict
{1, b = 2, 3}  -- 配列とdict
  • エスケープシーケンスがいらない [[]] が便利
'a\\a'    -- a\a
[[a\\a]]  -- a\\a
  • 配列のindexが1開始は慣れれば気にならない

JavaScriptのTypeScriptのように、Luaのコードに変換するtranspilersがあり、次のようなものがあります。

  • TypescriptをLuaに変換するTypeScriptToLua
  • coffeescriptと構文が似ているmoonscript
    nvim-moonmakerがあれば、moonscriptでNeovimのプラグイン開発ができます。
  • lispと構文が似ているFennel
  • Luaに型を付けたtl
  • 静的型付けのHaxe

Vim scriptとLuaの比較

Luaの良い点

  • 改行時の \ がいらない

Vim script

echo({ 'a': 1,
     \ 'b': 2 })  " `\` を削除するとエラー

Lua

print({ a = 1,
        b = 2 })
  • 変数や関数のスコープ周りが楽

Vim script

function s:hoo(num)
  let num = 1
  " 引数の `num` ではなく、関数内で宣言した `num` の値(1)が表示されます。
  " 引数の `num` を表示させたい場合は `echo a:num` と書きます。
  echo num
endfunction

Lua

function hoo(num)
 print(num)  -- 引数の `num` が表示されます。
end
  • dict時のkeyが文字列の場合、 '' で囲む必要がない

Vim script

{ 'a' : 1, 2 : 2 }

" `#{}` を使えばいりません。
" ネストする場合は、そのdictも `#{}` にする必要があります。
#{a: 1, 2: 2, d: {'a': 1}}

Lua

-- 数値の場合は `[]` で囲む必要があります。
-- 数値より文字列のkeyを使う場面が多いので、こちらのほうが楽です。
{ a = 1, [2] = 2 }
  • 関数を呼ぶときに call がいらない

Vim script

" `echo` は関数ではなくコマンドなので `call` はいりません。
call foo()

Lua

foo()
  • 関数やif文などの終わりが end に統一されている

Vim script

function foo()
  ...
endfunction

if i == 0
  ...
endif

for i in [1,2,3]
  ...
endfor

Lua

function foo()
  ...
end

if i == 0 then
  ...
end

for i, val in pairs({'a','b','c'}) do
  ...
end

Luaの気になる点

  • ifの then やforの done
    snippetを使えば自動で挿入されるので悪い点とまでは感じません。

Lua

if i == 0 then
  ...
end

for i, val in pairs({'a','b','c'}) do
  ...
end

LuaでのNeovim設定

Vim scriptよりLuaのほうが設定は楽に感じています。大きいのが次の2点です。

  • オプション(e.g. set number)やキーマップ(e.g. nnoremap <silent> <C-w>d :delete<CR>)などのコマンドを関数で実行できる
    キーマップなどで変数の値を利用するときに execute コマンドを使う必要がありません。

Lua

-- execute('nnoremap <silent> <leader>' .. l:key .. ' :' .. l:cmd .. '<CR>')
vim.api.nvim_set_keymap('n', '<leader>' .. key, ':' .. cmd .. '<CR>',
                        { noremap = true, silent = true })
  • 変数のスコープが楽
    Vim scriptのように引数を参照するため a: をつける必要はありません。ls, g などを気にするのはVim scriptの変数を利用するときのみです。

学習コスト

Vim scriptを知っている人の場合、slinさんが書いている記事も含む、次の参考資料を読めば30分もあれば理解できる印象です。Luaを知らない場合でも、Luaの文法は簡単なので合わせて1時間もあれば大丈夫な感じです。

変数やオプションなどの操作

LuaでNeovimの次の要素の管理や操作ができます。

Vim script変数

vim.{g|b|w|t|v|env}.<variable_name>でVim script変数を管理できます。

-- let g:hoo = 1
vim.g.hoo = 1
-- unlet g:hoo
vim.g.hoo = nil
-- let b:hoo = 1
vim.b.hoo = 1

-- let g:filename#variablename = 0
-- `''` で囲まないとエラーになります。
vim.g['filename#variablename'] = 0

vim.api.nvim_***_{set|get|del}_varでも、Vim script変数を管理できます。
vim.{b|w|t} と違い、バッファ/ウィンドウ/タブを指定できます。

option

vim.{o|go|bo|wo}.<option_name>でoptionの値を変更・取得できます。

-- set cmdheight=2
vim.o.cmdheight = 2
-- set cmdheight?
print(vim.o.cmdheight)

-- 切り替えオプションはbooleanを利用します。
-- set noruler
vim.o.ruler = false

-- 略名も使えます。
-- set slm=""
vim.o.slm = ""

-- optsには {cmdheight = 2, ...} のようなオプションと値のobjectが格納されています。
for opt, val in pairs(opts) do vim.o.[opt] = val end

setのようにグローバルとローカル両方変変更したい場合は o を使います。

-- set cmdheight=1
vim.o.winminheight = 1

setglobal のようにグローバルの値だけを変更する場合は go
setlocal のようにローカルの値だけを変更する場合は bo or wo を使います。
:help '<option-name>' で表示されるhelpに
global ならグローバル、local to {window|buffer} ならローカルオプションです。

-- setglobal laststatus=0
vim.go.laststatus = 0
-- setlocal nonumber
vim.wo.number = false

ローカル(bo or wo)の場合は、バッファとウィンドウの番号を指定できます。
0の場合は、カレントバッファ/ウィンドウを指定します。

vim.bo[0].tabstop = 2

opto と同じように set のように動作します。
オプションの値を返すとき、oopt で値が違います。
o の場合は文字列、数値、真偽値を返します。
opt の場合はoption objectを返します。

-- set showtabline=0
vim.opt.showtabline = 0

print(vim.o.colorcolumn)                       -- 80
print(vim.inspect(vim.opt.colorcolumn))        -- { _info = {  ... },  _name = "colorcolumn", _value = "80", <metatable> = <1>{ ... } }
print(vim.inspect(vim.opt.colorcolumn:get()))  -- { "80" }

set+= set-= set^= と同じ動作をするメソッドが opt にはあります。

-- set tabstop+=10
vim.opt.tabstop:append(10)
vim.o.tabstop = vim.opt.tabstop + 10
-- set tabstop-=10
vim.opt.tabstop:remove(10)
vim.o.tabstop = vim.opt.tabstop - 10
-- set backupdir^='~/.config/nvim'
vim.opt.backupdir:prepend('~/.config/nvim')

optset に対応しており、
opt_globalsetglobalopt_localsetlocal に対応しています。

-- setlocal foldlevel+=2
vim.opt_local.foldlevel:append(2)
lua vim.wo.foldlevel = vim.wo.foldlevel + 2

vim.api.nvim_***_{set|get}_optionでも、optionの値を変更・取得できます。
ただ、setvim.o のようにグローバル・ローカル両方の値を変更できません。

Keymap

vim.api.nvim_set_keymapvim.api.nvim_buf_set_keymapを使います。

-- nnoremap <silent> <C-w>d :bdelete<CR>
vim.api.nvim_set_keymap('n', '<C-w>d', ':bdelete<CR>',
                        { noremap = true, silent = true })

-- mapsには {l = 'Lint', ...} のようなkeyとコマンドのdictが格納されています。
for key, cmd in pairs(maps) do
  vim.api.nvim_set_keymap(
    'n',
    '<leader>' .. key
    ':ALE' .. cmd .. '<CR>',
    { noremap = true, silent = true }
  )
end

vim.keymap.nnoremap { '<leader>h', function() print("Hello world") end } のような書き方ができるAPIを開発中です。astronauta.nvimをインストールすると、開発中のAPIを使用できます。

関数

vim.fn.<func_name>を使います。

-- call abs(1)
vim.fn.abs(1)

-- call filename#funcname(1)
vim.fn['filename#funcname'](1)

Vim script実行

vim.cmdを使います。

-- echo 123
vim.cmd('echo 123')

次の設定のinterfaceは現在実装されていませんが、実装される予定はあります。
vim.cmd でVim scriptを書いても設定できます。

Luaファイルの読み込み

Vim scriptで書かれた設定 init.vim の代わりに、luaで書かれた設定 init.lua を読ませることができます。 init.lua を置く場所は init.vim と同じです。両方は読み込めず、両方ある場合は Conflicting configs: ... のメッセージが表示されます。

require

require('file_path') でluaファイルをモジュールとして読み込みます。Neovimの runtimepath ディレクトリ内の lua ディレクトリに格納されているLuaファイルを require で読み込めます。

init.vim

  • lua require('plugins')
    ~/.config/nvim/lua/plugins/init.lua を読み込みます。
  • lua require('plugins.completion')
    ~/.config/nvim/lua/plugins/completion.lua を読み込みます。
  • lua require('plugins/completion')
    . or / どっちでもOKです。 . のほうがよく見ます。
ディレクトリ構成
# `~/.config/nvim` は `runtimepath` に含まれるディレクトリです。
~/.config/nvim
> ls init.vim lua/plugins/
init.vim

lua/plugins/:
completion.lua               finder.lua  linter.lua    parser.lua
download_plugin_manager.lua  git.lua     lsp.lua       status_and_tab_line.lua
filer.lua                    init.lua    markdown.lua  template.lua

require はモジュールをキャッシュします

require でロードしたモジュールを変更した後、変更したモジュールをもう1度 require しても、モジュールは更新されません。どのモジュールをロードしたか require が記憶しているからです。
packer.loaded グローバルテーブルを変更すれば、モジュールを更新できます。

package.loaded['module_name'] = nil
require('module_name')

モジュールを更新するプラグイン

自動で読みこまれるLuaファイル

runtimepath内の特定のフォルダ内にあるluaファイルを自動的に読みこみます。
現在、次のフォルダ内のluaファイルを読みこみます。

  • colors
  • compiler
  • ftplugin
  • ftdetect
  • indent
  • plugin
  • syntax

Luaと他言語のプラグインの違い

他言語よりLuaで実装したプラグインのほうが処理速度は速いです。LuaはNeovim内部で動くため、プラグインとNeovim間のRTT(Round-Trip Time)が他の言語より短いです。

Vim scriptやLua以外の言語はRPC(MessagePack-RPC)を使用して、Neovimを動かせます。Luaと違いNeovim内部で完結しているわけではないため、LuaよりRTTが長くなります。

Vim9 script

VimでもVim scriptを改良したVim9 Scriptを開発中です。今までのVim scriptより書きやすく、速度も改善されています。LuaJITが有効ではないLuaより処理速度が速いです。開発中なので処理速度は今より速くなる可能性があります。Vim8.2のhelp fileに記載されている通り、開発中のVim9 scriptはVim8.2で試せます。

NeovimがVim9 Scriptに対応するかどうかは調べておらず、対応するという情報は見つけていません。ただ、NeovimのコントリビューターのTJさんがLuaJITとLPEGを利用してVim9 scriptのparserを開発しているようです。

Lua製のプラグイン

紹介するプラグインの大半は、開発中のv0.5のNeovimであることが必須条件です。

Packer.nvim

emacsのuse-packageに触発されたplugin/package managerです。
Packer.nvimの作者によるdein.vimとの比較

特徴

  • Native Packagesを使ってプラグインを読み込む
    プラグインの読み込みは packadd を利用する packages なため、Packer.nvim を利用しないor削除してもプラグインは読み込まれます。
  • Luaで設定を書ける
  • プラグインの遅延読み込みができる
  • プラグインの依存関係を設定できる
  • Post-install/update hook対応
  • Luarocks対応
詳細

Native Packagesを使ってプラグインを読み込む

Packagesはプラグインを含むディレクトリで、プラグインの数は1つor複数どちらでも大丈夫です。Pacakgesになるディレクトリは packpath (set packpath? で表示)にある pack という名前のディレクトリです。

PacakgeはNeovim起動時にプラグインを runtimepath へ追加して、scriptを読み込むか選択できます。これはプラグインを置いたディレクトリで決まります。

ディレクトリ 起動時にプラグインを読み込むか
start O
opt X

起動時に読み込まない opt ディレクトリ内のプラグインは packadd <direcotry_name> で読み込みます。次のls結果の opt/ 内にある packer.nvim を読み込む場合 packadd packer.nvim を実行します。packadd! <direcotry_name> のように ! をつけた場合は runtimepath へ追加されますが、プラグインのファイルやftdetect scriptsは読み込まれません。runtimepath に追加されるため、プラグインのautoload関数は使えます。この packadd!init.vim.vimrc に書くときに便利です。

# `~/.local/share/nvim/site` はpackpathに含まれます。
# packpathの `~/.local/share/nvim/site` にある `pack` ディレクトリ内の
# `packer` がpacakgesになります。
~/.local/share/nvim/site/pack/packer
> ls opt/ start/
# 起動時に読み込まないプラグイン
opt/:
ale                    neosnippet.vim           popup.nvim            vim-fugitive
auto-pairs             nnn.vim                  preview-markdown.vim  vim-quickrun
completion-buffers     nvim-treesitter          sonictemplate-vim     vim-surround
completion-nvim        nvim-treesitter-context  tcomment_vim
completion-treesitter  packer.nvim              telescope.nvim
neosnippet-snippets    plenary.nvim             undotree

# 起動時に読み込むプラグイン
start/:
indentLine  nvim-lsp  tokyonight-vim  vim-gitgutter  vimdoc-ja

start のプラグインを読み込む手順は次のとおりです。

  1. init.vim を読み込む
  2. start ディレクトリ内のプラグインを読み込む
  3. plugins ディレクトリ内のファイルを読み込む

このため pluigns ディレクトリに start のプラグインに依存しているVim scriptを書いてもエラーは発生しません。

Packer.nvim の大きな役割は次の2つです。

  • プラグイン管理
    • Install(PackerInstall)
    • Update(PackerUpdate)
    • 使用しないプラグインの削除(PackerClean)
  • プラグインの設定をするVim scriptファイルの生成(PackerCompile)
    デフォルトで ~/.config/nvim/plugin/packer_compiled.vim として出力します。 ~/.config/nvim/plugin 内のVim scriptは起動時に読み込まれるので、起動時に生成した設定ファイルは読み込まれす。 起動時に読み込むプラグインは plugin ディレクトリ内のscirptを読み込む前に利用できますのでエラーは発生しません。
    設定ファイルに書き込まれる内容は次のようなものがあります。
    • Neovim起動時に読み込む設定 (e.g. 変数やキーマップ)
      起動時に読み込むプラグインの場合Neovim起動時 = プラグイン読み込み時になりますが、遅延読み込みするプラグインの場合は違います。
    • プラグイン読み込み時の設定 (e.g. 変数やキーマップ)
    • 遅延読み込みするプラグインの読み込むトリガー
      • 特定keyを押す
      • 特定のfiletypeのバッファを開く
      • 特定のcommand or eventを実行

この2つの動作をするときに packer.nvim を読み込むよう作成者のWil Thomasonさんは設定しており、Neovim起動時に packer.nvim を読み込んでいません。

Luaで設定を書ける

次のように利用するプラグインを設定していきます。

return require('packer').startup(function()
  -- `packer.nvim` を管理するプラグインに追加します。
  -- `opt` はプラグインの設定の1つです。値の意味は後述します。
  use { 'wbthomason/packer.nvim', opt = true }

  -- 1つの `use` で複数のプラグインを追加できます。
  use { 'tpope/vim-fugitive', 'airblade/vim-gitgutter' }
  use {
    {
      'nvim-lua/completion-nvim',
      event = 'InsertEnter *',
      config = function() vim.cmd('packadd completion-buffers') end
    },
    { 'steelsojka/completion-buffers', opt = true },
  }
end)

プラグインの設定の1つの config はプラグイン読み込み時に実行するコードを記載します。プラグイン読み込み時なので、起動時に読み込まない遅延読み込みのプラグインの場合はNeovim起動時に実行されません。遅延読み込みするプラグインでNeovim起動時に実行したいコードがある場合は setup を使います。

use {
  'tpope/vim-fugitive',
  config = function()
    local maps = {s = 'status', ... }
    for key, cmd in pairs(maps) do
      vim.api.nvim_set_keymap('n', '[git]' .. key, ':G' .. cmd .. '<CR>',
                              { noremap = true, silent = false })
    end
  end
}

Luaの require を使い、プラグインを複数のファイルに分けることができます。

lua/plugins/init.lua
return require('packer').startup(function()
  -- for _, path in pairs{ 'git', ... } do use(require('plugins.' .. path)) end
  -- でもOKです。
  vim.tbl_map(
    function(path) use(require('plugins.' .. path)) end,
    {
      'git', 'template', 'linter', 'status_and_tab_line', 'parser', 'finder',
      'lsp', 'filer', 'completion', 'markdown'
    }
  )
end)
lua/plugins/git.lua
return {
  {
    'tpope/vim-fugitive',
    -- 長いコード
  },
  {
    'airblade/vim-gitgutter'
  }
}
# `~/.config/nvim` はruntimepahtに含まれるディレクトリです
~/.config/nvim
> ls init.vim lua/plugins/
init.vim

lua/plugins/:
completion.lua               finder.lua  linter.lua    parser.lua
download_plugin_manager.lua  git.lua     lsp.lua       status_and_tab_line.lua
filer.lua                    init.lua    markdown.lua  template.lua

プラグインの遅延読み込みができる

use { 'wbthomason/packer.nvim', opt = true }optプラグインの設定の1つです。opttrue ならプラグインを opt ディレクトリに配置して、起動時読み込まないようにします。読み込む場合は packadd <plugin_name> を使います。opt を記載しない場合の値は設定で変更でき、デフォルトでは false です。

opt 以外にも遅延読み込みプラグインに設定するオプションは次があります。設定するとプラグインは opt ディレクトリに配置され、起動時に読み込まれません。起動時に読み込むプラグインを遅延読み込みに変更する場合は PackerUpdate が必要です。

key プラグインを読み込む瞬間
cmd 値のcommandを実行
ft 値のfiletypeのバッファを開く
keys 値のkeyを押す
event 値のeventを実行
cond 値のtestが通ったとき
setup なし
use {
  'skanehira/preview-markdown.vim',

  -- プラグインを読み込むトリガーが2つある場合、
  -- 1つでも条件が成立したときに
  -- プラグインは読み込まれます。
  ft = 'markdown',
  cmd = 'PreviewMarkdown',
  setup = function()
    -- `<M-r>m` で `cmd` のコマンドを実行するので、
    -- `<M-r>m` を押せば、プラグインは読み込まれます。
    -- `setup` ではなく `config` にキーマップのコードを書いた場合、
    -- プラグイン読み込み時にキーマップが登録されるので、
    -- `<M-r>m` を押しても何も反応せず、プラグインも読み込まれません。
    vim.api.nvim_set_keymap('n', '<M-r>m', ':PreviewMarkdown<CR>',
                            { noremap = true, silent = true })
  end,
  config = function()
    vim.g.preview_markdown_parser      = "mdcat"
    vim.g.preview_markdown_vertical    = 1
    vim.g.preview_markdown_auto_update = 1
  end
}

プラグインの依存関係を設定できる

requires keyを使い依存するプラグインを設定できます。

use {
  ...
  requires = { 'depend/plugin' },
},

依存されている(i.e. depend/plugin)のプラグインがない場合、デフォルトではインストールします。インストールしないようにすることもできます。
依存しているプラグインが利用されていない場合、 requires 内のプラグインは必要ないプラグインとなり PackerClear で削除されます。

-- PackerCleanすると両方のプラグインは削除されます。
use {
  'plugin/a',
  opt = true,
  -- プラグインを無効にして利用しないようにします。
  disable = true,
  requires = { 'plugin/a', opt = true },
}

-- 1つの `use` で複数のプラグインを設定している場合
-- `plugin/a` のみ削除されます。
use {
  { 'plugin/a', opt = true, disable = true },
  { 'plugin/b', opt = true },
},

Post-install/update hook対応

installerupdater, run を使えばプラグインをinstall,update時に指定の処理を実行できます。

use {
  'iamcco/markdown-preview.nvim',
  -- Install,Update時に実行する処理
  run = 'cd app && yarn install'
}

run が文字列の場合、先頭の文字が : ならneovimのコマンドが実行され、: ではない場合shellコマンドとして実行されます。Shellの場合 $SHELL -c 'cd <plugin dir> && <run>' で実行します。

Luarocks対応

  • rocks: プラグインに依存しているrocksを設定します
  • use_rocks: インストールするrocksを設定します

注意事項

PackerCompile を実行しないと設定は反映されません

configevent などの設定を変更しても PackerCompile を実行して packer_compiled.vim を更新しないと設定は反映されません。use { ... } を記載している設定ファイルではなく、 PackerCompile で出力している packer_compiled.vim を、Neovimが起動時に読み込んでいるからです。

PackerCompile を変更するたびに実行するのがめんどくさい場合は autocmd BufWritePost plugins.lua PackerCompile のような設定をしてください。

configsetup の値の関数にエラーがあっても PackerCompile 実行時に発生しません

設定内のLuaにエラーがある場合、 PackerCompile 実行時にエラーは発生します。 config に文字列を設定した場合もそうです。しかし config に関数を設定し、その関数内にエラーがある場合 PackerCompile 実行時ではなく、プラグインが読み込まれ関数を実行するときにエラーは発生します。setupcond も同じです。

関数の場合なので config = require('<存在しないプラグイン名>') の場合は PackerCompile 実行時にエラーが発生します。config = function() require('<存在しないプラグイン名>') end の場合は発生せず、関数実行時に発生します。

vim:config = function() require('<存在しないプラグイン名>') end のときのエラー内容

" `packer_compiled.vim` は `PackerCompile` で生成するファイル
Error detected while processing ... /plugin/packer_compiled.vim:
line  297:
E5108: Error executing lua [string "..."]:0: module 'aaa' not found:
        no field package.preload['aaa']
        no file './aaa.lua'
	...

遅延読み込みのON・OFFの変更は PackerUpdate する必要があります

use { ..., opt = false }opttrue に変更して、起動時に読み込むのではなく遅延読み込みに変更する場合 PackerCompile を実行して packer_compiled.vim を更新し設定を変更するだけでは駄目です。PackerUpdate を実行する必要があります。
実行しないとプラグインが start ディレクトリに存在するため、Neovimが起動時に読み込んでしまい、遅延読み込みになりません。PackerUpdate を実行してプラグインのディレクトリを start から opt に移動すれば、起動時に読み込まれません。
遅延読み込みから起動時読み込むに変更するときも同じです。

この PackerUpdate 忘れによる startopt ディレクトリの移動し忘れは configsetup などでエラーが発生する原因になります。

起動時に読み込むプラグインの削除する場合は PackerClean をする必要があります

起動時に読み込むプラグインを削除したい場合、そのプラグインの use のコードを削除して PackerCompile して設定を変更するだけでは駄目です。 start ディレクトリにプラグインが残っているので起動時にNeovimはそのプラグインを読み込みます。 PackerClean を実行して start ディレクトリから削除する必要があります。disable = true も同じです。

PackerUpdate を実行しても削除はしません。PackerSync を実行すれば PackerClean をやったあとに PackerUpdate を実行してくれます。

userequires のプラグインが複数の場合、tableで書かないとオプションは適用されません

次の設定で completion-nvim はインストールされますが {} で囲まれていないので eventconfig の設定は PackerCompile しても packer_compiled.vim に反映されません。そのため completion-nvim は遅延読み込みではなく、起動時に読み込まれます。
{} で囲まれてないから PackerCompile 時にエラーが発生することはありません。

use {
  -- `{}` に囲まれていないため
  -- `{ 'nvim-lua/completion-nvim', { ... }` と
  -- 同じ設定になります。
  'nvim-lua/completion-nvim',
  event = 'InsertEnter *',
  config = vim.cmd('packadd completion-buffers')
  { 'steelsojka/completion-buffers', opt = true },
}

-- `requires` の場合も同じなのでtableにする必要があります。
use {
  ...,
  requires = {{ 'plugin/a', opt = true }, { 'plugin/b', opt = true }}`
}

nvim-lspconfig

v0.5で実装される組込みのLSP(Language Server Protocol)クライアント用の共通設定集です。

組み込みのLSPクライアントの設定が簡単になるだけで、これがないと組み込みのLSPクライアントが使えず、LSPの機能が使えないというわけではありません。クライアントはneovim本体に組み込まれており、Luaで書かれています。なぜNeovimにLSPクライアントが組み込まれたかはtjさんの動画が参考になります。

vim-lspcoc.nvim,LanguageClient-neovimなどの有名なLSPクライアントとNeovimの組み込みのLSPクライアントとの違いなどは次の記事を参考にしてみてください。

利用方法

Luaのlanguage serverを利用するには次の手順でやります。

  1. nvim-lspconfigをインストール
  2. LuaのLSP serverのsumneko_luaをインストール
    両方ともbuild systemのninjaが必要です。
    • 手動
      sumneko_luaのwikiの手順でインストールします。
      CONFIG.mdの設定をそのまま使用したいので git clone https://github.com/sumneko/lua-language-servergit clone https://github.com/sumneko/lua-language-server ~/.cache/nvim/lspconfig/sumneko_lua/lua-language-server に変更します。
    • nvim-lspinstallを利用
      nvim-lspinstallをインストールし、 LspInstall sumneko_lua コマンドを実行します。
      ~/.local/share/nvim/lspinstall/lua-langauge-server に配置されます。
  3. init.vim or init.luaCONFIG.mdの設定を追加
    nvim-lspinstallの場合は sumneko_root_pathvim.fn.stdpath('data') .. '/lspinstall/lua-langauge-server' に変更してください。
  4. Neovimを再起動
  5. set ft=lua で編集中のバッファをluaに変更する
  6. sumneko_lua が起動して、編集中のバッファに接続する
    診断機能が有効になっているのでsyntax errorがあると、syntax errorの診断メッセージがvirtual textで表示されます。
    LspInfo で起動しているLSPクライアントや編集中のバッファと接続しているLSPクライアントの情報がわかります。動かない場合は checkhealth を実行してみましょう。

定義ジャンプやsymbol参照などの方法は help lsp-configslinさんのNeovimのBuiltin LSPを使ってみるを参照してください。LSPで何ができるかは北川亮さんのvim-lspでできることも参考になります。vim-lspについて書いてますが、大体はNeovimの組み込みのLSPでもできるはずです。

注意事項

LspIntallLspInstallInfo の廃止

Remove all installers and install logic #498により廃止されました。Pull requestの作成者のmjlbachさんのコメントを読むと次の2点があるようです。

  • LSP serverが古いせいで発生するbugを修正する仕組みがない
  • LSP serverをインストールできるソース(npm, linux package manager, brew)が多種多様で、全てをサポートするのが大変

今後LSP serverをインストールする際は次の手段があります。

LSP関連の他のプラグイン

  • lspsaga.nvim
    定義や参照一覧、定義やdocument情報をfloating windowで表示できます。
  • nvim-lsputils
    使用できるcode actionsをfloating windowで表示したり、定義ジャンプやsymbol参照の一覧とそのpreviewをquickfixを使って表示できます。
  • nvim-lspfuzzy
    FZFを利用して、定義ジャンプなどのLSP操作を行います。
  • nvim-lightbulb
    現在の行で textDocument/codeAction ができるか表示します。
  • aerial.nvim
    Symbolsを目次形式で表示し、選択したsymbolに移動します。
  • folding-nvim
    折りたたみにLSPを使います。
  • nvim-lsp-smag
    NeovimにLSPをシームレスに統合できるようにして、タグファイルまたは vim.lsp.client を利用する処理全体を単純化します。 tagfunc オプションを利用して、tagの検索方法をカスタマイズできます。
  • lsp-status.nvim
    組み込みのLSPクライアントからstatuslineのcomponentを生成します。
  • onsails/lspkind-nvim
    VSCode-like iconsを追加します。
  • lsp_extensions.nvim

diagnostics

言語特有のプラグイン

  • nvim-lsp-ts-utils
    TypeScriptの開発を支援します。
    • importの整理(LspOrganize)
      未使用importの削除&並べ替えを行ないます。
    • 現在行に利用可能なコードアクション(vim.lsp.buf.code_action())を利用(LspFixCurrent)
      複数ある場合は1番最初のコードアクションを実行します。
    • ファイル名変更とimportの更新(LspRenameFile)
    • 足りないimportを追加(LspImportAll)

組み込みのLSPクライアントとcoc.nvimの違い

nvim-lspconfigのコントリビューターのmjlbachさんのコメントを自分なりに解釈して、それをまとめた文です。

  • 組み込みのLSPクライアントは拡張性が高く、neovimの設定を変更すれば多くの点を変更できる
    全てluaで書かれているため、簡単にカスタマイズできます。ハンドラベースで、動作を上書きするのも非常に簡単です。
  • nvim-lspconfigはCocプラグインと同等の機能を提供しない
    エコシステムではなく、最低限のdefault機能を提供するのをnvim-lspconfigは目的としているからです。nvim-jdtlsのようなCocプラグインと同等の機能を持ったプラグインは今後増えていく可能性があります。

参考にしたコメント

nvim-treesitter.nvim

Tree-sitterの設定をするプラグインです。Neovimに存在するtree-sitterのlibraryを利用しています。

Tree-sitterとは

詳細

役割

  • Parserの作成
  • 漸進的分析(Incremental Parsing) library

特徴

Tree-sitterの公式サイトでtree-sitterを試せて、syntax treeの動きを確認できます。

目標

  • どんなプログラミング言語でもparsesする
  • Key操作全てを解析できる速さ
  • Syntax errorがあっても有効な情報を提供する
  • 任意のアプリケーションへ組み込めるようにする
    tree-sitterは純粋なC言語で書かれているため依存しているものはありません。

エディタのどの機能に役立つか

実際に動かしている動画がリンク先にあります。

Language serversとの比較

  • ParseがLanguage serverより速い
  • 依存関係がない
    Language serverはそれぞれに依存関係がある

機能

  • highlight
    今まで色がついてない部分に色がつきます。
  • Incemental selection
    textobject単位の選択の拡大ができます。
  • Indent
    Indentを整える(={motion})処理をTreesitterのparseをもとにおこないます。

追加モジュールとプラグイン

モジュール

  • textobject

    • ipap のようにtextobjectを選択します。
    • 前後のtextobjectに移動します。
    • 関数の引数などの順番の前後を交換します。
    • textobject全体をfloating windowを使って表示します。
      Neovimの組み込みのLSPが必要です。
    • textobjectは設定で変えられます。
      言語によって利用できるtextobjectは違います。今後追加される可能性があります。
  • refactor

    • カーソルの下にあるsymbolの定義位置に移動したり、定義されているsymbol一覧を表示します。
    • カーソルの下にあるsymbolをrenameします。
      同じスコープ内のsymbolが対象のため、違うスコープ内のsymbolはrenameされません。
    • カーソルの下にあるsymbolをhighlightします。
      同じスコープ内のsymbolが対象のため、違うスコープ内のsymbolはhighlightされません。
      Colorschemeの設定でhighlightされているか分かりづらいことがあります。
    • カーソルが存在するスコープ全体をhighlightします。
      Colorschemeの設定でhighlightされているか分かりづらいことがあります。
  • rainbow
    vim-rainbowのように括弧(e.g. {([])})の色をネストごとに変更します。

  • nvim-ts-autotag
    HTMLタグの終了タグを自動的に挿入します。rename機能もあります。HTML専用のようです。

プラグイン

他にも拡張モジュールやプラグインはあります。

Command

  • TSInstall {language}
    language のparserをインストールします。 TSInstall のときにtabを押せば対応している言語一覧が表示されます。 all の場合全てインストールします。
    checkhealth でインストールしたparserの状態を確認できます。
  • TSUninstall {language}
    language のparserをuninstallします。
  • TSInstallInfo
    対応している言語のparserをインストールしているか確認できます。
  • TSUpdate
    インストールしているparser全てをupdateします。
  • TSConfigInfo
    tree-sitterの設定情報を表示します。

設定方法

context以外の機能はモジュールにあたり、デフォルトで無効になっているため動きません。動かすには TSEnableAll <機能名> を使うか設定を変えます。contextはデフォルトで有効になっているため動きます。

設定例
require'nvim-treesitter.configs'.setup { -- 設定された言語のparserがインストールされていない場合、
  -- インストールします。
  ensure_installed = {'ruby', 'lua'},

  highlight = {
    -- `false` の場合、highlight機能を動かしません。
    enable = true,

    -- highlightの機能を無効にする、filetypeを指定します。
    disable = { "bash", "c" },
  },

  incremental_selection = {
    enable = true,

    keymaps = {
      -- 範囲選択を開始します。
      init_selection = "gnn",

      -- 1つ上のnodeに選択範囲を拡大します。
      node_incremental = "grn",

      -- 1つ上のスコープに選択範囲を拡大します。
      scope_incremental = "grc",

      -- 1つ下のnodeに選択範囲を縮小します。
      node_decremental = "grm",
    },
  },

  indent = {
    enable = true
  },

  textobjects = {
    -- `ip` や `ap` のようにtextobjectを選択します。
    select = {
      enable = true,
      keymaps = {
        ["af"] = "@function.outer",
        ["if"] = "@function.inner",
        ["ac"] = "@class.outer",
        ["ic"] = "@class.inner",
      },
    },
    -- 前後のtextobjectに移動します。
    move = {
      enable = true,
      goto_next_start = {
        ["]m"] = "@function.outer",
        ["]]"] = "@class.outer",
      },
      goto_next_end = {
        ["]M"] = "@function.outer",
        ["]["] = "@class.outer",
      },
      goto_previous_start = {
        ["[m"] = "@function.outer",
        ["[["] = "@class.outer",
      },
      goto_previous_end = {
        ["[M"] = "@function.outer",
        ["[]"] = "@class.outer",
      },
    },

    -- 関数の引数の位置を交換します。
    swap = {
      enable = true,
      swap_next = {
        ["<leader>a"] = "@parameter.inner",
      },
      swap_previous = {
        ["<leader>A"] = "@parameter.inner",
      },
    },

    -- textobject全体をfloating windowを使って表示します。
    lsp_interop = {
      enable = true,
      peek_definition_code = {
        ["df"] = "@function.outer",
        ["dF"] = "@class.outer",
      },
    },
  },
  refactor = {
    -- カーソルの下にあるsymbolの定義位置に移動したり、
    -- 定義されているsymbol一覧を表示します。
    navigation = {
      enable = true,
      keymaps = {
        -- 定義に移動します。
        goto_definition = "gnd",

        -- 定義一覧を表示します。
        list_definitions = "gnD",

        -- 定義一覧を本の目次のようにネストがわかるように表示します。
        list_definitions_toc = "gO",

        -- カーソル下のsymbolの前後の利用位置に移動します。
        goto_next_usage = "<a-*>",
        goto_previous_usage = "<a-#>",
      },
    },

    -- カーソルの下にあるsymbolをrenameします。
    smart_rename = {
      enable = true,
      keymaps = {
        -- `grr` でrename処理が開始できます。
        smart_rename = "grr",
      },
    },
    -- カーソルの下にあるsymbolをhighlightします。
    highlight_definitions = { enable = true },

    -- カーソルが存在するスコープ全体をhighlightします。
    highlight_current_scope = { enable = true },
  },

  -- 括弧の色をネストごとに変更します。
  rainbow = {
    enable = true
  },
}

Colorschemes

nvim-treesitterから定義されたhighlight groupを利用しているcolorschemesがあります。

completion-nvim

非同期の補完フレームワークです。組み込みのLSPに補完を提供するのが目的です。

機能

設定方法

  • 全てのバッファで機能を有効する場合
    autocmd BufEnter * lua require'completion'.on_attach()
  • LSPが有効なバッファ場合のみ機能を有効する場合
" `vimls` の部分は使用するlnaguage serverに変更してください。
lua require'lspconfig'.vimls.setup{on_attach=require'completion'.on_attach}`

telescope.nvim

NeovimのコントリビューターのTJさんが作成している組み込みのLSPやtree-sitterとの連携ができるfuzzy finderです。
fzffzy,skとの依存性はありません。fzfやfzyと連携したい場合は拡張機能を利用してください。
プレビュー表示もあり、batがあれば色付きで表示されます。
今回紹介する残りのfuzzy finderと違いtree-sitterと連携できます。

TJさんはこのtelescopeも含むプラグインやneovimの新機能の開発をtwitchで配信しています。redditのneovimのコミュニティスレッドをたてたりコメントもしています。telescopeの作成理由もyoutubueに投稿しています。

fzffzf-preview,denite,ctrlpなどとの違いは、Tips集のKatapediaとプラグインの選択・管理のポイントを記載しているVimプラグイン"の"カテゴリまとめの作者のyutakatayさんのVimにたくさんあるファジーファインダー系プラグインを比較してみるを参考にしてみてください。

Picker

コマンドラインでの使い方

Telescope <piker_name> で実行できます。

  • Telescope builtin
    オプションなし
  • Telescope git_files cwd=~/dotfiles
    オプションを設定
  • Telescope find_files find_command=rg,--ignore,--hidden,--files
    table型のオブションの場合は , で区切る

次のpicker達は全てtelescopeに組み込まれています。独自のpickerを追加したい場合はREADMEのCustomizationWikiのConfiguration Recipesが参考になります。

組み込まれているpickerの紹介動画一覧

組み込まれているpicker一覧
ファイル名
  • find_files (alias fd)
    検索に利用するコマンドの優先順位は fd => fdfind => rg => find です。
  • oldfiles
  • file_browser
Git
  • git_files
  • git_status
  • git_commits
  • git_bcommits
  • git_branches
Grep
  • grep_string
  • live_grep
tags
  • tags
  • current_buffer_tags
バッファ
  • buffers
  • marks
  • current_buffer_fuzzy_find
    現在のバッファの行がリストされ、選択した行に移動します。
quickfix & location
  • quickfix
  • loclist
コマンド
  • commands
  • command_history
autocommand
  • autocommands
registers
  • registers
ヘルプ
  • help_tags
  • man_pages
キーマップ
  • keymaps
ファイルタイプ
  • filetypes
オプション
  • vim_options
colorscheme
  • colorscheme
highlight
  • highlights
spell
  • spell_suggest
    オプション spell が有効である必要があります。
パッケージ
  • reloader
    選択したパッケージモジュールを再読込します。
LSP
  • lsp_references
  • lsp_definitions
  • lsp_workspace_symbols
  • lsp_document_symbols
  • lsp_code_actions
  • lsp_range_code_actions
  • lsp_document_diagnostics
  • lsp_workspace_diagnostics
treesitter
  • treesitter
    編集しているバッファ内の関数や変数一覧から選択したものに移動します。
Telescope
  • builtin
    実行できるpickerをリストし、選択したものを実行します。
  • planets
    惑星のドット絵が見れます。

Layout

リストやプレビューなどのレイアウトは設定できます。デフォルトのレイアウトだと、ディスプレイが縦だとプレビューが表示されません。Dropdownを使えば縦画面でもプレビューは表示されます。

デフォルトのレイアウトを変更するには次のようにします。値はsupported layoutsが利用できます。

require'telescope'.setup{
  -- 画面幅に応じたレイアウトになるため、
  -- ディスプレイの向き関係なく、
  -- プレビューが表示されます。
  defaults = {layout_strategy = 'flex'}
}

拡張機能

もっと
  • z
    z -l を表示して、そのディレクトリに対してファイル検索(Telescope fd), cdlch を行います。
  • Packer
    プラグイン管理のpackerで管理しているプラグイン一覧を表示します。
  • node-modules
    node_modules directory内のpackagesを検索して、そのpackageのdirectoryに cdlcd を行ないます。
  • media-files
    画像や動画、pdfなどのpreviewを見ます。
  • sonic-template
    sonictemplateにあるtemplateを展開します。
  • memo
    mattn/memoに対して、listやgrepを行ないます。
  • cheat
    cheat.shの結果をします。cheat.shのデータはSQLiteに保存するのでsql.nvimが必要です。
  • snippets
    snippets.nvimとの統合です。
  • symbols
    絵文字や顔文字一覧を表示します。
  • openbrowser
    設定したbookmark listから選択したURIをブラウザで開きます。

Package Manager

  • paq-nvim
    シンプルなwrapperを目指している、小さい(約175行)なpackage managerです。Packerとは目指しているものが違うため、依存関係の設定や遅延読み込みはできません。

Fuzzy Finder

Statusline

Tabline

  • barbar.nvim
    • 全てのバッファをtablineに表示
    • tabの並び替え
    • ファイル名が同じバッファを開いた場合ディレクトリ名を追加して重複表示を防ぐ
  • nvim-bufferline.lua
    バッファ選択や並び替えもできます。barbar.nvimと違う点は、1つのウィンドウで複数のバッファを開いているときにそのバッファだけをタブラインに表示するMulti-window modeがあることです。

補完

  • nvim-compe
    vim-vsnip作成者のhrsh7thさんが開発している補完プラグインです。組み込みのLSPやvim-vsnip、hrsh7thさんが開発しているLSPクライアントのvim-lampと連携できます。

括弧・テキストオブジェクト

  • nvim-autopairs
    lexima.vimのように閉じ括弧などを自動入力します。
  • nvim-ts-autotag
    HTMLタグの終了タグを自動的に挿入します。rename機能もあります。HTML専用のようです。nvim-treesitterに依存しています。
  • surround.nvim
    vim-surroundのように括弧や引用符などで囲まれているテキストを編集できます。

インクリメント・デグリメント

デバック

Linter/Formater

REPL

  • iron.nvim
    NeovimからREPLを実行できるようにします。

Quickfix

  • nvim-bqf
    quickfixを拡張します。
  • popfix
    拡張性の高いquickfixとfloating windowのAPIを提供します。

インデント

検索

  • nvim-hlslens
    検索にヒットした行の右にvirtual textで何件目か表示します。virtual textの内容やhighlightは変更できます。

Git

  • gitsigns.nvim
    vim-gitgutterのように、diffの結果を記号で行の左端に表示します。
  • git-blame.nvim
    git blameをvirtual textとして表示します。
  • neogit
    Emacsからgitリポジトリ操作ができるMagitのNeovim版です。Vimの概念に合わせていくつか変更する予定があります。

GitHub

  • octo.nvim
    IssueとPRをNeovimから操作できます。 ListIssues <repo> でtelescopeと連携できます。
  • codeql.nvim
    コードの脆弱性を発見するCodeQLを実行できます。

コメント

  • prodoc.nvim
    コメント化・アノテーション追加します。
  • kommentary
    コメントの切り替えを行います。

エクスプローラー

  • nvim-tree.lua
    ツリー形式で表示します。ファイルの追加や削除、名前変更ができ、gitとの連携もできます。
  • lir.nvim
    シンプルなエクスプロラーで、ディレクトリ表示時(e.g. e .)に Netrw の代わりに利用できます。
  • dirbuf.nvim
    nvim-treeやvim-dirvish,vidirにインスパイアされています。
  • neofs
    floating windowで表示します。

Colors

  • nvim-colorizer.lua
    カラーコードのプレビューができ、外部依存関係はありません。
    同じ機能を持つvim-hexokinaseはGo言語が必要です。
  • lush.nvim
    colorscheme作成を支援します。色の変更をリアルタイムで反映します。

プロジェクト管理

  • nvim-projectconfig
    ~/.config/nvim/projects/{プロジェクト名} (変更可能)にある、プロジェクト専用の設定ファイル(Vimscript or lua)を読み込みます。
    lua require('nvim-projectconfig').load_project_config() で設定ファイルの読み込み、lua.require("nvim-projectconfig").edit_project_config() で設定ファイルを開きます。

ヘルプ

共同編集

  • instant.nvim
    複数人で同じバッファを共有するライブ編集ができます。

コピー/ペースト

ターミナル

アイコン

スクロールバー

  • scrollbar.nvim
    スクロールバーの見た目を変更します。

ウィンドウ

  • wintablib.nvim
    ウィンドウやタブを操作する関数を提供します。
    • 左 or 右の全てのタブを閉じる
    • 左 or 右のタブのバッファを、現在のタブで開く
    • 編集中のバッファを新規タブで開く
      タブは左右どちらに配置するか決めることができます。
    • 全てのfloating windowを閉じる
    • 上下左右のwindowを全て閉じる

マウス

  • gesture.nvim
    マウスジェスチャーができるようになります。

コマンドライン

  • cmdbuf.nvim
    q => : で開けるコマンドラインウィンドウをバッファとして開きます。

モーション

セッション

  • auto-session
    自動でセッションを保存、読み込みを行ないます。

Markdown

設定

  • fey_neovim
    Doom Emacsにインスパイアされた設定フレームワークで、minpacをパッケージマネージャーに使っています。
前まで表示してたもの

生活

Mode

  • nvim-libmodal
    このプラグインと連携するプラグインをインストールすれば、バッファタブ管理する独自のモードを追加できます。

エクスプローラー

  • nvim-filetree
    ツリー形式でファイルを表示します。

可視化

ゲーム

  • vim-apm
    1分間のキー操作の回数を表示します。
  • vim-be-good
    Vimの基本の動きを練習するゲームができます。

Luaのプラグイン作成に役立つプラグインやドキュメント

プラグイン

  • nlua.nvim
    Lua開発に役立つ機能を提供します。
  • nvim-luadev
    Luaプラグイン用のREPLとdebug機能を提供します。
  • nvim-luapad
    入力しているLuaのコードをリアルタイムで実行します。
  • manillua.nvim
    Luaの折りたたみ表示を見やすくします。
  • lreload.nvim
    Luaモジュールのhot-reloadingができます。

Library

  • plenary.nvim
    Neovim専用のluaライブラリです。Luaモジュール用のpackage mangerのLuaRocksを使ってlua packageをインストールできます。
  • popup.nvim
    VimのPopup APIと互換性があるAPIをNeovimに実装します。
  • vlog.nvim
    Luaプラグインに簡単にログファイルを実装できます。依存性はありません。
  • neovim-plugin
    プラグインのキーマップやコマンド、イベント設定をLuaのtable形式で設定できます。
  • impromptu.nvim
    コマンドや関数を呼び出すためのプロンプトを簡単に作成できます。
  • sql.nvim
    neovimからSQLiteを操作するwrapperです。

ドキュメント

参考資料

本文内でリンクを記載していない資料達です。

NeovimとLuaが相性が良い理由

Native Packages

nvim-lspconfig

nvim-treesitter

Tree-sitter

GitHubで編集を提案

Discussion