Skip to content

Latest commit

 

History

History
1457 lines (1040 loc) · 60 KB

README.ja.md

File metadata and controls

1457 lines (1040 loc) · 60 KB

↖️ (迷子になった? GitHub TOCを使いましょう!)

⚠️ ⚠️ ⚠️ このガイドの最新版は、Neovimのドキュメントにあります。 :help lua-guideを参照してください。 ⚠️ ⚠️ ⚠️

日本語はこちら→https://github.com/willelz/neovimdoc-ja/blob/main/doc/lua-guide.jax

Getting started using Lua in Neovim

はじめに

Neovimのファーストクラス言語としてのLuaはキラー機能の1つになりつつあります。 しかし、Luaでプラグインを書くための教材はVim script程多くありません。これは、Luaを始めるための基本的な情報を提供する試みです。

このガイドは少なくともNeovim 0.5を使用していることを前提としています。

Luaを学ぶ

まだLuaについて詳しくない場合、学ぶためのリソースはたくさんあります。:

Luaはとてもクリーンでシンプルな言語であることに注意してください。JavaScriptのようなスクリプト言語の経験があれば、学ぶことは簡単です。あなたはもう自分で思っているよりLuaについて知っているかもしれません!

Note: Neovimに埋め込まれているLuaはLuaJIT 2.1.0でLua 5.1と互換性を維持しています。

Luaを書くための既存のチュートリアル

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より良いシンタックスハイライト

Luaファイルを置く場所

init.lua

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

参照:

Tips

いくつかのLuaプラグインはlua/フォルダ内に同じ名前のファイルがあるかもしれません。これにより、名前空間の衝突を起こす可能性があります。

異なる2つのプラグインにlua/main.luaがある場合、require('main')は曖昧です。: どのファイルを読み込みますか?

トップレベルのフォルダで名前空間をつけることをお勧めします。: lua/plugin_name/main.lua

Runtime files

Vim scriptと同様に、runtimepath内にある特定のフォルダからLuaファイルを自動的に読み込めます。 現在、次のフォルダがサポートされています。:

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

Note: runtimeデイレクトリでは、すべての*.vimファイルは*.luaファイルの前に読み込まれます。

参照:

Tips

ランタイムファイルはLuaのモジュールシステムをベースとしていないため、2つのプラグインはplugin/main.luaを問題なく持つことができます。

Vim scriptからLuaを使用する

: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)
EOF

Note: 各:luaコマンドは独自のスコープを持っており、localを付けた変数はコマンドの外からアクセスできません。 次の例は動作しません。:

:lua local foo = 1
:lua print(foo)
" '1'ではなく'nil'が出力されます。

Note 2: Luaのprint():echomsgと同じように動作します。出力はメッセージ履歴に保存されます。また、:silentで抑制できます。

参照:

:luado

このコマンドはカレントバッファの範囲行にLuaチャンクを実行します。範囲を指定しない場合、バッファ全体に作用します。 チャンクからreturnされた文字列は、各行を置き換えるために使用されます。

次のコマンドは、カレントバッファのすべての行をhello worldに置き換えます。:

:luado return 'hello world'

2つの暗黙的な変数linelinerが提供されます。lineは対象行のテキストで、linerはその行数です。 次のコマンドは、すべての偶数行のテキストを大文字にします。

:luado if linenr % 2 == 0 then return line:upper() end

参照:

Luaファイルの読み込み

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を参照してください。

参照:

Sourcing a lua file vs calling require():

require()関数を呼ぶこととLuaファイルの読み込みの違いは何か、どちらを使うべきかを疑問に思うかもしれません。 それらには異なるユースケースがあります。:

  • require():
    • Luaの組込み関数です。Luaのモジュールを読み込むのに使用します。
    • 'runtimepath'内にあるlua/フォルダからモジュールを探します。
    • どのモジュールをロードしたかを記憶し、多重に実行されるのを防ぎます。Neovim実行中に、モジュールに含まれるコードを変更し、もう一度require()を実行してもモジュールは更新されません。
  • :luafile, :source, runtime:
    • Exコマンドです。モジュールには対応していません。
    • 以前に実行されたかどうかに関わらず実行されます。
    • :luafile:sourceは現在のウィンドウのディレクトリに対して相対パス・絶対パスを取ります。
    • runtimeは、'rutimepath'オプションを使用してファイルを探します。

