Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: query down instead of up #45

Merged
merged 2 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ watch:
-type f \
-name '*.lua' \
-o -name '*.js' \
! -path "./.tests/**/*" | entr -d make run_tests
! -path "./.tests/**/*" | entr -d make test

test:
@REGEXPLAINER_DEBOUNCE=false \
nvim \
--headless \
--noplugin \
-u tests/mininit.lua \
-c "lua require'plenary.test_harness'.test_directory('tests/regexplainer/', {minimal_init='tests/mininit.lua',sequential=true})"\
-c "PlenaryBustedDirectory tests/regexplainer/ {minimal_init='tests/mininit.lua',sequential=true,keep_going=false}"\
-c "qa!"
36 changes: 14 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ require'regexplainer'.setup {
},

-- Whether to log debug messages
debug = false,
debug = false,

-- 'split', 'popup'
display = 'popup',
Expand All @@ -73,7 +73,7 @@ require'regexplainer'.setup {
},

narrative = {
separator = '\n',
indendation_string = '> ', -- default ' '
},
}
```
Expand Down Expand Up @@ -122,41 +122,33 @@ your editor.

### Render Options

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

```lua
narrative = {
separator = function(component)
local sep = '\n';
if component.depth > 0 then
for _ = 1, component.depth do
sep = sep .. '> '
end
end
return sep
indentation_string = function(component)
return component.capture_depth .. '> '
end
},
```

Input:

```javascript
/zero(one(two(?<inner>three)))/;
/zero(one(two(three)))/;
```

Output:

```markdown
`zero`
capture group 1:
> `one`
> capture group 2:
> > `two`
> > named capture group 3 `inner`:
> > > `three`
`zero`
capture group 1:
1> `one`
1> capture group 2:
1> 2> `two`
1> 2> capture group 3:
1> 2> 3> `three`
```

## Yank
Expand Down
114 changes: 48 additions & 66 deletions lua/regexplainer.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
local component = require 'regexplainer.component'
local tree = require 'regexplainer.utils.treesitter'
local utils = require 'regexplainer.utils'
local buffers = require 'regexplainer.buffers'
local defer = require 'regexplainer.utils.defer'
local Buffers = require 'regexplainer.buffers'

local get_node_text = vim.treesitter.get_node_text
local deep_extend = vim.tbl_deep_extend
local map = vim.tbl_map
local buf_delete = vim.api.nvim_buf_delete
local ag = vim.api.nvim_create_augroup
local au = vim.api.nvim_create_autocmd

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

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

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

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

local get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text

---Maps config.mappings keys to vim command names and descriptions
--
local config_command_map = {
show = { 'RegexplainerShow', 'Show Regexplainer' },
hide = { 'RegexplainerHide', 'Hide Regexplainer' },
toggle = { 'RegexplainerToggle', 'Toggle Regexplainer' },
yank = { 'RegexplainerYank', 'Yank Regexplainer' },
toggle = { 'RegexplainerToggle', 'Toggle Regexplainer' }, yank = { 'RegexplainerYank', 'Yank Regexplainer' },
show_split = { 'RegexplainerShowSplit', 'Show Regexplainer in a split Window' },
show_popup = { 'RegexplainerShowPopup', 'Show Regexplainer in a popup' },
}
Expand All @@ -51,90 +54,73 @@ local default_config = {
auto = false,
filetypes = {
'html',
'js',
'cjs',
'mjs',
'ts',
'jsx',
'tsx',
'cjsx',
'mjsx',
'js', 'javascript', 'cjs', 'mjs',
'ts', 'typescript', 'cts', 'mts',
'tsx', 'typescriptreact', 'ctsx', 'mtsx',
'jsx', 'javascriptreact', 'cjsx', 'mjsx',
},
debug = false,
display = 'popup',
mappings = {
toggle = 'gR',
},
narrative = {
separator = '\n',
indentation_string = ' ',
},
}

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

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

if error and options.debug then
utils.notify('Rexexplainer: ' .. error, 'debug')
elseif node then
-- in the case of a pattern node, we need to get the first child 🤷
if node:type() == 'pattern' and node:child_count() == 1 then
node = node:child(0)
end

---@type RegexplainerRenderer
local renderer
elseif node and scratchnr then
---@type boolean, RegexplainerRenderer
local can_render, _renderer = pcall(require, 'regexplainer.renderers.' .. options.mode)
local can_render, renderer = pcall(require, 'regexplainer.renderers.' .. options.mode)

if can_render then
renderer = _renderer
else
if not can_render then
utils.notify(options.mode .. ' is not a valid renderer', 'warning')
utils.notify(renderer, 'error')
renderer = require 'regexplainer.renderers.narrative'
end

local components = component.make_components(node, nil, node)
local components = component.make_components(scratchnr, node, nil, node)

local buffer = buffers.get_buffer(options)
local buffer = Buffers.get_buffer(options)

if not buffer and options.debug then
local Debug = require 'regexplainer.renderers.debug'
return Debug.render(options, components)
renderer = require'regexplainer.renderers.debug'
end

buffers.render(buffer, renderer, components, options, {
full_regexp_text = get_node_text(node, 0),
})
local state = { full_regexp_text = get_node_text(node, scratchnr) }

Buffers.render(buffer, renderer, components, options, state)
buf_delete(scratchnr, { force = true })
else
buffers.hide_all()
Buffers.hide_all()
end
end

local disable_auto = false

local show_debounced_trailing, timer_trailing = defer.debounce_trailing(show, 5)

buffers.register_timer(timer_trailing)

local M = {}

--- Show the explainer for the regexp under the cursor
---@param options? RegexplainerOptions
function M.show(options)
disable_auto = true
show(options)
show_for_real(options)
disable_auto = false
end

Expand All @@ -145,11 +131,7 @@ function M.yank(options)
if type(options) == 'string' then
options = { register = options }
end
show(vim.tbl_deep_extend(
'force',
options,
{ display = 'register' }
))
show_for_real(deep_extend('force', options, { display = 'register' }))
disable_auto = false
end

Expand All @@ -158,16 +140,16 @@ end
---@return nil
--
function M.setup(config)
local_config = vim.tbl_deep_extend('keep', config or {}, default_config)
local_config = deep_extend('keep', config or {}, default_config)

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

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

-- setup autocommand
if local_config.auto then
vim.api.nvim_create_augroup(augroup_name, { clear = true })
vim.api.nvim_create_autocmd('CursorMoved', {
ag(augroup_name, { clear = true })
au('CursorMoved', {
group = 'Regexplainer',
pattern = vim.tbl_map(function(x) return '*.' .. x end, local_config.filetypes),
pattern = map(function(x) return '*.' .. x end, local_config.filetypes),
callback = function()
if not disable_auto then
show_debounced_trailing()
if tree.has_regexp_at_cursor() and not disable_auto then
show_for_real()
end
end,
})
Expand All @@ -194,13 +176,13 @@ end
--- Hide any displayed regexplainer buffers
--
function M.hide()
buffers.hide_all()
Buffers.hide_all()
end

--- Toggle Regexplainer
--
function M.toggle()
if buffers.is_open() then
if Buffers.is_open() then
M.hide()
else
M.show()
Expand All @@ -211,7 +193,7 @@ end
--
function M.teardown()
local_config = vim.tbl_deep_extend('keep', {}, default_config)
buffers.clear_timers()
Buffers.clear_timers()
pcall(vim.api.nvim_del_augroup_by_name, augroup_name)
end

Expand All @@ -220,7 +202,7 @@ end
function M.debug_components()
---@type any
local mode = 'debug'
show({ auto = false, display = 'split', mode = mode })
show_for_real({ auto = false, display = 'split', mode = mode })
end

return M
Loading
Loading