mirror of
https://github.com/Ascyii/typstar.git
synced 2026-01-01 05:24:24 -05:00
feat: smart snippets using treesitter
This commit is contained in:
@@ -43,13 +43,14 @@ function M.ri(insert_node_id)
|
|||||||
return luasnip.function_node(function(args) return args[1][1] end, insert_node_id)
|
return luasnip.function_node(function(args) return args[1][1] end, insert_node_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.snip(trigger, expand, insert, condition, priority)
|
function M.snip(trigger, expand, insert, condition, priority, wordTrig)
|
||||||
priority = priority or 1000
|
priority = priority or 1000
|
||||||
return luasnip.snippet(
|
return luasnip.snippet(
|
||||||
{
|
{
|
||||||
trig = trigger,
|
trig = trigger,
|
||||||
trigEngine = M.engine,
|
trigEngine = M.engine,
|
||||||
trigEngineOpts = { condition = condition },
|
trigEngineOpts = { condition = condition },
|
||||||
|
wordTrig = wordTrig,
|
||||||
priority = priority,
|
priority = priority,
|
||||||
snippetType = 'autosnippet'
|
snippetType = 'autosnippet'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function M.insert_drawing()
|
|||||||
local filename = os.date(cfg.filename)
|
local filename = os.date(cfg.filename)
|
||||||
local path = assets_dir .. '/' .. filename .. '.excalidraw.md'
|
local path = assets_dir .. '/' .. filename .. '.excalidraw.md'
|
||||||
local path_inserted = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted
|
local path_inserted = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted
|
||||||
utils.insert_snippet(string.format(affix, path_inserted))
|
utils.insert_text_block(string.format(affix, path_inserted))
|
||||||
launch_obsidian_open(path)
|
launch_obsidian_open(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ end
|
|||||||
local generate_index_snippets = function(letter)
|
local generate_index_snippets = function(letter)
|
||||||
for _, index in pairs(common_indices) do
|
for _, index in pairs(common_indices) do
|
||||||
table.insert(letter_snippets,
|
table.insert(letter_snippets,
|
||||||
snip(letter .. '(' .. index .. ') ', letter .. '_(<>) ', { cap(1) }, math, 200))
|
snip(letter .. '(' .. index .. ') ', letter .. '_<> ', { cap(1) }, math, 200))
|
||||||
table.insert(letter_snippets,
|
table.insert(letter_snippets,
|
||||||
snip('\\$' .. letter .. '\\$(' .. index .. ') ', '$' .. letter .. '_(<>)$ ', { cap(1) }, markup, 200))
|
snip('\\$' .. letter .. '\\$(' .. index .. ') ', '$' .. letter .. '_<>$ ', { cap(1) }, markup, 200))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ return {
|
|||||||
snip('pi', 'pi ', {}, math),
|
snip('pi', 'pi ', {}, math),
|
||||||
snip('in', 'in ', {}, math),
|
snip('in', 'in ', {}, math),
|
||||||
snip('(.*)iv', '<>^(-1)', { cap(1) }, math),
|
snip('(.*)iv', '<>^(-1)', { cap(1) }, math),
|
||||||
snip('(.*)sr', '<>^(2)', { cap(1) }, math),
|
snip('(.*)sr', '<>^2', { cap(1) }, math),
|
||||||
snip('(.*)rd', '<>^(<>)', { cap(1), i(1, 'n') }, math),
|
snip('(.*)jj', '<>_(<>)', { cap(1), i(1, 'n') }, math),
|
||||||
|
snip('(.*)kk', '<>^(<>)', { cap(1), i(1, 'n') }, math),
|
||||||
|
|
||||||
snip('ddx', '(d <>)(d <>)', { i(1, 'f'), i(2, 'x') }, math),
|
snip('ddx', '(d <>)(d <>)', { i(1, 'f'), i(2, 'x') }, math),
|
||||||
snip('it', 'integral_(<>)^(<>)', { i(1, 'a'), i(2, 'b') }, math),
|
snip('it', 'integral_(<>)^(<>)', { i(1, 'a'), i(2, 'b') }, math),
|
||||||
|
|||||||
@@ -1,72 +1,90 @@
|
|||||||
|
local ts = vim.treesitter
|
||||||
local ls = require('luasnip')
|
local ls = require('luasnip')
|
||||||
local i = ls.insert_node
|
|
||||||
local d = ls.dynamic_node
|
local d = ls.dynamic_node
|
||||||
local f = ls.function_node
|
local i = ls.insert_node
|
||||||
|
local s = ls.snippet_node
|
||||||
|
local t = ls.text_node
|
||||||
|
|
||||||
|
local utils = require('typstar.utils')
|
||||||
local helper = require('typstar.autosnippets')
|
local helper = require('typstar.autosnippets')
|
||||||
local math = helper.in_math
|
local math = helper.in_math
|
||||||
local snip = helper.snip
|
local snip = helper.snip
|
||||||
local cap = helper.cap
|
|
||||||
local get_visual = helper.get_visual
|
|
||||||
|
|
||||||
local snippets = {}
|
local snippets = {}
|
||||||
local operations = { -- boolean denotes whether an additional layer of () brackets should be removed
|
local operations = { -- first boolean: existing brackets should be kept; second boolean: brackets should be added
|
||||||
{ 'vi', '1/(', ')', true },
|
{ 'vi', '1/', '', true, false },
|
||||||
{ 'bb', '(', ')', false },
|
{ 'bb', '(', ')', true, false }, -- add round brackets
|
||||||
{ 'sq', '[', ']', true },
|
{ 'sq', '[', ']', true, false }, -- add square brackets
|
||||||
{ 'abs', 'abs(', ')', false },
|
{ 'bB', '(', ')', false, false }, -- replace with round brackets
|
||||||
{ 'ul', 'underline(', ')', false },
|
{ 'sQ', '[', ']', false, false }, -- replace with square brackets
|
||||||
{ 'ol', 'overline(', ')', false },
|
{ 'BB', '', '', false, false }, -- remove brackets
|
||||||
{ 'ub', 'underbrace(', ')', false },
|
{ 'ss', '"', '"', false, false },
|
||||||
{ 'ob', 'overbrace(', ')', false },
|
{ 'abs', 'abs', '', true, true },
|
||||||
{ 'ht', 'hat(', ')', false },
|
{ 'ul', 'underline', '', true, true },
|
||||||
{ 'br', 'macron(', ')', false },
|
{ 'ol', 'overline', '', true, true },
|
||||||
{ 'dt', 'dot(', ')', false },
|
{ 'ub', 'underbrace', '', true, true },
|
||||||
{ 'ci', 'circle(', ')', false },
|
{ 'ob', 'overbrace', '', true, true },
|
||||||
{ 'td', 'tilde(', ')', false },
|
{ 'ht', 'hat', '', true, true },
|
||||||
{ 'nr', 'norm(', ')', false },
|
{ 'br', 'macron', '', true, true },
|
||||||
{ 'vv', 'vec(', ')', false },
|
{ 'dt', 'dot', '', true, true },
|
||||||
{ 'rt', 'sqrt(', ')', false },
|
{ 'ci', 'circle', '', true, true },
|
||||||
|
{ 'td', 'tilde', '', true, true },
|
||||||
|
{ 'nr', 'norm', '', true, true },
|
||||||
|
{ 'vv', 'vec', '', true, true },
|
||||||
|
{ 'rt', 'sqrt', '', true, true },
|
||||||
}
|
}
|
||||||
|
|
||||||
local wrap_brackets = function(args, snippet, val)
|
local ts_wrap_query = ts.query.parse('typst', '[(call) (ident) (letter) (number)] @wrap')
|
||||||
local captured = snippet.captures[1]
|
local ts_wrapnobrackets_query = ts.query.parse('typst', '(group) @wrapnobrackets')
|
||||||
local bracket_types = { [')'] = '(', [']'] = '[', ['}'] = '{' }
|
|
||||||
local closing_bracket = captured:sub(-1, -1)
|
|
||||||
local opening_bracket = bracket_types[closing_bracket]
|
|
||||||
|
|
||||||
if opening_bracket == nil then
|
local process_ts_query = function(bufnr, cursor, query, root, insert1, insert2, cut_offset)
|
||||||
return captured
|
for _, match, _ in query:iter_matches(root, bufnr, cursor[1], cursor[1] + 1) do
|
||||||
|
if match then
|
||||||
|
local start_row, start_col, end_row, end_col = utils.treesitter_match_start_end(match)
|
||||||
|
if end_row == cursor[1] and end_col == cursor[2] then
|
||||||
|
vim.schedule(function() -- to not interfere with luasnip
|
||||||
|
local cursor_offset = 0
|
||||||
|
local old_len1, new_len1 = utils.insert_text(
|
||||||
|
bufnr, start_row, start_col, insert1, 0, cut_offset)
|
||||||
|
if start_row == cursor[1] then
|
||||||
|
cursor_offset = cursor_offset + (new_len1 - old_len1)
|
||||||
end
|
end
|
||||||
|
local old_len2, new_len2 = utils.insert_text(
|
||||||
local n_brackets = 0
|
bufnr, end_row, cursor[2] + cursor_offset, insert2, cut_offset, 0)
|
||||||
local char
|
if end_row == cursor[1] then
|
||||||
|
cursor_offset = cursor_offset + (new_len2 - old_len2)
|
||||||
for i = #captured, 1, -1 do
|
|
||||||
char = captured:sub(i, i)
|
|
||||||
if char == closing_bracket then
|
|
||||||
n_brackets = n_brackets + 1
|
|
||||||
elseif char == opening_bracket then
|
|
||||||
n_brackets = n_brackets - 1
|
|
||||||
end
|
end
|
||||||
|
vim.api.nvim_win_set_cursor(0, { cursor[1] + 1, cursor[2] + cursor_offset })
|
||||||
if n_brackets == 0 then
|
end)
|
||||||
local remove_additional = val[4] and opening_bracket == '('
|
return true
|
||||||
return captured:sub(1, i - 1) .. val[2]
|
|
||||||
.. captured:sub(i + (remove_additional and 1 or 0), -(remove_additional and 2 or 1)) .. val[3]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return captured
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local smart_wrap = function(args, parent, old_state, expand)
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
local cursor = utils.get_cursor_pos()
|
||||||
|
local root = utils.get_treesitter_root(bufnr)
|
||||||
|
|
||||||
|
if process_ts_query(bufnr, cursor, ts_wrapnobrackets_query, root, expand[2], expand[3], expand[4] and 0 or 1) then
|
||||||
|
return s(nil, t())
|
||||||
|
end
|
||||||
|
|
||||||
|
local expand1 = expand[5] and expand[2] .. '(' or expand[2]
|
||||||
|
local expand2 = expand[5] and expand[3] .. ')' or expand[3]
|
||||||
|
if process_ts_query(bufnr, cursor, ts_wrap_query, root, expand1, expand2) then
|
||||||
|
return s(nil, t())
|
||||||
|
end
|
||||||
|
if #parent.env.LS_SELECT_RAW > 0 then
|
||||||
|
return s(nil, t(expand1 .. table.concat(parent.env.LS_SELECT_RAW) .. expand2))
|
||||||
|
end
|
||||||
|
return s(nil, { t(expand1), i(1, '1+1'), t(expand2) })
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, val in pairs(operations) do
|
for _, val in pairs(operations) do
|
||||||
table.insert(snippets, snip(val[1], val[2] .. '<>' .. val[3], { d(1, get_visual) }, math))
|
table.insert(snippets, snip(val[1], '<>', { d(1, smart_wrap, {}, { user_args = { val } }) }, math, 1000, false))
|
||||||
table.insert(snippets, snip('[\\s$]' .. val[1], val[2] .. '<>' .. val[3], { i(1, '1') }, math))
|
|
||||||
table.insert(snippets,
|
|
||||||
snip('([\\w]+)'
|
|
||||||
.. val[1], val[2] .. '<>' .. val[3], { cap(1) }, math, 900))
|
|
||||||
table.insert(snippets,
|
|
||||||
snip('(.*[\\)|\\]|\\}])' .. val[1], '<>', { f(wrap_brackets, {}, { user_args = { val } }), nil }, math, 1100))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,27 +7,45 @@ function M.get_cursor_pos()
|
|||||||
return { cursor_row, cursor_col }
|
return { cursor_row, cursor_col }
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.insert_snippet(snip)
|
function M.insert_text(bufnr, row, col, snip, begin_offset, end_offset)
|
||||||
|
begin_offset = begin_offset or 0
|
||||||
|
end_offset = end_offset or 0
|
||||||
|
local line = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, true)[1]
|
||||||
|
local old_len = #line
|
||||||
|
line = line:sub(1, col - begin_offset) .. snip .. line:sub(col + 1 + end_offset, #line)
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, row, row + 1, false, { line })
|
||||||
|
return old_len, #line
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.insert_text_block(snip)
|
||||||
local line_num = M.get_cursor_pos()[1] + 1
|
local line_num = M.get_cursor_pos()[1] + 1
|
||||||
local lines = {}
|
local lines = {}
|
||||||
for line in snip:gmatch '[^\r\n]+' do
|
for line in snip:gmatch '[^\r\n]+' do
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
end
|
end
|
||||||
vim.api.nvim_buf_set_lines(0, line_num, line_num, false, lines)
|
vim.api.nvim_buf_set_lines(vim.api.nvim_get_current_buf(), line_num, line_num, false, lines)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.run_shell_command(cmd)
|
function M.run_shell_command(cmd)
|
||||||
vim.fn.jobstart(cmd)
|
vim.fn.jobstart(cmd)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.get_treesitter_root(bufnr)
|
||||||
|
return ts.get_parser(bufnr):parse()[1]:root()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.treesitter_match_start_end(match)
|
||||||
|
local start_row, start_col, _, _ = match[1]:range()
|
||||||
|
local _, _, end_row, end_col = match[#match]:range()
|
||||||
|
return start_row, start_col, end_row, end_col
|
||||||
|
end
|
||||||
|
|
||||||
function M.cursor_within_treesitter_query(query, match_tolerance, cursor)
|
function M.cursor_within_treesitter_query(query, match_tolerance, cursor)
|
||||||
cursor = cursor or M.get_cursor_pos()
|
cursor = cursor or M.get_cursor_pos()
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local root = ts.get_parser(bufnr):parse()[1]:root()
|
for _, match, _ in query:iter_matches(M.get_treesitter_root(bufnr), bufnr, cursor[1], cursor[1] + 1) do
|
||||||
for _, match, _ in query:iter_matches(root, bufnr, cursor[1], cursor[1] + 1) do
|
|
||||||
if match then
|
if match then
|
||||||
local start_row, start_col, _, _ = match[1]:range()
|
local start_row, start_col, end_row, end_col = M.treesitter_match_start_end(match)
|
||||||
local _, _, end_row, end_col = match[#match]:range()
|
|
||||||
local matched = M.cursor_within_coords(cursor, start_row, end_row, start_col, end_col,
|
local matched = M.cursor_within_coords(cursor, start_row, end_row, start_col, end_col,
|
||||||
match_tolerance)
|
match_tolerance)
|
||||||
if matched then
|
if matched then
|
||||||
|
|||||||
Reference in New Issue
Block a user