0
1

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

Posted at

本記事はvim駅伝2024/09/20の記事です!
前回記事はtadashi-aikawaさんのSlackに進捗状況を共有できるNeovimプラグインをつくってみた
でした!

はじめに

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

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

0. Requirements

  • neovim
    • 筆者のはv0.10.0でした
  • lazy.nvim
    • packerからの移行がお済みでない方は以下の記事も是非ご参考ください!

1. treesitterのinstall

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

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

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

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します。

このプラグインは、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のキーマップを設定していきます。

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

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

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

スクリーンショット 2024-09-20 21.32.49.png

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

最後に

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

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

Click to Open
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,
}

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1