Learn to use text-object in Vim&Neovim

You probably do not know what the text-object is in Vim/Neovim. However, you may use it in your daily life. For instance, When you are writing code, you may want to change the arguments of a function. Take the following code as an example, let’s say you want to change the function call to bar(3, 2, 1), and the cursor currently stays on the ,

python

def foo():
    ...
    res = bar(1, 2, 3)
                # ^
                # The position of your cursor(on `,`)
    ...

How will you achieve this goal? If you are familiar with Vim/Neovim, you will likely instinctively type ci) or ci(, and then you can quickly type in the new arguments. The i( or i) here are so-called text-object.

Warning
Bad practice: Use F( to jump to previous ( and then use x to delete char one by one
Tip
Check :h text-objects for more information

Simply put, text-object is a textual entity corresponding to a certain area of text, and this area of text contains structure within it. Therefore, they can be treated as a single text-object, which brings convenience to many operations.

The built-in text-object is represented as two-letter combinations:

  1. The first letter is either i or a
    1. i refers to text-object itself, and a also contains the next/previous whitespace or paired symbols(e.g. {}, (), ...)
    2. Mnemonic: i = (i)nside, a = (a)round
  2. The second letter has two possibilities
    1. One of the paired symbols
      1. Paired symbols: {}, (), [], '', "", <tag></tag>. Note that <tag></tag> is represented as t. For example, if you want to delete the foobar in <tag>foobar</tag>, you can put the cursor within the tag and type dit
      2. e.g. a{ = (around) {, it contains all the text within {}(inclusive)
    2. (w)ord, (s)entences, (p)aragraphs
      1. Each one of them can be used with i or a - iw, aw, is, as, ip, ap

Previously, we mentioned that the text-object is structured in some way. It reminds me of the code, which is highly structured text.

Occasionally, we want to delete the entire function body and rewrite it. If the function body is enclosed in {}, then typically we can use ci{ to accomplish this task. However, care must be taken regarding the cursor’s position to ensure the {} of the function body is closet to the cursor, which can be somewhat annoying.

Example

The function body in Rust is enclosed in {}. To delete the entire function body and rewrite it, we can place the cursor on the i in if, and then type ci{

rust

fn fib(n: i32) -> i32 {
 // The position of your cursor(on `i`)
 // v
    if n == 1 || n == 2 {
        1
    } else {
        fib(n - 1) + fib(n-2)
    }
}

But what if the function body is not enclosed in {}? For example, the Python code uses indentation to recognize the function body.

python

def fib(n: int):
    if n == 1 or n == 2:
        return 1

    return fib(n - 1) + fib(n - 2)

Is there a special text-object that corresponds to the function body considering that the code is highly structured? That is what the magic plugin - nvim-treesitter-textobjects does for us. By analyzing the syntax of code, it defines various text-object such as functions, classes, loops, etc.

Warning

Check my commit to see the changes and how I organize my configuration files.

Your file structure may not be the same, but you get the idea :)

If you also use lazy.nvim, put the followinglines in your plugin list.

lua

require("lazy").setup({
    ...
	{
		"nvim-treesitter/nvim-treesitter-textobjects",
		dependencies = "nvim-treesitter/nvim-treesitter",
		config = function()
			require("config.nvim-treesitter-textobjects")
		end,
	},
    ...
})

Create a new file nvim-treesitter-textobjects.lua and edit(Most of the code has been omitted by me, the complete file is located here):

lua

local is_ok, configs = pcall(require, "nvim-treesitter.configs")
if not is_ok then
	return
end

configs.setup({
	textobjects = {
		select = {
            ...
			keymaps = {
				-- outer: outer part
				-- inner: inner part
				["af"] = "@function.outer",
				["if"] = "@function.inner",
				["ac"] = "@class.outer",
				["ic"] = "@class.inner",
				["al"] = "@loop.outer",
				["il"] = "@loop.inner",
			},
			include_surrounding_whitespace = true,
		},
	},
    ...
})

Based on my personal needs, I add 6 text-object(af, if, ac, ic, al, il) to operate on function, class, and loop. You can find syntax-aware text-object in the official README.md file.

Now we can use dif to delete the entire function body in any position within the function body :)

The text-object organizes the text into objects, allowing us to operate them conveniently. With the help of nvim-treesitter-textobjects, we can operate on various programming language constructs🍺