Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 1.6.0 - 2026-06-14
PR #28

### Changed
- theorem snippet triggers have less conflicts (`thm`, `prf`, `prp`, `lmm`, `crl`, `axm`, `dfn`, `exm`, `rmr`)
- theorem snippets have been moved into a separate module (enabled by default)
- math triggers allow numbers in front of them (e.g. `3x1 ` for `3x_1 `)
- `br` snippet uses `dash` instead of `macron`
- matrix triggers are now `<dim>ma ` and `<dim>ma.`

### Fixed
- Anki string fronts will be compiled in Typst strings for full unicode compatibility
- matrix snippet edge cases (e.g. with dimension 1)
- letter prime snippet conflict edge cases

## 1.5.1 - 2026-03-27

### Changed
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Note that some greek letters have multiple latin ones mapped to them.
Markup snippets:
- Begin inline math with `kk` and multiline math with `dm`
- [Markup shorthands](./lua/typstar/snippets/markup.lua) (e.g. `HIG` &#8594; `#highlight[<cursor>]`, `IMP` &#8594; `$==>$ `)
- [ctheorems shorthands](./lua/typstar/snippets/markup.lua) (e.g. `tem` &#8594; empty theorem, `exa` &#8594; empty example)
- [ctheorems shorthands](./lua/typstar/snippets/markup.lua) (e.g. `thm` &#8594; empty theorem, `exm` &#8594; empty example)
- [Flashcards](#anki): `fla` and `flA`
- All above snippets support visual mode via the [selection key](#installation)

Expand All @@ -40,7 +40,7 @@ Math snippets:
- Series of numbered letters: `<letter> <z/o>t<optional last index> ` &#8594; `<letter>_<0/1>, <letter>_<1/2>, ... ` (e.g. `a ot ` &#8594; `a_1, a_2, ... `, `a zt4 ` &#8594; `a_0, a_1, a_2, a_3, a_4 `, `alpha otk ` &#8594; `alpha_1, alpha_2, ..., alpha_k `, `oti ` &#8594; `1, 2, ..., i `)
- Wrapping of any mathematical expression (see [operations](./lua/typstar/snippets/visual.lua), works nested, multiline and in visual mode via the [selection key](#installation)): `<expression><operation>` &#8594; `<operation>(<expression>)` (e.g. `(a^2+b^2)rt` &#8594; `sqrt(a^2+b^2)`, `lambdatd` &#8594; `tilde(lambda)`, `(1+1)sQ` &#8594; `[1+1]`, `(1+1)sq` &#8594; `[(1+1)]`)
- Simple functions: `fo<value> ` &#8594; `f(<value>) ` (e.g. `fox ` &#8594; `f(x) `, `ao5 ` &#8594; `a(5) `)
- Matrices: `<size>ma` and `<size>lma` (e.g. `23ma` &#8594; 2x3 matrix)
- Matrices: `<size>ma ` and `<size>ma.` (e.g. `23ma ` &#8594; 2x3 matrix and `32ma.` &#8594; 3x2 matrix with dots)

Note that you can [customize](#custom-snippets) (enable, disable and modify) every snippet.

Expand Down
2 changes: 1 addition & 1 deletion lua/tests/basic_init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ vim.opt.rtp:prepend(os.getenv('NVIM_PLUGIN_DEV') or '')
local ls = require('luasnip')
ls.config.set_config({
enable_autosnippets = true,
store_selection_keys = '<Tab>',
cut_selection_keys = '<Tab>',
})

local typstar = require('typstar')
Expand Down
5 changes: 4 additions & 1 deletion lua/tests/snippets/letters.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ helper:add_cases('letters', {
['index_greek'] = function() helper:test_snip(';b1 ', '$beta_1$ ') end,
['index_math'] = function() helper:test_snip_math(';a1 ', 'alpha_1 ') end,
['index_long'] = function() helper:test_snip(';e123 ', '$epsilon_123$ ') end,
['index_factor'] = function() helper:test_snip_math('3;f1 ', '3phi_1 ') end,
['index_prime'] = function() helper:test_snip(";e123'", "$epsilon'_123$ ") end,
['index_prime2'] = function() helper:test_snip(";e'123 ", "$epsilon'_123$ ") end,
['punctuation'] = function() helper:test_snip('$Xi$ 5.', '$Xi_5$.') end,
['punctuation2'] = function() helper:test_snip('$Xi$ 5,', '$Xi_5$, ') end,
['punctuation3'] = function() helper:test_snip("$Xi$ '.", "$Xi'$.") end,
['punctuation4'] = function() helper:test_snip("$Xi$ ',", "$Xi'$, ") end,
['conflict'] = function() helper:test_snip_math('Pi ', 'Pi ') end,
['conflict'] = function() helper:test_snip_math('Pi Aa ', 'Pi Aa ') end,
['conflict2'] = function() helper:test_snip_math(";x'1 ;x'1,;x'1. ;x'1;;x'1!", "xi'_1 xi'_1, xi'_1. xi'_1; xi'_1!") end,
['conflict3'] = function() helper:test_snip_math("Pin pii ;x' ;x, ;x>0 x'i ", "Pi_n pi_i xi' xi, xi>0 x'_i ") end,
['series'] = function() helper:test_snip_math('otk ', '1, 2, ..., k ') end,
['series2'] = function() helper:test_snip_math('zt5 ', '0, 1, ..., 5 ') end,
['series3'] = function() helper:test_snip_math('alpha otm ', 'alpha_1, alpha_2, ..., alpha_m ') end,
Expand Down
2 changes: 2 additions & 0 deletions lua/tests/snippets/math.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ helper:add_cases('math', {
['lim'] = function() helper:test_snip_math('limx\\j0\\jx', 'lim_(x -> 0) x') end,
['limsup'] = function() helper:test_snip_math('limx\\j0\\jsupx', 'limsup_(x -> 0) x') end,
['superscript'] = function() helper:test_snip_math('aivbsrMcmp', 'a^(-1) b^2 M^complement ') end,
['function'] = function() helper:test_snip_math('cos cox cot ', 'cos c(x) cot ') end,
['conflict'] = function() helper:test_snip_math('Aivequiv', 'A^(-1) equiv') end,
})

-- ensure space at end of expanded snippets
Expand Down
35 changes: 35 additions & 0 deletions lua/tests/snippets/matrix.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local helper = require('tests.helper'):setup()

helper:add_cases('matrix', {
['square'] = function() helper:test_snip_math('22ma ', 'mat(\n 1, ;\n , 1;\n)') end,
['rect'] = function() helper:test_snip_math('23ma ', 'mat(\n 1, , ;\n , 1, ;\n)') end,
['rect2'] = function() helper:test_snip_math('32ma ', 'mat(\n 1, ;\n , 1;\n , ;\n)') end,
['row'] = function() helper:test_snip_math('13ma ', 'mat(\n 1, , ;\n)') end,
['column'] = function() helper:test_snip_math('31ma ', 'mat(\n 1;\n ;\n ;\n)') end,
['zero'] = function() helper:test_snip_math('05ma ', 'mat(\n \n)') end,
['one'] = function() helper:test_snip_math('11ma ', 'mat(\n 1;\n)') end,
})

helper:add_cases('matrix_dots', {
['square'] = function()
helper:test_snip_math('22ma.', 'mat(\n 1, dots.c, 0;\n dots.v, dots.down, dots.v;\n 0, dots.c, 1;\n)')
end,
['rect'] = function()
helper:test_snip_math(
'23ma.',
'mat(\n 1, 0, dots.c, 0;\n dots.v, dots.v, dots.down, dots.v;\n 0, 1, dots.c, 0;\n)'
)
end,
['rect2'] = function()
helper:test_snip_math(
'32ma.',
'mat(\n 1, dots.c, 0;\n 0, dots.c, 1;\n dots.v, dots.down, dots.v;\n 0, dots.c, 0;\n)'
)
end,
['row'] = function() helper:test_snip_math('13ma.', 'mat(\n 1, 0, dots.c, 0;\n)') end,
['column'] = function() helper:test_snip_math('31ma.', 'mat(\n 1;\n 0;\n dots.v;\n 0;\n)') end,
['zero'] = function() helper:test_snip_math('00ma.', 'mat(\n \n)') end,
['one'] = function() helper:test_snip_math('11ma.', 'mat(\n 1;\n)') end,
})

return helper.test_set
2 changes: 1 addition & 1 deletion lua/tests/snippets/treesitter.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
local helper = require('tests.helper'):setup()

helper:add_cases('treesitter_markup', {
['start'] = function() helper:test_snip('temabc', '#theorem[\n abc\n]') end,
['start'] = function() helper:test_snip('thmabc', '#theorem[\n abc\n]') end,
['no_start'] = function() helper:test_snip('1 temabc') end,
['no_math'] = function() helper:test_snip('asr') end,
['wordtrig'] = function() helper:test_snip(']kk1', ']$1$') end,
Expand Down
2 changes: 1 addition & 1 deletion lua/tests/snippets/visual.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local helper = require('tests.helper'):setup()

helper:add_cases('visual_selection', {
['markup'] = function() helper:test_snip('kk', '$a+b+c$') end,
['markup_multiline'] = function() helper:test_snip('tem', '#theorem[\n a+b+c\n]') end,
['markup_multiline'] = function() helper:test_snip('thm', '#theorem[\n a+b+c\n]') end,
['precedence'] = function() helper:test_snip_math('(a)ht', '(a)hat(a+b+c)') end,
['precedence2'] = function() helper:test_snip_math('aht', 'ahat(a+b+c)') end,
['nested'] = function()
Expand Down
1 change: 1 addition & 0 deletions lua/typstar/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ local default_config = {
'math',
'matrix',
'markup',
'theorems',
'visual',
},
exclude = {}, -- list of triggers to exclude
Expand Down
18 changes: 15 additions & 3 deletions lua/typstar/engine.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ M.in_markup = function() return utils.cursor_within_treesitter_query(ts_markup_q
M.not_in_math = function() return not M.in_math() end
M.not_in_markup = function() return not M.in_markup() end
M.wordtrig_patterns = {
[M.in_math] = '[%w.]',
[M.in_math] = '[%a.]',
}
M.snippets_toggle = true

Expand Down Expand Up @@ -90,6 +90,14 @@ local alts_regex = '[\\[\\(](.*|.*)[\\)\\]]'
function M.engine(trigger, opts)
local base_engine = lsengines.ecma(trigger, opts)

-- blacklist setup
local blacklist_set = {}
local blacklist_length_set = {}
for _, black in ipairs(opts.blacklist) do
blacklist_length_set[#black] = true
end
utils.generate_bool_set(opts.blacklist, blacklist_set)

-- determine possibly max/fixed length of trigger
local max_length = opts.maxTrigLength
local is_fixed_length = false
Expand Down Expand Up @@ -159,8 +167,12 @@ function M.engine(trigger, opts)
end

-- blacklist
for _, w in ipairs(opts.blacklist) do
if line_full:sub(-#w) == w then return nil end
if opts.wordTrig then
if blacklist_set[whole] then return nil end
else
for length in pairs(blacklist_length_set) do
if blacklist_set[line_full:sub(-length)] then return nil end
end
end
return whole, captures
end
Expand Down
4 changes: 2 additions & 2 deletions lua/typstar/init.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
local M = {}
local luasnip
local config = require('typstar.config')
local VERSION = '1.5.1'
local VERSION_INFO = 'BREAKING: changed inline math trigger: ll -> kk'
local VERSION = '1.6.0'
local VERSION_INFO = 'Breaking: Changed theorem triggers (thm, lmm, prf, ...)\nSee lua/typstar/snippets/theorems.lua'

local notify_plugin_update = function()
local key = 'typstar_version'
Expand Down
25 changes: 17 additions & 8 deletions lua/typstar/snippets/letters.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,28 @@ local greek_keys = {}
local greek_letters_set = {}
local common_indices = { '\\d+', '[i-n]' }
-- builtins and calligraphic letters from github.com/lentilus/typst-scribe
local index_conflicts = { 'Im', 'in', 'ln', 'Pi', 'pi', 'Xi', 'xi', 'Ii', 'Jj', 'Kk', 'Ll', 'Mm', 'Nn' }
local index_conflicts_set = {}
local index_blacklist = { 'Im', 'in', 'ln', 'Pi', 'pi', 'Xi', 'xi', 'Ii', 'Jj', 'Kk', 'Ll', 'Mm', 'Nn' }
local index_blacklist_set = {}
local index_blacklist_full = {}
local punctuation_prepend_space = { ',', ';', "'" }
local punctuation_prepend_space_set = {}
local trigger_greek = ''
local trigger_index_pre = ''
local trigger_index_post = ''
utils.generate_bool_set(index_blacklist, index_blacklist_set)
utils.generate_bool_set(punctuation_prepend_space, punctuation_prepend_space_set)

local upper_first = function(str) return str:sub(1, 1):upper() .. str:sub(2, -1) end

-- fill blacklist
for _, conflict in ipairs(index_blacklist) do
table.insert(index_blacklist_full, conflict .. ' ')
for punct in pairs(punctuation_prepend_space_set) do
table.insert(index_blacklist_full, conflict .. punct)
end
end

-- fill latin greek map
local greek_full = {}
for latin, greek in pairs(greek_letters_map) do
greek_full[latin] = greek
Expand All @@ -64,20 +76,17 @@ for latin, greek in pairs(greek_letters_map) do
table.insert(greek_keys, latin:upper())
end

utils.generate_bool_set(index_conflicts, index_conflicts_set)
utils.generate_bool_set(punctuation_prepend_space, punctuation_prepend_space_set)

greek_letters_map = greek_full
trigger_greek = table.concat(greek_keys, '|')
trigger_index_pre = '[A-Za-z]' .. '|' .. table.concat(greek_letters_set, '|')
trigger_index_pre = '[A-Za-z]' .. '|' .. table.concat(greek_letters_set, '|') .. '|Pi|pi'
trigger_index_post = table.concat(common_indices, '|')

local get_greek = function(_, snippet) return s(nil, t(greek_letters_map[snippet.captures[1]])) end

local get_index = function(_, snippet, _, idx_letter, idx_prime, idx_index, check_conflict)
local letter, prime, index = snippet.captures[idx_letter], snippet.captures[idx_prime], snippet.captures[idx_index]
local trigger = letter .. index
if check_conflict and index_conflicts_set[trigger] then return s(nil, t(trigger)) end
if check_conflict and prime == '' and index_blacklist_set[trigger] then return s(nil, t(trigger)) end
if snippet.trigger:sub(-1) == "'" then prime = "'" end
return s(nil, t(letter .. prime .. '_' .. index))
end
Expand Down Expand Up @@ -134,7 +143,7 @@ return {
{ d(1, get_index, {}, { user_args = { 1, 2, 3, true } }), d(2, prepend_space, {}, { user_args = { 4 } }) },
math,
200,
{ maxTrigLength = 11 } -- epsilon'123
{ maxTrigLength = 11, blacklist = index_blacklist_full } -- epsilon'123
),

-- series of numbered letters
Expand Down
18 changes: 0 additions & 18 deletions lua/typstar/snippets/markup.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,6 @@ local start = helper.start_snip

local indent_visual = function(idx, default) return helper.visual(idx, default or '', '\t', 1) end

local ctheorems = {
{ 'tem', 'theorem' },
{ 'pro', 'proof' },
{ 'prp', 'proposition' },
{ 'axi', 'axiom' },
{ 'cor', 'corollary' },
{ 'lem', 'lemma' },
{ 'def', 'definition' },
{ 'exa', 'example' },
{ 'rem', 'remark' },
}

local wrappings = {
{ 'kk', '$', '$', '1+1' },
{ 'BLD', '*', '*', 'abc' },
Expand All @@ -30,14 +18,8 @@ local wrappings = {
}

local document_snippets = {}
local ctheoremsstr = '#%s[\n<>\n<>]'
local wrappingsstr = '%s<>%s'

for _, val in pairs(ctheorems) do
local snippet = start(val[1], string.format(ctheoremsstr, val[2]), { indent_visual(1), cap(1) }, markup)
table.insert(document_snippets, snippet)
end

for _, val in pairs(wrappings) do
local snippet = snip(val[1], string.format(wrappingsstr, val[2], val[3]), { helper.visual(1, val[4]) }, markup)
table.insert(document_snippets, snippet)
Expand Down
40 changes: 24 additions & 16 deletions lua/typstar/snippets/matrix.lua
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
local ls = require('luasnip')
local i = ls.insert_node
local d = ls.dynamic_node
local sn = ls.snippet_node
local s = ls.snippet_node
local t = ls.text_node
local r = ls.restore_node

local helper = require('typstar.autosnippets')
local snip = helper.snip
local math = helper.in_math

-- generating function
local parse_nums = function(snippet)
local rows = tonumber(snippet.captures[1])
local cols = tonumber(snippet.captures[2])
if rows == 0 or cols == 0 then return s(nil, i(1, '')), nil, nil end
return nil, rows, cols
end

local mat = function(_, sp)
local rows = tonumber(sp.captures[1])
local cols = tonumber(sp.captures[2])
local replace, rows, cols = parse_nums(sp)
if replace ~= nil then return replace end
local nodes = {}
local ins_indx = 1
for j = 1, rows do
Expand All @@ -34,21 +40,23 @@ local mat = function(_, sp)
table.insert(nodes, t({ ';', '\t' }))
end
nodes[#nodes] = t(';')
return sn(nil, nodes)
return s(nil, nodes)
end

local lmat = function(_, sp)
local rows = tonumber(sp.captures[1])
local cols = tonumber(sp.captures[2])
local dotmat = function(_, sp)
local replace, rows, cols = parse_nums(sp)
if replace ~= nil then return replace end
local nodes = {}
local ins_indx = 1
for j = 1, rows do
if j == rows then
for k = 1, cols + 1 do
if k == cols then
table.insert(nodes, t('dots.down, '))
elseif k == cols + 1 then
if j == rows and j ~= 1 then
local last = cols
if cols > 1 then last = cols + 1 end
for k = 1, last do
if k == last then
table.insert(nodes, t('dots.v'))
elseif k == cols then
table.insert(nodes, t('dots.down, '))
else
table.insert(nodes, t('dots.v, '))
end
Expand All @@ -74,10 +82,10 @@ local lmat = function(_, sp)
table.insert(nodes, t({ ';', '\t' }))
end
nodes[#nodes] = t(';')
return sn(nil, nodes)
return s(nil, nodes)
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, 1500),
snip('(\\d)(\\d)ma.', 'mat(\n\t<>\n)', { d(1, dotmat) }, math, 1500),
}
32 changes: 32 additions & 0 deletions lua/typstar/snippets/theorems.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
local helper = require('typstar.autosnippets')

local indent_visual = function(idx, default) return helper.visual(idx, default or '', '\t', 1) end

local environments = {
{ 'thm', 'theorem' },
{ 'prf', 'proof' },
{ 'prp', 'proposition' },
{ 'axm', 'axiom' },
{ 'crl', 'corollary' },
{ 'lmm', 'lemma' },
{ 'dfn', 'definition' },
{ 'exm', 'example' },
{ 'rmr', 'remark' },
}

local theorem_snippets = {}
local theorem_string = '#%s[\n<>\n<>]'

for _, val in pairs(environments) do
table.insert(
theorem_snippets,
helper.start_snip(
val[1],
string.format(theorem_string, val[2]),
{ indent_visual(1), helper.cap(1) },
helper.in_markup
)
)
end

return theorem_snippets
Loading