【Neovim】vifでメソッド内を範囲選択する方法

:::note info
本記事は[vim駅伝](https://vim-jp.org/ekiden)2024/09/20の記事です！
前回記事はtadashi-aikawaさんの[Slackに進捗状況を共有できるNeovimプラグインをつくってみた
](https://minerva.mamansoft.net/%F0%9F%93%98Articles/%F0%9F%93%98Slack%E3%81%AB%E9%80%B2%E6%8D%97%E7%8A%B6%E6%B3%81%E3%82%92%E5%85%B1%E6%9C%89%E3%81%A7%E3%81%8D%E3%82%8BNeovim%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E3%82%92%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F)でした！
:::

## はじめに

VisualMode、便利ですけど範囲選択するときのコマンド打つのが若干手間ですよね。
`viw`とかくらいならまあ良いですが、この行のこの位置からあの行のあの位置まで選択したいんだよな〜みたいな微調整が必要なときって面倒くさいですよね。

そういうVisualModeのちょっと痒いところに手の届く設定方法をご紹介します。

## 0. Requirements

- neovim
  - 筆者のは`v0.10.0`でした
- [lazy.nvim](https://github.com/folke/lazy.nvim)
  - packerからの移行がお済みでない方は以下の記事も是非ご参考ください！

https://qiita.com/ysmb-wtsg/items/e6c841f00354129a125c
  

## 1. treesitterのinstall

neovimでは、構文解析のための強力なプラグインとしてtreesitterというプラグインがあります。

https://github.com/nvim-treesitter/nvim-treesitter

メソッド・変数・キーワードなど、トークン解析のベースとなります。

以下でインストールします。

```lua
return {
	"nvim-treesitter/nvim-treesitter",
	event = { "BufReadPre", "BufNewFile" },
	build = ":TSUpdate",
	config = function()
		-- import nvim-treesitter plugin
		local treesitter = require("nvim-treesitter.configs")

		-- configure treesitter
		treesitter.setup({ -- enable syntax highlighting
			highlight = { enable = true },
			-- enable indentation
			indent = { enable = true },
			-- enable autotagging (w/ nvim-ts-autotag plugin)
			autotag = { enable = true },
			-- ensure these language parsers are installed
			ensure_installed = {
				"json",
				"javascript",
				"typescript",
				"tsx",
				"yaml",
				"html",
				"http",
				"css",
				"prisma",
				"markdown",
				"markdown_inline",
				"svelte",
				"graphql",
				"bash",
				"lua",
				"vim",
				"dockerfile",
				"gitignore",
				"query",
				"vimdoc",
				"c",
				"python",
				"csv",
				"dockerfile",
				"sql",
				"ssh_config",
			},
		    incremental_selection = {
			    enable = true,
		        keymaps = {
			        init_selection = "<CR>",
			        node_incremental = "<CR>",
			        scope_incremental = false,
			        node_decremental = "<bs>",
			    },
		    },
		})
	end,
}
```

※`ensure_installed`のところは各自お好みで設定してください

ちなみに`incremental_selection`の設定をしておくと、NormalModeでCRを押すのが楽しくなります。ぜひ。

## 2. treesitter-textobjectsのinstall

さてtreesitterで構文解析ができるよになったところで、本記事の本命であるプラグインをinstallします。

https://github.com/nvim-treesitter/nvim-treesitter-textobjects

このプラグインは、**treesitterの構文解析の結果に基づいてtextobjectをカスタマイズ**できます。
built-inでtextobjectとして扱えるtreesitterのトークンは以下のとおりです。

```
@assignment.inner
@assignment.lhs
@assignment.outer
@assignment.rhs
@attribute.inner
@attribute.outer
@block.inner
@block.outer
@call.inner
@call.outer
@class.inner
@class.outer
@comment.inner
@comment.outer
@conditional.inner
@conditional.outer
@frame.inner
@frame.outer
@function.inner
@function.outer
@loop.inner
@loop.outer
@number.inner
@parameter.inner
@parameter.outer
@regex.inner
@regex.outer
@return.inner
@return.outer
@scopename.inner
@statement.outer
```

こいつらを使って、`vif`のキーマップを設定していきます。

```lua
return {
	"nvim-treesitter/nvim-treesitter-textobjects",
	lazy = true,
	config = function()
		require("nvim-treesitter.configs").setup({
			textobjects = {
				select = {
					enable = true,

					-- Automatically jump forward to textobj, similar to targets.vim
					lookahead = true,

					keymaps = {
						-- You can use the capture groups defined in textobjects.scm
						["af"] = {
							query = "@function.outer",
							desc = "Select outer part of a method/function definition",
						},
						["if"] = {
							query = "@function.inner",
							desc = "Select inner part of a method/function definition",
						},
					},
				},
			},
		})
	end,
}
```

ついでに`vaf`も定義しました。

この状態で任意の関数内にカーソルを置いて`vif`してみると、、、

![スクリーンショット 2024-09-20 21.31.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2947418/d45b78a2-e748-1da1-0b8f-376f136ec244.png)

関数の内部だけが選択されていますね！

`vaf`もついでにやってみます。

![スクリーンショット 2024-09-20 21.32.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2947418/12f9e7a3-edfc-652e-0d57-69da11cf5715.png)

関数ごと選択されました:tada:

## 最後に

今回はtreesitter-textobjectを使ったtextobjectの拡張方法をご紹介しました！
また今回設定したのは`v`コマンドの拡張だけでしたが、READMEを見ながら他の設定も試してみてください！

筆者の設定をはっつけておきます。

<details><summary>Click to Open</summary>

```lua
return {
	"nvim-treesitter/nvim-treesitter-textobjects",
	lazy = true,
	config = function()
		require("nvim-treesitter.configs").setup({
			textobjects = {
				select = {
					enable = true,

					-- Automatically jump forward to textobj, similar to targets.vim
					lookahead = true,

					keymaps = {
						-- You can use the capture groups defined in textobjects.scm
						["a="] = { query = "@assignment.outer", desc = "Select outer part of an assignment" },
						["i="] = { query = "@assignment.inner", desc = "Select inner part of an assignment" },
						["h="] = { query = "@assignment.lhs", desc = "Select left hand side of an assignment" },
						["l="] = { query = "@assignment.rhs", desc = "Select right hand side of an assignment" },

						["a:"] = { query = "@property.outer", desc = "Select outer part of an object property" },
						["i:"] = { query = "@property.inner", desc = "Select inner part of an object property" },
						["l:"] = { query = "@property.lhs", desc = "Select left part of an object property" },
						["r:"] = { query = "@property.rhs", desc = "Select right part of an object property" },

						["aa"] = { query = "@parameter.outer", desc = "Select outer part of a parameter/argument" },
						["ia"] = { query = "@parameter.inner", desc = "Select inner part of a parameter/argument" },

						["ai"] = { query = "@conditional.outer", desc = "Select outer part of a conditional" },
						["ii"] = { query = "@conditional.inner", desc = "Select inner part of a conditional" },

						["al"] = { query = "@loop.outer", desc = "Select outer part of a loop" },
						["il"] = { query = "@loop.inner", desc = "Select inner part of a loop" },

						["af"] = { query = "@call.outer", desc = "Select outer part of a function call" },
						["if"] = { query = "@call.inner", desc = "Select inner part of a function call" },

						["am"] = {
							query = "@function.outer",
							desc = "Select outer part of a method/function definition",
						},
						["im"] = {
							query = "@function.inner",
							desc = "Select inner part of a method/function definition",
						},

						["ac"] = { query = "@class.outer", desc = "Select outer part of a class" },
						["ic"] = { query = "@class.inner", desc = "Select inner part of a class" },
					},
				},
				move = {
					enable = true,
					set_jumps = true, -- whether to set jumps in the jumplist
					goto_next_start = {
						["]f"] = { query = "@call.outer", desc = "Next function call start" },
						["]m"] = { query = "@function.outer", desc = "Next method/function def start" },
						["]c"] = { query = "@class.outer", desc = "Next class start" },
						["]i"] = { query = "@conditional.outer", desc = "Next conditional start" },
						["]l"] = { query = "@loop.outer", desc = "Next loop start" },

						-- You can pass a query group to use query from `queries/<lang>/<query_group>.scm file in your runtime path.
						-- Below example nvim-treesitter's `locals.scm` and `folds.scm`. They also provide highlights.scm and indent.scm.
						["]s"] = { query = "@scope", query_group = "locals", desc = "Next scope" },
						["]z"] = { query = "@fold", query_group = "folds", desc = "Next fold" },
					},
					goto_next_end = {
						["]F"] = { query = "@call.outer", desc = "Next function call end" },
						["]M"] = { query = "@function.outer", desc = "Next method/function def end" },
						["]C"] = { query = "@class.outer", desc = "Next class end" },
						["]I"] = { query = "@conditional.outer", desc = "Next conditional end" },
						["]L"] = { query = "@loop.outer", desc = "Next loop end" },
					},
					goto_previous_start = {
						["[f"] = { query = "@call.outer", desc = "Prev function call start" },
						["[m"] = { query = "@function.outer", desc = "Prev method/function def start" },
						["[c"] = { query = "@class.outer", desc = "Prev class start" },
						["[i"] = { query = "@conditional.outer", desc = "Prev conditional start" },
						["[l"] = { query = "@loop.outer", desc = "Prev loop start" },
					},
					goto_previous_end = {
						["[F"] = { query = "@call.outer", desc = "Prev function call end" },
						["[M"] = { query = "@function.outer", desc = "Prev method/function def end" },
						["[C"] = { query = "@class.outer", desc = "Prev class end" },
						["[I"] = { query = "@conditional.outer", desc = "Prev conditional end" },
						["[L"] = { query = "@loop.outer", desc = "Prev loop end" },
					},
				},
			},
		})
	end,
}
```
</details>

---

では、良いvimライフを！！