Skip to content

Instantly share code, notes, and snippets.

@trv6
Last active July 14, 2021 21:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trv6/353031dfb9b86f83e5318b93a2a2efb8 to your computer and use it in GitHub Desktop.
Save trv6/353031dfb9b86f83e5318b93a2a2efb8 to your computer and use it in GitHub Desktop.
local api = vim.api
local U = require 'snippets/common'
local ns = api.nvim_create_namespace('pull it, twist it, bop it, snippet')
local get_cursor = api.nvim_win_get_cursor
local set_text = api.nvim_buf_set_text
local get_window = api.nvim_get_current_win
local get_buffer = api.nvim_get_current_buf
local get_line = api.nvim_get_current_line
local get_lines = api.nvim_buf_get_lines
local concat = table.concat
local function strwidth(s, c)
return api.nvim_call_function('strwidth', {s})
end
local place_mark = api.nvim_buf_set_extmark
local get_mark = api.nvim_buf_get_extmark_by_id
local function entrypoint(structure)
local empty = {}
local head, tail, body
local marks = {}
local buf, win = get_buffer(), get_window()
local r_head, c = unpack(get_cursor(win))
local r_tail = r_head
local resolved_inputs = {}
local evaluator = U.evaluate_snippet(structure)
local inputs = evaluator.inputs
local struct = evaluator.structure
local eval_inputs = evaluator.evaluate_inputs
local eval_struct = evaluator.evaluate_structure
local function update()
return eval_struct(eval_inputs(resolved_inputs))
end
-- form the default text
local fragments = update()
body = concat(fragments, '')
set_text(buf, r_head-1, c, r_head-1, c, vim.split(body, '\n', true))
-- set the extmarks
local cursor_landing_mark
local head_mark, tail_mark
head_mark = place_mark(buf, ns, r_head - 1, c, empty)
for i = 1, #struct do
local frag = struct[i]
if type(frag) == 'string' then
if frag == '\n' then
r_tail = r_tail + 1
c = 0
elseif frag ~= '' then
c = c + strwidth(frag)
end
else
local n = strwidth(fragments[i])
if frag.is_input then
marks[i] = place_mark(buf, ns, r_tail - 1, c, {end_line = r_tail - 1, end_col = c+n, hl_group = 'Search', right_gravity = false, end_right_gravity = true})
else
marks[i] = place_mark(buf, ns, r_tail - 1, c, empty)
end
if frag.order == 0 then -- $0
cursor_landing_mark = marks[i]
end
c = c + n
end
end
tail_mark = place_mark(buf, ns, r_tail - 1, c, empty)
-- get the head and tail text
do
local lines = concat(get_lines(buf, r_head - 1, r_tail, true), '\n')
local n, m = string.find(lines, body)
head = string.sub(lines, 1, n-1)
tail = string.sub(lines, m+1)
end
local function delete_marks()
for k, id in pairs(marks) do
api.nvim_buf_del_extmark(buf, ns, id)
end
api.nvim_buf_del_extmark(buf, ns, tail_mark)
api.nvim_buf_del_extmark(buf, ns, head_mark)
end
local pattern
local current_index = 0
local R = {}
R.aborted = false
R.finished = false
-- this function is called by the user's keybinds
function R.advance(offset)
local previous_index = current_index
current_index = current_index + (offset or 0)
if current_index < 0 then
R.aborted = true
delete_marks()
return true
end
-- if there is a pattern, we should match the input
if pattern then
local write = concat(get_lines(buf, r_head - 1, r_tail, true),'\n')
local capture = string.match(write, pattern)
local first_index = inputs[previous_index].first_index
if fragments[first_index] ~= capture then -- new input detected
fragments[first_index] = capture
resolved_inputs[previous_index] = capture
local new_fragments = update()
-- snippets.nvim should have expanded the structure where possible (instances of $1, etc.)... replace the expanded components
for i = 1, #fragments do
if fragments[i] ~= new_fragments[i] then
local r, c = unpack(get_mark(buf, ns, marks[i], empty))
set_text(buf, r, c, r, c + strwidth(fragments[i]), vim.split(new_fragments[i], '\n'))
end
end
fragments = new_fragments
end
pattern = nil
end
-- are we done yet?
if current_index == 0 then return end
if current_index > #inputs then
R.finished = true
if cursor_landing_mark then
local pos = get_mark(buf, ns, cursor_landing_mark, empty)
pos[1], pos[2] = pos[1] + 1, pos[2] + 1
api.nvim_win_set_cursor(win, pos)
end
delete_marks()
return true
end
-- create the pattern for the current offset
local first = inputs[current_index].first_index
pattern = head .. concat(fragments, '', 1, first - 1) .. '(.+)' .. concat(fragments, '', first + 1) .. tail
-- set the cursor location
local relevant_mark = get_mark(buf, ns, marks[first], empty)
relevant_mark[1], relevant_mark[2] = relevant_mark[1] + 1, relevant_mark[2] + 1
api.nvim_win_set_cursor(win, relevant_mark)
-- switch to highlight mode
local scoot = strwidth(fragments[first]) - 1
api.nvim_input('<Esc>v' .. (scoot > 0 and tostring(scoot) .. 'l' or '') .. '<C-g>') -- there must be a better way to do this
end
return R
end
return entrypoint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment