mirror of
https://github.com/Ascyii/typstar.git
synced 2026-01-01 13:34:24 -05:00
Merge pull request #8 from arne314/dev: V1.2.0
Anki and snippet improvements
This commit is contained in:
10
.stylua.toml
Normal file
10
.stylua.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
column_width = 120
|
||||||
|
line_endings = "Unix"
|
||||||
|
indent_type = "Spaces"
|
||||||
|
indent_width = 4
|
||||||
|
quote_style = "AutoPreferSingle"
|
||||||
|
call_parentheses = "Always"
|
||||||
|
collapse_simple_statement = "Always"
|
||||||
|
|
||||||
|
[sort_requires]
|
||||||
|
enabled = true
|
||||||
16
README.md
16
README.md
@@ -12,13 +12,20 @@ Neovim plugin for efficient note taking in Typst
|
|||||||
Use `:TypstarToggleSnippets` to toggle all snippets at any time.
|
Use `:TypstarToggleSnippets` to toggle all snippets at any time.
|
||||||
Available snippets can mostly be intuitively derived from [here](././lua/typstar/snippets), they include:
|
Available snippets can mostly be intuitively derived from [here](././lua/typstar/snippets), they include:
|
||||||
|
|
||||||
|
Markup snippets:
|
||||||
|
- Begin inline math with `ll` and multiline math with `dm`
|
||||||
|
- [Markup shorthands](./lua/typstar/snippets/markup.lua) (e.g. `HIG` → `#highlight[<cursor>]`, `IMP` → `$=>$ `)
|
||||||
|
- [ctheorems shorthands](./lua/typstar/snippets/markup.lua) (e.g. `tem` → empty theorem, `exa` → empty example)
|
||||||
|
- [Flashcards](#anki): `fla` and `flA`
|
||||||
|
- All above snippets support visual mode via the [selection key](#installation)
|
||||||
|
|
||||||
|
Math snippets:
|
||||||
|
- [Many shorthands](./lua/typstar/snippets/math.lua) for mathematical expressions
|
||||||
- Alphanumeric characters: `:<char>` → `$<char>$ ` in markup (e.g. `:X` → `$X$ `, `:5` → `$5$ `)
|
- Alphanumeric characters: `:<char>` → `$<char>$ ` in markup (e.g. `:X` → `$X$ `, `:5` → `$5$ `)
|
||||||
- Greek letters: `;<latin>` → `<greek>` in math and `$<greek>$ ` in markup (e.g. `;a` → `alpha`/`$alpha$ `)
|
- Greek letters: `;<latin>` → `<greek>` in math and `$<greek>$ ` in markup (e.g. `;a` → `alpha`/`$alpha$ `)
|
||||||
- Common indices (numbers and letters `i-n`): `<letter><index>` → `<letter>_<index>` in math and `$<letter>$<index> ` → `$<letter>_<index>$ ` in markup (e.g `A314` → `A_314`, `$alpha$n ` → `$alpha_n$ `)
|
- Common indices (numbers and letters `i-n`): `<letter><index>` → `<letter>_<index>` in math and `$<letter>$<index> ` → `$<letter>_<index>$ ` in markup (e.g `A314` → `A_314`, `$alpha$n ` → `$alpha_n$ `)
|
||||||
- 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>` → `<operation>(<expression>)` (e.g. `(a^2+b^2)rt` → `sqrt(a^2+b^2)`, `lambdatd` → `tilde(lambda)`, `(1+1)sQ` → `[1+1]`, `(1+1)sq` → `[(1+1)]`)
|
- 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>` → `<operation>(<expression>)` (e.g. `(a^2+b^2)rt` → `sqrt(a^2+b^2)`, `lambdatd` → `tilde(lambda)`, `(1+1)sQ` → `[1+1]`, `(1+1)sq` → `[(1+1)]`)
|
||||||
- Matrices: `<size>ma` and `<size>lma` (e.g. `23ma` → 2x3 matrix)
|
- Matrices: `<size>ma` and `<size>lma` (e.g. `23ma` → 2x3 matrix)
|
||||||
- [ctheorems shorthands](./lua/typstar/snippets/document.lua) (e.g. `tem` → empty theorem, `exa` → empty example)
|
|
||||||
- [Many shorthands](./lua/typstar/snippets/math.lua) for mathematical expressions
|
|
||||||
|
|
||||||
Note that you can enable and disable collections of snippets in the [config](#configuration).
|
Note that you can enable and disable collections of snippets in the [config](#configuration).
|
||||||
|
|
||||||
@@ -50,6 +57,7 @@ To render the flashcard in your document as well add some code like this
|
|||||||
```
|
```
|
||||||
|
|
||||||
- Add a comment like `// ANKI: MY::DECK` to your document to set a deck used for all flashcards after this comment (You can use multiple decks per file)
|
- Add a comment like `// ANKI: MY::DECK` to your document to set a deck used for all flashcards after this comment (You can use multiple decks per file)
|
||||||
|
- Add a file named `.anki` containing a deck name to define a default deck on a directory base
|
||||||
- Add a file named `.anki.typ` to define a preamble on a directory base. You can find the default preamble [here](./src/anki/typst_compiler.py).
|
- Add a file named `.anki.typ` to define a preamble on a directory base. You can find the default preamble [here](./src/anki/typst_compiler.py).
|
||||||
- Tip: Despite the use of SVGs you can still search your flashcards in Anki as the typst source is added into an invisible html paragraph
|
- Tip: Despite the use of SVGs you can still search your flashcards in Anki as the typst source is added into an invisible html paragraph
|
||||||
|
|
||||||
@@ -72,9 +80,9 @@ require('typstar').setup({
|
|||||||
|
|
||||||
### Snippets
|
### Snippets
|
||||||
1. Install [LuaSnip](https://github.com/L3MON4D3/LuaSnip/), set `enable_autosnippets = true` and set a visual mode selection key (e.g. `store_selection_keys = '<Tab>'`) in the configuration
|
1. Install [LuaSnip](https://github.com/L3MON4D3/LuaSnip/), set `enable_autosnippets = true` and set a visual mode selection key (e.g. `store_selection_keys = '<Tab>'`) in the configuration
|
||||||
2. Install [jsregexp](https://github.com/kmarius/jsregexp) as described [here](https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#transformations) (running `:lua require('jsregexp')` in nvim should not result in an error)
|
2. Install [jsregexp](https://github.com/kmarius/jsregexp) as described [here](https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#transformations) (You will see a warning on startup if jsregexp isn't installed properly)
|
||||||
3. Install [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) and run `:TSInstall typst`
|
3. Install [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) and run `:TSInstall typst`
|
||||||
4. Optional: Setup [ctheorems](https://typst.app/universe/package/ctheorems/) with names like [here](./lua/typstar/snippets/document.lua)
|
4. Optional: Setup [ctheorems](https://typst.app/universe/package/ctheorems/) with names like [here](./lua/typstar/snippets/markup.lua)
|
||||||
|
|
||||||
### Excalidraw
|
### Excalidraw
|
||||||
1. Install [Obsidian](https://obsidian.md/) and create a vault in your typst note taking directory
|
1. Install [Obsidian](https://obsidian.md/) and create a vault in your typst note taking directory
|
||||||
|
|||||||
@@ -4,29 +4,26 @@ local utils = require('typstar.utils')
|
|||||||
|
|
||||||
local cfg = config.config.anki
|
local cfg = config.config.anki
|
||||||
|
|
||||||
|
|
||||||
local function run_typstar_anki(args)
|
local function run_typstar_anki(args)
|
||||||
local cwd = vim.fn.getcwd()
|
local cwd = vim.fn.getcwd()
|
||||||
local anki_key = ''
|
local anki_key = ''
|
||||||
if cfg.ankiKey ~= nil then
|
if cfg.ankiKey ~= nil then anki_key = ' --anki-key ' .. cfg.ankiKey end
|
||||||
anki_key = ' --anki-key ' .. cfg.ankiKey
|
|
||||||
end
|
|
||||||
local cmd = string.format(
|
local cmd = string.format(
|
||||||
'%s --root-dir %s --typst-cmd %s --anki-url %s %s %s',
|
'%s --root-dir %s --typst-cmd %s --anki-url %s %s %s',
|
||||||
cfg.typstarAnkiCmd, cwd, cfg.typstCmd, cfg.ankiUrl, anki_key, args)
|
cfg.typstarAnkiCmd,
|
||||||
|
cwd,
|
||||||
|
cfg.typstCmd,
|
||||||
|
cfg.ankiUrl,
|
||||||
|
anki_key,
|
||||||
|
args
|
||||||
|
)
|
||||||
utils.run_shell_command(cmd, true)
|
utils.run_shell_command(cmd, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.scan()
|
function M.scan() run_typstar_anki('') end
|
||||||
run_typstar_anki('')
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.scan_force()
|
function M.scan_force() run_typstar_anki('--force-scan ' .. vim.fn.getcwd()) end
|
||||||
run_typstar_anki('--force-scan ' .. vim.fn.getcwd())
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.scan_force_current()
|
function M.scan_force_current() run_typstar_anki('--force-scan ' .. vim.fn.expand('%:p')) end
|
||||||
run_typstar_anki('--force-scan ' .. vim.fn.expand('%:p'))
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
local cfg = require('typstar.config').config.snippets
|
local cfg = require('typstar.config').config.snippets
|
||||||
local utils = require('typstar.utils')
|
|
||||||
local luasnip = require('luasnip')
|
local luasnip = require('luasnip')
|
||||||
|
local utils = require('typstar.utils')
|
||||||
local fmta = require('luasnip.extras.fmt').fmta
|
local fmta = require('luasnip.extras.fmt').fmta
|
||||||
local lsengines = require('luasnip.nodes.util.trig_engines')
|
local lsengines = require('luasnip.nodes.util.trig_engines')
|
||||||
local ts = vim.treesitter
|
local ts = vim.treesitter
|
||||||
|
|
||||||
local last_keystroke_time = nil
|
local last_keystroke_time = nil
|
||||||
vim.api.nvim_create_autocmd('TextChangedI', {
|
vim.api.nvim_create_autocmd('TextChangedI', {
|
||||||
callback = function()
|
callback = function() last_keystroke_time = vim.loop.now() end,
|
||||||
last_keystroke_time = vim.loop.now()
|
|
||||||
end,
|
|
||||||
})
|
})
|
||||||
local lexical_result_cache = {}
|
local lexical_result_cache = {}
|
||||||
local ts_markup_query = ts.query.parse('typst', '(text) @markup')
|
local ts_markup_query = ts.query.parse('typst', '(text) @markup')
|
||||||
@@ -31,12 +29,15 @@ function M.cap(i)
|
|||||||
return luasnip.function_node(function(_, snip) return snip.captures[i] end)
|
return luasnip.function_node(function(_, snip) return snip.captures[i] end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.get_visual(args, parent)
|
function M.visual(idx, default)
|
||||||
if (#parent.snippet.env.LS_SELECT_RAW > 0) then
|
default = default or ''
|
||||||
return luasnip.snippet_node(nil, luasnip.insert_node(1, parent.snippet.env.LS_SELECT_RAW))
|
return luasnip.dynamic_node(idx, function(args, parent)
|
||||||
else -- If LS_SELECT_RAW is empty, return a blank insert node
|
if #parent.snippet.env.LS_SELECT_RAW > 0 then
|
||||||
return luasnip.snippet_node(nil, luasnip.insert_node(1))
|
return luasnip.snippet_node(nil, luasnip.text_node(parent.snippet.env.LS_SELECT_RAW))
|
||||||
end
|
else -- If LS_SELECT_RAW is empty, return an insert node
|
||||||
|
return luasnip.snippet_node(nil, luasnip.insert_node(1, default))
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.ri(insert_node_id)
|
function M.ri(insert_node_id)
|
||||||
@@ -52,34 +53,30 @@ function M.snip(trigger, expand, insert, condition, priority, wordTrig)
|
|||||||
trigEngineOpts = { condition = condition },
|
trigEngineOpts = { condition = condition },
|
||||||
wordTrig = wordTrig,
|
wordTrig = wordTrig,
|
||||||
priority = priority,
|
priority = priority,
|
||||||
snippetType = 'autosnippet'
|
snippetType = 'autosnippet',
|
||||||
},
|
},
|
||||||
fmta(expand, { unpack(insert) }),
|
fmta(expand, { unpack(insert) }),
|
||||||
{
|
{
|
||||||
condition = function() return M.snippets_toggle end
|
condition = function() return M.snippets_toggle end,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.start_snip(trigger, expand, insert, condition, priority)
|
function M.start_snip(trigger, expand, insert, condition, priority)
|
||||||
return M.snip('^\\s*' .. trigger, expand, insert, condition, priority)
|
return M.snip('^(\\s*)' .. trigger, '<>' .. expand, { M.cap(1), unpack(insert) }, condition, priority)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.engine(trigger, opts)
|
function M.engine(trigger, opts)
|
||||||
local base_engine = lsengines.ecma(trigger, opts)
|
local base_engine = lsengines.ecma(trigger, opts)
|
||||||
local condition = function()
|
local condition = function()
|
||||||
local cached = lexical_result_cache[opts.condition]
|
local cached = lexical_result_cache[opts.condition]
|
||||||
if cached ~= nil and cached[1] == last_keystroke_time then
|
if cached ~= nil and cached[1] == last_keystroke_time then return cached[2] end
|
||||||
return cached[2]
|
|
||||||
end
|
|
||||||
local result = opts.condition()
|
local result = opts.condition()
|
||||||
lexical_result_cache[opts.condition] = { last_keystroke_time, result }
|
lexical_result_cache[opts.condition] = { last_keystroke_time, result }
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
return function(line, trig)
|
return function(line, trig)
|
||||||
if not M.snippets_toggle or not condition() then
|
if not M.snippets_toggle or not condition() then return nil end
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return base_engine(line, trig)
|
return base_engine(line, trig)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -93,12 +90,16 @@ function M.setup()
|
|||||||
if cfg.enable then
|
if cfg.enable then
|
||||||
local autosnippets = {}
|
local autosnippets = {}
|
||||||
for _, file in ipairs(cfg.modules) do
|
for _, file in ipairs(cfg.modules) do
|
||||||
vim.list_extend(
|
vim.list_extend(autosnippets, require(('typstar.snippets.%s'):format(file)))
|
||||||
autosnippets,
|
|
||||||
require(('typstar.snippets.%s'):format(file))
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
luasnip.add_snippets('typst', autosnippets)
|
luasnip.add_snippets('typst', autosnippets)
|
||||||
|
local jsregexp_ok, _ = pcall(require, 'luasnip-jsregexp')
|
||||||
|
if not jsregexp_ok then
|
||||||
|
jsregexp_ok, _ = pcall(require, 'jsregexp')
|
||||||
|
end
|
||||||
|
if not jsregexp_ok then
|
||||||
|
vim.notify("WARNING: Most snippets won't work as jsregexp is not installed", vim.log.levels.WARN)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ local default_config = {
|
|||||||
snippets = {
|
snippets = {
|
||||||
enable = true,
|
enable = true,
|
||||||
modules = { -- enable modules from ./snippets
|
modules = { -- enable modules from ./snippets
|
||||||
'document',
|
|
||||||
'letters',
|
'letters',
|
||||||
'math',
|
'math',
|
||||||
'matrix',
|
'matrix',
|
||||||
|
'markup',
|
||||||
'visual',
|
'visual',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function M.merge_config(args)
|
function M.merge_config(args)
|
||||||
M.config = vim.tbl_deep_extend('force', default_config, args or {})
|
M.config = vim.tbl_deep_extend('force', default_config, args or {})
|
||||||
M.config.excalidraw.templatePath = M.config.excalidraw.templatePath or
|
M.config.excalidraw.templatePath = M.config.excalidraw.templatePath
|
||||||
{
|
or {
|
||||||
['%.excalidraw%.md$'] = M.config.typstarRoot .. '/res/excalidraw_template.excalidraw.md',
|
['%.excalidraw%.md$'] = M.config.typstarRoot .. '/res/excalidraw_template.excalidraw.md',
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ local affix = [[
|
|||||||
local function launch_obsidian(path)
|
local function launch_obsidian(path)
|
||||||
print(string.format('Opening %s in Excalidraw', path))
|
print(string.format('Opening %s in Excalidraw', path))
|
||||||
utils.run_shell_command(
|
utils.run_shell_command(
|
||||||
string.format('%s "obsidian://open?path=%s"', cfg.uriOpenCommand, utils.urlencode(path)), false
|
string.format('%s "obsidian://open?path=%s"', cfg.uriOpenCommand, utils.urlencode(path)),
|
||||||
|
false
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -22,9 +23,7 @@ function M.insert_drawing()
|
|||||||
local path = assets_dir .. '/' .. filename .. cfg.fileExtension
|
local path = assets_dir .. '/' .. filename .. cfg.fileExtension
|
||||||
local path_inserted = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted
|
local path_inserted = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted
|
||||||
|
|
||||||
if vim.fn.isdirectory(assets_dir) == 0 then
|
if vim.fn.isdirectory(assets_dir) == 0 then vim.fn.mkdir(assets_dir, 'p') end
|
||||||
vim.fn.mkdir(assets_dir, 'p')
|
|
||||||
end
|
|
||||||
local found_match = false
|
local found_match = false
|
||||||
for pattern, template_path in pairs(cfg.templatePath) do
|
for pattern, template_path in pairs(cfg.templatePath) do
|
||||||
if string.match(path, pattern) then
|
if string.match(path, pattern) then
|
||||||
@@ -44,9 +43,10 @@ end
|
|||||||
|
|
||||||
function M.open_drawing()
|
function M.open_drawing()
|
||||||
local line = vim.api.nvim_get_current_line()
|
local line = vim.api.nvim_get_current_line()
|
||||||
local path = vim.fn.expand('%:p:h') ..
|
local path = vim.fn.expand('%:p:h')
|
||||||
'/' .. string.match(line, '"(.*)' .. string.gsub(cfg.fileExtensionInserted, '%.', '%%%.')) ..
|
.. '/'
|
||||||
'.excalidraw.md'
|
.. string.match(line, '"(.*)' .. string.gsub(cfg.fileExtensionInserted, '%.', '%%%.'))
|
||||||
|
.. '.excalidraw.md'
|
||||||
launch_obsidian(path)
|
launch_obsidian(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
local ls = require('luasnip')
|
|
||||||
local i = ls.insert_node
|
|
||||||
local d = ls.dynamic_node
|
|
||||||
|
|
||||||
local helper = require('typstar.autosnippets')
|
|
||||||
local snip = helper.snip
|
|
||||||
local start = helper.start_snip
|
|
||||||
local markup = helper.in_markup
|
|
||||||
|
|
||||||
|
|
||||||
local ctheorems = {
|
|
||||||
{ 'tem', 'theorem', markup },
|
|
||||||
{ 'pro', 'proof', markup },
|
|
||||||
{ 'axi', 'axiom', markup },
|
|
||||||
{ 'cor', 'corollary', markup },
|
|
||||||
{ 'lem', 'lemma', markup },
|
|
||||||
{ 'def', 'definition', markup },
|
|
||||||
{ 'exa', 'example', markup },
|
|
||||||
{ 'rem', 'remark', markup },
|
|
||||||
}
|
|
||||||
|
|
||||||
local ctheoremsstr = '#%s[\n\t<>\n]'
|
|
||||||
local document_snippets = {}
|
|
||||||
|
|
||||||
for _, val in pairs(ctheorems) do
|
|
||||||
local snippet = start(val[1], string.format(ctheoremsstr, val[2]), { i(1) }, val[3])
|
|
||||||
table.insert(document_snippets, snippet)
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
start('dm', '$\n\t<>\n$', { i(1) }, markup),
|
|
||||||
snip('ll', ' $<>$', { i(1, '1+1') }, markup),
|
|
||||||
start('fla', '#flashcard(0)[<>][\n\t<>\n]', { i(1, "flashcard"), i(2) }, markup),
|
|
||||||
start('flA', '#flashcard(0, "<>")[\n\t<>\n]', { i(1, "flashcard"), i(2) }, markup),
|
|
||||||
unpack(document_snippets),
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ local cap = helper.cap
|
|||||||
local math = helper.in_math
|
local math = helper.in_math
|
||||||
local markup = helper.in_markup
|
local markup = helper.in_markup
|
||||||
|
|
||||||
|
|
||||||
local letter_snippets = {}
|
local letter_snippets = {}
|
||||||
local greek_letters_map = {
|
local greek_letters_map = {
|
||||||
['a'] = 'alpha',
|
['a'] = 'alpha',
|
||||||
@@ -25,33 +24,36 @@ local greek_letters_map = {
|
|||||||
['m'] = 'mu',
|
['m'] = 'mu',
|
||||||
['n'] = 'nu',
|
['n'] = 'nu',
|
||||||
['o'] = 'omega',
|
['o'] = 'omega',
|
||||||
['p'] = 'pi',
|
['p'] = 'psi',
|
||||||
['q'] = 'eta',
|
['q'] = 'eta',
|
||||||
['r'] = 'rho',
|
['r'] = 'rho',
|
||||||
['s'] = 'sigma',
|
['s'] = 'sigma',
|
||||||
['t'] = 'tau',
|
['t'] = 'tau',
|
||||||
|
['v'] = 'nu',
|
||||||
|
['w'] = 'omega',
|
||||||
['x'] = 'xi',
|
['x'] = 'xi',
|
||||||
|
['y'] = 'upsilon',
|
||||||
['z'] = 'zeta',
|
['z'] = 'zeta',
|
||||||
}
|
}
|
||||||
local greek_letters = {}
|
|
||||||
local greek_keys = {}
|
local greek_keys = {}
|
||||||
|
local greek_letters_set = {}
|
||||||
local common_indices = { '\\d+', '[i-n]' }
|
local common_indices = { '\\d+', '[i-n]' }
|
||||||
local index_conflicts = { 'in', 'ln', 'pi', 'xi' }
|
local index_conflicts = { 'in', 'ln', 'pi', 'xi', 'ak', 'sk' }
|
||||||
local index_conflicts_set = {}
|
local index_conflicts_set = {}
|
||||||
local trigger_greek = ''
|
local trigger_greek = ''
|
||||||
local trigger_index_pre = ''
|
local trigger_index_pre = ''
|
||||||
local trigger_index_post = ''
|
local trigger_index_post = ''
|
||||||
|
|
||||||
local upper_first = function(str)
|
local upper_first = function(str) return str:sub(1, 1):upper() .. str:sub(2, -1) end
|
||||||
return str:sub(1, 1):upper() .. str:sub(2, -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local greek_full = {}
|
local greek_full = {}
|
||||||
for latin, greek in pairs(greek_letters_map) do
|
for latin, greek in pairs(greek_letters_map) do
|
||||||
greek_full[latin] = greek
|
greek_full[latin] = greek
|
||||||
greek_full[latin:upper()] = upper_first(greek)
|
greek_full[latin:upper()] = upper_first(greek)
|
||||||
table.insert(greek_letters, greek)
|
if not greek_letters_set[greek] then
|
||||||
table.insert(greek_letters, upper_first(greek))
|
table.insert(greek_letters_set, greek)
|
||||||
|
table.insert(greek_letters_set, upper_first(greek))
|
||||||
|
end
|
||||||
table.insert(greek_keys, latin)
|
table.insert(greek_keys, latin)
|
||||||
table.insert(greek_keys, latin:upper())
|
table.insert(greek_keys, latin:upper())
|
||||||
end
|
end
|
||||||
@@ -62,31 +64,36 @@ end
|
|||||||
|
|
||||||
greek_letters_map = greek_full
|
greek_letters_map = greek_full
|
||||||
trigger_greek = table.concat(greek_keys, '|')
|
trigger_greek = table.concat(greek_keys, '|')
|
||||||
trigger_index_pre = '[A-Za-z]' .. '|' .. table.concat(greek_letters, '|')
|
trigger_index_pre = '[A-Za-z]' .. '|' .. table.concat(greek_letters_set, '|')
|
||||||
trigger_index_post = table.concat(common_indices, '|')
|
trigger_index_post = table.concat(common_indices, '|')
|
||||||
|
|
||||||
local get_greek = function(_, snippet)
|
local get_greek = function(_, snippet) return s(nil, t(greek_letters_map[snippet.captures[1]])) end
|
||||||
return s(nil, t(greek_letters_map[snippet.captures[1]]))
|
|
||||||
end
|
|
||||||
|
|
||||||
local get_index = function(_, snippet)
|
local get_index = function(_, snippet)
|
||||||
local letter, index = snippet.captures[1], snippet.captures[2]
|
local letter, index = snippet.captures[1], snippet.captures[2]
|
||||||
local trigger = letter .. index
|
local trigger = letter .. index
|
||||||
if index_conflicts_set[trigger] then
|
if index_conflicts_set[trigger] then return s(nil, t(trigger)) end
|
||||||
return s(nil, t(trigger))
|
|
||||||
end
|
|
||||||
return s(nil, t(letter .. '_' .. index))
|
return s(nil, t(letter .. '_' .. index))
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(letter_snippets, snip(':([A-Za-z0-9])', '$<>$ ', { cap(1) }, markup))
|
table.insert(letter_snippets, snip(':([A-Za-z0-9])', '$<>$ ', { cap(1) }, markup))
|
||||||
table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '$<>$ ', { d(1, get_greek) }, markup))
|
table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '$<>$ ', { d(1, get_greek) }, markup))
|
||||||
table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '<>', { d(1, get_greek) }, math))
|
table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '<>', { d(1, get_greek) }, math))
|
||||||
table.insert(letter_snippets,
|
table.insert(
|
||||||
snip('\\$(' .. trigger_index_pre .. ')\\$' .. '(' .. trigger_index_post .. ') ',
|
letter_snippets,
|
||||||
'$<>$ ', { d(1, get_index) }, markup, 500))
|
snip(
|
||||||
table.insert(letter_snippets,
|
'\\$(' .. trigger_index_pre .. ')\\$' .. '(' .. trigger_index_post .. ') ',
|
||||||
snip('(' .. trigger_index_pre .. ')' .. '(' .. trigger_index_post .. ') ', '<> ', { d(1, get_index) }, math, 200))
|
'$<>$ ',
|
||||||
|
{ d(1, get_index) },
|
||||||
|
markup,
|
||||||
|
500
|
||||||
|
)
|
||||||
|
)
|
||||||
|
table.insert(
|
||||||
|
letter_snippets,
|
||||||
|
snip('(' .. trigger_index_pre .. ')' .. '(' .. trigger_index_post .. ') ', '<> ', { d(1, get_index) }, math, 200)
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unpack(letter_snippets)
|
unpack(letter_snippets),
|
||||||
}
|
}
|
||||||
|
|||||||
51
lua/typstar/snippets/markup.lua
Normal file
51
lua/typstar/snippets/markup.lua
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
local ls = require('luasnip')
|
||||||
|
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 ctheorems = {
|
||||||
|
{ 'tem', 'theorem' },
|
||||||
|
{ 'pro', 'proof' },
|
||||||
|
{ 'axi', 'axiom' },
|
||||||
|
{ 'cor', 'corollary' },
|
||||||
|
{ 'lem', 'lemma' },
|
||||||
|
{ 'def', 'definition' },
|
||||||
|
{ 'exa', 'example' },
|
||||||
|
{ 'rem', 'remark' },
|
||||||
|
}
|
||||||
|
|
||||||
|
local wrappings = {
|
||||||
|
{ 'll', '$', '$', '1+1' },
|
||||||
|
{ 'BLD', '*', '*', 'abc' },
|
||||||
|
{ 'ITL', '_', '_', 'abc' },
|
||||||
|
{ 'HIG', '#highlight[', ']', 'abc' },
|
||||||
|
{ 'UND', '#underline[', ']', 'abc' },
|
||||||
|
}
|
||||||
|
|
||||||
|
local document_snippets = {}
|
||||||
|
local ctheoremsstr = '#%s[\n<>\t<>\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)
|
||||||
|
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)
|
||||||
|
table.insert(document_snippets, snippet)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
start('dm', '$\n<>\t<>\n<>$', { cap(1), visual(1), cap(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),
|
||||||
|
snip('IMP', '$=>>$ ', {}, markup),
|
||||||
|
snip('IFF', '$<<=>>$ ', {}, markup),
|
||||||
|
unpack(document_snippets),
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ return {
|
|||||||
snip('no', 'not ', {}, math),
|
snip('no', 'not ', {}, math),
|
||||||
snip('ip', '==>> ', {}, math),
|
snip('ip', '==>> ', {}, math),
|
||||||
snip('ib', '<<== ', {}, math),
|
snip('ib', '<<== ', {}, math),
|
||||||
|
snip('iff', '<<=>> ', {}, math),
|
||||||
|
|
||||||
-- relations
|
-- relations
|
||||||
snip('el', '= ', {}, math),
|
snip('el', '= ', {}, math),
|
||||||
@@ -33,8 +34,8 @@ return {
|
|||||||
snip('ge', '>>= ', {}, math),
|
snip('ge', '>>= ', {}, math),
|
||||||
|
|
||||||
-- operators
|
-- operators
|
||||||
snip('ak([^k])', '+<>', { cap(1) }, math, 500, false),
|
snip('ak([^k])', '+<>', { cap(1) }, math, 100, false),
|
||||||
snip('sk([^k])', '-<>', { cap(1) }, math, 500, false),
|
snip('sk([^k])', '-<>', { cap(1) }, math, 100, false),
|
||||||
snip('oak', 'plus.circle ', {}, math, 1100),
|
snip('oak', 'plus.circle ', {}, math, 1100),
|
||||||
snip('bak', 'plus.square ', {}, math, 1100),
|
snip('bak', 'plus.square ', {}, math, 1100),
|
||||||
snip('mak', 'plus.minus ', {}, math, 1100),
|
snip('mak', 'plus.minus ', {}, math, 1100),
|
||||||
@@ -63,10 +64,11 @@ return {
|
|||||||
snip('Oo', 'compose ', {}, math),
|
snip('Oo', 'compose ', {}, math),
|
||||||
snip('iso', 'tilde.equiv ', {}, math),
|
snip('iso', 'tilde.equiv ', {}, math),
|
||||||
snip('ep', 'exp(<>) ', { i(1, '1') }, math),
|
snip('ep', 'exp(<>) ', { i(1, '1') }, math),
|
||||||
snip('rrn', 'RR^n ', {}, math),
|
|
||||||
snip('cc', 'cases(\n\t<>\n)\\', { i(1, '1') }, math),
|
snip('cc', 'cases(\n\t<>\n)\\', { i(1, '1') }, math),
|
||||||
|
snip('(K|M|N|Q|R|S|Z)([\\dn]) ', '<><>^<> ', { cap(1), cap(1), cap(2) }, math),
|
||||||
snip('(.*)iv', '<>^(-1)', { cap(1) }, math),
|
snip('(.*)iv', '<>^(-1)', { cap(1) }, math),
|
||||||
snip('(.*)sr', '<>^2', { cap(1) }, math),
|
snip('(.*)sr', '<>^2', { cap(1) }, math),
|
||||||
|
snip('(.*)cb', '<>^3', { cap(1) }, math),
|
||||||
snip('(.*)jj', '<>_(<>)', { cap(1), i(1, 'n') }, math),
|
snip('(.*)jj', '<>_(<>)', { cap(1), i(1, 'n') }, math),
|
||||||
snip('(.*)kk', '<>^(<>)', { cap(1), i(1, 'n') }, math),
|
snip('(.*)kk', '<>^(<>)', { cap(1), i(1, 'n') }, math),
|
||||||
|
|
||||||
|
|||||||
@@ -61,9 +61,7 @@ local lmat = function(_, sp)
|
|||||||
ins_indx = ins_indx + 1
|
ins_indx = ins_indx + 1
|
||||||
for k = 2, cols do
|
for k = 2, cols do
|
||||||
table.insert(nodes, t(', '))
|
table.insert(nodes, t(', '))
|
||||||
if k == cols then
|
if k == cols then table.insert(nodes, t('dots, ')) end
|
||||||
table.insert(nodes, t('dots, '))
|
|
||||||
end
|
|
||||||
if j == k then
|
if j == k then
|
||||||
table.insert(nodes, r(ins_indx, tostring(j) .. 'x' .. tostring(k), i(1, '1')))
|
table.insert(nodes, r(ins_indx, tostring(j) .. 'x' .. tostring(k), i(1, '1')))
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -5,36 +5,37 @@ local i = ls.insert_node
|
|||||||
local s = ls.snippet_node
|
local s = ls.snippet_node
|
||||||
local t = ls.text_node
|
local t = ls.text_node
|
||||||
|
|
||||||
local utils = require('typstar.utils')
|
|
||||||
local helper = require('typstar.autosnippets')
|
local helper = require('typstar.autosnippets')
|
||||||
|
local utils = require('typstar.utils')
|
||||||
local math = helper.in_math
|
local math = helper.in_math
|
||||||
local snip = helper.snip
|
local snip = helper.snip
|
||||||
|
|
||||||
local snippets = {}
|
local snippets = {}
|
||||||
local operations = { -- first boolean: existing brackets should be kept; second boolean: brackets should be added
|
|
||||||
{ 'vi', '1/', '', true, false },
|
local operations = { -- first boolean: existing brackets should be kept; second boolean: brackets should be added
|
||||||
{ 'bb', '(', ')', true, false }, -- add round brackets
|
{ 'vi', '1/', '', true, false },
|
||||||
{ 'sq', '[', ']', true, false }, -- add square brackets
|
{ 'bb', '(', ')', true, false }, -- add round brackets
|
||||||
{ 'st', '{', '}', true, false }, -- add curly brackets
|
{ 'sq', '[', ']', true, false }, -- add square brackets
|
||||||
{ 'bB', '(', ')', false, false }, -- replace with round brackets
|
{ 'st', '{', '}', true, false }, -- add curly brackets
|
||||||
{ 'sQ', '[', ']', false, false }, -- replace with square brackets
|
{ 'bB', '(', ')', false, false }, -- replace with round brackets
|
||||||
{ 'BB', '', '', false, false }, -- remove brackets
|
{ 'sQ', '[', ']', false, false }, -- replace with square brackets
|
||||||
{ 'ss', '"', '"', false, false },
|
{ 'BB', '', '', false, false }, -- remove brackets
|
||||||
{ 'abs', 'abs', '', true, true },
|
{ 'ss', '"', '"', false, false },
|
||||||
{ 'ul', 'underline', '', true, true },
|
{ 'abs', 'abs', '', true, true },
|
||||||
{ 'ol', 'overline', '', true, true },
|
{ 'ul', 'underline', '', true, true },
|
||||||
{ 'ub', 'underbrace', '', true, true },
|
{ 'ol', 'overline', '', true, true },
|
||||||
{ 'ob', 'overbrace', '', true, true },
|
{ 'ub', 'underbrace', '', true, true },
|
||||||
{ 'ht', 'hat', '', true, true },
|
{ 'ob', 'overbrace', '', true, true },
|
||||||
{ 'br', 'macron', '', true, true },
|
{ 'ht', 'hat', '', true, true },
|
||||||
{ 'dt', 'dot', '', true, true },
|
{ 'br', 'macron', '', true, true },
|
||||||
{ 'ci', 'circle', '', true, true },
|
{ 'dt', 'dot', '', true, true },
|
||||||
{ 'td', 'tilde', '', true, true },
|
{ 'ci', 'circle', '', true, true },
|
||||||
{ 'nr', 'norm', '', true, true },
|
{ 'td', 'tilde', '', true, true },
|
||||||
{ 'vv', 'vec', '', true, true },
|
{ 'nr', 'norm', '', true, true },
|
||||||
{ 'rt', 'sqrt', '', true, true },
|
{ 'vv', 'vec', '', true, true },
|
||||||
{ 'flo', 'floor', '', true, true },
|
{ 'rt', 'sqrt', '', true, true },
|
||||||
{ 'cei', 'ceil', '', true, true },
|
{ 'flo', 'floor', '', true, true },
|
||||||
|
{ 'cei', 'ceil', '', true, true },
|
||||||
}
|
}
|
||||||
|
|
||||||
local ts_wrap_query = ts.query.parse('typst', '[(call) (ident) (letter) (number)] @wrap')
|
local ts_wrap_query = ts.query.parse('typst', '[(call) (ident) (letter) (number)] @wrap')
|
||||||
@@ -47,16 +48,11 @@ local process_ts_query = function(bufnr, cursor, query, root, insert1, insert2,
|
|||||||
if end_row == cursor[1] and end_col == cursor[2] then
|
if end_row == cursor[1] and end_col == cursor[2] then
|
||||||
vim.schedule(function() -- to not interfere with luasnip
|
vim.schedule(function() -- to not interfere with luasnip
|
||||||
local cursor_offset = 0
|
local cursor_offset = 0
|
||||||
local old_len1, new_len1 = utils.insert_text(
|
local old_len1, new_len1 = utils.insert_text(bufnr, start_row, start_col, insert1, 0, cut_offset)
|
||||||
bufnr, start_row, start_col, insert1, 0, cut_offset)
|
if start_row == cursor[1] then cursor_offset = cursor_offset + (new_len1 - old_len1) end
|
||||||
if start_row == cursor[1] then
|
local old_len2, new_len2 =
|
||||||
cursor_offset = cursor_offset + (new_len1 - old_len1)
|
utils.insert_text(bufnr, end_row, cursor[2] + cursor_offset, insert2, cut_offset, 0)
|
||||||
end
|
if end_row == cursor[1] then cursor_offset = cursor_offset + (new_len2 - old_len2) end
|
||||||
local old_len2, new_len2 = utils.insert_text(
|
|
||||||
bufnr, end_row, cursor[2] + cursor_offset, insert2, cut_offset, 0)
|
|
||||||
if end_row == cursor[1] then
|
|
||||||
cursor_offset = cursor_offset + (new_len2 - old_len2)
|
|
||||||
end
|
|
||||||
vim.api.nvim_win_set_cursor(0, { cursor[1] + 1, cursor[2] + cursor_offset })
|
vim.api.nvim_win_set_cursor(0, { cursor[1] + 1, cursor[2] + cursor_offset })
|
||||||
end)
|
end)
|
||||||
return true
|
return true
|
||||||
@@ -77,9 +73,7 @@ local smart_wrap = function(args, parent, old_state, expand)
|
|||||||
|
|
||||||
local expand1 = expand[5] and expand[2] .. '(' or expand[2]
|
local expand1 = expand[5] and expand[2] .. '(' or expand[2]
|
||||||
local expand2 = expand[5] and expand[3] .. ')' or expand[3]
|
local expand2 = expand[5] and expand[3] .. ')' or expand[3]
|
||||||
if process_ts_query(bufnr, cursor, ts_wrap_query, root, expand1, expand2) then
|
if process_ts_query(bufnr, cursor, ts_wrap_query, root, expand1, expand2) then return s(nil, t()) end
|
||||||
return s(nil, t())
|
|
||||||
end
|
|
||||||
if #parent.env.LS_SELECT_RAW > 0 then
|
if #parent.env.LS_SELECT_RAW > 0 then
|
||||||
return s(nil, t(expand1 .. table.concat(parent.env.LS_SELECT_RAW) .. expand2))
|
return s(nil, t(expand1 .. table.concat(parent.env.LS_SELECT_RAW) .. expand2))
|
||||||
end
|
end
|
||||||
@@ -91,5 +85,5 @@ for _, val in pairs(operations) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unpack(snippets)
|
unpack(snippets),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ end
|
|||||||
function M.insert_text_block(snip)
|
function M.insert_text_block(snip)
|
||||||
local line_num = M.get_cursor_pos()[1] + 1
|
local line_num = M.get_cursor_pos()[1] + 1
|
||||||
local lines = {}
|
local lines = {}
|
||||||
for line in snip:gmatch '[^\r\n]+' do
|
for line in snip:gmatch('[^\r\n]+') do
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
end
|
end
|
||||||
vim.api.nvim_buf_set_lines(vim.api.nvim_get_current_buf(), line_num, line_num, false, lines)
|
vim.api.nvim_buf_set_lines(vim.api.nvim_get_current_buf(), line_num, line_num, false, lines)
|
||||||
@@ -35,45 +35,32 @@ function M.run_shell_command(cmd, show_output)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if show_output then
|
if show_output then
|
||||||
vim.fn.jobstart(
|
vim.fn.jobstart(cmd, {
|
||||||
cmd,
|
on_stdout = function(_, data, _) handle_output(data, false) end,
|
||||||
{
|
on_stderr = function(_, data, _) handle_output(data, true) end,
|
||||||
on_stdout = function(_, data, _)
|
stdout_buffered = false,
|
||||||
handle_output(data, false)
|
stderr_buffered = true,
|
||||||
end,
|
})
|
||||||
on_stderr = function(_, data, _)
|
|
||||||
handle_output(data, true)
|
|
||||||
end,
|
|
||||||
stdout_buffered = false,
|
|
||||||
stderr_buffered = true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else
|
else
|
||||||
vim.fn.jobstart(cmd)
|
vim.fn.jobstart(cmd)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.char_to_hex(c)
|
function M.char_to_hex(c) return string.format('%%%02X', string.byte(c)) end
|
||||||
return string.format("%%%02X", string.byte(c))
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.urlencode(url)
|
function M.urlencode(url)
|
||||||
if url == nil then
|
if url == nil then return '' end
|
||||||
return ''
|
|
||||||
end
|
|
||||||
url = string.gsub(url, '\n', '\r\n')
|
url = string.gsub(url, '\n', '\r\n')
|
||||||
url = string.gsub(url, '([^%w _%%%-%.~])', M.char_to_hex)
|
url = string.gsub(url, '([^%w _%%%-%.~])', M.char_to_hex)
|
||||||
url = string.gsub(url, ' ', '%%20')
|
url = string.gsub(url, ' ', '%%20')
|
||||||
return url
|
return url
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.get_treesitter_root(bufnr)
|
function M.get_treesitter_root(bufnr) return ts.get_parser(bufnr):parse()[1]:root() end
|
||||||
return ts.get_parser(bufnr):parse()[1]:root()
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.treesitter_match_start_end(match)
|
function M.treesitter_match_start_end(match)
|
||||||
local start_row, start_col, _, _ = match[1]:range()
|
local start_row, start_col, _, _ = match[1]:range()
|
||||||
local _, _, end_row, end_col = match[#match]:range()
|
local _, _, end_row, end_col = match[#match]:range()
|
||||||
return start_row, start_col, end_row, end_col
|
return start_row, start_col, end_row, end_col
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -83,11 +70,8 @@ function M.cursor_within_treesitter_query(query, match_tolerance, cursor)
|
|||||||
for _, match, _ in query:iter_matches(M.get_treesitter_root(bufnr), bufnr, cursor[1], cursor[1] + 1) do
|
for _, match, _ in query:iter_matches(M.get_treesitter_root(bufnr), bufnr, cursor[1], cursor[1] + 1) do
|
||||||
if match then
|
if match then
|
||||||
local start_row, start_col, end_row, end_col = M.treesitter_match_start_end(match)
|
local start_row, start_col, end_row, end_col = M.treesitter_match_start_end(match)
|
||||||
local matched = M.cursor_within_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)
|
||||||
match_tolerance)
|
if matched then return true end
|
||||||
if matched then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "pdm.backend"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "typstar"
|
name = "typstar"
|
||||||
version = "1.1.1"
|
version = "1.2.0"
|
||||||
description = "Neovim plugin for efficient note taking in Typst"
|
description = "Neovim plugin for efficient note taking in Typst"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "arne314" }
|
{ name = "arne314" }
|
||||||
|
|||||||
35
src/anki/config_parser.py
Normal file
35
src/anki/config_parser.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
from functools import cache
|
||||||
|
from glob import glob
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class RecursiveConfigParser:
|
||||||
|
dir: Path
|
||||||
|
targets: set[str]
|
||||||
|
results: dict[str, dict[Path, str]]
|
||||||
|
|
||||||
|
def __init__(self, dir, targets):
|
||||||
|
self.dir = dir
|
||||||
|
self.targets = set(targets)
|
||||||
|
self.results = defaultdict(dict)
|
||||||
|
self._parse_recursive()
|
||||||
|
|
||||||
|
def _parse_recursive(self):
|
||||||
|
files = []
|
||||||
|
for target in self.targets:
|
||||||
|
files.extend(glob(f"{self.dir}/**/{target}", include_hidden=target.startswith("."), recursive=True))
|
||||||
|
for file in files:
|
||||||
|
file = Path(file)
|
||||||
|
if file.name in self.targets:
|
||||||
|
self.results[file.name][file.parent] = file.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def get_config(self, path: Path, target) -> str | None:
|
||||||
|
root_parent = self.dir.parent.resolve()
|
||||||
|
path = Path(path.resolve())
|
||||||
|
target_results = self.results[target]
|
||||||
|
while path != root_parent:
|
||||||
|
if result := target_results.get(path):
|
||||||
|
return result
|
||||||
|
path = path.parent
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import glob
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from functools import cache
|
from glob import glob
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
|
|
||||||
@@ -9,6 +8,7 @@ import appdirs
|
|||||||
import tree_sitter
|
import tree_sitter
|
||||||
from tree_sitter_typst import language as get_typst_language
|
from tree_sitter_typst import language as get_typst_language
|
||||||
|
|
||||||
|
from .config_parser import RecursiveConfigParser
|
||||||
from .file_handler import FileHandler
|
from .file_handler import FileHandler
|
||||||
from .flashcard import Flashcard
|
from .flashcard import Flashcard
|
||||||
|
|
||||||
@@ -38,6 +38,8 @@ ts_deck_query = """
|
|||||||
deck_regex = re.compile(r"\W+ANKI:\s*([\S ]*)")
|
deck_regex = re.compile(r"\W+ANKI:\s*([\S ]*)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FlashcardParser:
|
class FlashcardParser:
|
||||||
typst_language: tree_sitter.Language
|
typst_language: tree_sitter.Language
|
||||||
typst_parser: tree_sitter.Parser
|
typst_parser: tree_sitter.Parser
|
||||||
@@ -56,7 +58,7 @@ class FlashcardParser:
|
|||||||
self.file_handlers = []
|
self.file_handlers = []
|
||||||
self._load_file_hashes()
|
self._load_file_hashes()
|
||||||
|
|
||||||
def _parse_file(self, file: FileHandler, preamble: str | None) -> List[Flashcard]:
|
def _parse_file(self, file: FileHandler, preamble: str | None, default_deck: str | None) -> List[Flashcard]:
|
||||||
cards = []
|
cards = []
|
||||||
tree = self.typst_parser.parse(file.get_bytes(), encoding="utf8")
|
tree = self.typst_parser.parse(file.get_bytes(), encoding="utf8")
|
||||||
card_captures = self.flashcard_query.captures(tree.root_node)
|
card_captures = self.flashcard_query.captures(tree.root_node)
|
||||||
@@ -73,7 +75,7 @@ class FlashcardParser:
|
|||||||
|
|
||||||
deck_refs: List[Tuple[int, str | None]] = []
|
deck_refs: List[Tuple[int, str | None]] = []
|
||||||
deck_refs_idx = -1
|
deck_refs_idx = -1
|
||||||
current_deck = None
|
current_deck = default_deck
|
||||||
if deck_captures:
|
if deck_captures:
|
||||||
deck_captures["deck"].sort(key=row_compare)
|
deck_captures["deck"].sort(key=row_compare)
|
||||||
for comment in deck_captures["deck"]:
|
for comment in deck_captures["deck"]:
|
||||||
@@ -108,6 +110,7 @@ class FlashcardParser:
|
|||||||
return cards
|
return cards
|
||||||
|
|
||||||
def parse_directory(self, root_dir: Path, force_scan: Path | None = None):
|
def parse_directory(self, root_dir: Path, force_scan: Path | None = None):
|
||||||
|
flashcards = []
|
||||||
single_file = None
|
single_file = None
|
||||||
is_force_scan = force_scan is not None
|
is_force_scan = force_scan is not None
|
||||||
if is_force_scan:
|
if is_force_scan:
|
||||||
@@ -123,22 +126,9 @@ class FlashcardParser:
|
|||||||
f"Parsing flashcards in {scan_dir if single_file is None else single_file} ...",
|
f"Parsing flashcards in {scan_dir if single_file is None else single_file} ...",
|
||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
preambles = {}
|
configs = RecursiveConfigParser(root_dir, {".anki", ".anki.typ"})
|
||||||
flashcards = []
|
|
||||||
|
|
||||||
@cache
|
for file in glob(f"{scan_dir}/**/**.typ", recursive=True):
|
||||||
def get_preamble(path: Path) -> str | None:
|
|
||||||
while path != root_dir.parent:
|
|
||||||
if preamble := preambles.get(path):
|
|
||||||
return preamble
|
|
||||||
path = path.parent
|
|
||||||
|
|
||||||
for file in glob.glob(f"{root_dir}/**/.anki.typ", include_hidden=True, recursive=True):
|
|
||||||
file = Path(file)
|
|
||||||
if file.name == ".anki.typ":
|
|
||||||
preambles[file.parent] = file.read_text(encoding="utf-8")
|
|
||||||
|
|
||||||
for file in glob.glob(f"{scan_dir}/**/**.typ", recursive=True):
|
|
||||||
file = Path(file)
|
file = Path(file)
|
||||||
if single_file is not None and file != single_file:
|
if single_file is not None and file != single_file:
|
||||||
continue
|
continue
|
||||||
@@ -146,7 +136,7 @@ class FlashcardParser:
|
|||||||
fh = FileHandler(file)
|
fh = FileHandler(file)
|
||||||
file_changed = self._hash_changed(fh)
|
file_changed = self._hash_changed(fh)
|
||||||
if is_force_scan or file_changed:
|
if is_force_scan or file_changed:
|
||||||
cards = self._parse_file(fh, get_preamble(file.parent))
|
cards = self._parse_file(fh, configs.get_config(file, ".anki.typ"), configs.get_config(file, ".anki"))
|
||||||
self.file_handlers.append((fh, cards))
|
self.file_handlers.append((fh, cards))
|
||||||
flashcards.extend(cards)
|
flashcards.extend(cards)
|
||||||
return flashcards
|
return flashcards
|
||||||
|
|||||||
Reference in New Issue
Block a user