chrisgrieser/nvim-various-textobjs

github github
editing-supportkeybindingtext-object
star 198
alert-circle 2
users 5
git-branch 8
CREATED

2022-12-19

UPDATED

2 days ago


nvim-various-textobjs

Bundle of more than a dozen new text objects for Neovim.

List of Text Objects

textobj description inner / outer forward-seeking default keymaps filetypes (for default keymaps)
indentation surrounding lines with same or higher indentation see overview from vim-indent-object no ii, ai, aI, (iI) all
restOfIndentation lines down with same or higher indentation - no R all
subword like iw, but treating -, _, and . as word delimiters and only part of camelCase outer includes trailing _ or - yes iS, aS all
toNextClosingBracket from cursor to next closing ], ), or } - no % all
restOfParagraph like }, but linewise - no r all
entireBuffer entire buffer as one text object - - gG all
nearEoL from cursor position to end of line, minus one character - no n all
lineCharacterwise current line, but characterwise - no _ all
column column down until indent or shorter line. Accepts {count} for multiple columns. - no | (pipe char) all
value value of key-value pair, or right side of a variable assignment (inside one line) outer includes trailing commas or semicolons yes iv, av all
key key of key-value pair, or left side of a variable assignment outer includes the = or : yes ik, ak all
url link beginning with "http" - yes L all
number* numbers, similar to <C-a> inner: only pure digits, outer: number including minus sign and decimal point yes in, an all
diagnostic LSP diagnostic (requires built-in LSP) - yes ! all
mdlink markdown link like [title](url) inner is only the link title (between the []) yes il, al markdown, toml
mdFencedCodeBlock markdown fenced code (enclosed by three backticks) outer includes the enclosing backticks yes iC, aC markdown
cssSelector class in CSS, like .my-class outer includes trailing comma and space yes ic, ac css, scss
htmlAttribute attribute in html or xml, like href="foobar.com" inner is only the value inside the quotes trailing comma and space yes ix, ax html, xml, css, scss
jsRegex* JavaScript regex pattern outer includes the slashes and any flags yes i/, a/ javascript, typescript
doubleSquareBrackets text enclosed by [[]] outer includes the four square brackets yes iD, aD lua, shell, neorg, markdown
shellPipe command stdout is piped to outer includes the front pipe character yes iP,aP bash, zsh, fish, sh

Warning
* Textobject deprecated due to treesitter-textobject introducing a similar textobject that is more capable.

Installation

-- packer
use {
    "chrisgrieser/nvim-various-textobjs",
    config = function () 
        require("various-textobjs").setup({ useDefaultKeymaps = true })
    end,
}

-- lazy.nvim
{
    "chrisgrieser/nvim-various-textobjs",
    opts = { useDefaultKeymaps = true },
},

Configuration

The .setup() call is optional if you are fine with the defaults below. (Note that the default is to not set any keymaps.)

-- default config
require("various-textobjs").setup {
    lookForwardLines = 5, -- set to 0 to only look in the current line
    useDefaultKeymaps = false, -- use suggested keymaps (see README)
}

If you want to set your own keybindings, you can do so by calling the respective functions:

  • The function names correspond to the textobj-names from the overview table.
  • The text objects that differentiate between outer and inner require a boolean parameter, true always meaning "inner," and false meaning "outer."
-- example: `?` for diagnostic textobj
vim.keymap.set({"o", "x"}, "?", function () require("various-textobjs").diagnostic() end)

-- example: `an` for outer subword, `in` for inner subword
vim.keymap.set({"o", "x"}, "aS", function () require("various-textobjs").subword(false) end)
vim.keymap.set({"o", "x"}, "iS", function () require("various-textobjs").subword(true) end)

-- exception: indentation textobj requires two parameters, the first for 
-- exclusion of the starting border, the second for the exclusion of ending
-- border
vim.keymap.set({"o", "x"}, "ii", function () require("various-textobjs").indentation(true, true) end)
vim.keymap.set({"o", "x"}, "ai", function () require("various-textobjs").indentation(false, true) end)

For your convenience, here the code to create mappings for all text objects. You can copypaste this list and enter your own bindings.

local keymap = vim.keymap.set
local vt = require("various-textobjs") -- inline this for lazy-loading

keymap( { "o", "x" }, "ii" , function() vt.indentation(true, true) end)
keymap( { "o", "x" }, "ai" , function() vt.indentation(false, true) end)
keymap( { "o", "x" }, "iI" , function() vt.indentation(true, true) end)
keymap( { "o", "x" }, "aI" , function() vt.indentation(false, false) end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.subword(true) end)
keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.subword(false) end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.toNextClosingBracket() end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.restOfParagraph() end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.entireBuffer() end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.nearEoL() end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.lineCharacterwise() end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.column() end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.value(true) end)
keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.value(false) end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.key(true) end)
keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.key(false) end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.url() end)

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.diagnostic() end)

--------------------------------------------------------------------------------------
-- put these into the ftplugins or autocms for the filetypes you want to use them with

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.mdlink(true) end, { buffer = true })
keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.mdlink(false) end, { buffer = true })

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.mdFencedCodeBlock(true) end, { buffer = true })
keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.mdFencedCodeBlock(false) end, { buffer = true })

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.cssSelector(true) end, { buffer = true })
keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.cssSelector(false) end, { buffer = true })

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.htmlAttribute(true) end, { buffer = true })
keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.htmlAttribute(false) end, { buffer = true })

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.doubleSquareBrackets(true) end, { buffer = true })
keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.doubleSquareBrackets(false) end, { buffer = true })

keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.shellPipe(true) end, { buffer = true })
keymap( { "o", "x" }, "YOUR_MAPPING" , function() vt.shellPipe(false) end, { buffer = true })

Advanced Usage

Smart Alternative to gx

Using the URL textobj, you can also write a small snippet to replace netrw's gx. The code below retrieves the next URL (within the amount of lines configured in the setup call), and opens it in your browser. While this is already an improvement to vim's built-in gx, which requires the cursor to be standing on a URL to work, you can even go one step further. If no URL has been found within the next few lines, the :UrlView command from urlview.nvim is triggered, searching the entire buffer for URLs from which you can choose one to open.

vim.keymap.set("n", "gx", function ()
    require("various-textobjs").url() -- select URL
    -- this works since the plugin switched to visual mode
    -- if the textobj has been found
    local foundURL = vim.fn.mode():find("v")

    -- if not found in proximity, search whole buffer via urlview.nvim instead
    if not foundURL then
        vim.cmd.UrlView("buffer")
        return
    end
    
    -- retrieve URL with the z-register as intermediary
    vim.cmd.normal { '"zy', bang = true }
    local url = vim.fn.getreg("z")

    -- open with the OS-specific shell command
    local opener
    if vim.fn.has("macunix") == 1 then
        opener = "open"
    elseif vim.fn.has("linux") == 1 then
        opener = "xdg-open"
    elseif vim.fn.has("win64") == 1 or vim.fn.has("win32") == 1 then
        opener = "start"
    end
    local openCommand = string.format ("%s '%s' >/dev/null 2>&1", opener, url)
    os.execute(openCommand)
end, {desc = "Smart URL Opener"})

Limitations

Other Text-Object Plugins

Credits

Thanks
Kudos to the Valuable Dev for their blog post on how to get started with creating custom text objects.

About Me
In my day job, I am a sociologist studying the social mechanisms underlying the digital economy. For my PhD project, I investigate the governance of the app economy and how software ecosystems manage the tension between innovation and compatibility. If you are interested in this subject, feel free to get in touch.

Profiles

Buy Me a Coffee