neovimプラグインの作成 lua
過去に書いたもの
luaを使用してpluginを開発する方法について学びたいと思います。
この記事は前回の記事の続きからになっています。
先に読んでおくと理解しやすいと思います。
対象者
- vimの基本操作を理解していること,入力方法,検索方法,コマンドモード,visualモード等
- luaでneovimのpluginを作成したい人
- luaのpluginを読んでみたい人
- lazyvimを使い始めた人
動作環境
- Arch系 Linux (garuda linux)
OS詳細情報
$ uname -a
Linux tosi 6.6.9-zen1-1-zen #1 ZEN SMP PREEMPT_DYNAMIC Tue, 02 Jan 2024 02:28:04 +0000 x86_64 GNU/Linux
$ cat /etc/os-release
File: /etc/os-release
NAME="Garuda Linux"
PRETTY_NAME="Garuda Linux"
ID=garuda
ID_LIKE=arch
BUILD_ID=rolling
ANSI_COLOR="38;2;23;147;209"
HOME_URL="https://garudalinux.org/"
DOCUMENTATION_URL="https://wiki.garudalinux.org/"
SUPPORT_URL="https://forum.garudalinux.org/"
BUG_REPORT_URL="https://gitlab.com/groups/garuda-linux/"
PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
LOGO=garudalinux
- neovim NVIM v0.10.2
neovim詳細情報
$ nvim --version
NVIM v0.10.2
Build type: RelWithDebInfo
LuaJIT 2.1.1702233742
Run "nvim -V1 -v" for more info
プラグインのファイル構造
まずはプラグインのファイル構造から考えたいと思います。
plugin/
フォルダ内のファイルはpluginのロード時に起動時に実行されます。
pluginと同じ名前で保存されています。
lua/
ほとんどの場合プラグインの起動時にすべてを実行することは望ましくありません。
ファイルを開いたとき、コマンドが呼び出されたときなど、何かしらイベントに応じて関数を呼び出す場合このファイル内で整理します。
ディレクトリーを呼び出すには、runtimepath
によって検索されrequire
で呼び出します。
runtimepath
ではrequire(foo.bar)
と書かれている場合lua/
フォルダーの下からlua/foo/bar.lua
または,lua/foo/bar/init.lua
を読み込みます。拡張子.lua
は省略してピリオド(.)区切りで表記します。
プラグインのファイル構造の採用例
lazyvim
や、terescope
の場合パターン1の構成のディレクトリーを採用しています。
neo-tree
や、前回使用したhex.nvim
の場合はパターン2の構成のディレクトリーをを採用しています。
参考例として呼び出し部分しか記載していませんが、もちろんファイルは他に存在していて問題ありません。
パターン1
プラグイン名のフォルダーの下にinit.luaを作成して呼び出し
├── lua
│ └── plugin_name
│ └── init.lua
パターン2
luaフォルダーの下にプラグイン名のファイル名を作成して呼び出し
├── lua
│ └── plugin_name.lua
参考一覧
プラグインの作成
簡単なpluginとしてhello worldを表示するプラグインを作成します。
1章ではhello worldを固定して表示します。
2章ではメッセージを設定ファイルに保存して表示します。
3章ではコマンド実行時にhello worldを表示します。
4章ではhello worldから変えて bashの機能を呼び出してみます。
以降記事の中で
nvim/
と記載する場合以下のフォルダー以下を指します。
windows:C:\Users\user\AppData\Local\nvim\
linux:/home/user/.config/nvim
1.hellow worldを固定して表示
作業用のディレクトリーとしてホームディレクトリ内にdev_plugin
を作成します。
その下にpluginをmyexample
と名前を付けて保存します。
mkdir ~/dev_plugin/myexample
最初の方にも書きましたがpluginのロード時に起動されるファイルはplugin/
になります。
以下のようなファイル構成になるようにフォルダーを作ります。
dev_plugin
└─myexample
└─plugin
└─myexample.lua
myexample/plugin/myexample.lua
では単純にhello worldを表示するためのプログラムを書きます。
print("hello world")
lazyvimで自分の環境内にあるpluginを導入するにはdirで呼び出します。
nvim/lua/plugin/
にmyexample.lua
を作成します。lazyvimの設定内容は前回の記事に記載しています。
nvim/lua/plugin/myexample
return {
{ dir = "~/dev_plugin/myexample/" },
}
新しくnvimを開いて:messages
をコマンドに入力すると、hellow worldが入力されていることが分かります。
初めてpluginを作成しました。
これでは1つのファイルしか使用できないので、別のファイルを読み込んで実行したいと思います。
2.設定ファイルで文字列を変えてみる。
新しくlua
ファイルを作成します。
一旦lua/
フォルダー以下でpluginが実行できるようにします。
先ほど見たパターン2のファイル構造ですね。
dev_plugin
└─myexample
├─lua
│ └─myexample.lua
│
└─plugin
└─myexample.lua
myexample/lua/myexample.lua
にprint("Hellow world")
を記入して
myexample/lua/myexample.lua
print("Hellow world")
myexample/plugin/myexample.lua
にはrequire("myexample")
と書いてみよう
myexample/plugin/myexample.lua
require("myexample")
一度nvimを閉じて:messages
をコマンドに入力すると、pluginフォルダーからluaフォルダー以下を呼び出していることが分かります。
:Lazy
のコマンドでlazy.nvim
を呼び出すと、myexampleがロードされていることに気づきます。
別の場所にファイルを設定できるようにしたので、設定ファイルで文字列を変えてみる。ことに戻ります。
myexample/lua/myexample.lua
を編集します。
local M = {}
M.config = {
message = "hello world",
}
M.setup = function(args)
M.config = vim.tbl_deep_extend("force", M.config, args or {})
print(M.config.message)
end
return M
一瞬複雑なコードを見てうぅとなるかもしれませんが説明します。
1行目にモジュール名を宣言して最後に
returnでモジュール返すことでpluginが機能します。
仮にモジュール名をM
としていますがM
をすべてHello
に変えても動作します。
実際のプラグインでもM
にしている人がいたりtelescope
などplugin名にしている人もいます。
nvimでpluginを呼び出した場合setup()関数が自動的に呼び出されます。
luaの関数はfunction()
で始まりend
で終わります。
vim.tbl_deep_extend()でpluginマネージャから引き受けたconfigの項目をM.configに結合して、printで結果を表示します。
lazyvimの設定はこのようになります。
一度nvimを閉じて起動した場合にgood bye
が表示されると思います。
nvim/lua/plugin/myexample
return {
{
dir = "~/dev_plugin/myexample/",
config = {
message = "good bye",
},
},
}
3.コマンド作成
起動時に実行するのもいいですが特定の動作をした際に起動したいといったこともあります。
そのような場合には関数からコマンドを作成しておき、特定の動作の時に実行することができます。
myexample/lua/myexample.lua
local M = {}
M.config = {
message = "hello world",
first_name = "yamda",
nickname = "taro",
}
M.hello_name = function()
print(M.config.message..M.config.first_name)
end
M.hello_nick = function()
print(M.config.message..M.config.nickname)
end
M.setup = function(args)
M.config = vim.tbl_deep_extend("force", M.config, args or {})
print(M.config.message)
vim.api.nvim_create_user_command('HelloName', M.hello_name, {})
vim.api.nvim_create_user_command('HelloNick', M.hello_nick, {})
end
return M
M.hello_nick()
,M.hello_name()
の関数を作成しました。
M.setup()
関数内でvim.api.nvim_create_user_command()
で関数をvimに登録します。
vim.api.nvim_create_user_command()
の使用方法はコマンドモードで:h nvim_create_user_command()
を実行すると確認できます。
nvim_create_user_command({name},{command},{opts})
と書かれています。
{name}には新しいコマンドをuppercaseでつけろと書いてあります。
{command}にはコマンドを実行した際に呼び出される。luafunctionと書かれています。
{opts}にはコマンドの説明などを記載するようです。
lazyvimの設定は以下のようにしてコマンドを導入できるようにしておきます。
一度nvimを終了して:HelloName
,:HelloNick
とすると反応が返ってくると思います。
nvim/lua/plugin/myexample
return {
{
dir = "~/dev_plugin/myexample/",
config = {
message = "good bye ",
first_name = "yamamoto"
nickname = "ziro"
},
cmd = {
"HelloName",
"HelloNick",
}
},
}
4.bashのコマンド実行
単純なcommnadとしてログインユーザーを確認するコマンドを実行してみます。
bashでwhoを実行するとログインユーザーが確認できます。
awkを実行するとデータファイルの処理を行えます例として1列目だけを表示します。
$ who
tosi tty2 2024-11-26 17:29 (:0)
root pts/1 2024-11-26 17:29 (:0)
$ who | awk '{print $1}'
tosi
root
これを使用してログインユーザーに挨拶して見ようと思います。
myexample/lua/myexample.lua
local M = {}
M.config = {
message = "hello world",
first_name = "yamda",
nickname = "taro",
}
M.hello_name = function()
print(M.config.message .. M.config.first_name)
end
M.hello_nick = function()
print(M.config.message .. M.config.nickname)
end
M.hello_loginuser = function()
local job1 = vim.system({ "who" }):wait()
local job2 = vim.system({ "awk", "{print $1}" }, { stdin = job1.stdout }):wait()
vim.iter(vim.gsplit(job2.stdout, "\n")):each(function(line)
if line ~= "" then
print(line .. " Hello")
end
end)
end
M.setup = function(args)
M.config = vim.tbl_deep_extend("force", M.config, args or {})
vim.api.nvim_create_user_command("HelloName", M.hello_name, {})
vim.api.nvim_create_user_command("HelloNick", M.hello_nick, {})
vim.api.nvim_create_user_command("HelloLoginuser", M.hello_loginuser, {})
end
return M
luaからbashのコマンドを使用するにはvim.system()を使用します。
bash上ではpipe|
が使えますがlua上では使用できないのでjob1
の実行結果をjob2のstdin
に設定して実行します。
vim.iter他の言語でいうところの「配列」や「連想配列」に対して様々な操作をするための関数です。
コマンドの実行結果を改行ごとに分割して、それぞれにHelloと出力します。
内部に何かしらの文字列があるときだけ helloと表示します。
nvim/lua/plugin/myexample
return {
{
dir = "~/dev_plugin/myexample/",
dependencies = {
"nvim-lua/plenary.nvim",
},
config = {
message = "good bye ",
first_name = "yamamoto",
nickname = "ziro",
},
cmd = {
"HelloName",
"HelloNick",
"HelloLoginuser",
},
},
}
最後に
Neovim プラグインを開発する場合、開始方法を調べることが最も難しい部分に思います。
自分もこのzennの記事を作成するにあたり複数の英語のページ見てきました。
このチュートリアルがお役に立てば幸いです。
インデントめちゃくちゃになってる...
移行できたらGitHub管理で使ってみます。
参考文献
Discussion