Skip to content

Commit

Permalink
feat: add experimental insert mode virtual markers
Browse files Browse the repository at this point in the history
* feat: add experimental insert mode virtual markers

Requires nvim nightly (0.10.0+).
  • Loading branch information
dcampos authored Nov 10, 2023
1 parent c3e289e commit 7a67653
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 21 deletions.
24 changes: 22 additions & 2 deletions doc/snippy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ local_snippet_dir ~
hl_group ~
Highlight group used for placeholders.
Type: `String`
Default: nil
Default: SnippyPlaceholder

choice_delay ~
Defines the delay in milliseconds before the choice menu is displayed.
Expand Down Expand Up @@ -511,12 +511,32 @@ mappings ~
| previous | snippy.mapping.Previous |
| cut_text | snippy.mapping.CutText |

expand_options~
expand_options ~
Add new `option`s to limit triggering in certain scenarios. See
|snippy-snippet-options|.
Type: `Table`
Default: {}

virtual_markers ~
Enable virtual text markers that show all available (inactive) tabstops
and placeholders.
Type: `Table`
Default: >lua

virtual_markers = {
enabled = false,
-- Marker for all placeholders (non-empty)
default = '',
-- Marker for all empty tabstops
empty = '',
-- Marker highlighing
hl_group = 'SnippyMarker',
}
<
The marker value can contain the following special formatting symbols:

- `%n`: current stop number.


==============================================================================
BUFFER OPTIONS *snippy-buffer-options*
Expand Down
69 changes: 52 additions & 17 deletions lua/snippy/buf.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local shared = require('snippy.shared')
local util = require('snippy.util')

local Stop = require('snippy.stop')