:source:runtime、ランタイムディレクトリから自動的に読み込まれたファイルもscriptnames--startuptimeに表示されます。

luaeval()

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'

参照:

v:lua

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)

Tips

設定ファイルに、let g:vimsyn_embed = 'l'を追加すると.vimファイル内のLuaを構文ハイライトできます。 詳細は:help g:vimsyn_embedを参照してください。

vim名前空間

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にあります。

Tips

オブジェクトの中身を検査するのに毎回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。詳しくは後述します。)を見てください。おそらく、探しものはそこにあります。

LuaからVim scriptを使用する

vim.api.nvim_eval()

文字列で与えられた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')) -- nil

警告

luaeval()と違い、式にデータを渡すための暗黙的な変数_Aを提供しません。

vim.api.nvim_exec()

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:)をサポートしていません。

vim.api.nvim_command()

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.cmd()

vim.api.nvim_exec()のエイリアスです。コマンドの引数のみを必要とし、outputは常にfalseに設定されます。

vim.cmd('buffers')
vim.cmd([[
let g:multiline_list = [
            \ 1,
            \ 2,
            \ 3,
            \ ]

echo g:multiline_list
]])

Tips

これらの関数は文字列を渡すため、多くの場合、バックスラッシュをエスケープする必要があります。:

vim.cmd('%s/\\Vfoo/bar/g')

二重括弧の文字列はエスケープが必要ないため使いやすいです。:

vim.cmd([[%s/\Vfoo/bar/g]])

vim.api.nvim_replace_termcodes()

この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})

参照:

vimオプションを管理する

API関数を使用する

Neovimは、オプションの値を読み書きできるAPI関数を提供しています。

それらはオプションの名前と設定したい値を含む文字列を受け取ります。

boolな((no)numberのような)オプションはtruefalseのどちらかに設定する必要があります。:

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を参照してください。

参照:

vim内部の変数を管理する

API関数を使用する

オプションのように、内部変数にもAPI関数があります。

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.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 scriptの関数を呼び出す

vim.fn.{function}()

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.fnvim.callと同じ動作ですが、よりLuaらしい構文を使用できます。

vim.api.nvim_call_functionとは、Vim/Luaオブジェクトを自動で変換する点が異なります。: vim.api.nvim_call_functionは浮動小数点数のテーブルを返しLuaのクロージャーを受け入れませんが、vim.fnはこれらの型を扱えます。

参照:

Tips

Neovimにはプラグインに便利な強力な組込み関数を含むライブラリがあります。 アルファベット順のリストは:help vim-functionを参照してください。 :help function-listは機能別に分類されたリストです。

NeovimのAPI関数はvim.api{..}のように直接使用できます。 詳細は:help apiを参照してください。

警告

いくつかのVim関数はbool値の変わりに10を返します。これは、Vim scriptでは1は真で0は偽になるため問題ありません。 次のようなことが可能です。:

if has('nvim')
    " do something...
endif

しかし、Luaで偽になるのはfalsenilのみで、数値は値に関係なく常にtrueと評価されます。 明示的に10かをチェックする必要があります。:

if vim.fn.has('nvim') == 1 then
    -- do something...
end

マッピングを定義する

API関数

Neovimはマッピングを設定、取得、削除するための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 nmap

vim.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>

vim.keymap

⚠️ このセクションで説明するAPI関数はNeovim 0.7.0+のみで使用できます。

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})

ユーザーコマンドを定義する

⚠️ このセクションで説明するAPI関数はNeovim 0.7.0+のみで使用できます。

