Neovim vim.cmd 使用法

[Neovim Lua API の vim.cmd](<https://neovim.io/doc/user/lua.html#vim.cmd()>)、皆さん使ってらっしゃいますよね？

https://neovim.io/doc/user/lua.html#vim.cmd()

`vim.cmd('echo 42')` のように文字列を引数に渡せば Vim script のコマンドとして実行できる関数です
また、`vim.cmd.echo('42')` のようにコマンド名をキーとすることでそのコマンドを実行する関数を取得することもできます
この機能ですが、前者と後者で厳密に挙動がおなじになるわけではありません。

```lua
local command_str = 'belowright vnew'
vim.cmd(command_str) -- 正常に実行される
local i = command_str:find(' ')
vim.cmd[command_str:sub(1, i - 1)](command_str:sub(i + 1)) -- エラー
```

まあ `vim.cmd('foo')` 形式であれば恐らく任意の形式のコマンドが実行できるのでそちらを使えば良いのですが、個人的に `vim.cmd.foo()` 形式の方が好みなので色々調べました

# `vim.cmd.foo()` 形式での `belowright vnew` の実現方法
結論から言うと、`vim.cmd.vnew({ mods = { split = 'belowright' } })` で実現できます。
「`mods` とか `split` とかどこから出てきたんだよ」って感じですが、不明なことがあればヘルプを見るのが良いと理性では理解しているので非常に重い腰を上げて読んでみます

```
cmd({command})                                                     *vim.cmd()*
(略)
    Parameters:  
      • {command}  string|table Command(s) to execute. If a string, executes
                   multiple lines of Vim script at once. In this case, it is
                   an alias to |nvim_exec2()|, where `opts.output` is set to
                   false. Thus it works identical to |:source|. If a table,
                   executes a single command. In this case, it is an alias to
                   |nvim_cmd()| where `opts` is empty.
```

これを読んだところ、引数が文字列であれば `:source` と同様の挙動、そうでなければ `nvim_cmd()` が呼ばれるとあります。`:source` と同様の挙動であれば `belowright vnew` も実行できそうなので次は `nvim_cmd()` について調べます

```
nvim_cmd({*cmd}, {*opts})                                         *nvim_cmd()*
(略)
      • {cmd}   Command to execute. Must be a Dictionary that can contain the
                same values as the return value of |nvim_parse_cmd()| except
                "addr", "nargs" and "nextcmd" which are ignored if provided.
                All values except for "cmd" are optional.
```

`nvim_parse_cmd()` の返り値の形式を引数に取れるようなのでそちらも読みに行きます

```
nvim_parse_cmd({str}, {opts})                               *nvim_parse_cmd()*
(略)
    Parse command line.

    Doesn't check the validity of command arguments.

    Attributes:  
        |api-fast|

    Parameters:  
      • {str}   Command line string to parse. Cannot contain "\n".
```

これにコマンドを渡すと `nvim_cmd()` に渡せる形式のオブジェクトが得られそうなので、`belowright vnew` を渡してみます

```lua
local obj = vim.api.nvim_parse_cmd('belowright vnew', {})
vim.notify(vim.inspect(obj))
--[[
{
  addr = "?",
  args = {},
  bang = false,
  cmd = "vnew",
  mods = {
(略)
    split = "belowright",
(略)
]]
```

良さそうなオブジェクトが得られました。読み飛ばしましたが、多分 `vim.cmd.foo()` とすると `vim.cmd({ cmd = 'foo' })` が渡ると思うので `mods.split` だけ引数に渡してみようと思います

```lua
vim.cmd.vnew({ mods = { split = 'belowright' } })
```

問題なく実行できました。これで任意のコマンドを `vim.cmd.foo()` 形式で実行できるようになりました。めでたい！
早速ほかのコマンドも試してみましょう。新しいタブでターミナルを開く `tabe +terminal` を実行できるようにしたかったんですよね

```lua
local obj = vim.api.nvim_parse_cmd('tabe +terminal', {})
vim.notify(vim.inspect(obj))
--[[
{
  args = { "+terminal" },
  cmd = "tabedit",
(略)
]]
vim.cmd.tabedit({ args = { "+terminal" } })
```

![tab-titled-plus-terminal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369264/352e3629-90b9-1d62-b366-358a5c4a177c.png)

`+terminal` というタイトルの空のタブが開きました・・・
しばらく試行錯誤と調査をしたところ、下記の Issue を見つけました

https://github.com/neovim/neovim/issues/21687

現時点では `++opt` と `+cmd` はサポートされていないようです。無念[^1]


[^1]: コントリビュートチャンスかもしれない