https://github.com/oxy2dev/patterns.nvim
A simple pattern viewer for Neovim with support for Lua patterns & regex.
https://github.com/oxy2dev/patterns.nvim
Last synced: about 1 year ago
JSON representation
A simple pattern viewer for Neovim with support for Lua patterns & regex.
- Host: GitHub
- URL: https://github.com/oxy2dev/patterns.nvim
- Owner: OXY2DEV
- License: mit
- Created: 2025-02-12T16:31:20.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-04-20T08:14:52.000Z (about 1 year ago)
- Last Synced: 2025-04-20T08:28:14.631Z (about 1 year ago)
- Language: Lua
- Size: 1.74 MB
- Stars: 87
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# 🧩 patterns.nvim

A simple pattern explainer & editor for Neovim.
## 📖 Table of contents
- [✨ Features](#-features)
- [📚 Requirements](#-requirements)
- [📐 Installation](#-installation)
- [🧭 Configuration](#-configuration)
- [💡 Commands](#-commands)
- [🎹 keymaps](#-keymaps)
## ✨ Features
- Tree-sitter based pattern explainer.
- LSP-like hover window for strings.
- A real-time pattern editor & matcher.
- Support for multiple pattern languages,
+ `regex`
+ `lua_patterns`(requires custom parser)
- Highly configurable! Almost everything can be configured(without needing to leave your editor).
## 📚 Requirements
- Tree-sitter parser.
+ `regex`(install through `nvim-treesitter` via `:TSInstall regex`)
+ `lua_patterns`(optional, See [parser installation](https://github.com/OXY2DEV/tree-sitter-lua_patterns?tab=readme-ov-file#-installation)).
- A tree-sitter supported colorscheme(optional).
- A nerd font.
- Node.js(optional, needed if you use javascript's regexp).
## 📐 Installation
### 🧩 Vim-plug
Add this to your plugin list.
```vim
Plug "OXY2DEV/patterns.nvim"
```
### 💤 lazy.nvim
>[!NOTE]
> Lazy loading is NOT needed for this plugin!
For `plugins.lua` users,
```lua
{
"OXY2DEV/patterns.nvim",
},
```
For `plugins/patterns.lua`,
```lua
return {
"OXY2DEV/patterns.nvim",
};
```
## 🦠 mini.deps
```lua
local MiniDeps = require("mini.deps");
MiniDeps.add({
source = "OXY2DEV/patterns.nvim"
});
```
### 🌒 Rocks.nvim
>[!WARNING]
> `luarocks package` may sometimes be a bit behind `main`.
```vim
:Rocks install patterns.nvim
```
### 📥 GitHub release
Tagged releases can be found in the [release page](https://github.com/OXY2DEV/patterns.nvim/releases).
>[!NOTE]
> `Github releases` may sometimes be slightly behind `main`.
## 🧭 Configuration
See the default configuration [here](https://github.com/OXY2DEV/patterns.nvim/blob/main/lua/patterns/spec.lua#L8-#L602).
Show type definitions
```lua
--- Configuration for `patterns.nvim`.
---@class patterns.config
---
--- WARNING, This just changes the priority
--- of the matchers.
--- Both matchers will be tried!
---@field preferred_regex_matcher
---| "node" Allows matching Javascript's regexp.
---| "vim" Allows matching Vim's regexp.
---
--- Delay for updating explainer UI.
---@field update_delay integer
---
---@field keymaps? patterns.keymaps
---@field windows patterns.windows
---@field lua_patterns patterns.lua_patterns
---@field regex patterns.regex
---@class patterns.keymaps
---
---@field hover table
---@field explain_input table
---@field explain_preview table
--- Action names for the explainer.
---@alias explain_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.
---@alias hover_actions
---| "close" Closes hover window.
---| "edit" Edit pattern.
---@class patterns.keymap_opts
---
---@field desc? string
---@field callback explain_actions | hover_actions | function
--- Window configurations for various
--- windows.
---@class patterns.windows
---
---@field hover? table | fun(q1: "left" | "right" | "center", q2: "top" | "bottom" | "center"): table
---
---@field input? table | fun(): table
---@field preview? table | fun(): table
--- Options for Lua patterns.
--- Option name matches the tree-sitter node name.
---@class patterns.lua_patterns
---
---@field indent_size integer Indentation size.
---@field indent_marker string Marker used for indentation.
---@field indent_hl? string Highlight group for the indentation markers.
---
---@field pattern pattern_item.opts
---
---@field anchor_start pattern_item.opts
---@field anchor_end pattern_item.opts
---
---@field quantifier_optional pattern_item.opts
---@field quantifier_minus pattern_item.opts
---@field quantifier_plus pattern_item.opts
---@field quantifier_star pattern_item.opts
---
---@field literal_character pattern_item.opts
---@field any_character pattern_item.opts
---@field escape_sequence pattern_item.opts
---@field escaped_character pattern_item.opts
---
---@field capture_group pattern_item.opts
---@field character_set pattern_item.opts
---@field character_set_content pattern_item.opts
---@field character_range pattern_item.opts
---@field character_class pattern_item.opts
--- Options for Regex.
--- Option name matches the tree-sitter node name.
---@class patterns.regex
---
---@field indent_size integer Indentation size.
---@field indent_marker string Marker used for indentation.
---@field indent_hl? string Highlight group for the indentation markers.
---
---@field pattern pattern_item.opts
---@field alternation pattern_item.opts
---@field term pattern_item.opts
---
---@field start_assertion pattern_item.opts
---@field end_assertion pattern_item.opts
---@field boundary_assertion pattern_item.opts
---@field non_boundary_assertion pattern_item.opts
---@field lookaround_assertion pattern_item.opts
---
---@field quantifier_count pattern_item.opts
---@field quantifier_optional pattern_item.opts
---@field quantifier_plus pattern_item.opts
---@field quantifier_star pattern_item.opts
---
---@field pattern_character pattern_item.opts
---@field class_character pattern_item.opts
---@field any_character pattern_item.opts
---@field decimal_escape pattern_item.opts
---@field character_class_escape pattern_item.opts
---@field unicode_character_escape pattern_item.opts
---@field unicode_property_value pattern_item.opts
---@field control_escape pattern_item.opts
---@field control_letter_escape pattern_item.opts
---@field identity_escape pattern_item.opts
---@field backreference_escape pattern_item.opts
---@field unicode_property_value_expression pattern_item.opts
---
---@field character_class pattern_item.opts
---@field posix_character_class pattern_item.opts
---@field named_group_backreference pattern_item.opts
---@field capturing_group pattern_item.opts
---@field non_capturing_group pattern_item.opts
---
---@field flags_group pattern_item.opts
---@field flags pattern_item.opts
--- Options for each node type.
---@class pattern_item.opts
---
--- Can be set to `false` to disable rendering of
--- a specific node type.
---@field enable? boolean | fun(buffer: integer, item: __patterns.item): boolean
---
--- Can be set to `true` to show the range of a
--- node.
---@field show_range? boolean | fun(buffer: integer, item: __patterns.item): boolean
---
--- Highlight group for the text.
---@field text_hl? string | fun(buffer: integer, item: __patterns.item): string?
---
--- Text to show for a node.
---@field text? 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.
---@field show_tip? boolean | fun(buffer: integer, item: __patterns.item): boolean
---
--- Highlight group for the tooltip text.
---@field tip_hl? string | fun(buffer: integer, item: __patterns.item): string
---
--- Number of spaces to add before tooltip text.
--- This is added AFTER the indentation.
---@field tip_offset? integer | fun(buffer: integer, item: __patterns.item): integer
---
--- Highlight group for the node range.
---@field range_hl? string | fun(buffer: integer, item: __patterns.item): string?
---
--- Bade highlight group. Used by other *_hl
--- options when they don't have a value.
---@field hl? string | fun(buffer: integer, item: __patterns.item): string?
```
Show default configuration
```lua
spec.default = {
preferred_regex_matcher = "vim",
update_delay = 150,
keymaps = {
explain_input = {
[""] = {
callback = "apply"
},
["q"] = {
callback = "close"
},
[""] = {
callback = "toggle"
},
["H"] = {
callback = "lang_prev"
},
["L"] = {
callback = "lang_next"
},
},
explain_preview = {
["q"] = {
callback = "close"
},
[""] = {
callback = "toggle"
},
["T"] = {
callback = "mode_change"
}
},
hover = {
["q"] = {
callback = "close"
},
["i"] = {
callback = "edit"
}
}
},
windows = {
hover = function (q1, q2)
local border = { "╭", "─", "╮", "│", "╯", "─", "╰", "│" };
if q2 == "top" then
if q1 == "left" then
border[5] = "┤";
elseif q1 == "right" then
border[7] = "├";
end
elseif q2 == "bottom" then
if q1 == "left" then
border[3] = "┤";
elseif q1 == "right" then
border[1] = "├";
end
end
local ft;
if package.loaded["patterns.hover"] and package.loaded["patterns.hover"].buf then
ft = vim.bo[package.loaded["patterns.hover"].buf].ft;
end
return {
width = math.floor(vim.o.columns * 0.6),
height = math.floor(vim.o.lines * 0.5),
border = border,
footer_pos = "right",
footer = {
{ "╸", "FloatBorder" },
{ " " .. (ft or "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)
if item.text == "\\" then
return ' Character: "\\"';
else
return string.format(" Character: %s", vim.inspect(item.text));
end
end,
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)
return string.format(' Escape sequence: "%s"', item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette1Bg",
hl = "PatternsPalette1"
},
escaped_character = {
text = function (_, item)
return string.format(' Escaped character: "%s"', item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette6Bg",
hl = "PatternsPalette6"
},
----------------------------------------
capture_group = {
text = function (_, item)
return string.format(" Capture group, %d", item.id or -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)
return string.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)
return string.format(" Regex term(#%d)", item.id or -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)
if string.match(item.text, "^%(%?%<") then
return " Look behind";
else
return " Look ahead";
end
end,
show_tip = on_current,
tip_hl = "PatternsPalette3Bg",
hl = "PatternsPalette3"
},
----------------------------------------
quantifier_count = {
text = function (_, item)
if string.match(item.text, "^%d+$") then
return string.format(" Repeats exactly %s times", item.text);
elseif string.match(item.text, "^%d+,$") then
return string.format(
" Repeats at least %s times",
string.match(item.text, "^(%d+)")
);
elseif string.match(item.text, "^,%d+$") then
return string.format(
" Repeats at most %s times",
string.match(item.text, "^,(%d+)$")
);
else
return string.format(
" Repeats between %s & %s times",
string.match(item.text, "^(%d+),"),
string.match(item.text, "^%d+,(%d+)$")
);
end
end,
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)
return string.format(" Character: %s", vim.inspect(item.text));
end,
show_tip = on_current,
tip_hl = "PatternsPalette2Bg",
hl = "PatternsPalette2"
},
class_character = {
text = function (_, item)
return string.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)
return string.format(" Decimal escape: %s", item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette1Bg",
hl = "PatternsPalette1"
},
character_class_escape = {
text = function (_, item)
return string.format(" Character class escape: %s", item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette1Bg",
hl = "PatternsPalette1"
},
unicode_character_escape = {
text = function (_, item)
return string.format(" Unicode character escape: %s", item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette1Bg",
hl = "PatternsPalette1"
},
unicode_property_value = {
text = function (_, item)
return string.format(" Unicode property value: %s", item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette6Bg",
hl = "PatternsPalette6"
},
control_escape = {
text = function (_, item)
return string.format(" Control character escape: %s", item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette1Bg",
hl = "PatternsPalette1"
},
control_letter_escape = {
text = function (_, item)
return string.format(" Control letter escape: %s", item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette1Bg",
hl = "PatternsPalette1"
},
identity_escape = {
text = function (_, item)
return string.format(" Identity escape: %s", item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette1Bg",
hl = "PatternsPalette1"
},
backreference_escape = {
text = function (_, item)
return string.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)
return string.format(" POSIX Character class: ", item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette5Bg",
hl = "PatternsPalette5"
},
named_group_backreference = {
text = function (_, item)
return string.format(" Named backreference: ", string.match(item.text, "^%(%?P%=(.-)%)$"));
end,
show_tip = on_current,
tip_hl = "PatternsPalette5Bg",
hl = "PatternsPalette5"
},
capturing_group = {
text = function (_, item)
if type(item.id) == "string" then
return string.format(" Capture group(#%s)", item.id or "???");
else
return string.format(" Capture group(#%d)", item.id or -1);
end
end,
show_tip = on_current,
tip_hl = "PatternsPalette5Bg",
hl = "PatternsPalette5"
},
non_capturing_group = {
text = function (_, item)
return string.format(" Non-capture group(#%d)", item.id or -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)
return string.format(" Flag(s): %s", item.text);
end,
show_tip = on_current,
tip_hl = "PatternsPalette2Bg",
hl = "PatternsPalette2"
},
}
};
```
## 💡 Commands
This plugin creates the `:Patterns` command. It has 2 sub-commands,
- `explain`
Explains the pattern under the cursor.
- `hover`
LSP-like hover for the pattern under cursor.
It's behavior is similar to `K`(or `vim.lsp.buf.hover()`).
When `:Patterns` is run without any arguments, it opens the explain window.
## 🎹 keymaps
The `hover` & `explain` buffers have some pre-defined keymaps.
>[!TIP]
> You can disable these keymaps individually via the config.
>
> ```lua
> {
> keymaps = {
> hover = {
> ["i"] = { enable = false }
> }
> }
> }
> ```
### ⭐ hover
The hover buffer has the following keymaps,
+ `q`
Closes hover window.
+ `i`
Opens the pattern inside the explainer.
### ⭐ explain
#### ⭐ explain_input
The text input buffer has the following keymaps,
+ ``
Replaces the pattern under cursor with the text in the input buffer.
+ `q`
Quits the explainer.
+ ``
Switches to the explanation/preview buffer.
+ `H`
Cycles backward through the list of supported languages(lua_patterns, regex).
+ `L`
Cycles forward through the list of supported languages(lua_patterns, regex).
#### ⭐ explain_preview
The pattern preview/explanation buffer has the following keymaps,
+ `q`
Quits the explainer.
+ ``
Switches to the input buffer.
+ `T`
Toggles between the explanation and the matcher.