mirror of
https://github.com/Ascyii/typstar.git
synced 2026-01-01 05:24:24 -05:00
refactor(snip): separate engine and helpers
This commit is contained in:
@@ -1,32 +1,5 @@
|
||||
local M = {}
|
||||
local cfg = require('typstar.config').config.snippets
|
||||
local M = require('typstar.engine') -- inherit all functions
|
||||
local luasnip = require('luasnip')
|
||||
local utils = require('typstar.utils')
|
||||
local fmta = require('luasnip.extras.fmt').fmta
|
||||
local lsengines = require('luasnip.nodes.util.trig_engines')
|
||||
local ts = vim.treesitter
|
||||
|
||||
local exclude_triggers_set = {}
|
||||
local last_keystroke_time = nil
|
||||
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')
|
||||
|
||||
utils.generate_bool_set(cfg.exclude, exclude_triggers_set)
|
||||
vim.api.nvim_create_autocmd('TextChangedI', {
|
||||
callback = function() last_keystroke_time = vim.loop.now() end,
|
||||
})
|
||||
|
||||
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.snippets_toggle = true
|
||||
|
||||
function M.cap(i)
|
||||
return luasnip.function_node(function(_, snip) return snip.captures[i] end)
|
||||
@@ -68,33 +41,6 @@ function M.ri(insert_node_id)
|
||||
return luasnip.function_node(function(args) return args[1][1] end, insert_node_id)
|
||||
end
|
||||
|
||||
function M.snip(trigger, expand, insert, condition, priority, options)
|
||||
priority = priority or 1000
|
||||
options = vim.tbl_deep_extend('force', {
|
||||
maxTrigLength = nil,
|
||||
wordTrig = true,
|
||||
blacklist = {},
|
||||
prepend = nil,
|
||||
}, options or {})
|
||||
if options.prepend ~= nil then
|
||||
expand, insert = M.blocktransform(expand, insert, options.prepend, true)
|
||||
end
|
||||
return luasnip.snippet(
|
||||
{
|
||||
trig = trigger,
|
||||
trigEngine = M.engine,
|
||||
trigEngineOpts = vim.tbl_deep_extend('keep', { condition = condition }, options),
|
||||
wordTrig = false,
|
||||
priority = priority,
|
||||
snippetType = 'autosnippet',
|
||||
},
|
||||
fmta(expand, { unpack(insert) }),
|
||||
{
|
||||
condition = function() return M.snippets_toggle end,
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
function M.start_snip(trigger, expand, insert, condition, priority, options)
|
||||
return M.snip('^(\\s*)' .. trigger, '<>' .. expand, { M.cap(1), unpack(insert) }, condition, priority, options)
|
||||
end
|
||||
@@ -176,119 +122,4 @@ function M.bulletpoint_snip(trigger, expand, insert, condition, priority, option
|
||||
options
|
||||
)
|
||||
end
|
||||
|
||||
local alts_regex = '[\\[\\(](.*|.*)[\\)\\]]'
|
||||
|
||||
function M.engine(trigger, opts)
|
||||
local base_engine = lsengines.ecma(trigger, opts)
|
||||
|
||||
-- determine possibly max/fixed length of trigger
|
||||
local max_length = opts.maxTrigLength
|
||||
local is_fixed_length = false
|
||||
if max_length == nil and alts_regex ~= '' and not trigger:match('[%+%*]') then
|
||||
max_length = #trigger
|
||||
- utils.count_string(trigger, '\\')
|
||||
- utils.count_string(trigger, '%(')
|
||||
- utils.count_string(trigger, '%)')
|
||||
- utils.count_string(trigger, '%?')
|
||||
is_fixed_length = not trigger:match('[%+%*%?%[%]|]')
|
||||
|
||||
local alts_match = alts_regex:match(trigger) -- find longest trigger in [...|...]
|
||||
if alts_match then
|
||||
for _, alts in ipairs(alts_match) do
|
||||
local max_alt_length = 1
|
||||
for alt in alts:gmatch('([^|]+)') do
|
||||
local len
|
||||
if alt:match('%[.*-.*%]') then -- [A-Za-z0-9] and similar
|
||||
len = 2
|
||||
else
|
||||
len = #alt
|
||||
end
|
||||
max_alt_length = math.max(max_alt_length, len)
|
||||
end
|
||||
max_length = max_length - (#alts - max_alt_length)
|
||||
end
|
||||
else -- [^...] and similar
|
||||
max_length = max_length - utils.count_string(trigger, '%[') - utils.count_string(trigger, '%]')
|
||||
end
|
||||
end
|
||||
|
||||
-- cache preanalysis results
|
||||
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
|
||||
|
||||
-- matching
|
||||
return function(line_full, trig)
|
||||
if not M.snippets_toggle or not condition() then return nil end
|
||||
local first_idx = 1
|
||||
if max_length ~= nil then
|
||||
first_idx = #line_full - max_length -- include additional char for wordtrig
|
||||
if first_idx < 0 then
|
||||
if is_fixed_length then
|
||||
return nil
|
||||
else
|
||||
first_idx = 1
|
||||
end
|
||||
end
|
||||
if first_idx > 0 then
|
||||
if string.byte(line_full, first_idx) > 127 then return nil end
|
||||
end
|
||||
end
|
||||
local line = line_full:sub(first_idx)
|
||||
local whole, captures = base_engine(line, trig)
|
||||
if whole == nil then return nil end
|
||||
|
||||
-- custom word trig
|
||||
local from = #line - #whole + 1
|
||||
if opts.wordTrig and from ~= 1 and string.match(string.sub(line, from - 1, from - 1), '[%w.]') ~= nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- blacklist
|
||||
for _, w in ipairs(opts.blacklist) do
|
||||
if line_full:sub(-#w) == w then return nil end
|
||||
end
|
||||
return whole, captures
|
||||
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()
|
||||
if cfg.enable then
|
||||
local jsregexp_ok, jsregexp = pcall(require, 'luasnip-jsregexp')
|
||||
if not jsregexp_ok then
|
||||
jsregexp_ok, jsregexp = pcall(require, 'jsregexp')
|
||||
end
|
||||
if jsregexp_ok then
|
||||
if type(alts_regex) == 'string' then alts_regex = jsregexp.compile_safe(alts_regex) end
|
||||
else
|
||||
alts_regex = ''
|
||||
vim.notify("WARNING: Most snippets won't work as jsregexp is not installed", vim.log.levels.WARN)
|
||||
end
|
||||
local autosnippets = {}
|
||||
for _, file in ipairs(cfg.modules) do
|
||||
for _, sn in ipairs(require(('typstar.snippets.%s'):format(file))) do
|
||||
local exclude
|
||||
local is_start = sn.trigger:match('^%^%(\\s%*%)')
|
||||
if is_start then
|
||||
exclude = exclude_triggers_set[sn.trigger:sub(7)]
|
||||
else
|
||||
exclude = exclude_triggers_set[sn.trigger]
|
||||
end
|
||||
if not exclude then table.insert(autosnippets, sn) end
|
||||
end
|
||||
end
|
||||
luasnip.add_snippets('typst', autosnippets)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
172
lua/typstar/engine.lua
Normal file
172
lua/typstar/engine.lua
Normal file
@@ -0,0 +1,172 @@
|
||||
local M = {}
|
||||
local cfg = require('typstar.config').config.snippets
|
||||
local luasnip = require('luasnip')
|
||||
local utils = require('typstar.utils')
|
||||
local fmta = require('luasnip.extras.fmt').fmta
|
||||
local lsengines = require('luasnip.nodes.util.trig_engines')
|
||||
local ts = vim.treesitter
|
||||
|
||||
local exclude_triggers_set = {}
|
||||
local last_keystroke_time = nil
|
||||
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')
|
||||
|
||||
utils.generate_bool_set(cfg.exclude, exclude_triggers_set)
|
||||
vim.api.nvim_create_autocmd('TextChangedI', {
|
||||
callback = function() last_keystroke_time = vim.loop.now() end,
|
||||
})
|
||||
|
||||
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.snippets_toggle = true
|
||||
|
||||
function M.snip(trigger, expand, insert, condition, priority, options)
|
||||
priority = priority or 1000
|
||||
options = vim.tbl_deep_extend('force', {
|
||||
maxTrigLength = nil,
|
||||
wordTrig = true,
|
||||
blacklist = {},
|
||||
prepend = nil,
|
||||
}, options or {})
|
||||
if options.prepend ~= nil then
|
||||
expand, insert = M.blocktransform(expand, insert, options.prepend, true)
|
||||
end
|
||||
return luasnip.snippet(
|
||||
{
|
||||
trig = trigger,
|
||||
trigEngine = M.engine,
|
||||
trigEngineOpts = vim.tbl_deep_extend('keep', { condition = condition }, options),
|
||||
wordTrig = false,
|
||||
priority = priority,
|
||||
snippetType = 'autosnippet',
|
||||
},
|
||||
fmta(expand, { unpack(insert) }),
|
||||
{
|
||||
condition = function() return M.snippets_toggle end,
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
local alts_regex = '[\\[\\(](.*|.*)[\\)\\]]'
|
||||
|
||||
function M.engine(trigger, opts)
|
||||
local base_engine = lsengines.ecma(trigger, opts)
|
||||
|
||||
-- determine possibly max/fixed length of trigger
|
||||
local max_length = opts.maxTrigLength
|
||||
local is_fixed_length = false
|
||||
if max_length == nil and alts_regex ~= '' and not trigger:match('[%+%*]') then
|
||||
max_length = #trigger
|
||||
- utils.count_string(trigger, '\\')
|
||||
- utils.count_string(trigger, '%(')
|
||||
- utils.count_string(trigger, '%)')
|
||||
- utils.count_string(trigger, '%?')
|
||||
is_fixed_length = not trigger:match('[%+%*%?%[%]|]')
|
||||
|
||||
local alts_match = alts_regex:match(trigger) -- find longest trigger in [...|...]
|
||||
if alts_match then
|
||||
for _, alts in ipairs(alts_match) do
|
||||
local max_alt_length = 1
|
||||
for alt in alts:gmatch('([^|]+)') do
|
||||
local len
|
||||
if alt:match('%[.*-.*%]') then -- [A-Za-z0-9] and similar
|
||||
len = 2
|
||||
else
|
||||
len = #alt
|
||||
end
|
||||
max_alt_length = math.max(max_alt_length, len)
|
||||
end
|
||||
max_length = max_length - (#alts - max_alt_length)
|
||||
end
|
||||
else -- [^...] and similar
|
||||
max_length = max_length - utils.count_string(trigger, '%[') - utils.count_string(trigger, '%]')
|
||||
end
|
||||
end
|
||||
|
||||
-- cache preanalysis results
|
||||
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
|
||||
|
||||
-- matching
|
||||
return function(line_full, trig)
|
||||
if not M.snippets_toggle or not condition() then return nil end
|
||||
local first_idx = 1
|
||||
if max_length ~= nil then
|
||||
first_idx = #line_full - max_length -- include additional char for wordtrig
|
||||
if first_idx < 0 then
|
||||
if is_fixed_length then
|
||||
return nil
|
||||
else
|
||||
first_idx = 1
|
||||
end
|
||||
end
|
||||
if first_idx > 0 then
|
||||
if string.byte(line_full, first_idx) > 127 then return nil end
|
||||
end
|
||||
end
|
||||
local line = line_full:sub(first_idx)
|
||||
local whole, captures = base_engine(line, trig)
|
||||
if whole == nil then return nil end
|
||||
|
||||
-- custom word trig
|
||||
local from = #line - #whole + 1
|
||||
if opts.wordTrig and from ~= 1 and string.match(string.sub(line, from - 1, from - 1), '[%w.]') ~= nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- blacklist
|
||||
for _, w in ipairs(opts.blacklist) do
|
||||
if line_full:sub(-#w) == w then return nil end
|
||||
end
|
||||
return whole, captures
|
||||
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()
|
||||
if cfg.enable then
|
||||
local jsregexp_ok, jsregexp = pcall(require, 'luasnip-jsregexp')
|
||||
if not jsregexp_ok then
|
||||
jsregexp_ok, jsregexp = pcall(require, 'jsregexp')
|
||||
end
|
||||
if jsregexp_ok then
|
||||
if type(alts_regex) == 'string' then alts_regex = jsregexp.compile_safe(alts_regex) end
|
||||
else
|
||||
alts_regex = ''
|
||||
vim.notify("WARNING: Most snippets won't work as jsregexp is not installed", vim.log.levels.WARN)
|
||||
end
|
||||
local autosnippets = {}
|
||||
for _, file in ipairs(cfg.modules) do
|
||||
for _, sn in ipairs(require(('typstar.snippets.%s'):format(file))) do
|
||||
local exclude
|
||||
local is_start = sn.trigger:match('^%^%(\\s%*%)')
|
||||
if is_start then
|
||||
exclude = exclude_triggers_set[sn.trigger:sub(7)]
|
||||
else
|
||||
exclude = exclude_triggers_set[sn.trigger]
|
||||
end
|
||||
if not exclude then table.insert(autosnippets, sn) end
|
||||
end
|
||||
end
|
||||
luasnip.add_snippets('typst', autosnippets)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user