From fe0da6738dd884ea71890f64c173f7ecd407fe69 Mon Sep 17 00:00:00 2001 From: arne314 <73391160+arne314@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:18:39 +0100 Subject: [PATCH 1/5] feat: snippets for common indices --- lua/typstar/snippets/letters.lua | 35 ++++++++++++++++++++++++++------ lua/typstar/snippets/math.lua | 1 + 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/lua/typstar/snippets/letters.lua b/lua/typstar/snippets/letters.lua index 6d07553..50b661c 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..691faf5 100644 --- a/lua/typstar/snippets/math.lua +++ b/lua/typstar/snippets/math.lua @@ -62,6 +62,7 @@ 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('(.*)iv', '<>^(-1)', { cap(1) }, math), snip('(.*)sr', '<>^(2)', { cap(1) }, math), snip('(.*)rd', '<>^(<>)', { cap(1), i(1, 'n') }, math), From 39a73ca8cb515db55f9f40b462754dc35ebe29cb Mon Sep 17 00:00:00 2001 From: arne314 <73391160+arne314@users.noreply.github.com> Date: Wed, 4 Dec 2024 00:24:09 +0100 Subject: [PATCH 2/5] perf: jsregexp instead of lua pattern matching --- lua/typstar/autosnippets.lua | 11 +++++++++-- lua/typstar/snippets/letters.lua | 4 ++-- lua/typstar/snippets/math.lua | 6 ++---- lua/typstar/snippets/matrix.lua | 4 ++-- lua/typstar/snippets/visual.lua | 6 +++--- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lua/typstar/autosnippets.lua b/lua/typstar/autosnippets.lua index 8bf494e..3111cdd 100644 --- a/lua/typstar/autosnippets.lua +++ b/lua/typstar/autosnippets.lua @@ -32,7 +32,14 @@ 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 = 'ecma', + regTrig = true, + wordtrig = false, + priority = priority, + snippetType = 'autosnippet' + }, fmta(expand, { unpack(insert) }), { condition = function() @@ -46,7 +53,7 @@ function M.snip(trigger, expand, insert, condition, priority) 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.toggle_autosnippets() diff --git a/lua/typstar/snippets/letters.lua b/lua/typstar/snippets/letters.lua index 50b661c..4230039 100644 --- a/lua/typstar/snippets/letters.lua +++ b/lua/typstar/snippets/letters.lua @@ -29,7 +29,7 @@ local greek_letters = { { '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 common_indices = { '\\d+', 'i', 'j', 'k', 'n' } for _, letter in ipairs({ unpack(latin_letters) }) do table.insert(latin_letters, letter:upper()) @@ -40,7 +40,7 @@ local generate_index_snippets = function(letter) table.insert(letter_snippets, snip(letter .. '(' .. index .. ') ', letter .. '_(<>) ', { cap(1) }, math, 200)) table.insert(letter_snippets, - snip('$' .. letter .. '$(' .. index .. ') ', '$' .. letter .. '_(<>)$ ', { cap(1) }, markup, 200)) + snip('\\$' .. letter .. '\\$(' .. index .. ') ', '$' .. letter .. '_(<>)$ ', { cap(1) }, markup, 200)) end end diff --git a/lua/typstar/snippets/math.lua b/lua/typstar/snippets/math.lua index 691faf5..7a0ee5c 100644 --- a/lua/typstar/snippets/math.lua +++ b/lua/typstar/snippets/math.lua @@ -75,8 +75,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..693bf8c 100644 --- a/lua/typstar/snippets/visual.lua +++ b/lua/typstar/snippets/visual.lua @@ -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 { From 999fd0dfef4b1c7ce3b8b3afbcb742ee274f6e84 Mon Sep 17 00:00:00 2001 From: arne314 <73391160+arne314@users.noreply.github.com> Date: Wed, 4 Dec 2024 01:11:41 +0100 Subject: [PATCH 3/5] perf: eval snippet condition before regex --- lua/typstar/autosnippets.lua | 38 +++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/lua/typstar/autosnippets.lua b/lua/typstar/autosnippets.lua index 3111cdd..d8bf6a4 100644 --- a/lua/typstar/autosnippets.lua +++ b/lua/typstar/autosnippets.lua @@ -2,7 +2,15 @@ local M = {} local cfg = require('typstar.config').config.snippets local luasnip = require('luasnip') local fmta = require('luasnip.extras.fmt').fmta +local lsengines = require('luasnip.nodes.util.trig_engines') +local last_keystroke_time = nil +vim.api.nvim_create_autocmd("TextChangedI", { + callback = function() + last_keystroke_time = vim.loop.now() + end, +}) +local lexical_result_cache = {} 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 @@ -34,7 +42,8 @@ function M.snip(trigger, expand, insert, condition, priority) return luasnip.snippet( { trig = trigger, - trigEngine = 'ecma', + trigEngine = M.engine, + trigEngineOpts = { condition = condition }, regTrig = true, wordtrig = false, priority = priority, @@ -42,12 +51,7 @@ function M.snip(trigger, expand, insert, condition, priority) }, 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 @@ -56,8 +60,28 @@ function M.start_snip(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() From 5529a29327b20fd2f19e0391e15a05be600a1e4b Mon Sep 17 00:00:00 2001 From: arne314 <73391160+arne314@users.noreply.github.com> Date: Sun, 8 Dec 2024 18:39:58 +0100 Subject: [PATCH 4/5] perf: use treesitter to detect math/markup --- lua/typstar/autosnippets.lua | 20 +++++++++++------ lua/typstar/snippets/math.lua | 1 + lua/typstar/snippets/visual.lua | 2 +- lua/typstar/utils.lua | 38 ++++++++++++++++++++++++++++++++- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/lua/typstar/autosnippets.lua b/lua/typstar/autosnippets.lua index d8bf6a4..c2b7d2e 100644 --- a/lua/typstar/autosnippets.lua +++ b/lua/typstar/autosnippets.lua @@ -1,24 +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 local last_keystroke_time = nil -vim.api.nvim_create_autocmd("TextChangedI", { +vim.api.nvim_create_autocmd('TextChangedI', { callback = function() last_keystroke_time = vim.loop.now() end, }) local lexical_result_cache = {} -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 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_inside_treesitter_query(ts_math_query, cursor) + and not utils.cursor_inside_treesitter_query(ts_string_query, cursor) +end +M.in_markup = function() return utils.cursor_inside_treesitter_query(ts_markup_query) 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) diff --git a/lua/typstar/snippets/math.lua b/lua/typstar/snippets/math.lua index 7a0ee5c..b9d5aa8 100644 --- a/lua/typstar/snippets/math.lua +++ b/lua/typstar/snippets/math.lua @@ -63,6 +63,7 @@ return { 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), diff --git a/lua/typstar/snippets/visual.lua b/lua/typstar/snippets/visual.lua index 693bf8c..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 }, diff --git a/lua/typstar/utils.lua b/lua/typstar/utils.lua index 1e378bf..4bc849b 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,33 @@ function M.run_shell_command(cmd) vim.fn.jobstart(cmd) end +function M.cursor_inside_treesitter_query(query, 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_inside_coords(cursor, start_row, end_row, start_col, end_col) + if matched then + return true + end + end + end + return false +end + +function M.cursor_inside_coords(cursor, start_row, end_row, start_col, end_col) + if start_row <= cursor[1] and end_row >= cursor[1] then + if start_row == cursor[1] and start_col > cursor[2] then + return false + elseif end_row == cursor[1] and end_col < cursor[2] then + return false + end + return true + end + return false +end + return M From 628290704e7f33d98fb0cde045debad7f69e2763 Mon Sep 17 00:00:00 2001 From: arne314 <73391160+arne314@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:10:58 +0100 Subject: [PATCH 5/5] fix: common index snippets in markup --- lua/typstar/autosnippets.lua | 8 +++----- lua/typstar/utils.lua | 11 ++++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lua/typstar/autosnippets.lua b/lua/typstar/autosnippets.lua index c2b7d2e..1790302 100644 --- a/lua/typstar/autosnippets.lua +++ b/lua/typstar/autosnippets.lua @@ -19,10 +19,10 @@ local ts_string_query = ts.query.parse('typst', '(string) @string') M.in_math = function() local cursor = utils.get_cursor_pos() - return utils.cursor_inside_treesitter_query(ts_math_query, cursor) - and not utils.cursor_inside_treesitter_query(ts_string_query, cursor) + 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_inside_treesitter_query(ts_markup_query) 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.snippets_toggle = true @@ -50,8 +50,6 @@ function M.snip(trigger, expand, insert, condition, priority) trig = trigger, trigEngine = M.engine, trigEngineOpts = { condition = condition }, - regTrig = true, - wordtrig = false, priority = priority, snippetType = 'autosnippet' }, diff --git a/lua/typstar/utils.lua b/lua/typstar/utils.lua index 4bc849b..e1c8dbe 100644 --- a/lua/typstar/utils.lua +++ b/lua/typstar/utils.lua @@ -20,7 +20,7 @@ function M.run_shell_command(cmd) vim.fn.jobstart(cmd) end -function M.cursor_inside_treesitter_query(query, cursor) +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() @@ -28,7 +28,8 @@ function M.cursor_inside_treesitter_query(query, cursor) if match then local start_row, start_col, _, _ = match[1]:range() local _, _, end_row, end_col = match[#match]:range() - local matched = M.cursor_inside_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) if matched then return true end @@ -37,11 +38,11 @@ function M.cursor_inside_treesitter_query(query, cursor) return false end -function M.cursor_inside_coords(cursor, start_row, end_row, start_col, end_col) +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 > cursor[2] then + if start_row == cursor[1] and start_col - match_tolerance >= cursor[2] then return false - elseif end_row == cursor[1] and end_col < cursor[2] then + elseif end_row == cursor[1] and end_col + match_tolerance <= cursor[2] then return false end return true