A Neovim plugin that highlights matching pairs of braces {} and brackets [] when you visually select lines containing them. Additionally, it can highlight the scope (via line numbers) of any selected line using treesitter.
- Automatically highlights matching pairs for braces
{}and brackets[] - Supports forward matching: select a line ending with
{or[to highlight the closing pair - Supports backward matching: select a line starting with
}or]to highlight the opening pair - NEW: Highlights the scope of any visually selected line using treesitter (line numbers only)
- NEW: Text object for selecting inner scope (
Iby default) - NEW: Incremental/decremental node selection using treesitter (
<Tab>/<S-Tab>by default) - Works in visual, visual-line, and visual-block modes
- Customizable highlight groups
- Lightweight and performant
Using lazy.nvim
{
"jugarpeupv/visual-match-paren.nvim",
config = function()
require("visual-match-paren").setup({
-- Optional configuration
highlight_group = "MatchParen", -- Highlight group for braces/brackets (default: "MatchParen")
scope_highlight_group = "MatchParen", -- Highlight group for scope line numbers (default: "MatchParen")
enabled = true, -- Enable/disable brace/bracket matching (default: true)
scope_enabled = true, -- Enable/disable scope highlighting (default: true)
scope_textobject = "I", -- Text object for inner scope (default: "I", set to "" to disable)
incremental_selection = {
enabled = true, -- Enable/disable incremental selection (default: true)
keymaps = {
increment = "<Tab>", -- Key to increment node selection (default: "<Tab>")
decrement = "<S-Tab>", -- Key to decrement node selection (default: "<S-Tab>")
},
},
})
end,
}Using packer.nvim
use {
"jugarpeupv/visual-match-paren.nvim",
config = function()
require("visual-match-paren").setup()
end,
}Simply enter visual mode and select a line that:
- Ends with
{or[- highlights the matching closing pair - Starts with
}or]- highlights the matching opening pair
{
"foo": {
"bar": "bazz"
}
}When you visually select the line "foo": {, the closing } on line 4 will be highlighted.
The plugin also works with brackets:
const arr = [
1, 2, 3
]When you select the line const arr = [, the closing ] will be highlighted. Similarly, selecting a line with the closing bracket will highlight its matching opening bracket.
When you visually select any line, the plugin uses treesitter to detect the scope and highlights the line numbers of all lines within that scope. This is non-intrusive as it only affects the line number column, not the text itself.
- Microfrontends:
- Visión general: docs/overview.md
- Arquitectura: docs/architecture.md
- Configuration:
- Setup: docs/setup.md
- Advanced: docs/advanced.mdWhen you select the line - Microfrontends:, the line numbers for all nested items will be highlighted, making it easy to see the scope of that section.
Use the I text object (configurable) to quickly select the inner scope:
- From normal mode:
VI- Enter visual line mode and select inner scope - From visual mode:
I- Expand selection to inner scope - Toggle back: Press
Iagain in visual mode to restore your previous selection - With operators:
dI- Delete inner scope,yI- Yank inner scope
- Inner scope first: If the line has nested content, selects that content
- Fallback to parent: If no inner content (e.g., a leaf node like
- Configuración librerías compartidas:), selects the parent scope - Toggle feature: Press
Iagain to restore your previous selection (useful if you want to go back to your original selection)
Position your cursor on - Microfrontends: and press VI to select all nested lines under it. Position on a leaf item and press VI to select its parent scope.
Toggle example:
- Select a single line with
V - Press
I→ expands to scope - Press
Iagain → returns to your original single line selection
Use <Tab> and <S-Tab> (configurable) to incrementally expand or shrink your visual selection based on treesitter syntax nodes:
- Increment selection (
<Tab>): Expand the current visual selection to the parent syntax node - Decrement selection (
<S-Tab>): Shrink the selection back to the previous node
- Enter visual mode (e.g.,
Vfor line-wise, orvfor character-wise) - Optionally use the
Itext object to select a scope - Press
<Tab>to expand selection to the parent node - Press
<Tab>again to continue expanding to larger parent nodes - Press
<S-Tab>to shrink back to the previous selection
function hello()
local x = 1
if x > 0 then
print("positive")
end
end- Position cursor on
print("positive")and pressVto select the line - Press
<Tab>→ expands to the if block - Press
<Tab>→ expands to the function body - Press
<Tab>→ expands to the entire function - Press
<S-Tab>→ shrinks back to the function body - Press
<S-Tab>→ shrinks back to the if block
This feature is powered by treesitter and works with any language that has a treesitter parser installed.
:VisualMatchParenToggle- Toggle the plugin on/off
The plugin can be configured with the following options:
require("visual-match-paren").setup({
highlight_group = "MatchParen", -- The highlight group for braces/brackets
scope_highlight_group = "MatchParen", -- The highlight group for scope line numbers
enabled = true, -- Enable brace/bracket matching by default
scope_enabled = true, -- Enable scope highlighting by default
scope_textobject = "I", -- Text object for inner scope (set to "" to disable)
incremental_selection = {
enabled = true, -- Enable incremental selection feature
keymaps = {
increment = "<Tab>", -- Key to increment node selection
decrement = "<S-Tab>", -- Key to decrement node selection
},
},
})You can customize the scope highlight to be more subtle if desired:
-- Create a custom highlight group for scope
vim.api.nvim_set_hl(0, "ScopeHighlight", { fg = "#6c7086", italic = true })
require("visual-match-paren").setup({
scope_highlight_group = "ScopeHighlight", -- Use custom highlight for scope
})To use a different key for the text object:
require("visual-match-paren").setup({
scope_textobject = "S", -- Use 'S' instead of 'I'
-- or set to "" to disable the text object mapping
})To customize the incremental selection keymaps or disable the feature:
require("visual-match-paren").setup({
incremental_selection = {
enabled = true, -- Set to false to disable the feature
keymaps = {
increment = "<C-n>", -- Use Ctrl+n to increment
decrement = "<C-p>", -- Use Ctrl+p to decrement
},
},
})The plugin listens for mode changes and cursor movements in visual mode. When you're in visual mode, it:
- Detects if the selected line ends with
{or[, or starts with}or] - Positions the cursor on the opening or closing character
- Uses Neovim's built-in
searchpairposto find the matching pair - Highlights both the selected and matching character using the specified highlight group
This ensures that nested structures are matched correctly, even in deeply nested JSON, JavaScript, or other brace/bracket-based languages.
- Uses treesitter to get the syntax node at the selected line
- Gets the parent node to determine the scope
- Highlights the line numbers (using
number_hl_groupextmark option) for all lines within that scope - Only highlights if the scope is meaningful (more than just the current line)
This feature requires treesitter to be installed and a parser available for the current filetype.
- When you press the increment key (default
<Tab>) in visual mode:- Gets the treesitter node at the current selection
- Finds the parent node and expands the selection to match it
- Saves the previous selection to a stack for decrementing
- When you press the decrement key (default
<S-Tab>):- Pops the previous selection from the stack
- Restores that selection
This allows you to navigate the syntax tree structure of your code naturally, expanding and shrinking selections based on the actual syntax nodes.
See tests/README.md for information about running tests.
make testMIT

