Text Objects

Text objects are what made vim superior from other editors for me by any means.

Text objects are "movements" that only work after an operator.
A text object consists of a start and an end.
The default and the most used ones are inner/a + X.

Modifiers:

  • i - as Inner.
  • a - as A.

For example:

  • yiw - yank inner word, will yank the current word.
  • ciw - change inner word, will change the current word.
  • daw - delete a word, will delete the current word and the space before it, removes a word from a sentence.
  • ci' - change inner ', will change the inner text inside the next/current pair of ' aka a string.
  • ca( - delete a (, will delete the text inside and the ( of the next/current pair of (.

You can do these actions for every pair, (/{/'/".
You can do these actions for html tags(t), paragraphs(p), and more.

You can repeat the action when entering a number before, e.g: 3daw deletes the current word and the next 2.

Feel free to enter visual mode and test how it works.

You can read more about this at :help text-objects.


One of my favorite text objects is the paragraph, it makes editing code feel so natural.

If you want to change the order of boo and goo, you can you delete the goo paragraph with dap, go back a paragraph with { and paste the deleted paragraph with p.

def foo():
	result = boo()
	if result is not None:
		return result
	
	result = goo() # <--- cursor here
	if result is not None:
		return result
	
	return None

Treesitter Text Objects

nvim-treesitter-textobjects is an awesome/must have plugin that creates text-objects from the treesitter query, which means text-objects for an actual part of your code!

For the config I use, f is a function, so if I want to change the current content of the function I am on, I hit cif, change inner function, and you can do this on other code objects.

It changes the way you think about editing code, with code object movements rather than how to move the cursor and to where.
For example, change the 3rd argument in the next function.

def foo(a: int, b: Any, c: Tuple[int, Optional[str]]):
	pass # <--- cursor here

You can use [m to move up to the function signature, then ]a 3 times (unfortunately you can't do 3]a to do it yet), then you can press cia to keep the , but change the content of the argument or you can press cad to delete the entire argument.

In a standard editor you would need to go the signature manually or using reverse search, hold ctrl and hit the arrows until you reach c, hold ctrl+shift and hitting right arrow 9 times (depends on the editor, tested on sublime), that's tedious.

It changed the way I think about editing code, it allows me to think with elements from the code, go to next/prev function, yank inner function, delete a function, change inner argument, rather thinking how to move the cursor which relates to the code elements.


Config

My config (adds pair movement as well):

textobjects = {
	move = {
		enable = true,
		set_jumps = true, -- whether to set jumps in the jumplist
		goto_next_start = {
			["]m"] = "@function.outer",
			["gj"] = "@function.outer",
			["]]"] = "@class.outer",
			["]b"] = "@block.outer",
			["]a"] = "@parameter.inner",
		},
		goto_next_end = {
			["]M"] = "@function.outer",
			["gJ"] = "@function.outer",
			["]["] = "@class.outer",
			["]B"] = "@block.outer",
			["]A"] = "@parameter.inner",
		},
		goto_previous_start = {
			["[m"] = "@function.outer",
			["gk"] = "@function.outer",
			["[["] = "@class.outer",
			["[b"] = "@block.outer",
			["[a"] = "@parameter.inner",
		},
		goto_previous_end = {
			["[M"] = "@function.outer",
			["gK"] = "@function.outer",
			["[]"] = "@class.outer",
			["[B"] = "@block.outer",
			["[A"] = "@parameter.inner",
		},
	},
	select = {
		enable = true,
		lookahead = true,
		keymaps = {
			["af"] = "@function.outer",
			["if"] = "@function.inner",
			["ac"] = "@class.outer",
			["ic"] = "@class.inner",
			["ab"] = "@block.outer",
			["ib"] = "@block.inner",
			["al"] = "@loop.outer",
			["il"] = "@loop.inner",
			["a/"] = "@comment.outer",
			["i/"] = "@comment.outer", -- no inner for comment
			["aa"] = "@parameter.outer", -- parameter -> argument
			["ia"] = "@parameter.inner",
		},
	},
},

For LunarVim:

lvim.builtin.treesitter.textobjects.select = {
	enable = true,
	lookahead = true,
	keymaps = {
		["af"] = "@function.outer",
		["if"] = "@function.inner",
		["ac"] = "@class.outer",
		["ic"] = "@class.inner",
		["ab"] = "@block.outer",
		["ib"] = "@block.inner",
		["al"] = "@loop.outer",
		["il"] = "@loop.inner",
		["a/"] = "@comment.outer",
		["i/"] = "@comment.outer", -- no inner for comment
		["aa"] = "@parameter.outer", -- parameter -> argument
		["ia"] = "@parameter.inner",
	},
}
lvim.builtin.treesitter.textobjects.move = {
	enable = true,
	set_jumps = true, -- whether to set jumps in the jumplist
	goto_next_start = {
		["]m"] = "@function.outer",
		["gj"] = "@function.outer",
		["]]"] = "@class.outer",
		["]b"] = "@block.outer",
		["]a"] = "@parameter.inner",
	},
	goto_next_end = {
		["]M"] = "@function.outer",
		["gJ"] = "@function.outer",
		["]["] = "@class.outer",
		["]B"] = "@block.outer",
		["]A"] = "@parameter.inner",
	},
	goto_previous_start = {
		["[m"] = "@function.outer",
		["gk"] = "@function.outer",
		["[["] = "@class.outer",
		["[b"] = "@block.outer",
		["[a"] = "@parameter.inner",
	},
	goto_previous_end = {
		["[M"] = "@function.outer",
		["gK"] = "@function.outer",
		["[]"] = "@class.outer",
		["[B"] = "@block.outer",
		["[A"] = "@parameter.inner",
	},
}