Expand All @@ -24,7 +25,7 @@ setmetatable(M, {
elseif key == 'stops' then
self.state().stops = value
else
return rawset(self, key, value)
rawset(self, key, value)
end
end,
})
Expand All @@ -35,32 +36,48 @@ setmetatable(M, {
---@param startcol number
---@param endrow number
---@param endcol number
---@param right_gravity number
---@param end_right_gravity number
---@param right_gravity boolean
---@param end_right_gravity boolean
---@param current boolean
---@return number Extmark identifier
local function add_mark(id, startrow, startcol, endrow, endcol, right_gravity, end_right_gravity)
local mark = api.nvim_buf_set_extmark(0, shared.namespace, startrow, startcol, {
local function add_mark(id, startrow, startcol, endrow, endcol, right_gravity, end_right_gravity, current, order)
local config = shared.config
local hl_group = config.hl_group
local opts = {
id = id,
end_line = endrow,
end_col = endcol,
hl_group = shared.config.hl_group,
hl_group = hl_group,
right_gravity = right_gravity,
end_right_gravity = end_right_gravity,
})
}
if vim.fn.has('nvim-0.10') == 1 and config.virtual_markers.enabled then
if not current and order > 0 then
opts.virt_text_pos = 'inline'
if startrow == endrow and startcol == endcol then
local text = util.expand_virtual_marker(config.virtual_markers.empty, order)
opts.virt_text = { { text, config.virtual_markers.hl_group } }
else
local text = util.expand_virtual_marker(config.virtual_markers.default, order)
opts.virt_text = { { text, config.virtual_markers.hl_group } }
end
end
end
local mark = api.nvim_buf_set_extmark(0, shared.namespace, startrow, startcol, opts)
return mark
end

local function activate_parents(number)
local function activate_parents(number, current)
local parents = M.stops[number]:get_parents()
for _, n in ipairs(parents) do
local stop = M.state().stops[n]
local from, to = stop:get_range()
local mark_id = stop.mark
local _ = add_mark(mark_id, from[1], from[2], to[1], to[2], false, true)
local _ = add_mark(mark_id, from[1], from[2], to[1], to[2], false, true, current, stop.order)
end
end

--- Activates a stop (and all its mirrors) by changing its extmark's gravity.
--- Activates a stop (and all its mirrors) by current its extmark's gravity.
--- Parents (outer stops) must also be activated.
---@param number number Stop number (index)
local function activate_stop_and_parents(number)
Expand All @@ -69,10 +86,22 @@ local function activate_stop_and_parents(number)
if stop.id == value.id then
local from, to = stop:get_range()
local mark_id = stop.mark
local _ = add_mark(mark_id, from[1], from[2], to[1], to[2], false, true)
activate_parents(n)
local _ = add_mark(mark_id, from[1], from[2], to[1], to[2], false, true, n == number, stop.order)
activate_parents(n, n == number)
end
end
end

--- Gets the ordering number for the next added tabstop.
---@return integer
local function next_order()
local n = 1
for _, stop in ipairs(M.state().stops) do
if stop.traversable then
n = n + 1
end
end
return n
end

--- Mirrors a stop and its parents by number.
Expand Down Expand Up @@ -133,7 +162,7 @@ function M.clear_children(stop_num)
end

function M.state()
local bufnr = api.nvim_buf_get_number(0)
local bufnr = api.nvim_get_current_buf()
if not M._state[bufnr] then
M._state[bufnr] = {
stops = {},
Expand All @@ -158,8 +187,14 @@ function M.add_stop(spec, pos)
local endrow = spec.endpos[1] - 1
local endcol = spec.endpos[2]
local stops = M.state().stops
local smark = add_mark(nil, startrow, startcol, endrow, endcol, true, true)
table.insert(stops, pos, Stop.new({ id = spec.id, traversable = is_traversable(), mark = smark, spec = spec }))
local traversable = is_traversable()
local order = traversable and next_order() or -1
local smark = add_mark(nil, startrow, startcol, endrow, endcol, true, true, false, order)
table.insert(
stops,
pos,
Stop.new({ id = spec.id, order = order, traversable = traversable, mark = smark, spec = spec })
)
M.state().stops = stops
end

Expand Down Expand Up @@ -188,7 +223,7 @@ function M.deactivate_stops()
for _, stop in ipairs(M.state().stops) do
local from, to = stop:get_range()
local mark_id = stop.mark
local _ = add_mark(mark_id, from[1], from[2], to[1], to[2], true, true)
local _ = add_mark(mark_id, from[1], from[2], to[1], to[2], true, true, false, stop.order)
end
end

Expand All @@ -212,7 +247,7 @@ function M.fix_current_stop()
if new ~= old and current_line:sub(1, #old) == old then
local stop = M.stops[M.current_stop]
local from, to = stop:get_range()
add_mark(stop.mark, from[1], #old, to[1], to[2], false, true)
local _ = add_mark(stop.mark, from[1], #old, to[1], to[2], false, true, true, stop.order)
end
end

Expand Down
14 changes: 13 additions & 1 deletion lua/snippy/shared.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ end
local default_config = {
snippet_dirs = nil,
local_snippet_dir = '.snippets',
hl_group = nil,
hl_group = 'SnippyPlaceholder',
scopes = {},
mappings = {},
choice_delay = 100,
Expand All @@ -35,6 +35,12 @@ local default_config = {
enabled = false,
level = 'debug',
},
virtual_markers = {
enabled = false,
empty = '',
default = '',
hl_group = 'SnippyMarker',
},
}

M.get_scopes = get_scopes
Expand Down Expand Up @@ -99,6 +105,12 @@ function M.set_config(params)
end,
})
end
if params.logging then
params.logging = vim.tbl_extend('keep', params.logging, M.config.logging)
end
if params.virtual_markers then
params.virtual_markers = vim.tbl_extend('keep', params.virtual_markers, M.config.virtual_markers)
end
M.config = vim.tbl_extend('force', M.config, params)
end

Expand Down
12 changes: 11 additions & 1 deletion lua/snippy/stop.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@ local util = require('snippy.util')
local api = vim.api
local fn = vim.fn

---@class Stop
---@field id number
---@field order number
---@field traversable boolean
---@field mark number
---@field spec table
local Stop = {}

function Stop.new(o)
local self = setmetatable(o, {
__index = Stop,
id = -1,
order = -1,
traversable = false,
mark = nil,
spec = {},
})
return self
end
Expand All @@ -22,7 +31,7 @@ function Stop:get_range()
local endrow, endcol = mark[3].end_row, mark[3].end_col
return { startrow, startcol }, { endrow, endcol }
end
return nil
error('No mark found for stop')
end

function Stop:get_text()
Expand All @@ -45,6 +54,7 @@ end

function Stop:get_before()
local from, _ = self:get_range()
assert(from, 'from should not be nil')
local current_line = fn.getline(from[1] + 1)
local pre = current_line:sub(1, from[2])
return pre
Expand Down
7 changes: 7 additions & 0 deletions lua/snippy/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,11 @@ function M.merge_snippets(current, added)
return result
end

function M.expand_virtual_marker(marker_text, number)
-- Use %n to insert the stop number
local result = marker_text:gsub('([^%%]-)%%n', '%1' .. number)
result = result:gsub('%%%%', '%')
return result
end

return M
62 changes: 62 additions & 0 deletions test/functional/snippy_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -905,4 +905,66 @@ describe('Snippy', function()
exec_lua('snippy.next()')
eq('ExpandedJumpedFinished', exec_lua([[return Snippy_autocmds]]))
end)

it('virtual markers', function()
command('set filetype=lua')
exec_lua([[snippy.setup({
virtual_markers ={
enabled = true,
default = '>',
empty = '|',
}
})]])
exec_lua('snippy.expand_snippet([[local ${1:var} = ${2:val}${0}]])')
screen:expect({
grid = [[
local ^v{3:ar} = >val| |
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- SELECT --} |
]],
})
exec_lua([[snippy.next()]])
screen:expect({
grid = [[
local >var = ^v{3:al}| |
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- SELECT --} |
]],
})
end)

it('virtual markers with numbers', function()
command('set filetype=lua')
exec_lua([[snippy.setup({
virtual_markers = {
enabled = true,
default = '%n:>',
empty = '%n:|',
}
})]])
exec_lua('snippy.expand_snippet([[local ${1:var} = ${2:val}${0}]])')
screen:expect({
grid = [[
local ^v{3:ar} = 2:>val3:| |
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- SELECT --} |
]],
})
exec_lua([[snippy.next()]])
screen:expect({
grid = [[
local 1:>var = ^v{3:al}3:| |
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- SELECT --} |
]],
})
end)
end)

0 comments on commit 7a67653

Please sign in to comment.