:help lua-guideを参照してください。
日本語はこちら→https://github.com/willelz/neovimdoc-ja/blob/main/doc/lua-guide.jax
Neovimのファーストクラス言語としてのLuaはキラー機能の1つになりつつあります。 しかし、Luaでプラグインを書くための教材はVim script程多くありません。これは、Luaを始めるための基本的な情報を提供する試みです。
このガイドは少なくともNeovim 0.5を使用していることを前提としています。
まだLuaについて詳しくない場合、学ぶためのリソースはたくさんあります。:
- Learn X in Y minutes page about Luaは基本的な概要を説明します。
- このガイドも素早く始めるのに良いチュートリアルです。
- 動画が好きなら、Derek Banasの動画があります。1-hour tutorial on the language
- 実行できるサンプルを使い、対話的に学びたいですか?LuaScript tutorialを試してみてください。
- lua-users wikiにはLua関連のトピックごとの便利な情報がたくさんあります。
- official reference manual for Luaには最も包括的な情報があります。(エディタで快適に読みたいなら、Vimdocプラグインがあります。:milisims/nvim-luaref)
Luaはとてもクリーンでシンプルな言語であることに注意してください。JavaScriptのようなスクリプト言語の経験があれば、学ぶことは簡単です。あなたはもう自分で思っているよりLuaについて知っているかもしれません!
Note: Neovimに埋め込まれているLuaはLuaJIT 2.1.0でLua 5.1と互換性を維持しています。
Luaでプラグインを書くためのチュートリアルが既にいくつかあります。それらはこのガイドを書くのに役に立ちました。筆者に感謝します。
- teukka.tech - init.vimからinit.luaへ
- dev.to - プラグインをLuaで書く方法
- dev.to - プラグインのUIをLuaで作る方法
- ms-jpq - Neovim Async Tutorial
- oroques.dev - Neovim 0.5の機能とinit.luaへの切り替え
- ゼロからステータスラインを作る - jdhao's blog
- LuaでNeovimを設定する
- Devlog | Luaで設定するために知る必要のあること
- Vimpeccable - .vimrc内でLuaを書くのに役に立つプラグイン
- plenary.nvim - 二度書きたくないLua関数のすべて
- popup.nvim - vimのPopup APIのNeovimでの実装
- nvim_utils
- nvim-luadev - REPL/debugコンソール
- nvim-luapad - 組込みLuaエンジンのインタラクティブなリアルタイムスクラッチパッド
- nlua.nvim - NeovimのLua開発
- BetterLua.vim - Vim/Neovimより良いシンタックスハイライト
Neovimは、init.vimの代わりに設定ファイルとしてinit.luaを読み込むことをサポートしています。
Note: init.luaは 完全に オプションです。init.vimは廃止されず、設定として有効です。
いくつかの機能は、まだ100%Luaに公開されていないので注意してください。
参照:
Luaモジュールは、runtimepath内のlua/フォルダにあります(ほとんどの場合、*nixでは~/.config/nvim/lua、Windowsでは~/AppData/Local/nvim/luaを意味します)。
このフォルダにあるファイルをLuaモジュールとしてrequire()できます。
例として次のフォルダ構造を取り上げましょう。:
📂 ~/.config/nvim
├── 📁 after
├── 📁 ftplugin
├── 📂 lua
│ ├── 🌑 myluamodule.lua
│ └── 📂 other_modules
│ ├── 🌑 anothermodule.lua
│ └── 🌑 init.lua
├── 📁 pack
├── 📁 plugin
├── 📁 syntax
└── 🇻 init.vim
次のLuaコードはmyluamodule.luaをロードします。:
require('myluamodule').lua拡張子がないことに注意してください。
同様に、other_modules/anothermodule.lua のロードは次のように行います。:
require('other_modules.anothermodule')
-- or
require('other_modules/anothermodule')パスの区切りはドット.またはスラッシュ/で示されます。
フォルダにinit.luaが含まれている場合、ファイル名を指定せずにロードできます。
require('other_modules') -- other_modules/init.luaをロード存在しないモジュール、構文エラーを含むモジュールをrequireすると実行中のスクリプトは停止します。
エラーを防ぐために、pcall()を使用できます。
local ok, _ = pcall(require, 'module_with_error')
if not ok then
-- not loaded
end参照:
いくつかのLuaプラグインはlua/フォルダ内に同じ名前のファイルがあるかもしれません。これにより、名前空間の衝突を起こす可能性があります。
異なる2つのプラグインにlua/main.luaがある場合、require('main')は曖昧です。: どのファイルを読み込みますか?
トップレベルのフォルダで名前空間をつけることをお勧めします。: lua/plugin_name/main.lua
Vim scriptと同様に、runtimepath内にある特定のフォルダからLuaファイルを自動的に読み込めます。
現在、次のフォルダがサポートされています。:
colors/compiler/ftplugin/ftdetect/indent/plugin/syntax/
Note: runtimeデイレクトリでは、すべての*.vimファイルは*.luaファイルの前に読み込まれます。
参照:
ランタイムファイルはLuaのモジュールシステムをベースとしていないため、2つのプラグインはplugin/main.luaを問題なく持つことができます。
Luaのチャンクを実行します。
:lua require('myluamodule')ヒアドキュメント構文を使用すると複数行に書くことができます。:
echo "Here's a bigger chunk of Lua code"
lua << EOF
local mod = require('mymodule')
local tbl = {1, 2, 3}
for k, v in ipairs(tbl) do
mod.method(v)
end
print(tbl)
EOFNote: 各:luaコマンドは独自のスコープを持っており、localを付けた変数はコマンドの外からアクセスできません。
次の例は動作しません。:
:lua local foo = 1
:lua print(foo)
" '1'ではなく'nil'が出力されます。Note 2: Luaのprint()は:echomsgと同じように動作します。出力はメッセージ履歴に保存されます。また、:silentで抑制できます。
参照:
このコマンドはカレントバッファの範囲行にLuaチャンクを実行します。範囲を指定しない場合、バッファ全体に作用します。
チャンクからreturnされた文字列は、各行を置き換えるために使用されます。
次のコマンドは、カレントバッファのすべての行をhello worldに置き換えます。:
:luado return 'hello world'2つの暗黙的な変数lineとlinerが提供されます。lineは対象行のテキストで、linerはその行数です。
次のコマンドは、すべての偶数行のテキストを大文字にします。
:luado if linenr % 2 == 0 then return line:upper() end参照:
NeovimはLuaファイルを読み込むためのEXコマンドを3つ提供しています。
:luafile:source:runtime
:luafileと:sourceはとてもよく似ています。:
:luafile ~/foo/bar/baz/myluafile.lua
:luafile %
:source ~/foo/bar/baz/myluafile.lua
:source %:sourceは範囲指定もサポートしており、スクリプトの一部を実行するのに役立ちます。:
:1,10source:runtimeは少し異なります。: 'runtimepath'オプションで読み込むファイルを指定します。
詳細は:help :runtimeを参照してください。
参照:
require()関数を呼ぶこととLuaファイルの読み込みの違いは何か、どちらを使うべきかを疑問に思うかもしれません。
それらには異なるユースケースがあります。:
require():- Luaの組込み関数です。Luaのモジュールを読み込むのに使用します。
'runtimepath'内にあるlua/フォルダからモジュールを探します。- どのモジュールをロードしたかを記憶し、多重に実行されるのを防ぎます。Neovim実行中に、モジュールに含まれるコードを変更し、もう一度
require()を実行してもモジュールは更新されません。
:luafile,:source,runtime:- Exコマンドです。モジュールには対応していません。
- 以前に実行されたかどうかに関わらず実行されます。
:luafileと:sourceは現在のウィンドウのディレクトリに対して相対パス・絶対パスを取ります。runtimeは、'rutimepath'オプションを使用してファイルを探します。
:sourceや:runtime、ランタイムディレクトリから自動的に読み込まれたファイルもscriptnamesと--startuptimeに表示されます。
Vim scriptの組込み関数です。文字列のLua式を評価して返します。 Luaの型は自動的にVim scriptの型に変換されます。(その逆も同様です。)
" 変数に結果を代入することができます。
let variable = luaeval('1 + 1')
echo variable
" 2
let concat = luaeval('"Lua".." is ".."awesome"')
echo concat
" 'Lua is awesome'
" リストのようなテーブルはVimのリストに変換されます。
let list = luaeval('{1, 2, 3, 4}')
echo list[0]
" 1
echo list[1]
" 2
" 注意 Luaのテーブルと違い、Vimのリストは0インデックスです。
" 辞書のようなテーブルはVimの辞書に変換されます。
let dict = luaeval('{foo = "bar", baz = "qux"}')
echo dict.foo
" 'bar'
" bool値とnilも同様です。
echo luaeval('true')
" v:true
echo luaeval('nil')
" v:null
" Lua関数のエイリアスをVim scriptで作ることができます。
let LuaMathPow = luaeval('math.pow')
echo LuaMathPow(2, 2)
" 4
let LuaModuleFunction = luaeval('require("mymodule").myfunction')
call LuaModuleFunction()
" Vimの関数にLuaの関数を値として渡すこともできます。
lua X = function(k, v) return string.format("%s:%s", k, v) end
echo map([1, 2, 3], luaeval("X"))luaeval()は式にデータを渡すことのできる任意の2つ目の引数があります。Luaからは_Aとしてアクセスできます。
echo luaeval('_A[1] + _A[2]', [1, 1])
" 2
echo luaeval('string.format("Lua is %s", _A)', 'awesome')
" 'Lua is awesome'参照:
Vimのグローバル変数です。Vim scriptからLuaのグローバル名前空間(_G) 内の関数を直接呼ぶことができます。
この場合でも、Vim scriptの型はLuaの型に変換されます。逆も同様です。
call v:lua.print('Hello from Lua!')
" 'Hello from Lua!'
let scream = v:lua.string.rep('A', 10)
echo scream
" 'AAAAAAAAAA'
" How about a nice statusline?
lua << EOF
function _G.statusline()
local filepath = '%f'
local align_section = '%='
local percentage_through_file = '%p%%'
return string.format(
'%s%s%s',
filepath,
align_section,
percentage_through_file
)
end
EOF
set statusline=%!v:lua.statusline()
" Also works in expression mappings
lua << EOF
function _G.check_back_space()
local col = vim.api.nvim_win_get_cursor(0)[2]
return (col == 0 or vim.api.nvim_get_current_line():sub(col, col):match('%s')) and true
end
EOF
inoremap <silent> <expr> <Tab>
\ pumvisible() ? "\<C-N>" :
\ v:lua.check_back_space() ? "\<Tab>" :
\ completion#trigger_completion()
" シングルクォートを使用したり、括弧を省略して、Luaモジュールから関数を呼び出します:
call v:lua.require'module'.foo()参照:
この変数は関数呼び出しにのみ使用できます。次の例はエラーになります。:
" 関数のエイリアスは動作しません
let LuaPrint = v:lua.print
" 辞書アクセスは動作しません
echo v:lua.some_global_dict['key']
" 関数を値として使用できません
echo map([1, 2, 3], v:lua.global_callback)設定ファイルに、let g:vimsyn_embed = 'l'を追加すると.vimファイル内のLuaを構文ハイライトできます。
詳細は:help g:vimsyn_embedを参照してください。
NeovimはLuaからAPIを使うためのエントリーポイントとして、vimグローバル変数を公開しています。
これは、拡張された標準ライブラリやさまざまなサブモジュールを提供します。
いくつかの注目すべき関数とモジュール:
vim.inspect: Luaオブジェクトを人間が読みやすい文字列に変換する(テーブルを調べるのに便利です。)vim.regex: LuaからVimの正規表現を使うvim.api: API関数を公開するモジュール(リモートプラグインで使うAPIと同じです)vim.ui: プラグインから利用できる上書き可能な関数vim.loop: Neovimのイベントループ機能を公開するモジュール(LibUVを使います)vim.lsp: 組込みのLSPクライアントを操作するモジュールvim.treesitter: tree-sitterライブラリの機能を公開するモジュール
このリストは決して包括的なリストではありません。vim変数で何かできるかを詳しく知りたい場合は、:help lua-stdlibと:help lua-vimが最適です。
または、:lua print(vim.inspect(vim))を実行してすべてのモジュールのリストを取得できます。
API関数は、:help api-globalにあります。
オブジェクトの中身を検査するのに毎回print(vim.inspect(x))を書くのは面倒です。設定にグローバルなラッパー関数を含めることは価値があるかもしれません。(Neovim 0.7.0+では、この関数は組込み関数です。参照 :help vim.pretty_print()):
function _G.put(...)
local objects = {}
for i = 1, select('#', ...) do
local v = select(i, ...)
table.insert(objects, vim.inspect(v))
end
print(table.concat(objects, '\n'))
return ...
endコードまたはコマンドラインからとても早くオブジェクトの中身を検査できます。
put({1, 2, 3}):lua put(vim.loop)または、:luaコマンドでLua式の前に = をつけて、整列させて表示できます。(Neovim 0.7+のみ)
:lua =vim.loop加えて、他の言語と比較して組込みのLua関数が不足している場合があります(例えば、os.clockはミリ秒ではなく秒数のみを返します)。
必ず、Neovim stdlib(それとvim.fn。詳しくは後述します。)を見てください。おそらく、探しものはそこにあります。
文字列で与えられたVim scriptの式を評価してその値を返します。Vim scriptの型は自動的にLuaの型に変換されます。(その逆も同様です。)
これは、Vim scriptのluaeval()と同様です。
-- 型は正しく変換されます。
print(vim.api.nvim_eval('1 + 1')) -- 2
print(vim.inspect(vim.api.nvim_eval('[1, 2, 3]'))) -- { 1, 2, 3 }
print(vim.inspect(vim.api.nvim_eval('{"foo": "bar", "baz": "qux"}'))) -- { baz = "qux", foo = "bar" }
print(vim.api.nvim_eval('v:true')) -- true
print(vim.api.nvim_eval('v:null')) -- nilluaeval()と違い、式にデータを渡すための暗黙的な変数_Aを提供しません。
Vim scriptのチャンクを実行します。実行するソースコートを含む文字列と、コードの出力を返すかどうかを決めるbool値を受け取ります(例えば、出力を変数に格納できます)。
local result = vim.api.nvim_exec(
[[
let s:mytext = 'hello world'
function! s:MyFunction(text)
echo a:text
endfunction
call s:MyFunction(mytext)
]],
true)
print(result) -- 'hello world'Neovim 0.6.0より前のバージョンでは 、nvim_exec はスクリプトローカル変数(s:)をサポートしていません。
Exコマンドを実行します。実行するコマンドを含む文字列を受け取ります。
vim.api.nvim_command('new')
vim.api.nvim_command('wincmd H')
vim.api.nvim_command('set nonumber')
vim.api.nvim_command('%s/foo/bar/g')vim.api.nvim_exec()のエイリアスです。コマンドの引数のみを必要とし、outputは常にfalseに設定されます。
vim.cmd('buffers')
vim.cmd([[
let g:multiline_list = [
\ 1,
\ 2,
\ 3,
\ ]
echo g:multiline_list
]])これらの関数は文字列を渡すため、多くの場合、バックスラッシュをエスケープする必要があります。:
vim.cmd('%s/\\Vfoo/bar/g')二重括弧の文字列はエスケープが必要ないため使いやすいです。:
vim.cmd([[%s/\Vfoo/bar/g]])このAPI関数はターミナルコードとVimのキーコードをエスケープできます。
次のようなマッピングを見たことがあるかもしれません。:
inoremap <expr> <Tab> pumvisible() ? "\<C-N>" : "\<Tab>"同じことをLuaでやると大変です。次のようにやるかもしれません。:
function _G.smart_tab()
return vim.fn.pumvisible() == 1 and [[\<C-N>]] or [[\<Tab>]]
end
vim.api.nvim_set_keymap('i', '<Tab>', 'v:lua.smart_tab()', {expr = true, noremap = true})マッピングに \<Tab> と \<C-N> が挿入されているのを知るためだけに...
キーコードをエスケープできるのは、Vim scriptの機能です。\r, \42 や \x10 のような多くのプログラミング言語に共通する通常のエスケープシーケンスとは別に、Vim scriptの expr-quotes (ダブルクォートで囲まれる文字列)を使用すると、人間が読める表現のVimキーコードをエスケープします。
Luaにはそのような機能は組み込まれていません。嬉しいことに、NeovimにはターミナルコードとキーコードをエスケープするAPI関数 nvim_replace_termcodes() があります。:
print(vim.api.nvim_replace_termcodes('<Tab>', true, true, true))これは少し冗長です。再利用できるラッパーを作ると便利です。:
-- `termcodes` 専用の `t` 関数です
-- この名前で呼ばなくてもいいですが、この簡潔さが便利です
local function t(str)
-- 必要に応じてboolean引数で調整します
return vim.api.nvim_replace_termcodes(str, true, true, true)
end
print(t'<Tab>')先程の例はこれで期待通りに動きます:
local function t(str)
return vim.api.nvim_replace_termcodes(str, true, true, true)
end
function _G.smart_tab()
return vim.fn.pumvisible() == 1 and t'<C-N>' or t'<Tab>'
end
vim.api.nvim_set_keymap('i', '<Tab>', 'v:lua.smart_tab()', {expr = true, noremap = true})vim.keymap.set()では、このハックは必要ありません。exprが有効な場合、デフォルトで自動的に変換されます。:
vim.keymap.set('i', '<Tab>', function()
return vim.fn.pumvisible() == 1 and '<C-N>' or '<Tab>'
end, {expr = true})参照:
Neovimは、オプションの値を読み書きできるAPI関数を提供しています。
- グローバルオプション:
- バッファオプション:
- ウィンドウオプション:
それらはオプションの名前と設定したい値を含む文字列を受け取ります。
boolな((no)numberのような)オプションはtrueかfalseのどちらかに設定する必要があります。:
vim.api.nvim_set_option('smarttab', false)
print(vim.api.nvim_get_option('smarttab')) -- false当然ながら、文字列のオプションには文字列を設定する必要があります。:
vim.api.nvim_set_option('selection', 'exclusive')
print(vim.api.nvim_get_option('selection')) -- 'exclusive'数値のオプションは数値を受け取ります。:
vim.api.nvim_set_option('updatetime', 3000)
print(vim.api.nvim_get_option('updatetime')) -- 3000バッファローカルとウィンドウローカルなオプションはそれぞれの番号も必要です。(0を指定した場合、カレントバッファ/ウィンドウが対象になります。):
vim.api.nvim_win_set_option(0, 'number', true)
vim.api.nvim_buf_set_option(10, 'shiftwidth', 4)
print(vim.api.nvim_win_get_option(0, 'number')) -- true
print(vim.api.nvim_buf_get_option(10, 'shiftwidth')) -- 4もっと使い慣れた方法でオプションを設定したい場合、いくつかのメタアクセサーを使用できます。それらは、上記のAPI関数をラップしたものでオプションを変数のように操作できます。:
vim.o.{option}::let &{option-name}のように動作しますvim.go.{option}::let &g:{option-name}のように動作しますvim.bo.{option}: バッファローカルオプションの場合:let &l:{option-name}のように動作しますvim.wo.{option}: ウィンドウローカルオプションの場合:let &l:{option-name}のように動作します
vim.o.smarttab = false -- let &smarttab = v:false
print(vim.o.smarttab) -- false
vim.o.isfname = vim.o.isfname .. ',@-@' -- on Linux: let &isfname = &isfname .. ',@-@'
print(vim.o.isfname) -- '@,48-57,/,.,-,_,+,,,#,$,%,~,=,@-@'
vim.bo.shiftwidth = 4
print(vim.bo.shiftwidth) -- 4バッファとウィンドウの番号を指定できます。0を指定した場合、カレントバッファ/ウィンドウが使用されます。:
vim.bo[4].expandtab = true -- same as vim.api.nvim_buf_set_option(4, 'expandtab', true)
vim.wo.number = true -- same as vim.api.nvim_win_set_option(0, 'number', true)これらには、Luaで設定するのに便利なより洗練されたラッパーとしてvim.optがあります。
init.vimで慣れているものと似ています。:
vim.opt.{option}::setのように動作しますvim.opt_global.{option}::setglobalのように動作しますvim.opt_local.{option}::setlocalのように動作します
vim.opt.smarttab = false
print(vim.opt.smarttab:get()) -- falseいくつかのオプションはLuaのテーブルを使用して設定できます。:
vim.opt.completeopt = {'menuone', 'noselect'}
print(vim.inspect(vim.opt.completeopt:get())) -- { "menuone", "noselect" }list、map、setのようなオプションのラッパーには、Vim scriptの:set+=, :set^=, :set-=と同じように動作するメソッドとメタメソッドが用意されています。
vim.opt.shortmess:append({ I = true })
-- どちらも等価です:
vim.opt.shortmess = vim.opt.shortmess + { I = true }
vim.opt.whichwrap:remove({ 'b', 's' })
-- どちらも等価です:
vim.opt.whichwrap = vim.opt.whichwrap - { 'b', 's' }詳細は、必ず:help vim.optを参照してください。
参照:
オプションのように、内部変数にもAPI関数があります。
- グローバル変数 (
g:): - バッファ変数 (
b:): - ウィンドウ変数 (
w:): - タブ変数 (
t:): - Vimの定義済み変数 (
v:):
Vimの定義済み変数を除いて、削除できます(Vim scriptの:unletと同様です)。
ローカル変数(l:)、スクリプト変数(s:)、関数の引数(a:)はVim script内でのみ意味があるため操作できません。
Luaには独自のスコープルールがあります。
これらの変数が不慣れな場合、:help internal-variablesに説明があります。
これらの関数は対象の変数名と、設定したい値を含む文字列を受け取ります。
vim.api.nvim_set_var('some_global_variable', { key1 = 'value', key2 = 300 })
print(vim.inspect(vim.api.nvim_get_var('some_global_variable'))) -- { key1 = "value", key2 = 300 }
vim.api.nvim_del_var('some_global_variable')バッファ、ウィンドウ、タブページなスコープを持つ変数はそれぞれの番号を受け取ります(0を指定した場合は現在のバッファ/ウィンドウ/タブページが使われます。)。:
vim.api.nvim_win_set_var(0, 'some_window_variable', 2500)
vim.api.nvim_tab_set_var(3, 'some_tabpage_variable', 'hello world')
print(vim.api.nvim_win_get_var(0, 'some_window_variable')) -- 2500
print(vim.api.nvim_buf_get_var(3, 'some_tabpage_variable')) -- 'hello world'
vim.api.nvim_win_del_var(0, 'some_window_variable')
vim.api.nvim_buf_del_var(3, 'some_tabpage_variable')内部の変数はメタアクセサーを使用し、もっと直感的に操作できます。:
vim.g.{name}: グローバル変数vim.b.{name}: バッファ変数vim.w.{name}: ウィンドウ変数vim.t.{name}: タブ変数vim.v.{name}: Vimの定義済み変数vim.env.{name}: 環境変数
vim.g.some_global_variable = {
key1 = 'value',
key2 = 300
}
print(vim.inspect(vim.g.some_global_variable)) -- { key1 = "value", key2 = 300 }
-- 特定のバッファ/ウィンドウ/タブを対象とします(Neovim 0.6+)
vim.b[2].myvar = 1一部の変数名には、Luaの識別子に使用できない文字が含まれている場合があります。
この構文を使用してこれらの変数を操作できます。: vim.g['my#variable']
変数を削除するには単にnilを代入します。:
vim.g.some_global_variable = nil参照:
辞書の1つのキーを追加/更新/削除できません。例えば、次のVim scriptは期待通りに動きません。:
let g:variable = {}
lua vim.g.variable.key = 'a'
echo g:variable
" {}一時的な変数を使用する回避策があります:
let g:variable = {}
lua << EOF
local tmp = vim.g.variable
tmp.key = 'a'
vim.g.variable = tmp
EOF
echo g:variable
" {'key': 'a'}既知のissue:
vim.fnは、Vim script組込みの関数を呼び出せます。
型はVimとLuaとで変換されます。
print(vim.fn.printf('Hello from %s', 'Lua'))
local reversed_list = vim.fn.reverse({ 'a', 'b', 'c' })
print(vim.inspect(reversed_list)) -- { "c", "b", "a" }
local function print_stdout(chan_id, data, name)
print(data[1])
end
vim.fn.jobstart('ls', { on_stdout = print_stdout })ハッシュ(#)はLuaで有効な識別子ではないため、autoload関数は次の構文で呼び出す必要があります。:
vim.fn['my#autoload#function']()vim.fnはvim.callと同じ動作ですが、よりLuaらしい構文を使用できます。
vim.api.nvim_call_functionとは、Vim/Luaオブジェクトを自動で変換する点が異なります。:
vim.api.nvim_call_functionは浮動小数点数のテーブルを返しLuaのクロージャーを受け入れませんが、vim.fnはこれらの型を扱えます。
参照:
Neovimにはプラグインに便利な強力な組込み関数を含むライブラリがあります。
アルファベット順のリストは:help vim-functionを参照してください。
:help function-listは機能別に分類されたリストです。
NeovimのAPI関数はvim.api{..}のように直接使用できます。
詳細は:help apiを参照してください。
いくつかのVim関数はbool値の変わりに1か0を返します。これは、Vim scriptでは1は真で0は偽になるため問題ありません。
次のようなことが可能です。:
if has('nvim')
" do something...
endifしかし、Luaで偽になるのはfalseとnilのみで、数値は値に関係なく常にtrueと評価されます。
明示的に1か0かをチェックする必要があります。:
if vim.fn.has('nvim') == 1 then
-- do something...
endNeovimはマッピングを設定、取得、削除するためのAPI関数を提供します。:
- グローバルマッピング:
- バッファローカルマッピング:
vim.api.nvim_set_keymap()とvim.api.nvim_buf_set_keymap()から始めましょう。
最初の引数には有効にするモードの名前を含む文字列を渡します。:
| String value | Help page | Affected modes | Vimscript equivalent |
|---|---|---|---|
'' (an empty string) |
mapmode-nvo |
Normal, Visual, Select, Operator-pending | :map |
'n' |
mapmode-n |
Normal | :nmap |
'v' |
mapmode-v |
Visual and Select | :vmap |
's' |
mapmode-s |
Select | :smap |
'x' |
mapmode-x |
Visual | :xmap |
'o' |
mapmode-o |
Operator-pending | :omap |
'!' |
mapmode-ic |
Insert and Command-line | :map! |
'i' |
mapmode-i |
Insert | :imap |
'l' |
mapmode-l |
Insert, Command-line, Lang-Arg | :lmap |
'c' |
mapmode-c |
Command-line | :cmap |
't' |
mapmode-t |
Terminal | :tmap |
2つ目の引数は、左側のマッピングを含む文字列(マッピングで定義されたコマンドを起動するためのキー)です。
空の文字列は<Nop>と同じで、キーを無効にします。
3つ目の引数は、右側のマッピングを含む文字列(実行するコマンド)です。
最後の引数は、:help :map-argumentsで定義されているbool型のオプションのテーブルです(noremapを含み、bufferを除く)。
Neovim 0.7.0から、マッピング実行時、右側のマッピングの代わりに callback オプションに渡した関数を呼び出せます。
バッファローカルなマッピングは、バッファ番号を引数の最初に受け取ります(0を指定した場合、カレントバッファです)。
vim.api.nvim_set_keymap('n', '<Leader><Space>', ':set hlsearch!<CR>', { noremap = true, silent = true })
-- :nnoremap <silent> <Leader><Space> :set hlsearch<CR>
vim.api.nvim_set_keymap('n', '<Leader>tegf', [[<Cmd>lua require('telescope.builtin').git_files()<CR>]], { noremap = true, silent = true })
-- :nnoremap <silent> <Leader>tegf <Cmd>lua require('telescope.builtin').git_files()<CR>
vim.api.nvim_buf_set_keymap(0, '', 'cc', 'line(".") == 1 ? "cc" : "ggcc"', { noremap = true, expr = true })
-- :noremap <buffer> <expr> cc line('.') == 1 ? 'cc' : 'ggcc'
vim.api.nvim_set_keymap('n', '<Leader>ex', '', {
noremap = true,
callback = function()
print('My example')
end,
-- Lua関数は便利な文字列表現を持っていないため、 "desc" オプションを使用してマッピングの説明を記入できます。
desc = 'Prints "My example" in the message area',
})vim.api.nvim_get_keymap()は、モードの省略名(上記の表を参照)を含む文字列を受け取ります。
そのモードにあるすべてのグローバルマッピングのテーブルを返します。
print(vim.inspect(vim.api.nvim_get_keymap('n')))
-- :verbose nmapvim.api.nvim_buf_get_keymap()は、最初の引数に追加でバッファ番号を受け取ります(0を指定した場合、カレントバッファです)。
print(vim.inspect(vim.api.nvim_buf_get_keymap(0, 'i')))
-- :verbose imap <buffer>vim.api.nvim_del_keymap()は、モードと左側のマッピングを受け取ります。
vim.api.nvim_del_keymap('n', '<Leader><Space>')
-- :nunmap <Leader><Space>この場合でも、vim.api.nvim_buf_del_keymap()は最初の引数にバッファ番号を受け取ります。0を指定した場合、カレントバッファです。
vim.api.nvim_buf_del_keymap(0, 'i', '<Tab>')
-- :iunmap <buffer> <Tab>Neovimはマッピングを設定/削除できる2つの関数を提供します:
これらは、上記のAPI関数に糖類構文を追加したようなものです。
vim.keymap.set() は最初の引数として文字列を受け取ります。
また、複数のモードのマッピングを1度に定義するため、文字列のテーブルを受け取ることもできます:
vim.keymap.set('n', '<Leader>ex1', '<Cmd>lua vim.notify("Example 1")<CR>')
vim.keymap.set({'n', 'c'}, '<Leader>ex2', '<Cmd>lua vim.notify("Example 2")<CR>')2つ目の引数は左側のマッピングです。
3つ目の引数は右側のマッピングで、文字列かLua関数を受け取れます。
vim.keymap.set('n', '<Leader>ex1', '<Cmd>echomsg "Example 1"<CR>')
vim.keymap.set('n', '<Leader>ex2', function() print("Example 2") end)
vim.keymap.set('n', '<Leader>pl1', require('plugin').plugin_action)
-- モジュールの読み込みによる起動コストを避けるため、マッピングを呼び出したときにモジュールの遅延読みこみができるように関数でラップすることができます。:
vim.keymap.set('n', '<Leader>pl2', function() require('plugin').plugin_action() end)4つ目の引数(省略可能)はオプションのテーブルで、 vim.api.nvim_set_keymap() に渡されるオプションに対応しており、いくつか追加項目があります(:help vim.keymap.set()に一覧があります)。
vim.keymap.set('n', '<Leader>ex1', '<Cmd>echomsg "Example 1"<CR>', {buffer = true})
vim.keymap.set('n', '<Leader>ex2', function() print('Example 2') end, {desc = 'Prints "Example 2" to the message area'})文字列を使用して定義するキーマップとLua関数で定義したキーマップは違います。
通常の:nmap <Leader>ex1のようなキーマップ情報を表示する方法では、
Lua function とだけ表示され有用な情報(関数の内容自体)が表示されません。
キーマップの動作を説明するdescキーを追加するのを推奨します。
これはプラグインのマッピングのドキュメント化に特に重要です。
ユーザーはキーマップの使用方法をより簡単に理解できます。
このAPIが面白いところとして、Vimのマッピングの歴史的な癖をいくつか解消しています。
rhsが<Plug>マッピングである場合以外、デフォルトでnoremapです。 このため、マッピングが再帰的であるかを考える必要はあまりないです。
vim.keymap.set('n', '<Leader>test1', '<Cmd>echo "test"<CR>')
-- :nnoremap <Leader>test <Cmd>echo "test"<CR>
-- マッピングを再帰的に行ないたい場合は、 `remap` オプションを `true` にします
vim.keymap.set('n', '>', ']', {remap = true})
-- :nmap > ]
-- <Plug> マッピングは再帰的でないと機能しませんが、 vim.keymap.set() は自動的に処理します
vim.keymap.set('n', '<Leader>plug', '<Plug>(plugin)')
-- :nmap <Leader>plug <Plug>(plugin)exprマッピングが有効なら、 Lua関数が返す文字列に対してnvim_replace_termcodes()が自動的に適用されます:
vim.keymap.set('i', '<Tab>', function()
return vim.fn.pumvisible == 1 and '<C-N>' or '<Tab>'
end, {expr = true})参照:
vim.keymap.del() も同じように機能しますが、マッピングを削除します:
vim.keymap.del('n', '<Leader>ex1')
vim.keymap.del({'n', 'c'}, '<Leader>ex2', {buffer = true})Neovimはユーザーコマンドを作成するAPI関数を提供します。
- Global user commands:
- Buffer-local user commands:
まず vim.api.nvim_create_user_command() から始めます
最初の引数はコマンドの名前です(名前は大文字で始める必要があります)。
2つめの引数はコマンドが呼びだされたときに実行するコードです。次のどちらかでコードを指定できます:
文字列(この場合、VimScriptとして実行されます)。:commands のように、<q-args>, <range> などのエスケープシーケンスを使用できます。
vim.api.nvim_create_user_command('Upper', 'echo toupper(<q-args>)', { nargs = 1 })
-- :command! -nargs=1 Upper echo toupper(<q-args>)
vim.cmd('Upper hello world') -- prints "HELLO WORLD"もしくは、Lua関数。通常のエスケープシーケンスによって提供されるデータを含む、辞書のようなテーブルを受け取ります(利用できるキーのリストは:help nvim_create_user_command()で確認できます)。
vim.api.nvim_create_user_command(
'Upper',
function(opts)
print(string.upper(opts.args))
end,
{ nargs = 1 }
)3つめの引数はコマンドの属性をテーブルとして渡せます(:help command-attributesを参照)。vim.api.nvim_buf_create_user_command()を使用すればバッファローカルなユーザーコマンドを定義できるため、-bufferは有効な属性ではありません。
追加された2つの属性:
descはLuaのコールバックとして定義されたコマンドに対して:command {cmd}を実行したときの表示内容を制御できます。 キーマップと同様、Lua関数として定義するコマンドにはdescキーを追加するのを推奨します。forceは:command!を呼び出すのと同じで、同じ名前のユーザーコマンドが既に存在する場合、そのコマンドを置き換えます。Vimscriptとは異なり、デフォルトでtrueです。
-complete属性は:help :command-completeに記載されている属性に加え、Lua関数を取ることができます。
vim.api.nvim_create_user_command('Upper', function() end, {
nargs = 1,
complete = function(ArgLead, CmdLine, CursorPos)
-- return completion candidates as a list-like table
return { 'foo', 'bar', 'baz' }
end,
})バッファローカルなユーザーコマンドも第1引数にバッファ番号を受け取ります。現在のバッファ用のコマンドを定義することができる-bufferより、これは便利です。
vim.api.nvim_buf_create_user_command(4, 'Upper', function() end, {})vim.api.nvim_del_user_command() はコマンド名を受け取ります。
vim.api.nvim_del_user_command('Upper')
-- :delcommand Upperここでも、vim.api.nvim_buf_del_user_command()はバッファ番号を第1引数として受け取り、0は現在のバッファを表します。
vim.api.nvim_buf_del_user_command(4, 'Upper')参照:
-complete=custom属性は自動的に補完候補をフィルタリングし、ワイルドカード(:help wildcard)をサポートする機能を組み込みます:
function! s:completion_function(ArgLead, CmdLine, CursorPos) abort
return join([
\ 'strawberry',
\ 'star',
\ 'stellar',
\ ], "\n")
endfunction
command! -nargs=1 -complete=custom,s:completion_function Test echo <q-args>
" `:Test st[ae]<Tab>` と入力すると "star" と "stellar" を返しますcomplete にLua関数を渡すと、ユーザーにフィルタ方法を任せるcustomlistのような動作をします。
vim.api.nvim_create_user_command('Test', function() end, {
nargs = 1,
complete = function(ArgLead, CmdLine, CursorPos)
return {
'strawberry',
'star',
'stellar',
}
end,
})
-- 候補リストをフィルタしてないので `:Test z<Tab>` と入力すると全ての補完候補を返します(この章は現在作成中です)
Neovim 0.7.0はオートコマンド用のAPI関数を持っています。詳細は :help api-autocmd を参照してください。
- Pull request #14661 (lua: autocmds take 2)
(この章は現在作成中です)
Neovim 0.7.0はハイライトグループ用のAPI関数を持っています。
参照:
Luaでは、require()関数がモジュールをキャッシュします。
これはパフォーマンスには良いですが、後からrequire()を呼んでもモジュールは更新されないため少し面倒です。
特定のモジュールのキャッシュを更新する場合、package.loadedグローバルテーブルを変更する必要があります。:
package.loaded['modname'] = nil
require('modname') -- 新しい'modname'モジュールを読み込みますnvim-lua/plenary.nvimには、これを行う関数があります。
二重括弧の文字列を使用するとき、パディングの誘惑に負けないでください! スペースを無視するときは問題ないですが、スペースが重要な意味を持つときはデバックが困難な問題の原因になる可能性があります。:
vim.api.nvim_set_keymap('n', '<Leader>f', [[ <Cmd>call foo()<CR> ]], {noremap = true})上記の例では、<Leader>fは<Cmd>call foo()<CR>ではなく<Space><Cmd>call foo()<CR><Space>にマッピングされます。
VimからLua、LuaからVimのオブジェクトの参照を直接操作できません。
例えば、Vim scriptのmap()は変数をその場で変更します(破壊的)。
let s:list = [1, 2, 3]
let s:newlist = map(s:list, {_, v -> v * 2})
echo s:list
" [2, 4, 6]
echo s:newlist
" [2, 4, 6]
echo s:list is# s:newlist
" 1Luaからこの関数を使用すると、代りにコピーが作られます
local tbl = {1, 2, 3}
local newtbl = vim.fn.map(tbl, function(_, v) return v * 2 end)
print(vim.inspect(tbl)) -- { 1, 2, 3 }
print(vim.inspect(newtbl)) -- { 2, 4, 6 }
print(tbl == newtbl) -- falseこれは主に関数とテーブルに影響します。
Luaのリストと辞書が混在するテーブルは変換できません。
print(vim.fn.count({1, 1, number = 1}, 1))
-- E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keysLuaでvim.fnを使用してVim関数を呼べますが、それらの参照を保持できません。
それは不測の動作の原因になります。:
local FugitiveHead = vim.fn.funcref('FugitiveHead')
print(FugitiveHead) -- vim.NIL
vim.cmd("let g:test_dict = {'test_lambda': {-> 1}}")
print(vim.g.test_dict.test_lambda) -- nil
print(vim.inspect(vim.g.test_dict)) -- {}Luaの関数をVimの関数に渡せますが、Vimの変数に格納できません。 (Neovim 0.7.0+で修正されて、格納できるようになりました。)
-- This works:
vim.fn.jobstart({'ls'}, {
on_stdout = function(chan_id, data, name)
print(vim.inspect(data))
end
})
-- This doesn't:
vim.g.test_dict = {test_lambda = function() return 1 end} -- Error: Cannot convert given lua typeただし、Vim scriptからluaeval()を使用して同じことをすると動作します。:
let g:test_dict = {'test_lambda': luaeval('function() return 1 end')}
echo g:test_dict
" {'test_lambda': function('<lambda>4714')}Vim scriptの一般的なパターンではbool値の代わりに1と0を使用します。
実際、Vimにはバージョン7.4.1154まで区別されたbool型がありませんでした。
Luaのbool値は数値ではなく、Vim scriptの実際のbool値に変換されます。:
lua vim.g.lua_true = true
echo g:lua_true
" v:true
lua vim.g.lua_false = false
echo g:lua_false
" v:falseLuaのプロジェクトでリンターや言語サーバーを使用して、診断と自動補完を利用している場合、Neovim固有の設定が必要になる場合があります。人気のあるツールの推奨設定は次のとおりです。:
次の設定を ~/.luacheckrc (もしくは $XDG_CONFIG_HOME/luacheck/.luacheckrc)に配置すれば、luacheckでvimモジュールを認識できます。:
globals = {
"vim",
}言語サーバーのAlloyed/lua-lspは luacheck を使用してリンティングを提供し、同じファイルを読み込みます。
luacheck の設定方法の詳細はドキュメントを参照してください。
nvim-lspconfigリポジトリにsumneko/lua-language-serverの設定方法があります(例は組込みのLSPクライアントを使っていますが、他のLSPクライアントでも同じ設定である必要があります)。
sumneko/lua-language-serverの設定方法の詳細は"Setting"を見てください。
coc.nvimの補完ソースであるrafcamlet/coc-nvim-luaはNeovim stdlibの項目を提供しています。
別のNeovimインスタンスで実行しているLuaコードをjbyuki/one-small-step-for-vimkindでデバッグできます。
このプラグインはDebug Adapter Protocolを使用しています。 デバッグアダプターに接続するには、mfussenegger/nvim-dapやpuremourning/vimspectorのようなDAPクライアントが必要です。
マッピング/コマンド/オートコマンドが定義されている位置を :verbose コマンドで確認できます:
:verbose map mn m_ * <Cmd>echo 'example'<CR>
Last set from ~/.config/nvim/init.vim line 26
デフォルトでは、Luaのパフォーマンス上の理由でこの機能は無効です。 Neovim起動時にverboseのレベルが0より上なら、この機能を有効にできます:
nvim -V1参照:
wbthomason/packer.nvimはLuarocksパッケージをサポートしています。 使い方はREADMEにあります。
vim.loopはLibUV APIを公開するモジュールです。いくつかのリソース:
参照:
vim.lspは組込みのLSPクライアントを操作するためのモジュールです。
neovim/nvim-lspconfigは有名なLanguage Serverの設定集です。
クライアントの動作は"lsp-handlers"を使用して設定できます。詳細はこちら:
LSPクライアントを利用したプラグインも見たいかもしれません。
参照:
vim.treesitterはNeovim内のTree-sitterライブラリを操作するためのモジュールです。
Tree-sitterについてもっと知りたいなら、このプレゼン (38:37)に興味があるかもしれません。
nvim-treesitterオリジネーションは、ライブラリを利用して様々なプラグインをホストしています。
参照:
Luaを使用する利点の1つは実際にLuaを書く必要がないことです!利用できるトランスパイラはたくさんあります。
おそらく、最も知られているLuaのトランスパイラです。クラス、リスト内包表記、関数リテラルなどの便利な機能を多数追加します。 svermeulen/nvim-moonmakerはNeovimのプラグインと設定をMoonscriptで直接書けるようにします。
lispをLuaにコンパイルします。Olical/aniseedまたは、Hotpotを使用するとNeovimのプラグインと設定を書くことができます。 さらに、Olical/conjureは対話的な開発環境を提供します(他の言語の中で)。
Tealの名前の由来はTL(typed lua)の発音からです。 まさにその通りで、強力な型をLuaに追加し、それ以外は標準のLuaの構文に近づけています。 nvim-teal-makerプラグインを使用して、 TealでNeovimプラグインや設定ファイルを書けます。
その他の興味深いプロジェクト: