diff --git a/lua/typstar/autosnippets.lua b/lua/typstar/autosnippets.lua index 8bf494e..1790302 100644 --- a/lua/typstar/autosnippets.lua +++ b/lua/typstar/autosnippets.lua @@ -1,16 +1,30 @@ local M = {} local cfg = require('typstar.config').config.snippets +local utils = require('typstar.utils') local luasnip = require('luasnip') local fmta = require('luasnip.extras.fmt').fmta +local lsengines = require('luasnip.nodes.util.trig_engines') +local ts = vim.treesitter -M.in_math = function() return vim.api.nvim_eval('typst#in_math()') == 1 end -M.in_markup = function() return vim.api.nvim_eval('typst#in_markup()') == 1 end -M.in_code = function() return vim.api.nvim_eval('typst#in_code()') == 1 end -M.in_comment = function() return vim.api.nvim_eval('typst#in_comment()') == 1 end +local last_keystroke_time = nil +vim.api.nvim_create_autocmd('TextChangedI', { + callback = function() + last_keystroke_time = vim.loop.now() + end, +}) +local lexical_result_cache = {} +local ts_markup_query = ts.query.parse('typst', '(text) @markup') +local ts_math_query = ts.query.parse('typst', '(math) @math') +local ts_string_query = ts.query.parse('typst', '(string) @string') + +M.in_math = function() + local cursor = utils.get_cursor_pos() + return utils.cursor_within_treesitter_query(ts_math_query, 0, cursor) + and not utils.cursor_within_treesitter_query(ts_string_query, 0, cursor) +end +M.in_markup = function() return utils.cursor_within_treesitter_query(ts_markup_query, 2) end M.not_in_math = function() return not M.in_math() end M.not_in_markup = function() return not M.in_markup() end -M.not_in_code = function() return not M.in_code() end -M.not_in_comment = function() return not M.in_comment() end M.snippets_toggle = true function M.cap(i) @@ -32,25 +46,46 @@ end function M.snip(trigger, expand, insert, condition, priority) priority = priority or 1000 return luasnip.snippet( - { trig = trigger, regTrig = true, wordtrig = false, priority = priority, snippetType = 'autosnippet' }, + { + trig = trigger, + trigEngine = M.engine, + trigEngineOpts = { condition = condition }, + priority = priority, + snippetType = 'autosnippet' + }, fmta(expand, { unpack(insert) }), { - condition = function() - if not M.snippets_toggle then - return false - end - return condition() - end + condition = function() return M.snippets_toggle end } ) end function M.start_snip(trigger, expand, insert, condition, priority) - return M.snip('^%s*' .. trigger, expand, insert, condition, priority) + return M.snip('^\\s*' .. trigger, expand, insert, condition, priority) +end + +function M.engine(trigger, opts) + local base_engine = lsengines.ecma(trigger, opts) + local condition = function() + local cached = lexical_result_cache[opts.condition] + if cached ~= nil and cached[1] == last_keystroke_time then + return cached[2] + end + local result = opts.condition() + lexical_result_cache[opts.condition] = { last_keystroke_time, result } + return result + end + return function(line, trig) + if not M.snippets_toggle or not condition() then + return nil + end + return base_engine(line, trig) + end end function M.toggle_autosnippets() M.snippets_toggle = not M.snippets_toggle + print(string.format('%sabled typstar autosnippets', M.snippets_toggle and 'En' or 'Dis')) end function M.setup() diff --git a/lua/typstar/snippets/letters.lua b/lua/typstar/snippets/letters.lua index 6d07553..4230039 100644 --- a/lua/typstar/snippets/letters.lua +++ b/lua/typstar/snippets/letters.lua @@ -1,7 +1,11 @@ local helper = require('typstar.autosnippets') local snip = helper.snip +local cap = helper.cap +local math = helper.in_math +local markup = helper.in_markup -local letters = { +local letter_snippets = {} +local greek_letters = { { 'a', 'alpha' }, { 'A', 'Alpha' }, { 'b', 'beta' }, { 'B', 'Beta' }, { 'c', 'chi' }, { 'C', 'Chi' }, @@ -24,13 +28,32 @@ local letters = { { 'x', 'xi' }, { 'X', 'xi' }, { 'z', 'zeta' }, { 'Z', 'Zeta' }, } +local latin_letters = { 'f', 'u', 'v', 'w', 'y' } -- remaining ones are added dynamically +local common_indices = { '\\d+', 'i', 'j', 'k', 'n' } -local letter_snippets = {} +for _, letter in ipairs({ unpack(latin_letters) }) do + table.insert(latin_letters, letter:upper()) +end -for _, val in pairs(letters) do - table.insert(letter_snippets, snip(';' .. val[1], val[2], {}, helper.in_math)) - table.insert(letter_snippets, snip(';' .. val[1], '$' .. val[2] .. '$ ', {}, helper.in_markup)) - table.insert(letter_snippets, snip(':' .. val[1], '$' .. val[1] .. '$ ', {}, helper.in_markup)) +local generate_index_snippets = function(letter) + for _, index in pairs(common_indices) do + table.insert(letter_snippets, + snip(letter .. '(' .. index .. ') ', letter .. '_(<>) ', { cap(1) }, math, 200)) + table.insert(letter_snippets, + snip('\\$' .. letter .. '\\$(' .. index .. ') ', '$' .. letter .. '_(<>)$ ', { cap(1) }, markup, 200)) + end +end + +for _, val in pairs(greek_letters) do + table.insert(letter_snippets, snip(';' .. val[1], val[2], {}, math)) + table.insert(letter_snippets, snip(';' .. val[1], '$' .. val[2] .. '$ ', {}, markup)) + generate_index_snippets(val[2]) + table.insert(latin_letters, val[1]) +end + +for _, letter in pairs(latin_letters) do + generate_index_snippets(letter) + table.insert(letter_snippets, snip(':' .. letter, '$' .. letter .. '$ ', {}, markup)) end return { diff --git a/lua/typstar/snippets/math.lua b/lua/typstar/snippets/math.lua index 7c475db..b9d5aa8 100644 --- a/lua/typstar/snippets/math.lua +++ b/lua/typstar/snippets/math.lua @@ -62,6 +62,8 @@ return { snip('iso', 'tilde.equiv ', {}, math), snip('rrn', 'RR^n ', {}, math), snip('cc', 'cases(\n\t<>\n)\\', { i(1, '1') }, math), + snip('pi', 'pi ', {}, math), + snip('in', 'in ', {}, math), snip('(.*)iv', '<>^(-1)', { cap(1) }, math), snip('(.*)sr', '<>^(2)', { cap(1) }, math), snip('(.*)rd', '<>^(<>)', { cap(1), i(1, 'n') }, math), @@ -74,8 +76,6 @@ return { snip('lm', 'lim <>', { i(1, 'a_n') }, math), snip('lim', 'lim_(<> ->> <>) <>', { i(1, 'n'), i(2, 'oo'), i(3, 'a_n') }, math), - snip('lim sup', 'limsup <>', { i(1, 'a_n') }, math), - snip('lim(_.*%-.*) sup', 'limsup<> <>', { cap(1), i(1, 'a_n') }, math), - snip('lim inf', 'liminf <>', { i(1, 'a_n') }, math), - snip('lim(_.*%-.*) inf', 'liminf<> <>', { cap(1), i(1, 'a_n') }, math), + snip('lim (sup|inf)', 'lim<> <>', { cap(1), i(1, 'a_n') }, math), + snip('lim(_.*-.*) (sup|inf)', 'lim<><> <>', { cap(2), cap(1), i(1, 'a_n') }, math), } diff --git a/lua/typstar/snippets/matrix.lua b/lua/typstar/snippets/matrix.lua index bb077da..8c76aac 100644 --- a/lua/typstar/snippets/matrix.lua +++ b/lua/typstar/snippets/matrix.lua @@ -78,6 +78,6 @@ local lmat = function(_, sp) end return { - snip('(%d)(%d)ma', 'mat(\n\t<>\n)', { d(1, mat) }, math), - snip('(%d)(%d)lma', 'mat(\n\t<>\n)', { d(1, lmat) }, math), + snip('(\\d)(\\d)ma', 'mat(\n\t<>\n)', { d(1, mat) }, math), + snip('(\\d)(\\d)lma', 'mat(\n\t<>\n)', { d(1, lmat) }, math), } diff --git a/lua/typstar/snippets/visual.lua b/lua/typstar/snippets/visual.lua index 6bf869d..fef8eab 100644 --- a/lua/typstar/snippets/visual.lua +++ b/lua/typstar/snippets/visual.lua @@ -14,7 +14,7 @@ local operations = { -- boolean denotes whether an additional layer of () bracke { 'vi', '1/(', ')', true }, { 'bb', '(', ')', false }, { 'sq', '[', ']', true }, - { 'abs', '|', '|', false }, + { 'abs', 'abs(', ')', false }, { 'ul', 'underline(', ')', false }, { 'ol', 'overline(', ')', false }, { 'ub', 'underbrace(', ')', false }, @@ -61,12 +61,12 @@ end 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('[%s$]' .. val[1], val[2] .. '<>' .. val[3], { i(1, '1') }, math)) + table.insert(snippets, snip('[\\s$]' .. val[1], val[2] .. '<>' .. val[3], { i(1, '1') }, math)) table.insert(snippets, - snip('([%w]+)' + 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)) + snip('(.*[\\)|\\]|\\}])' .. val[1], '<>', { f(wrap_brackets, {}, { user_args = { val } }), nil }, math, 1100)) end return { diff --git a/lua/typstar/utils.lua b/lua/typstar/utils.lua index 1e378bf..e1c8dbe 100644 --- a/lua/typstar/utils.lua +++ b/lua/typstar/utils.lua @@ -1,7 +1,14 @@ local M = {} +local ts = vim.treesitter + +function M.get_cursor_pos() + local cursor_row, cursor_col = unpack(vim.api.nvim_win_get_cursor(0)) + cursor_row = cursor_row - 1 + return { cursor_row, cursor_col } +end function M.insert_snippet(snip) - local line_num = vim.fn.getcurpos()[2] + local line_num = M.get_cursor_pos()[1] + 1 local lines = {} for line in snip:gmatch '[^\r\n]+' do table.insert(lines, line) @@ -13,4 +20,34 @@ function M.run_shell_command(cmd) vim.fn.jobstart(cmd) end +function M.cursor_within_treesitter_query(query, match_tolerance, cursor) + cursor = cursor or M.get_cursor_pos() + local bufnr = vim.api.nvim_get_current_buf() + local root = ts.get_parser(bufnr):parse()[1]:root() + for _, match, _ in query:iter_matches(root, bufnr, cursor[1], cursor[1] + 1) do + if match then + local start_row, start_col, _, _ = match[1]:range() + local _, _, end_row, end_col = match[#match]:range() + local matched = M.cursor_within_coords(cursor, start_row, end_row, start_col, end_col, + match_tolerance) + if matched then + return true + end + end + end + return false +end + +function M.cursor_within_coords(cursor, start_row, end_row, start_col, end_col, match_tolerance) + if start_row <= cursor[1] and end_row >= cursor[1] then + if start_row == cursor[1] and start_col - match_tolerance >= cursor[2] then + return false + elseif end_row == cursor[1] and end_col + match_tolerance <= cursor[2] then + return false + end + return true + end + return false +end + return M