Neovimはユーザーコマンドを作成するAPI関数を提供します。

まず 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 を参照してください。

ハイライトを定義する

(この章は現在作成中です)

Neovim 0.7.0はハイライトグループ用のAPI関数を持っています。

参照:

一般的なTipsと推奨

キャッシュされたモジュールのリロード

Luaでは、require()関数がモジュールをキャッシュします。 これはパフォーマンスには良いですが、後からrequire()を呼んでもモジュールは更新されないため少し面倒です。

特定のモジュールのキャッシュを更新する場合、package.loadedグローバルテーブルを変更する必要があります。:

package.loaded['modname'] = nil
require('modname') -- 新しい'modname'モジュールを読み込みます

nvim-lua/plenary.nvimには、これを行う関数があります。

Luaの文字列をパディングしないでください!

二重括弧の文字列を使用するとき、パディングの誘惑に負けないでください! スペースを無視するときは問題ないですが、スペースが重要な意味を持つときはデバックが困難な問題の原因になる可能性があります。:

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 script <--> Lua 型変換の注意

変数を変換するとコピーが作られます:

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
" 1

Luaからこの関数を使用すると、代りにコピーが作られます

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 keys

Luaで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 booleans

Vim scriptの一般的なパターンではbool値の代わりに10を使用します。 実際、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:false

リンターと言語サーバーの設定

Luaのプロジェクトでリンターや言語サーバーを使用して、診断と自動補完を利用している場合、Neovim固有の設定が必要になる場合があります。人気のあるツールの推奨設定は次のとおりです。:

luacheck

次の設定を ~/.luacheckrc (もしくは $XDG_CONFIG_HOME/luacheck/.luacheckrc)に配置すれば、luacheckでvimモジュールを認識できます。:

globals = {
    "vim",
}

言語サーバーのAlloyed/lua-lspluacheck を使用してリンティングを提供し、同じファイルを読み込みます。

luacheck の設定方法の詳細はドキュメントを参照してください。

sumneko/lua-language-server

nvim-lspconfigリポジトリにsumneko/lua-language-serverの設定方法があります(例は組込みのLSPクライアントを使っていますが、他のLSPクライアントでも同じ設定である必要があります)。

sumneko/lua-language-serverの設定方法の詳細は"Setting"を見てください。

coc.nvim

coc.nvimの補完ソースであるrafcamlet/coc-nvim-luaはNeovim stdlibの項目を提供しています。

Luaコードのデバッグ

別のNeovimインスタンスで実行しているLuaコードをjbyuki/one-small-step-for-vimkindでデバッグできます。

このプラグインはDebug Adapter Protocolを使用しています。 デバッグアダプターに接続するには、mfussenegger/nvim-dappuremourning/vimspectorのようなDAPクライアントが必要です。

Luaマッピング/コマンド/オートコマンドのデバッグ

マッピング/コマンド/オートコマンドが定義されている位置を :verbose コマンドで確認できます:

:verbose map m
n  m_          * <Cmd>echo 'example'<CR>
        Last set from ~/.config/nvim/init.vim line 26

デフォルトでは、Luaのパフォーマンス上の理由でこの機能は無効です。 Neovim起動時にverboseのレベルが0より上なら、この機能を有効にできます:

nvim -V1

参照:

Luaコードのテスト

Luarocksパッケージを使用する

wbthomason/packer.nvimはLuarocksパッケージをサポートしています。 使い方はREADMEにあります。

その他

vim.loop

vim.loopはLibUV APIを公開するモジュールです。いくつかのリソース:

参照:

vim.lsp

vim.lspは組込みのLSPクライアントを操作するためのモジュールです。 neovim/nvim-lspconfigは有名なLanguage Serverの設定集です。

クライアントの動作は"lsp-handlers"を使用して設定できます。詳細はこちら:

LSPクライアントを利用したプラグインも見たいかもしれません。

参照:

vim.treesitter

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プラグインや設定ファイルを書けます。

その他の興味深いプロジェクト: