Merge pull request #14 from arne314/dev

V1.3.2
This commit is contained in:
Arne
2025-05-07 17:51:19 +02:00
committed by GitHub
12 changed files with 137 additions and 78 deletions

27
flake.lock generated
View File

@@ -5,11 +5,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1736143030,
"narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=",
"lastModified": 1743550720,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de",
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1737525964,
"narHash": "sha256-3wFonKmNRWKq1himW9N3TllbeGIHFACI5vmLpk6moF8=",
"lastModified": 1746518791,
"narHash": "sha256-MiJ11L7w18S2G5ftcoYtcrrS0JFqBaj9d5rwJFpC5Wk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5757bbb8bd7c0630a0cc4bb19c47e588db30b97c",
"rev": "1cb1c02a6b1b7cf67e3d7731cbbf327a53da9679",
"type": "github"
},
"original": {
@@ -36,14 +36,17 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1735774519,
"narHash": "sha256-CewEm1o2eVAnoqb6Ml+Qi9Gg/EfNAxbRx1lANGVyoLI=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz"
"lastModified": 1743296961,
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
"type": "github"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz"
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {

View File

@@ -28,6 +28,10 @@
pkgs.vimPlugins.luasnip
pkgs.vimPlugins.nvim-treesitter-parsers.typst
];
# TODO: make this check pass instead of skipping
neovimRequireCheckHook = ''
echo "Skipping neovimRequireCheckHook"
'';
};
in {
packages = {
@@ -43,7 +47,8 @@
}
require('luasnip').config.set_config({
enable_autosnippets = true,
enable_autosnippets = true,
store_selection_keys = "<Tab>",
})
require('typstar').setup()

View File

@@ -32,23 +32,34 @@ function M.cap(i)
return luasnip.function_node(function(_, snip) return snip.captures[i] end)
end
function M.leading_white_spaces(i)
-- isolate whitespaces of captured group
return luasnip.function_node(function(_, snip)
local capture = snip.captures[i] or '' -- Return capture or empty string if nil
-- Extract only leading whitespace using pattern matching
local whitespace = capture:match('^%s*') or ''
return whitespace
end)
local compute_leading_white_spaces = function(snip, i)
local capture = snip.captures[i] or ''
return capture:match('^%s*') or ''
end
function M.visual(idx, default)
function M.leading_white_spaces(i)
return luasnip.function_node(function(_, snip) return compute_leading_white_spaces(snip, i) end)
end
function M.visual(idx, default, line_prefix, indent_capture_idx)
default = default or ''
return luasnip.dynamic_node(idx, function(args, parent)
if #parent.snippet.env.LS_SELECT_RAW > 0 then
return luasnip.snippet_node(nil, luasnip.text_node(parent.snippet.env.LS_SELECT_RAW))
line_prefix = line_prefix or ''
return luasnip.dynamic_node(idx, function(_, snip)
local select_raw = snip.snippet.env.LS_SELECT_RAW
if #select_raw > 0 then
if line_prefix ~= '' then -- e.g. indentation
for i, s in ipairs(select_raw) do
select_raw[i] = line_prefix .. s
end
end
return luasnip.snippet_node(nil, luasnip.text_node(select_raw))
else -- If LS_SELECT_RAW is empty, return an insert node
return luasnip.snippet_node(nil, luasnip.insert_node(1, default))
local leading = ''
if indent_capture_idx ~= nil then leading = compute_leading_white_spaces(snip, indent_capture_idx) end
return luasnip.snippet_node(nil, {
luasnip.text_node(leading .. line_prefix),
luasnip.insert_node(1, default),
})
end
end)
end
@@ -57,14 +68,18 @@ 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, wordTrig, maxTrigLength)
function M.snip(trigger, expand, insert, condition, priority, trigOptions)
priority = priority or 1000
if wordTrig == nil then wordTrig = true end
trigOptions = vim.tbl_deep_extend('force', {
maxTrigLength = nil,
wordTrig = true,
blacklist = {},
}, trigOptions or {})
return luasnip.snippet(
{
trig = trigger,
trigEngine = M.engine,
trigEngineOpts = { condition = condition, wordTrig = wordTrig, maxTrigLength = maxTrigLength },
trigEngineOpts = vim.tbl_deep_extend('keep', { condition = condition }, trigOptions),
wordTrig = false,
priority = priority,
snippetType = 'autosnippet',
@@ -76,18 +91,18 @@ function M.snip(trigger, expand, insert, condition, priority, wordTrig, maxTrigL
)
end
function M.start_snip(trigger, expand, insert, condition, priority)
return M.snip('^(\\s*)' .. trigger, '<>' .. expand, { M.cap(1), unpack(insert) }, condition, priority)
function M.start_snip(trigger, expand, insert, condition, priority, trigOptions)
return M.snip('^(\\s*)' .. trigger, '<>' .. expand, { M.cap(1), unpack(insert) }, condition, priority, trigOptions)
end
function M.start_snip_in_newl(trigger, expand, insert, condition, priority)
function M.start_snip_in_newl(trigger, expand, insert, condition, priority, trigOptions)
return M.snip(
'([^\\s]\\s+)' .. trigger,
'<>\n<>' .. expand,
{ M.cap(1), M.leading_white_spaces(1), unpack(insert) },
condition,
priority,
false
vim.tbl_deep_extend('keep', { wordTrig = false }, trigOptions or {})
)
end
@@ -99,7 +114,7 @@ function M.engine(trigger, opts)
-- determine possibly max/fixed length of trigger
local max_length = opts.maxTrigLength
local is_fixed_length = false
if alts_regex ~= '' and not trigger:match('[%+%*]') then
if max_length == nil and alts_regex ~= '' and not trigger:match('[%+%*]') then
max_length = #trigger
- utils.count_string(trigger, '\\')
- utils.count_string(trigger, '%(')
@@ -137,10 +152,11 @@ function M.engine(trigger, opts)
end
-- matching
return function(line, trig)
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
local first_idx = #line - max_length -- include additional char for wordtrig
first_idx = #line_full - max_length -- include additional char for wordtrig
if first_idx < 0 then
if is_fixed_length then
return nil
@@ -149,10 +165,10 @@ function M.engine(trigger, opts)
end
end
if first_idx > 0 then
if string.byte(line, first_idx) > 127 then return nil end
if string.byte(line_full, first_idx) > 127 then return nil end
end
line = line:sub(first_idx)
end
local line = line_full:sub(first_idx)
local whole, captures = base_engine(line, trig)
if whole == nil then return nil end
@@ -161,6 +177,11 @@ function M.engine(trigger, opts)
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
@@ -177,7 +198,7 @@ function M.setup()
jsregexp_ok, jsregexp = pcall(require, 'jsregexp')
end
if jsregexp_ok then
alts_regex = jsregexp.compile_safe(alts_regex)
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)

View File

@@ -116,8 +116,7 @@ return {
{ d(1, get_index, {}, { user_args = { 1, 2, false } }), d(2, prepend_space, {}, { user_args = { 3 } }) },
markup,
500,
true,
13
{ maxTrigLength = 13 }
),
snip(
'(' .. trigger_index_pre .. ')' .. '(' .. trigger_index_post .. ')([^\\w])',
@@ -125,14 +124,13 @@ return {
{ d(1, get_index, {}, { user_args = { 1, 2, true } }), d(2, prepend_space, {}, { user_args = { 3 } }) },
math,
200,
true,
10 -- epsilon123
{ maxTrigLength = 10 } -- epsilon123
),
-- series of numbered letters
snip('(' .. trigger_index_pre .. ') ot ', '<>_1, <>_2, ... ', { cap(1), cap(1) }, math), -- a_1, a_2, ...
snip('(' .. trigger_index_pre .. ') ot(\\w+) ', '<> ', { d(1, get_series) }, math, 1000, true, 13), -- a_1, a_2, ... a_j or a_1, a_2, a_2, a_3, a_4, a_5
snip('(' .. trigger_index_pre .. ') ot(\\w+) ', '<> ', { d(1, get_series) }, math, 1000, { maxTrigLength = 13 }), -- a_1, a_2, ... a_j or a_1, a_2, a_2, a_3, a_4, a_5
-- misc
snip('(' .. trigger_index_pre .. ')bl', 'B_<> (<>)', { cap(1), i(1, 'x_0') }, math),
snip('(' .. trigger_index_pre .. ')bl', 'B_<> (<>) ', { cap(1), i(1, 'x_0') }, math, 100),
}

View File

@@ -4,13 +4,15 @@ local i = ls.insert_node
local helper = require('typstar.autosnippets')
local cap = helper.cap
local markup = helper.in_markup
local visual = helper.visual
local snip = helper.snip
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' },
@@ -28,29 +30,24 @@ local wrappings = {
}
local document_snippets = {}
local ctheoremsstr = '#%s[\n<>\t<>\n<>]'
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]), { cap(1), visual(1), cap(1) }, markup)
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]), { visual(1, val[4]) }, markup)
local snippet = snip(val[1], string.format(wrappingsstr, val[2], val[3]), { helper.visual(1, val[4]) }, markup)
table.insert(document_snippets, snippet)
end
return {
start('dm', '$\n<>\t<>\n<>$', { cap(1), visual(1), cap(1) }, markup),
helper.start_snip_in_newl(
'dm',
'$\n<>\t<>\n<>$',
{ helper.leading_white_spaces(1), visual(1), helper.leading_white_spaces(1) },
markup
),
start('fla', '#flashcard(0)[<>][\n<>\t<>\n<>]', { i(1, 'flashcard'), cap(1), visual(2), cap(1) }, markup),
start('flA', '#flashcard(0, "<>")[\n<>\t<>\n<>]', { i(1, 'flashcard'), cap(1), visual(2), cap(1) }, markup),
start('dm', '$\n<>\n<>$', { indent_visual(1), cap(1) }, markup),
helper.start_snip_in_newl('dm', '$\n<>\n<>$', { indent_visual(1), helper.leading_white_spaces(1) }, markup),
start('fla', '#flashcard(0)[<>][\n<>\n<>]', { i(1, 'flashcard'), indent_visual(2), cap(1) }, markup),
start('flA', '#flashcard(0, "<>")[\n<>\n<>]', { i(1, 'flashcard'), indent_visual(2), cap(1) }, markup),
snip('IMP', '$==>>$ ', {}, markup),
snip('IFF', '$<<==>>$ ', {}, markup),
unpack(document_snippets),

View File

@@ -19,7 +19,7 @@ return {
snip('een', 'exists epsilon>>0 ', {}, math),
-- boolean logic
snip('no', 'not ', {}, math),
snip('not', 'not ', {}, math),
snip('ip', '==>> ', {}, math),
snip('ib', '<<== ', {}, math),
snip('iff', '<<==>> ', {}, math),
@@ -34,8 +34,8 @@ return {
snip('ge', '>>= ', {}, math),
-- operators
snip('ak([^k ])', '+ <>', { cap(1) }, math, 100, false),
snip('sk([^k ])', '- <>', { cap(1) }, math, 100, false),
snip('ak([^k ])', '+ <>', { cap(1) }, math, 100, { wordTrig = false }),
snip('sk([^k ])', '- <>', { cap(1) }, math, 100, { wordTrig = false }),
snip('oak', 'plus.circle ', {}, math),
snip('bak', 'plus.square ', {}, math),
snip('mak', 'plus.minus ', {}, math),
@@ -45,11 +45,11 @@ return {
snip('ff', '(<>) / (<>) <>', { i(1, 'a'), i(2, 'b'), i(3) }, math),
-- exponents
snip('iv', '^(-1) ', {}, math, 500, false),
snip('sr', '^2 ', {}, math, 500, false),
snip('cb', '^3 ', {}, math, 500, false),
snip('jj', '_(<>) ', { i(1, 'n') }, math, 500, false),
snip('kk', '^(<>) ', { i(1, 'n') }, math, 500, false),
snip('iv', '^(-1) ', {}, math, 500, { wordTrig = false, blacklist = { 'equiv' } }),
snip('sr', '^2 ', {}, math, 500, { wordTrig = false }),
snip('cb', '^3 ', {}, math, 500, { wordTrig = false }),
snip('jj', '_(<>) ', { i(1, 'n') }, math, 500, { wordTrig = false }),
snip('kk', '^(<>) ', { i(1, 'n') }, math, 500, { wordTrig = false }),
snip('ep', 'exp(<>) ', { i(1, '1') }, math),
-- sets
@@ -73,6 +73,10 @@ return {
snip('Oo', 'compose ', {}, math),
snip('iso', 'tilde.equiv ', {}, math),
snip('cc', 'cases(\n\t<>\n)\\', { i(1, '1') }, math),
snip('([A-Za-z])o([A-Za-z0-9])', '<>(<>) ', { cap(1), cap(2) }, math, 100, {
maxTrigLength = 3,
blacklist = { 'bot', 'cos', 'col', 'com', 'con', 'dol', 'dot', 'loz', 'mod', 'top', 'won', 'xor' },
}),
snip('(K|M|N|Q|R|S|Z)([\\dn]) ', '<><>^<> ', { cap(1), cap(1), cap(2) }, math),
snip('dx', 'dif / (dif <>) ', { i(1, 'x') }, math, 900),
@@ -90,5 +94,12 @@ return {
snip('lm', 'lim ', {}, math),
snip('lim', 'lim_(<> ->> <>) ', { i(1, 'n'), i(2, 'oo') }, math),
snip('lim (sup|inf)', 'lim<> ', { cap(1) }, math),
snip('lim(_\\(\\s?\\w+\\s?->\\s?\\w+\\s?\\)) (sup|inf)', 'lim<><> ', { cap(2), cap(1) }, math, 1000, true, 25),
snip(
'lim(_\\(\\s?\\w+\\s?->\\s?\\w+\\s?\\)) (sup|inf)',
'lim<><> ',
{ cap(2), cap(1) },
math,
1000,
{ maxTrigLength = 25 }
),
}

View File

@@ -61,7 +61,7 @@ local lmat = function(_, sp)
ins_indx = ins_indx + 1
for k = 2, cols do
table.insert(nodes, t(', '))
if k == cols then table.insert(nodes, t('dots, ')) end
if k == cols then table.insert(nodes, t('dots.c, ')) end
if j == k then
table.insert(nodes, r(ins_indx, tostring(j) .. 'x' .. tostring(k), i(1, '1')))
else

View File

@@ -21,6 +21,7 @@ local operations = { -- first boolean: existing brackets should be kept; second
{ 'sQ', '[', ']', false, false }, -- replace with square brackets
{ 'BB', '', '', false, false }, -- remove brackets
{ 'ss', '"', '"', false, false },
{ 'agl', 'lr(angle.l ', ' angle.r)', false, false },
{ 'abs', 'abs', '', true, true },
{ 'ul', 'underline', '', true, true },
{ 'ol', 'overline', '', true, true },
@@ -42,9 +43,9 @@ local ts_wrap_query = ts.query.parse('typst', '[(call) (ident) (letter) (number)
local ts_wrapnobrackets_query = ts.query.parse('typst', '(group) @wrapnobrackets')
local process_ts_query = function(bufnr, cursor, query, root, insert1, insert2, cut_offset)
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)
for _, match in ipairs(utils.treesitter_iter_matches(root, query, bufnr, cursor[1], cursor[1] + 1)) do
for _, nodes in pairs(match) do
local start_row, start_col, end_row, end_col = utils.treesitter_match_start_end(nodes)
if end_row == cursor[1] and end_col == cursor[2] then
vim.schedule(function() -- to not interfere with luasnip
local cursor_offset = 0
@@ -81,7 +82,10 @@ local smart_wrap = function(args, parent, old_state, expand)
end
for _, val in pairs(operations) do
table.insert(snippets, snip(val[1], '<>', { d(1, smart_wrap, {}, { user_args = { val } }) }, math, 1500, false))
table.insert(
snippets,
snip(val[1], '<>', { d(1, smart_wrap, {}, { user_args = { val } }) }, math, 1500, { wordTrig = false })
)
end
return {

View File

@@ -71,6 +71,21 @@ end
function M.get_treesitter_root(bufnr) return ts.get_parser(bufnr):parse()[1]:root() end
function M.treesitter_iter_matches(root, query, bufnr, start, stop)
local result = {}
local idx = 1
for _, matches, _ in query:iter_matches(root, bufnr, start, stop) do
if #matches then
if type(matches[1]) == 'userdata' then -- nvim version < 0.11
matches = { matches }
end
result[idx] = matches
idx = idx + 1
end
end
return result
end
function M.treesitter_match_start_end(match)
local start_row, start_col, _, _ = match[1]:range()
local _, _, end_row, end_col = match[#match]:range()
@@ -80,9 +95,10 @@ 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()
for _, match, _ in query:iter_matches(M.get_treesitter_root(bufnr), bufnr, cursor[1], cursor[1] + 1) do
if match then
local start_row, start_col, end_row, end_col = M.treesitter_match_start_end(match)
local root = M.get_treesitter_root(bufnr)
for _, match in ipairs(M.treesitter_iter_matches(root, query, bufnr, cursor[1], cursor[1] + 1)) do
for _, nodes in pairs(match) do
local start_row, start_col, end_row, end_col = M.treesitter_match_start_end(nodes)
local matched = M.cursor_within_coords(cursor, start_row, end_row, start_col, end_col, match_tolerance)
if matched then return true end
end

View File

@@ -4,7 +4,7 @@ build-backend = "pdm.backend"
[project]
name = "typstar"
version = "1.3.1"
version = "1.3.2"
description = "Neovim plugin for efficient note taking in Typst"
authors = [
{ name = "arne314" }

View File

@@ -1,3 +1,5 @@
import html
import tree_sitter
from .file_handler import FileHandler
@@ -48,7 +50,9 @@ class Flashcard:
return f"#flashcard({self.note_id})[{self.front if front else ''}][{self.back if not front else ''}]"
def as_html(self, front: bool) -> str:
prefix = f"<p hidden>{self.front}: {self.back}{' ' * 10}</p>" # indexable via anki search
safe_front = html.escape(self.front)
safe_back = html.escape(self.back)
prefix = f"<p hidden>{safe_front}: {safe_back}{' ' * 10}</p>" # indexable via anki search
image = f'<img src="{self.svg_filename(front)}" />'
return prefix + image

2
uv.lock generated
View File

@@ -437,7 +437,7 @@ wheels = [
[[package]]
name = "typstar"
version = "1.3.1"
version = "1.3.2"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },