Skip to content

Commit 41c1b90

Browse files
committed
fix!: query down, not up
fix: lookaround narratives fix: don't debounce refactor: remove last global state refactor: destructure apis BREAKING CHANGES: - require tree-sitter-regexp 0.21 - require nvim 0.10 - replace `narrative.separator` with `narrative.indentation_string` - change `component.depth` to `component.capture_depth` in `narrative.indentation_string` function
1 parent 3a7303d commit 41c1b90

File tree

17 files changed

+614
-942
lines changed

17 files changed

+614
-942
lines changed

README.md

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ require'regexplainer'.setup {
5858
},
5959

6060
-- Whether to log debug messages
61-
debug = false,
61+
debug = false,
6262

6363
-- 'split', 'popup'
6464
display = 'popup',
@@ -73,7 +73,7 @@ require'regexplainer'.setup {
7373
},
7474

7575
narrative = {
76-
separator = '\n',
76+
indendation_string = '> ', -- default ' '
7777
},
7878
}
7979
```
@@ -122,41 +122,33 @@ your editor.
122122

123123
### Render Options
124124

125-
`narrative.separator` can also be a function taking the current component and
126-
returning a string clause separator. For example, to separate clauses by a new
127-
line, followed by `> ` for each level of capture-group depth, define the
128-
following function:
125+
`narrative.indendation_string` can be a function taking the current component and
126+
returning an indendation indicator string. For example, to show the capture group on each line:
129127

130128
```lua
131129
narrative = {
132-
separator = function(component)
133-
local sep = '\n';
134-
if component.depth > 0 then
135-
for _ = 1, component.depth do
136-
sep = sep .. '> '
137-
end
138-
end
139-
return sep
130+
indentation_string = function(component)
131+
return component.capture_depth .. '> '
140132
end
141133
},
142134
```
143135

144136
Input:
145137

146138
```javascript
147-
/zero(one(two(?<inner>three)))/;
139+
/zero(one(two(three)))/;
148140
```
149141

150142
Output:
151143

152144
```markdown
153-
`zero`
154-
capture group 1:
155-
> `one`
156-
> capture group 2:
157-
> > `two`
158-
> > named capture group 3 `inner`:
159-
> > > `three`
145+
`zero`
146+
capture group 1:
147+
1> `one`
148+
1> capture group 2:
149+
1> 2> `two`
150+
1> 2> capture group 3:
151+
1> 2> 3> `three`
160152
```
161153

162154
## Yank

lua/regexplainer.lua

Lines changed: 48 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
local component = require 'regexplainer.component'
22
local tree = require 'regexplainer.utils.treesitter'
33
local utils = require 'regexplainer.utils'
4-
local buffers = require 'regexplainer.buffers'
5-
local defer = require 'regexplainer.utils.defer'
4+
local Buffers = require 'regexplainer.buffers'
5+
6+
local get_node_text = vim.treesitter.get_node_text
7+
local deep_extend = vim.tbl_deep_extend
8+
local map = vim.tbl_map
9+
local buf_delete = vim.api.nvim_buf_delete
10+
local ag = vim.api.nvim_create_augroup
11+
local au = vim.api.nvim_create_autocmd
612

713
---@class RegexplainerOptions
8-
---@field mode? 'narrative' # TODO: 'ascii', 'graphical'
14+
---@field mode? 'narrative'|'debug' # TODO: 'ascii', 'graphical'
915
---@field auto? boolean # Automatically display when cursor enters a regexp
1016
---@field filetypes? string[] # Filetypes (extensions) to automatically show regexplainer.
1117
---@field debug? boolean # Notify debug logs
@@ -15,10 +21,10 @@ local defer = require 'regexplainer.utils.defer'
1521
---@field popup? NuiPopupBufferOptions # options for the popup buffer
1622
---@field split? NuiSplitBufferOptions # options for the split buffer
1723

18-
---@class RegexplainerRenderOptions : RegexplainerOptions
24+
---@class RegexplainerRenderOptions: RegexplainerOptions
1925
---@field register "*"|"+"|'"'|":"|"."|"%"|"/"|"#"|"0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
2026

21-
---@class RegexplainerYankOptions : RegexplainerOptions
27+
---@class RegexplainerYankOptions: RegexplainerOptions
2228
---@field register "*"|"+"|'"'|":"|"."|"%"|"/"|"#"|"0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
2329

2430
---@class RegexplainerMappings
@@ -29,15 +35,12 @@ local defer = require 'regexplainer.utils.defer'
2935
---@field show_split? string # shows regexplainer in a split window
3036
---@field show_popup? string # shows regexplainer in a popup window
3137

32-
local get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text
33-
3438
---Maps config.mappings keys to vim command names and descriptions
3539
--
3640
local config_command_map = {
3741
show = { 'RegexplainerShow', 'Show Regexplainer' },
3842
hide = { 'RegexplainerHide', 'Hide Regexplainer' },
39-
toggle = { 'RegexplainerToggle', 'Toggle Regexplainer' },
40-
yank = { 'RegexplainerYank', 'Yank Regexplainer' },
43+
toggle = { 'RegexplainerToggle', 'Toggle Regexplainer' }, yank = { 'RegexplainerYank', 'Yank Regexplainer' },
4144
show_split = { 'RegexplainerShowSplit', 'Show Regexplainer in a split Window' },
4245
show_popup = { 'RegexplainerShowPopup', 'Show Regexplainer in a popup' },
4346
}
@@ -51,90 +54,73 @@ local default_config = {
5154
auto = false,
5255
filetypes = {
5356
'html',
54-
'js',
55-
'cjs',
56-
'mjs',
57-
'ts',
58-
'jsx',
59-
'tsx',
60-
'cjsx',
61-
'mjsx',
57+
'js', 'javascript', 'cjs', 'mjs',
58+
'ts', 'typescript', 'cts', 'mts',
59+
'tsx', 'typescriptreact', 'ctsx', 'mtsx',
60+
'jsx', 'javascriptreact', 'cjsx', 'mjsx',
6261
},
6362
debug = false,
6463
display = 'popup',
6564
mappings = {
6665
toggle = 'gR',
6766
},
6867
narrative = {
69-
separator = '\n',
68+
indentation_string = ' ',
7069
},
7170
}
7271

7372
--- A deep copy of the default config.
7473
--- During setup(), any user-provided config will be folded in
7574
---@type RegexplainerOptions
7675
--
77-
local local_config = vim.tbl_deep_extend('keep', default_config, {})
76+
local local_config = deep_extend('keep', default_config, {})
7877

7978
--- Show the explainer for the regexp under the cursor
8079
---@param options? RegexplainerOptions overrides for this call
81-
---@return nil
80+
---@return nil|number bufnr the bufnr of the regexplaination
8281
--
83-
local function show(options)
84-
options = vim.tbl_deep_extend('force', local_config, options or {})
85-
local node, error = tree.get_regexp_pattern_at_cursor(options)
82+
local function show_for_real(options)
83+
options = deep_extend('force', local_config, options or {})
84+
local node, scratchnr, error = tree.get_regexp_pattern_at_cursor()
8685

8786
if error and options.debug then
8887
utils.notify('Rexexplainer: ' .. error, 'debug')
89-
elseif node then
90-
-- in the case of a pattern node, we need to get the first child 🤷
91-
if node:type() == 'pattern' and node:child_count() == 1 then
92-
node = node:child(0)
93-
end
94-
95-
---@type RegexplainerRenderer
96-
local renderer
88+
elseif node and scratchnr then
9789
---@type boolean, RegexplainerRenderer
98-
local can_render, _renderer = pcall(require, 'regexplainer.renderers.' .. options.mode)
90+
local can_render, renderer = pcall(require, 'regexplainer.renderers.' .. options.mode)
9991

100-
if can_render then
101-
renderer = _renderer
102-
else
92+
if not can_render then
10393
utils.notify(options.mode .. ' is not a valid renderer', 'warning')
10494
utils.notify(renderer, 'error')
10595
renderer = require 'regexplainer.renderers.narrative'
10696
end
10797

108-
local components = component.make_components(node, nil, node)
98+
local components = component.make_components(scratchnr, node, nil, node)
10999

110-
local buffer = buffers.get_buffer(options)
100+
local buffer = Buffers.get_buffer(options)
111101

112102
if not buffer and options.debug then
113-
local Debug = require 'regexplainer.renderers.debug'
114-
return Debug.render(options, components)
103+
renderer = require'regexplainer.renderers.debug'
115104
end
116105

117-
buffers.render(buffer, renderer, components, options, {
118-
full_regexp_text = get_node_text(node, 0),
119-
})
106+
local state = { full_regexp_text = get_node_text(node, scratchnr) }
107+
108+
Buffers.render(buffer, renderer, components, options, state)
109+
buf_delete(scratchnr, { force = true })
120110
else
121-
buffers.hide_all()
111+
Buffers.hide_all()
122112
end
123113
end
124114

125115
local disable_auto = false
126116

127-
local show_debounced_trailing, timer_trailing = defer.debounce_trailing(show, 5)
128-
129-
buffers.register_timer(timer_trailing)
130-
131117
local M = {}
132118

133119
--- Show the explainer for the regexp under the cursor
134120
---@param options? RegexplainerOptions
135121
function M.show(options)
136122
disable_auto = true
137-
show(options)
123+
show_for_real(options)
138124
disable_auto = false
139125
end
140126

@@ -145,11 +131,7 @@ function M.yank(options)
145131
if type(options) == 'string' then
146132
options = { register = options }
147133
end
148-
show(vim.tbl_deep_extend(
149-
'force',
150-
options,
151-
{ display = 'register' }
152-
))
134+
show_for_real(deep_extend('force', options, { display = 'register' }))
153135
disable_auto = false
154136
end
155137

@@ -158,16 +140,16 @@ end
158140
---@return nil
159141
--
160142
function M.setup(config)
161-
local_config = vim.tbl_deep_extend('keep', config or {}, default_config)
143+
local_config = deep_extend('keep', config or {}, default_config)
162144

163145
-- bind keys from config
164146
local has_which_key = pcall(require, 'which-key')
165-
for cmd, binding in pairs(local_config.mappings) do
166-
local command = ':' .. config_command_map[cmd][1] .. '<CR>'
147+
for cmdmap, binding in pairs(local_config.mappings) do
148+
local cmd, description = unpack(config_command_map[cmdmap])
149+
local command = ':' .. cmd .. '<CR>'
167150

168151
if has_which_key then
169152
local wk = require 'which-key'
170-
local description = config_command_map[cmd][2]
171153
wk.register({ [binding] = { command, description } }, { mode = 'n' })
172154
else
173155
utils.map('n', binding, command)
@@ -176,13 +158,13 @@ function M.setup(config)
176158

177159
-- setup autocommand
178160
if local_config.auto then
179-
vim.api.nvim_create_augroup(augroup_name, { clear = true })
180-
vim.api.nvim_create_autocmd('CursorMoved', {
161+
ag(augroup_name, { clear = true })
162+
au('CursorMoved', {
181163
group = 'Regexplainer',
182-
pattern = vim.tbl_map(function(x) return '*.' .. x end, local_config.filetypes),
164+
pattern = map(function(x) return '*.' .. x end, local_config.filetypes),
183165
callback = function()
184-
if not disable_auto then
185-
show_debounced_trailing()
166+
if tree.has_regexp_at_cursor() and not disable_auto then
167+
show_for_real()
186168
end
187169
end,
188170
})
@@ -194,13 +176,13 @@ end
194176
--- Hide any displayed regexplainer buffers
195177
--
196178
function M.hide()
197-
buffers.hide_all()
179+
Buffers.hide_all()
198180
end
199181

200182
--- Toggle Regexplainer
201183
--
202184
function M.toggle()
203-
if buffers.is_open() then
185+
if Buffers.is_open() then
204186
M.hide()
205187
else
206188
M.show()
@@ -211,7 +193,7 @@ end
211193
--
212194
function M.teardown()
213195
local_config = vim.tbl_deep_extend('keep', {}, default_config)
214-
buffers.clear_timers()
196+
Buffers.clear_timers()
215197
pcall(vim.api.nvim_del_augroup_by_name, augroup_name)
216198
end
217199

@@ -220,7 +202,7 @@ end
220202
function M.debug_components()
221203
---@type any
222204
local mode = 'debug'
223-
show({ auto = false, display = 'split', mode = mode })
205+
show_for_real({ auto = false, display = 'split', mode = mode })
224206
end
225207

226208
return M

0 commit comments

Comments
 (0)