A simple pattern explainer & editor for Neovim.
Tree-sitter based pattern explainer.
LSP-like hover window for strings.
A real-time pattern editor & matcher.
Support for multiple pattern languages,
regexlua_patterns(requires custom parser)
Highly configurable! Almost everything can be configured(without needing to leave your editor).
Tree-sitter parser.
regex(install throughnvim-treesittervia:TSInstall regex)lua_patterns(optional, See parser installation).
A tree-sitter supported colorscheme(optional).
A nerd font.
Node.js(optional, needed if you use javascript's regexp).
Add this to your plugin list.
Plug "OXY2DEV/patterns.nvim"Note
Lazy loading is NOT needed for this plugin!
For plugins.lua users,
{
"OXY2DEV/patterns.nvim",
},For plugins/patterns.lua,
return {
"OXY2DEV/patterns.nvim",
};localMiniDeps=require("mini.deps");
MiniDeps.add({
source="OXY2DEV/patterns.nvim"
});Warning
luarocks package may sometimes be a bit behind main.
:Rocks install patterns.nvimTagged releases can be found in the release page.
Note
Github releases may sometimes be slightly behind main.
See the default configuration here.
Show type definitions
--- Configuration for `patterns.nvim`.---@classpatterns.config------ WARNING, This just changes the priority--- of the matchers.--- Both matchers will be tried!---@fieldpreferred_regex_matcher---| "node" Allows matching Javascript's regexp.---| "vim" Allows matching Vim's regexp.------ Delay for updating explainer UI.---@fieldupdate_delayinteger------@fieldkeymaps? patterns.keymaps---@fieldwindowspatterns.windows---@fieldlua_patternspatterns.lua_patterns---@fieldregexpatterns.regex---@classpatterns.keymaps------@fieldhovertable<string, patterns.keymap_opts>---@fieldexplain_inputtable<string, patterns.keymap_opts>---@fieldexplain_previewtable<string, patterns.keymap_opts>--- Action names for the explainer.---@aliasexplain_actions---| "toggle" Toggle focus of window.---| "mode_change" Switches between the explainer & the matcher.------ Changes pattern language backwards.---| "lang_prev"---| "lang_next" Changes pattern language forwards.------ Closes explainer.---| "close"---| "apply" Applies changes.--- Action names for the hover.---@aliashover_actions---| "close" Closes hover window.---| "edit" Edit pattern.---@classpatterns.keymap_opts------@fielddesc? string---@fieldcallbackexplain_actions | hover_actions | function--- Window configurations for various--- windows.---@classpatterns.windows------@fieldhover? table | fun(q1: "left" | "right" | "center", q2: "top" | "bottom" | "center"): table------@fieldinput? table | fun(): table---@fieldpreview? table | fun(): table--- Options for Lua patterns.--- Option name matches the tree-sitter node name.---@classpatterns.lua_patterns------@fieldindent_sizeinteger Indentation size.---@fieldindent_markerstring Marker used for indentation.---@fieldindent_hl? string Highlight group for the indentation markers.------@fieldpatternpattern_item.opts------@fieldanchor_startpattern_item.opts---@fieldanchor_endpattern_item.opts------@fieldquantifier_optionalpattern_item.opts---@fieldquantifier_minuspattern_item.opts---@fieldquantifier_pluspattern_item.opts---@fieldquantifier_starpattern_item.opts------@fieldliteral_characterpattern_item.opts---@fieldany_characterpattern_item.opts---@fieldescape_sequencepattern_item.opts---@fieldescaped_characterpattern_item.opts------@fieldcapture_grouppattern_item.opts---@fieldcharacter_setpattern_item.opts---@fieldcharacter_set_contentpattern_item.opts---@fieldcharacter_rangepattern_item.opts---@fieldcharacter_classpattern_item.opts--- Options for Regex.--- Option name matches the tree-sitter node name.---@classpatterns.regex------@fieldindent_sizeinteger Indentation size.---@fieldindent_markerstring Marker used for indentation.---@fieldindent_hl? string Highlight group for the indentation markers.------@fieldpatternpattern_item.opts---@fieldalternationpattern_item.opts---@fieldtermpattern_item.opts------@fieldstart_assertionpattern_item.opts---@fieldend_assertionpattern_item.opts---@fieldboundary_assertionpattern_item.opts---@fieldnon_boundary_assertionpattern_item.opts---@fieldlookaround_assertionpattern_item.opts------@fieldquantifier_countpattern_item.opts---@fieldquantifier_optionalpattern_item.opts---@fieldquantifier_pluspattern_item.opts---@fieldquantifier_starpattern_item.opts------@fieldpattern_characterpattern_item.opts---@fieldclass_characterpattern_item.opts---@fieldany_characterpattern_item.opts---@fielddecimal_escapepattern_item.opts---@fieldcharacter_class_escapepattern_item.opts---@fieldunicode_character_escapepattern_item.opts---@fieldunicode_property_valuepattern_item.opts---@fieldcontrol_escapepattern_item.opts---@fieldcontrol_letter_escapepattern_item.opts---@fieldidentity_escapepattern_item.opts---@fieldbackreference_escapepattern_item.opts---@fieldunicode_property_value_expressionpattern_item.opts------@fieldcharacter_classpattern_item.opts---@fieldposix_character_classpattern_item.opts---@fieldnamed_group_backreferencepattern_item.opts---@fieldcapturing_grouppattern_item.opts---@fieldnon_capturing_grouppattern_item.opts------@fieldflags_grouppattern_item.opts---@fieldflagspattern_item.opts--- Options for each node type.---@classpattern_item.opts------ Can be set to `false` to disable rendering of --- a specific node type.---@fieldenable? boolean | fun(buffer: integer, item: __patterns.item): boolean------ Can be set to `true` to show the range of a--- node.---@fieldshow_range? boolean | fun(buffer: integer, item: __patterns.item): boolean------ Highlight group for the text.---@fieldtext_hl? string | fun(buffer: integer, item: __patterns.item): string?------ Text to show for a node.---@fieldtext? string | fun(buffer: integer, item: __patterns.item): string------ When set to `true`, shows tooltip for nodes.--- By default this only shows tips for the current--- node.---@fieldshow_tip? boolean | fun(buffer: integer, item: __patterns.item): boolean------ Highlight group for the tooltip text.---@fieldtip_hl? string | fun(buffer: integer, item: __patterns.item): string------ Number of spaces to add before tooltip text.--- This is added AFTER the indentation.---@fieldtip_offset? integer | fun(buffer: integer, item: __patterns.item): integer------ Highlight group for the node range.---@fieldrange_hl? string | fun(buffer: integer, item: __patterns.item): string?------ Bade highlight group. Used by other *_hl--- options when they don't have a value.---@fieldhl? string | fun(buffer: integer, item: __patterns.item): string?Show default configuration
spec.default= {
preferred_regex_matcher="vim",
update_delay=150,
keymaps= {
explain_input= {
["<CR>"] = {
callback="apply"
},
["q"] = {
callback="close"
},
["<tab>"] = {
callback="toggle"
},
["H"] = {
callback="lang_prev"
},
["L"] = {
callback="lang_next"
},
},
explain_preview= {
["q"] = {
callback="close"
},
["<tab>"] = {
callback="toggle"
},
["T"] = {
callback="mode_change"
}
},
hover= {
["q"] = {
callback="close"
},
["i"] = {
callback="edit"
}
}
},
windows= {
hover=function (q1, q2)
localborder= { "╭", "─", "╮", "│", "╯", "─", "╰", "│" };
ifq2=="top" thenifq1=="left" thenborder[5] ="┤";
elseifq1=="right" thenborder[7] ="├";
endelseifq2=="bottom" thenifq1=="left" thenborder[3] ="┤";
elseifq1=="right" thenborder[1] ="├";
endendlocalft;
ifpackage.loaded["patterns.hover"] andpackage.loaded["patterns.hover"].bufthenft=vim.bo[package.loaded["patterns.hover"].buf].ft;
endreturn {
width=math.floor(vim.o.columns*0.6),
height=math.floor(vim.o.lines*0.5),
border=border,
footer_pos="right",
footer= {
{ "╸", "FloatBorder" },
{ " " .. (ftor"Patterns") .."", "FloatBorder" },
{ "╺", "FloatBorder" },
}
}
end
},
lua_patterns= {
indent_size=2,
indent_marker="│",
indent_hl="PatternsPalette0Fg",
pattern= {
text=" Pattern",
show_tip=on_current,
tip_hl="PatternsPalette0Bg",
hl="PatternsPalette0";
},
----------------------------------------anchor_start= {
text=" From start",
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
anchor_end= {
text=" To end",
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
----------------------------------------quantifier_minus= {
text=" Zero or more times(non-greedily)",
show_tip=on_current,
tip_hl="PatternsPalette7Bg",
hl="PatternsPalette7"
},
quantifier_optional= {
text=" Zero or one time",
show_tip=on_current,
tip_hl="PatternsPalette7Bg",
hl="PatternsPalette7"
},
quantifier_plus= {
text=" One or more times",
show_tip=on_current,
tip_hl="PatternsPalette7Bg",
hl="PatternsPalette7"
},
quantifier_star= {
text=" Zero or more times(greedily)",
show_tip=on_current,
tip_hl="PatternsPalette7Bg",
hl="PatternsPalette7"
},
----------------------------------------literal_character= {
text=function (_, item)
ifitem.text=="\\" thenreturn' Character: "\\"';
elsereturnstring.format(" Character: %s", vim.inspect(item.text));
endend,
show_tip=on_current,
tip_hl="PatternsPalette4Bg",
hl="PatternsPalette4"
},
any_character= {
text=" Any character",
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
escape_sequence= {
text=function (_, item)
returnstring.format(' Escape sequence: "%s"', item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette1Bg",
hl="PatternsPalette1"
},
escaped_character= {
text=function (_, item)
returnstring.format(' Escaped character: "%s"', item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette6Bg",
hl="PatternsPalette6"
},
----------------------------------------capture_group= {
text=function (_, item)
returnstring.format(" Capture group, %d", item.idor-1);
end,
show_tip=on_current,
tip_hl="PatternsPalette6Bg",
hl="PatternsPalette6"
},
character_set= {
text=" Character set",
show_tip=on_current,
tip_hl="PatternsPalette3Bg",
hl="PatternsPalette3"
},
character_set_content= {
text=" Character set content,",
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
character_range= {
text=function (_, item)
returnstring.format(" Character range: %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette6Bg",
hl="PatternsPalette6"
},
character_class= {
text=function (_, item)
return" Character class: " ..vim.inspect(item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette4Bg",
hl="PatternsPalette4"
},
},
regex= {
indent_size=2,
indent_marker="│",
indent_hl="PatternsPalette0Fg",
pattern= {
text=" Pattern",
show_tip=on_current,
tip_hl="PatternsPalette0Bg",
hl="PatternsPalette0"
},
alternation= {
text=" Alternative pattern(s)",
show_tip=on_current,
tip_hl="PatternsPalette6Bg",
hl="PatternsPalette6"
},
term= {
text=function (_, item)
returnstring.format(" Regex term(#%d)", item.idor-1);
end,
show_tip=on_current,
tip_hl="PatternsPalette6Bg",
hl="PatternsPalette6"
},
----------------------------------------start_assertion= {
text=" From start",
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
end_assertion= {
text=" To end",
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
boundary_assertion= {
text=" Match as a word",
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
non_boundary_assertion= {
text=" Match as part of a word",
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
lookaround_assertion= {
text=function (_, item)
ifstring.match(item.text, "^%(%?%<") thenreturn" Look behind";
elsereturn" Look ahead";
endend,
show_tip=on_current,
tip_hl="PatternsPalette3Bg",
hl="PatternsPalette3"
},
----------------------------------------quantifier_count= {
text=function (_, item)
ifstring.match(item.text, "^%d+$") thenreturnstring.format(" Repeats exactly %s times", item.text);
elseifstring.match(item.text, "^%d+,$") thenreturnstring.format(
" Repeats at least %s times",
string.match(item.text, "^(%d+)")
);
elseifstring.match(item.text, "^,%d+$") thenreturnstring.format(
" Repeats at most %s times",
string.match(item.text, "^,(%d+)$")
);
elsereturnstring.format(
" Repeats between %s & %s times",
string.match(item.text, "^(%d+),"),
string.match(item.text, "^%d+,(%d+)$")
);
endend,
show_tip=on_current,
tip_hl="PatternsPalette7Bg",
hl="PatternsPalette7"
},
quantifier_optional= {
text=" Repeats zero or one time",
show_tip=on_current,
tip_hl="PatternsPalette7Bg",
hl="PatternsPalette7"
},
quantifier_plus= {
text=" Repeats one or more times",
show_tip=on_current,
tip_hl="PatternsPalette7Bg",
hl="PatternsPalette7"
},
quantifier_star= {
text=" Repeats zero or more times",
show_tip=on_current,
tip_hl="PatternsPalette7Bg",
hl="PatternsPalette7"
},
----------------------------------------pattern_character= {
text=function (_, item)
returnstring.format(" Character: %s", vim.inspect(item.text));
end,
show_tip=on_current,
tip_hl="PatternsPalette2Bg",
hl="PatternsPalette2"
},
class_character= {
text=function (_, item)
returnstring.format(" Character: %s", vim.inspect(item.text));
end,
show_tip=on_current,
tip_hl="PatternsPalette2Bg",
hl="PatternsPalette2"
},
any_character= {
text=" Any character",
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
decimal_escape= {
text=function (_, item)
returnstring.format(" Decimal escape: %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette1Bg",
hl="PatternsPalette1"
},
character_class_escape= {
text=function (_, item)
returnstring.format(" Character class escape: %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette1Bg",
hl="PatternsPalette1"
},
unicode_character_escape= {
text=function (_, item)
returnstring.format(" Unicode character escape: %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette1Bg",
hl="PatternsPalette1"
},
unicode_property_value= {
text=function (_, item)
returnstring.format(" Unicode property value: %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette6Bg",
hl="PatternsPalette6"
},
control_escape= {
text=function (_, item)
returnstring.format(" Control character escape: %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette1Bg",
hl="PatternsPalette1"
},
control_letter_escape= {
text=function (_, item)
returnstring.format(" Control letter escape: %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette1Bg",
hl="PatternsPalette1"
},
identity_escape= {
text=function (_, item)
returnstring.format(" Identity escape: %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette1Bg",
hl="PatternsPalette1"
},
backreference_escape= {
text=function (_, item)
returnstring.format(" Backreference escape: %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette1Bg",
hl="PatternsPalette1"
},
----------------------------------------unicode_property_value_expression= {
text=" Unicode property value expression",
show_tip=on_current,
-- show_content = true,tip_hl="PatternsPalette0Bg",
hl="PatternsPalette0"
},
----------------------------------------character_class= {
text=" Character class",
show_tip=on_current,
tip_hl="PatternsPalette4Bg",
hl="PatternsPalette4"
},
posix_character_class= {
text=function (_, item)
returnstring.format(" POSIX Character class: ", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
named_group_backreference= {
text=function (_, item)
returnstring.format(" Named backreference: ", string.match(item.text, "^%(%?P%=(.-)%)$"));
end,
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
capturing_group= {
text=function (_, item)
iftype(item.id) =="string" thenreturnstring.format(" Capture group(#%s)", item.idor"???");
elsereturnstring.format(" Capture group(#%d)", item.idor-1);
endend,
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
non_capturing_group= {
text=function (_, item)
returnstring.format(" Non-capture group(#%d)", item.idor-1);
end,
show_tip=on_current,
tip_hl="PatternsPalette5Bg",
hl="PatternsPalette5"
},
flags_group= {
text=" Flags group",
show_tip=on_current,
tip_hl="PatternsPalette2Bg",
hl="PatternsPalette2"
},
flags= {
text=function (_, item)
returnstring.format(" Flag(s): %s", item.text);
end,
show_tip=on_current,
tip_hl="PatternsPalette2Bg",
hl="PatternsPalette2"
},
}
};This plugin creates the :Patterns command. It has 2 sub-commands,
explainExplains the pattern under the cursor.hoverLSP-like hover for the pattern under cursor. It's behavior is similar toK(orvim.lsp.buf.hover()).
When :Patterns is run without any arguments, it opens the explain window.
The hover & explain buffers have some pre-defined keymaps.
Tip
You can disable these keymaps individually via the config.
{
keymaps= {
hover= {
["i"] = { enable=false }
}
}
}The hover buffer has the following keymaps,
qCloses hover window.iOpens the pattern inside the explainer.
The text input buffer has the following keymaps,
<CR>Replaces the pattern under cursor with the text in the input buffer.qQuits the explainer.<tab>Switches to the explanation/preview buffer.HCycles backward through the list of supported languages(lua_patterns, regex).LCycles forward through the list of supported languages(lua_patterns, regex).
The pattern preview/explanation buffer has the following keymaps,
qQuits the explainer.<tab>Switches to the input buffer.TToggles between the explanation and the matcher.



