NeovimのためのLua入門 init.lua編

公開:2020/11/03
更新:2020/12/02
5 min読了の目安(約5200字TECH技術記事

追記

この記事の内容のほとんどはnanotee/nvim-lua-guideと同じものです(参照元が同じなので)。
よくまとまっていて、日本語訳もあるのでそちらを読むことをおすすめします。

はじめに

今回はlua.txtからLuaで設定を書くために必要なものを紹介します。

どうやって読み込むのか

2020/12/2追記
init.luaを読み込むPRがマージされました。
.config/nvim/init.vimの代わりに.config/nvim/init.luaを置くと、設定ファイルとして読み込まれるようになりました。
init.viminit.luaが同時に存在するとエラーになります。
:h configに情報が追加されていくと思います。
プラグインとして配置するなら下記のまま変更はありません。

前回はファイルやコマンドランモードからLuaを直接実行していました。
runtimepath内のluaディレクトリ内にファイルを置くと、Luaのrequireから読み込めるようになります。
plugin/hoge.vim等の.vimファイルと違い、パスが通るだけで自動で読み込まれません。
[1]
そのため、init.vim等のどこかしらでLuaを呼ぶ必要があります。

-- lua/hi.lua
return 'hi'
--

:lua print(require('hi"))
"hi

lua/hello/init.luaのようにディレクトリを作るとディレクトリ名でrequireできます。

-- lua/hello/init.lua
return 'hello'
--

:lua print(require('hello')) 
"hello

実際にLuaで設定を書く場合は、~/.config/nvim/lua/init.lua等のパスの通った場所に配置します。
そして、init.vimから呼ぶlua require('init')といった感じになります。

LuaからVim script

vimモジュールを使って、LuaからVim scriptを利用できます。

関数

vim.fnでvim側の関数を使用できます。

-- vim組込み
vim.fn.abs(-10)

-- autoload関数の場合
vim.fn['dein#update']()

vim.apiでNvim API(:h api)を呼ぶことができます。
vimの操作はこれを使うことになります。
api.txtのサンプルコードはVim scriptなのでLuaから呼ぶ場合は読み変える必要があります。

print( tostring( vim.api.nvim_get_current_line() ) )

vim.apiはテーブルなので少しタイプ数を減らせます。

local api = vim.api
api.nvim_buf_line_count(0)

EXコマンド

文字列がEXコマンドとして実行されます。
何でもできる最終手段です。

vim.cmd('echo 1234')

変数

vim.g.loaded_netrw = 1の用に変数に直接アクセスできます。
スコープはg,b,w,t,vと、Vim scriptと同様です。

環境変数はenvを使います。

print( vim.env.TERM )

オプション

オプションも変数と同じ様に読み書きできます。
vim.oはグローバル、vim.boはバッファ、vim.woはウィンドウです。

vim.o.background = 'light'
print( vim.o.bg ) -- 省略してもOK

Vim scriptならなんでも:setなのであまり意識していなかったのですが、'number'はウィンドウオプションだったりとちゃんと区別する必要があります。(vim.o.numberは存在せずvim.wo.numberを使う)
[2]

マッピング

nvim_set_keymap()を使います。

vim.api.nvim_set_keymap( 'n', 'j', 'gj', {noremap = true} )

autocmd

autocmd用の物は(まだ)ないのでVim scriptを使います。

vim.cmd('augroup lua')
vim.cmd('autocmd!')
vim.cmd('autocmd InsertEnter * echo "insert enter"')
vim.cmd('augroup END')

ユーザー定義コマンド

autocmdと同様に(まだ)ないのでVim scriptを使います。

vim.cmd('command Hi echo "Hi"')

Vim scriptからLua

ヒアドキュメント :lua-heredoc

Vim script内にLuaをそのまま埋め込むことができます。
ヒアドキュメント内のローカル変数はその中でしか使えません。

function! CurrentLineInfo()
lua << EOF
local linenr = vim.api.nvim_win_get_cursor(0)[1]
local curline = vim.api.nvim_buf_get_lines(0, linenr, linenr + 1, false)[1]
print(string.format("Current line [%d] has %d bytes",linenr, #curline))
EOF
endfunction

v:lua

Luaのグローバルな関数を呼ぶことができます。

lua << EOF
function Hello()
  print("Hello")
end
EOF

call v:lua.Hello()
"Hello

Vim scriptの関数を設定するところにも使えるようです。

vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omnifunc')

v:luaは関数を呼ぶことしかできず、式では使用できません。

let g:Myvar = v:lua.myfunc        " Error
call SomeFunc(v:lua.mycallback)   " Error
let g:foo = v:lua                 " Error
let g:foo = v:['lua']             " Error

Luaプラグイン例

よく使いそうな物の紹介は以上です。Luaで設定が書けるということはプラグインが書けるということです。
:h lua-require-exampleのプラグイン例を見てみます。
~/.config/nvim内等のパスの通ったところに配置すると手元で動かすことができます。

charblob.luaはLuaで完結しているので参考までに、注目して欲しいのはcharblob.vimです。
この例では普通のvimプラグインのようにVim scriptからLuaの関数を呼ぶ形になっています。
関数の提供方法次第ではautoloadやpluginディレクトリは必須ではありません。
しかし、LuaだLuaだと言っても利用する時はマッピングやコマンドになるので、コマンドラインモードから呼びやすい形が良いですね。

"autoload/charblob.vim
function charblob#encode_buffer()
  call setline(1, luaeval(
  \    'require("charblob").encode(unpack(_A))',
  \    [getline(1, '$'), &textwidth, '  ']))
endfunction

"plugin/charblob.vim
if exists('g:charblob_loaded')
  finish
endif
let g:charblob_loaded = 1

command MakeCharBlob :call charblob#encode_buffer()
-- lua/charblob.lua
local function charblob_bytes_iter(lines)
  local init_s = {
    next_line_idx = 1,
    next_byte_idx = 1,
    lines = lines,
  }
  local function next(s, _)
    if lines[s.next_line_idx] == nil then
      return nil
    end
    if s.next_byte_idx > #(lines[s.next_line_idx]) then
      s.next_line_idx = s.next_line_idx + 1
      s.next_byte_idx = 1
      return ('\n'):byte()
    end
    local ret = lines[s.next_line_idx]:byte(s.next_byte_idx)
    if ret == ('\n'):byte() then
      ret = 0  -- See :h NL-used-for-NUL.
    end
    s.next_byte_idx = s.next_byte_idx + 1
    return ret
  end
  return next, init_s, nil
end

local function charblob_encode(lines, textwidth, indent)
  local ret = {
    'const unsigned char blob[] = {',
    indent,
  }
  for byte in charblob_bytes_iter(lines) do
    --                .- space + number (width 3) + comma
    if #(ret[#ret]) + 5 > textwidth then
      ret[#ret + 1] = indent
    else
      ret[#ret] = ret[#ret] .. ' '
    end
    ret[#ret] = ret[#ret] .. (('%3u,'):format(byte))
  end
  ret[#ret + 1] = '};'
  return ret
end

return {
  bytes_iter = charblob_bytes_iter,
  encode = charblob_encode,
}

おわりに

init.lua編としましたが、init.vimを置き換える利点はよくわかりません。
Lua製プラグインを含め、これからの動向を楽しみにしています。

参考

脚注
  1. https://github.com/neovim/neovim/pull/8720 ↩︎

  2. ちゃんとoptions.txtの冒頭に書いてあります ↩︎