mirror of
https://github.com/Ascyii/typstar.git
synced 2026-01-01 05:24:24 -05:00
81
README.md
81
README.md
@@ -14,7 +14,7 @@ Available snippets can mostly be intuitively derived from [here](././lua/typstar
|
|||||||
|
|
||||||
Markup snippets:
|
Markup snippets:
|
||||||
- Begin inline math with `ll` and multiline math with `dm`
|
- Begin inline math with `ll` and multiline math with `dm`
|
||||||
- [Markup shorthands](./lua/typstar/snippets/markup.lua) (e.g. `HIG` → `#highlight[<cursor>]`, `IMP` → `$=>$ `)
|
- [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)
|
- [ctheorems shorthands](./lua/typstar/snippets/markup.lua) (e.g. `tem` → empty theorem, `exa` → empty example)
|
||||||
- [Flashcards](#anki): `fla` and `flA`
|
- [Flashcards](#anki): `fla` and `flA`
|
||||||
- All above snippets support visual mode via the [selection key](#installation)
|
- All above snippets support visual mode via the [selection key](#installation)
|
||||||
@@ -23,11 +23,12 @@ Math snippets:
|
|||||||
- [Many shorthands](./lua/typstar/snippets/math.lua) for mathematical expressions
|
- [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$ `)
|
||||||
|
- Series of numbered letters: `<letter> ot<optional last index> ` → `<letter>_1, <letter>_2, ... ` (e.g. `a ot ` → `a_1, a_2, ... `, `a ot4 ` → `a_1, a_2, a_3, a_4 `, `alpha otk ` → `alpha_1, alpha_2, ..., alpha_k `)
|
||||||
- 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)
|
||||||
|
|
||||||
Note that you can enable and disable collections of snippets in the [config](#configuration).
|
Note that you can [customize](#custom-snippets) (enable, disable and modify) every snippet.
|
||||||
|
|
||||||
### Excalidraw
|
### Excalidraw
|
||||||
- Use `:TypstarInsertExcalidraw` to create a new drawing using the configured template, insert a figure displaying it and open it in Obsidian.
|
- Use `:TypstarInsertExcalidraw` to create a new drawing using the configured template, insert a figure displaying it and open it in Obsidian.
|
||||||
@@ -65,16 +66,19 @@ To render the flashcard in your document as well add some code like this
|
|||||||
- Use `:TypstarAnkiScan` to scan the current nvim working directory and compile all flashcards in its context, unchanged files will be ignored
|
- Use `:TypstarAnkiScan` to scan the current nvim working directory and compile all flashcards in its context, unchanged files will be ignored
|
||||||
- Use `:TypstarAnkiForce` to force compilation of all flashcards in the current working directory even if the files haven't changed since the last scan (e.g. on preamble change)
|
- Use `:TypstarAnkiForce` to force compilation of all flashcards in the current working directory even if the files haven't changed since the last scan (e.g. on preamble change)
|
||||||
- Use `:TypstarAnkiForceCurrent` to force compilation of all flashcards in the file currently edited
|
- Use `:TypstarAnkiForceCurrent` to force compilation of all flashcards in the file currently edited
|
||||||
|
- Use `:TypstarAnkiReimport` to also add flashcards that have already been asigned an id but are not currently
|
||||||
|
present in Anki
|
||||||
|
- Use `:TypstarAnkiForceReimport` and `:TypstarAnkiForceCurrentReimport` to combine features accordingly
|
||||||
|
|
||||||
#### Standalone
|
#### Standalone
|
||||||
- Run `typstar-anki --help` to show the available options
|
- Run `typstar-anki --help` to show the available options
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Install the plugin in Neovim and set the `typstarRoot` config or alternatively clone typstar into `~/typstar`.
|
Install the plugin in Neovim (see [Nix instructions](#in-a-nix-flake-optional)) and run the plugin setup.
|
||||||
```lua
|
```lua
|
||||||
require('typstar').setup({
|
require('typstar').setup({ -- depending on your neovim plugin system
|
||||||
typstarRoot = '/path/to/typstar/repo' -- depending on your nvim plugin system
|
-- your typstar config goes here
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,6 +92,7 @@ require('typstar').setup({
|
|||||||
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
|
||||||
2. Install the [obsidian-excalidraw-plugin](https://github.com/zsviczian/obsidian-excalidraw-plugin) and enable `Auto-export SVG` (in plugin settings at `Embedding Excalidraw into your Notes and Exporting > Export Settings > Auto-export Settings`)
|
2. Install the [obsidian-excalidraw-plugin](https://github.com/zsviczian/obsidian-excalidraw-plugin) and enable `Auto-export SVG` (in plugin settings at `Embedding Excalidraw into your Notes and Exporting > Export Settings > Auto-export Settings`)
|
||||||
3. Have the `xdg-open` command working or set a different command at `uriOpenCommand` in the [config](#configuration)
|
3. Have the `xdg-open` command working or set a different command at `uriOpenCommand` in the [config](#configuration)
|
||||||
|
4. If you encounter issues try cloning the repo into `~/typstar` or setting the `typstarRoot` config accordingly, feel free to open an issue
|
||||||
|
|
||||||
### Anki
|
### Anki
|
||||||
0. Typst version `0.12.0` or higher is required
|
0. Typst version `0.12.0` or higher is required
|
||||||
@@ -96,6 +101,70 @@ require('typstar').setup({
|
|||||||
3. Install the typstar python package (I recommend using [pipx](https://github.com/pypa/pipx) via `pipx install git+https://github.com/arne314/typstar`, you will need to have python build tools and clang installed) \[Note: this may take a while\]
|
3. Install the typstar python package (I recommend using [pipx](https://github.com/pypa/pipx) via `pipx install git+https://github.com/arne314/typstar`, you will need to have python build tools and clang installed) \[Note: this may take a while\]
|
||||||
4. Make sure the `typstar-anki` command is available in your `PATH` or modify the `typstarAnkiCmd` option in the [config](#configuration)
|
4. Make sure the `typstar-anki` command is available in your `PATH` or modify the `typstarAnkiCmd` option in the [config](#configuration)
|
||||||
|
|
||||||
|
### In a Nix Flake (optional)
|
||||||
|
You can add typstar to your `nix-flake` like so
|
||||||
|
```nix
|
||||||
|
# `flake.nix`
|
||||||
|
inputs = {
|
||||||
|
# ... other inputs
|
||||||
|
typstar = {
|
||||||
|
url = "github:arne314/typstar";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Now you can use `typstar` in any package-set
|
||||||
|
```nix
|
||||||
|
with pkgs; [
|
||||||
|
# ... other packges
|
||||||
|
(pkgs.vimUtils.buildVimPlugin {
|
||||||
|
name = "typstar";
|
||||||
|
src = inputs.typstar;
|
||||||
|
buildInputs = [
|
||||||
|
vimPlugins.luasnip
|
||||||
|
vimPlugins.nvim-treesitter-parsers.typst
|
||||||
|
];
|
||||||
|
})
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
Configuration options can be intuitively derived from the table [here](./lua/typstar/config.lua).
|
Configuration options can be intuitively derived from the table [here](./lua/typstar/config.lua).
|
||||||
|
|
||||||
|
### Custom snippets
|
||||||
|
The [config](#configuration) allows you to
|
||||||
|
- disable all snippets via `snippets.enable = false`
|
||||||
|
- only include specific modules from the snippets folder via e.g. `snippets.modules = { 'letters' }`
|
||||||
|
- exclude specific triggers via e.g. `snippets.exclude = { 'dx', 'ddx' }`
|
||||||
|
|
||||||
|
For further customization you can make use of the provided wrappers from within your [LuaSnip](https://github.com/L3MON4D3/LuaSnip/) config.
|
||||||
|
Let's say you prefer the short `=>` arrow over the long `==>` one and would like to change the `ip` trigger to `imp`.
|
||||||
|
Your `typstar` config could look like
|
||||||
|
```lua
|
||||||
|
require('typstar').setup({
|
||||||
|
snippets = {
|
||||||
|
exclude = { 'ip' },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
while your LuaSnip `typst.lua` could look like this (`<` and `>` require escaping as `<>` [introduces a new node](https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#fmt))
|
||||||
|
```lua
|
||||||
|
local tp = require('typstar.autosnippets')
|
||||||
|
local snip = tp.snip
|
||||||
|
local math = tp.in_math
|
||||||
|
local markup = tp.in_markup
|
||||||
|
|
||||||
|
return {
|
||||||
|
-- add a new snippet (the old one is excluded via the config)
|
||||||
|
snip('imp', '=>> ', {}, math),
|
||||||
|
|
||||||
|
-- override existing triggers by setting a high priority
|
||||||
|
snip('ib', '<<= ', {}, math, 2000),
|
||||||
|
snip('iff', '<<=>> ', {}, math, 2000),
|
||||||
|
|
||||||
|
-- setup markup snippets accordingly
|
||||||
|
snip('IMP', '$=>>$ ', {}, markup, 2000),
|
||||||
|
snip('IFF', '$<<=>>$ ', {}, markup, 2000),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
58
flake.lock
generated
Normal file
58
flake.lock
generated
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1736143030,
|
||||||
|
"narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1737525964,
|
||||||
|
"narHash": "sha256-3wFonKmNRWKq1himW9N3TllbeGIHFACI5vmLpk6moF8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "5757bbb8bd7c0630a0cc4bb19c47e588db30b97c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1735774519,
|
||||||
|
"narHash": "sha256-CewEm1o2eVAnoqb6Ml+Qi9Gg/EfNAxbRx1lANGVyoLI=",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/NixOS/nixpkgs/archive/e9b51731911566bbf7e4895475a87fe06961de0b.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
64
flake.nix
Normal file
64
flake.nix
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
description = "typstar nix flake for development";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs @ {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
flake-parts,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
flake-parts.lib.mkFlake {inherit inputs;} {
|
||||||
|
systems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"aarch64-darwin"
|
||||||
|
];
|
||||||
|
|
||||||
|
perSystem = {system, ...}: let
|
||||||
|
pkgs = import nixpkgs {inherit system;};
|
||||||
|
typstar = pkgs.vimUtils.buildVimPlugin {
|
||||||
|
name = "typstar";
|
||||||
|
src = self;
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.vimPlugins.luasnip
|
||||||
|
pkgs.vimPlugins.nvim-treesitter-parsers.typst
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
packages = {
|
||||||
|
default = typstar;
|
||||||
|
nvim = let
|
||||||
|
config = pkgs.neovimUtils.makeNeovimConfig {
|
||||||
|
customRC = ''
|
||||||
|
lua << EOF
|
||||||
|
print("Welcome to Typstar! This is just a demo.")
|
||||||
|
|
||||||
|
require('nvim-treesitter.configs').setup {
|
||||||
|
highlight = { enable = true },
|
||||||
|
}
|
||||||
|
|
||||||
|
require('luasnip').config.set_config({
|
||||||
|
enable_autosnippets = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
require('typstar').setup()
|
||||||
|
EOF
|
||||||
|
'';
|
||||||
|
plugins = [
|
||||||
|
typstar
|
||||||
|
pkgs.vimPlugins.luasnip
|
||||||
|
pkgs.vimPlugins.nvim-treesitter
|
||||||
|
pkgs.vimPlugins.nvim-treesitter-parsers.typst
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.wrapNeovimUnstable pkgs.neovim-unwrapped config;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -22,8 +22,14 @@ end
|
|||||||
|
|
||||||
function M.scan() run_typstar_anki('') end
|
function M.scan() run_typstar_anki('') end
|
||||||
|
|
||||||
|
function M.scan_reimport() run_typstar_anki('--reimport') end
|
||||||
|
|
||||||
function M.scan_force() run_typstar_anki('--force-scan ' .. vim.fn.getcwd()) end
|
function M.scan_force() run_typstar_anki('--force-scan ' .. vim.fn.getcwd()) end
|
||||||
|
|
||||||
|
function M.scan_force_reimport() run_typstar_anki('--reimport --force-scan ' .. vim.fn.getcwd()) end
|
||||||
|
|
||||||
function M.scan_force_current() run_typstar_anki('--force-scan ' .. vim.fn.expand('%:p')) end
|
function M.scan_force_current() run_typstar_anki('--force-scan ' .. vim.fn.expand('%:p')) end
|
||||||
|
|
||||||
|
function M.scan_force_current_reimport() run_typstar_anki('--reimport --force-scan ' .. vim.fn.expand('%:p')) end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -6,15 +6,18 @@ 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 exclude_triggers_set = {}
|
||||||
local last_keystroke_time = nil
|
local last_keystroke_time = nil
|
||||||
vim.api.nvim_create_autocmd('TextChangedI', {
|
|
||||||
callback = function() 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')
|
||||||
local ts_math_query = ts.query.parse('typst', '(math) @math')
|
local ts_math_query = ts.query.parse('typst', '(math) @math')
|
||||||
local ts_string_query = ts.query.parse('typst', '(string) @string')
|
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()
|
M.in_math = function()
|
||||||
local cursor = utils.get_cursor_pos()
|
local cursor = utils.get_cursor_pos()
|
||||||
return utils.cursor_within_treesitter_query(ts_math_query, 0, cursor)
|
return utils.cursor_within_treesitter_query(ts_math_query, 0, cursor)
|
||||||
@@ -44,14 +47,15 @@ function M.ri(insert_node_id)
|
|||||||
return luasnip.function_node(function(args) return args[1][1] end, insert_node_id)
|
return luasnip.function_node(function(args) return args[1][1] end, insert_node_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.snip(trigger, expand, insert, condition, priority, wordTrig)
|
function M.snip(trigger, expand, insert, condition, priority, wordTrig, maxTrigLength)
|
||||||
priority = priority or 1000
|
priority = priority or 1000
|
||||||
|
if wordTrig == nil then wordTrig = true end
|
||||||
return luasnip.snippet(
|
return luasnip.snippet(
|
||||||
{
|
{
|
||||||
trig = trigger,
|
trig = trigger,
|
||||||
trigEngine = M.engine,
|
trigEngine = M.engine,
|
||||||
trigEngineOpts = { condition = condition },
|
trigEngineOpts = { condition = condition, wordTrig = wordTrig, maxTrigLength = maxTrigLength },
|
||||||
wordTrig = wordTrig,
|
wordTrig = false,
|
||||||
priority = priority,
|
priority = priority,
|
||||||
snippetType = 'autosnippet',
|
snippetType = 'autosnippet',
|
||||||
},
|
},
|
||||||
@@ -66,8 +70,43 @@ function M.start_snip(trigger, expand, insert, condition, priority)
|
|||||||
return M.snip('^(\\s*)' .. trigger, '<>' .. expand, { M.cap(1), unpack(insert) }, condition, priority)
|
return M.snip('^(\\s*)' .. trigger, '<>' .. expand, { M.cap(1), unpack(insert) }, condition, priority)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local alts_regex = '[\\[\\(](.*|.*)[\\)\\]]'
|
||||||
|
|
||||||
function M.engine(trigger, opts)
|
function M.engine(trigger, opts)
|
||||||
local base_engine = lsengines.ecma(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 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 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 return cached[2] end
|
if cached ~= nil and cached[1] == last_keystroke_time then return cached[2] end
|
||||||
@@ -75,9 +114,33 @@ function M.engine(trigger, opts)
|
|||||||
lexical_result_cache[opts.condition] = { last_keystroke_time, result }
|
lexical_result_cache[opts.condition] = { last_keystroke_time, result }
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- matching
|
||||||
return function(line, trig)
|
return function(line, trig)
|
||||||
if not M.snippets_toggle or not condition() then return nil end
|
if not M.snippets_toggle or not condition() then return nil end
|
||||||
return base_engine(line, trig)
|
if max_length ~= nil then
|
||||||
|
local first_idx = #line - 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, first_idx) > 127 then return nil end
|
||||||
|
end
|
||||||
|
line = line:sub(first_idx)
|
||||||
|
end
|
||||||
|
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
|
||||||
|
return whole, captures
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -88,18 +151,30 @@ end
|
|||||||
|
|
||||||
function M.setup()
|
function M.setup()
|
||||||
if cfg.enable then
|
if cfg.enable then
|
||||||
local autosnippets = {}
|
local jsregexp_ok, jsregexp = pcall(require, 'luasnip-jsregexp')
|
||||||
for _, file in ipairs(cfg.modules) do
|
|
||||||
vim.list_extend(autosnippets, require(('typstar.snippets.%s'):format(file)))
|
|
||||||
end
|
|
||||||
luasnip.add_snippets('typst', autosnippets)
|
|
||||||
local jsregexp_ok, _ = pcall(require, 'luasnip-jsregexp')
|
|
||||||
if not jsregexp_ok then
|
if not jsregexp_ok then
|
||||||
jsregexp_ok, _ = pcall(require, 'jsregexp')
|
jsregexp_ok, jsregexp = pcall(require, 'jsregexp')
|
||||||
end
|
end
|
||||||
if not jsregexp_ok then
|
if jsregexp_ok then
|
||||||
|
alts_regex = jsregexp.compile_safe(alts_regex)
|
||||||
|
else
|
||||||
|
alts_regex = ''
|
||||||
vim.notify("WARNING: Most snippets won't work as jsregexp is not installed", vim.log.levels.WARN)
|
vim.notify("WARNING: Most snippets won't work as jsregexp is not installed", vim.log.levels.WARN)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local default_config = {
|
local default_config = {
|
||||||
typstarRoot = '~/typstar',
|
typstarRoot = nil,
|
||||||
anki = {
|
anki = {
|
||||||
typstarAnkiCmd = 'typstar-anki',
|
typstarAnkiCmd = 'typstar-anki',
|
||||||
typstCmd = 'typst',
|
typstCmd = 'typst',
|
||||||
@@ -25,11 +25,15 @@ local default_config = {
|
|||||||
'markup',
|
'markup',
|
||||||
'visual',
|
'visual',
|
||||||
},
|
},
|
||||||
|
exclude = {}, -- list of triggers to exclude
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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.typstarRoot = M.config.typstarRoot
|
||||||
|
or debug.getinfo(1).source:match('^@(.*)/lua/typstar/config%.lua$')
|
||||||
|
or '~/typstar'
|
||||||
M.config.excalidraw.templatePath = M.config.excalidraw.templatePath
|
M.config.excalidraw.templatePath = M.config.excalidraw.templatePath
|
||||||
or {
|
or {
|
||||||
['%.excalidraw%.md$'] = M.config.typstarRoot .. '/res/excalidraw_template.excalidraw.md',
|
['%.excalidraw%.md$'] = M.config.typstarRoot .. '/res/excalidraw_template.excalidraw.md',
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ M.setup = function(args)
|
|||||||
vim.api.nvim_create_user_command('TypstarOpenExcalidraw', excalidraw.open_drawing, {})
|
vim.api.nvim_create_user_command('TypstarOpenExcalidraw', excalidraw.open_drawing, {})
|
||||||
|
|
||||||
vim.api.nvim_create_user_command('TypstarAnkiScan', anki.scan, {})
|
vim.api.nvim_create_user_command('TypstarAnkiScan', anki.scan, {})
|
||||||
|
vim.api.nvim_create_user_command('TypstarAnkiReimport', anki.scan_reimport, {})
|
||||||
vim.api.nvim_create_user_command('TypstarAnkiForce', anki.scan_force, {})
|
vim.api.nvim_create_user_command('TypstarAnkiForce', anki.scan_force, {})
|
||||||
|
vim.api.nvim_create_user_command('TypstarAnkiForceReimport', anki.scan_force_reimport, {})
|
||||||
vim.api.nvim_create_user_command('TypstarAnkiForceCurrent', anki.scan_force_current, {})
|
vim.api.nvim_create_user_command('TypstarAnkiForceCurrent', anki.scan_force_current, {})
|
||||||
|
vim.api.nvim_create_user_command('TypstarAnkiForceCurrentReimport', anki.scan_force_current_reimport, {})
|
||||||
|
|
||||||
autosnippets.setup()
|
autosnippets.setup()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
local ls = require('luasnip')
|
local ls = require('luasnip')
|
||||||
local d = ls.dynamic_node
|
local d = ls.dynamic_node
|
||||||
|
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 helper = require('typstar.autosnippets')
|
local helper = require('typstar.autosnippets')
|
||||||
|
local utils = require('typstar.utils')
|
||||||
local snip = helper.snip
|
local snip = helper.snip
|
||||||
local cap = helper.cap
|
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 greek_letters_map = {
|
local greek_letters_map = {
|
||||||
['a'] = 'alpha',
|
['a'] = 'alpha',
|
||||||
['b'] = 'beta',
|
['b'] = 'beta',
|
||||||
['c'] = 'chi',
|
['c'] = 'chi',
|
||||||
['d'] = 'delta',
|
['d'] = 'delta',
|
||||||
['e'] = 'epsilon',
|
['e'] = 'epsilon',
|
||||||
|
['f'] = 'phi',
|
||||||
['g'] = 'gamma',
|
['g'] = 'gamma',
|
||||||
['h'] = 'phi',
|
['h'] = 'eta',
|
||||||
['i'] = 'iotta',
|
['i'] = 'iota',
|
||||||
['j'] = 'theta',
|
['j'] = 'theta',
|
||||||
['k'] = 'kappa',
|
['k'] = 'kappa',
|
||||||
['l'] = 'lambda',
|
['l'] = 'lambda',
|
||||||
@@ -38,8 +40,11 @@ local greek_letters_map = {
|
|||||||
local greek_keys = {}
|
local greek_keys = {}
|
||||||
local greek_letters_set = {}
|
local greek_letters_set = {}
|
||||||
local common_indices = { '\\d+', '[i-n]' }
|
local common_indices = { '\\d+', '[i-n]' }
|
||||||
local index_conflicts = { 'in', 'ln', 'pi', 'xi' }
|
-- buitins and caligraphic letters from github.com/lentilus/readable-typst
|
||||||
|
local index_conflicts = { 'in', 'ln', 'pi', 'xi', 'Ii', 'Jj', 'Kk', 'Ll', 'Mm', 'Nn' }
|
||||||
local index_conflicts_set = {}
|
local index_conflicts_set = {}
|
||||||
|
local punctuation_prepend_space = { ',', ';' }
|
||||||
|
local punctuation_prepend_space_set = {}
|
||||||
local trigger_greek = ''
|
local trigger_greek = ''
|
||||||
local trigger_index_pre = ''
|
local trigger_index_pre = ''
|
||||||
local trigger_index_post = ''
|
local trigger_index_post = ''
|
||||||
@@ -58,9 +63,8 @@ for latin, greek in pairs(greek_letters_map) do
|
|||||||
table.insert(greek_keys, latin:upper())
|
table.insert(greek_keys, latin:upper())
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, conflict in ipairs(index_conflicts) do
|
utils.generate_bool_set(index_conflicts, index_conflicts_set)
|
||||||
index_conflicts_set[conflict] = true
|
utils.generate_bool_set(punctuation_prepend_space, punctuation_prepend_space_set)
|
||||||
end
|
|
||||||
|
|
||||||
greek_letters_map = greek_full
|
greek_letters_map = greek_full
|
||||||
trigger_greek = table.concat(greek_keys, '|')
|
trigger_greek = table.concat(greek_keys, '|')
|
||||||
@@ -69,31 +73,66 @@ 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_greek = function(_, snippet) return s(nil, t(greek_letters_map[snippet.captures[1]])) end
|
||||||
|
|
||||||
local get_index = function(_, snippet)
|
local get_index = function(_, snippet, _, idx1, idx2, check_conflict)
|
||||||
local letter, index = snippet.captures[1], snippet.captures[2]
|
local letter, index = snippet.captures[idx1], snippet.captures[idx2]
|
||||||
local trigger = letter .. index
|
local trigger = letter .. index
|
||||||
if index_conflicts_set[trigger] then return s(nil, t(trigger)) end
|
if check_conflict and index_conflicts_set[trigger] then 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))
|
local get_series = function(_, snippet)
|
||||||
table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '$<>$ ', { d(1, get_greek) }, markup))
|
local letter, target = snippet.captures[1], snippet.captures[2]
|
||||||
table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '<>', { d(1, get_greek) }, math))
|
local target_num = tonumber(target)
|
||||||
table.insert(
|
local result
|
||||||
letter_snippets,
|
if target_num then
|
||||||
snip(
|
local res = {}
|
||||||
'\\$(' .. trigger_index_pre .. ')\\$' .. '(' .. trigger_index_post .. ') ',
|
for n = 1, target_num do
|
||||||
'$<>$ ',
|
table.insert(res, string.format('%s_%d', letter, n))
|
||||||
{ d(1, get_index) },
|
if n ~= target_num then table.insert(res, ', ') end
|
||||||
markup,
|
end
|
||||||
500
|
result = table.concat(res, '')
|
||||||
)
|
else
|
||||||
)
|
result = string.format('%s_1, %s_2, ..., %s_%s', letter, letter, letter, target)
|
||||||
table.insert(
|
end
|
||||||
letter_snippets,
|
return s(nil, t(result))
|
||||||
snip('(' .. trigger_index_pre .. ')' .. '(' .. trigger_index_post .. ') ', '<> ', { d(1, get_index) }, math, 200)
|
end
|
||||||
)
|
|
||||||
|
local prepend_space = function(_, snippet, _, idx)
|
||||||
|
local punc = snippet.captures[idx]
|
||||||
|
if punctuation_prepend_space_set[punc] then punc = punc .. ' ' end
|
||||||
|
return s(nil, t(punc))
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unpack(letter_snippets),
|
-- latin/greek
|
||||||
|
snip(':([A-Za-z0-9])', '$<>$ ', { cap(1) }, markup),
|
||||||
|
snip(';(' .. trigger_greek .. ')', '$<>$ ', { d(1, get_greek) }, markup),
|
||||||
|
snip(';(' .. trigger_greek .. ')', '<>', { d(1, get_greek) }, math),
|
||||||
|
|
||||||
|
-- indices
|
||||||
|
snip(
|
||||||
|
'\\$(' .. trigger_index_pre .. ')\\$' .. ' (' .. trigger_index_post .. ')([^\\w])',
|
||||||
|
'$<>$<>',
|
||||||
|
{ d(1, get_index, {}, { user_args = { 1, 2, false } }), d(2, prepend_space, {}, { user_args = { 3 } }) },
|
||||||
|
markup,
|
||||||
|
500,
|
||||||
|
true,
|
||||||
|
13
|
||||||
|
),
|
||||||
|
snip(
|
||||||
|
'(' .. trigger_index_pre .. ')' .. '(' .. trigger_index_post .. ')([^\\w])',
|
||||||
|
'<><>',
|
||||||
|
{ d(1, get_index, {}, { user_args = { 1, 2, true } }), d(2, prepend_space, {}, { user_args = { 3 } }) },
|
||||||
|
math,
|
||||||
|
200,
|
||||||
|
true,
|
||||||
|
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
|
||||||
|
|
||||||
|
-- misc
|
||||||
|
snip('(' .. trigger_index_pre .. ')bl', 'B_<> (<>)', { cap(1), i(1, 'x_0') }, math),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,26 +36,35 @@ return {
|
|||||||
-- operators
|
-- operators
|
||||||
snip('ak([^k ])', '+ <>', { cap(1) }, math, 100, false),
|
snip('ak([^k ])', '+ <>', { cap(1) }, math, 100, false),
|
||||||
snip('sk([^k ])', '- <>', { cap(1) }, math, 100, false),
|
snip('sk([^k ])', '- <>', { cap(1) }, math, 100, false),
|
||||||
snip('oak', 'plus.circle ', {}, math, 1100),
|
snip('oak', 'plus.circle ', {}, math),
|
||||||
snip('bak', 'plus.square ', {}, math, 1100),
|
snip('bak', 'plus.square ', {}, math),
|
||||||
snip('mak', 'plus.minus ', {}, math, 1100),
|
snip('mak', 'plus.minus ', {}, math),
|
||||||
snip('xx', 'times ', {}, math),
|
snip('xx', 'times ', {}, math, 900),
|
||||||
snip('oxx', 'times.circle ', {}, math),
|
snip('oxx', 'times.circle ', {}, math),
|
||||||
snip('bxx', 'times.square ', {}, math),
|
snip('bxx', 'times.square ', {}, math),
|
||||||
|
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('ep', 'exp(<>) ', { i(1, '1') }, math),
|
||||||
|
|
||||||
-- sets
|
-- sets
|
||||||
-- 'st' to '{<>} in ./visual.lua
|
-- 'st' to '{<>} in ./visual.lua
|
||||||
snip('set', '{<> | <>}', { i(1), i(2) }, math),
|
snip('set', '{<> | <>}', { i(1), i(2) }, math),
|
||||||
snip('es', 'emptyset ', {}, math),
|
snip('es', 'emptyset ', {}, math, 900),
|
||||||
snip('ses', '{emptyset} ', {}, math),
|
snip('ses', '{emptyset} ', {}, math),
|
||||||
snip('sp', 'supset ', {}, math),
|
snip('sp', 'supset ', {}, math),
|
||||||
snip('sb', 'subset ', {}, math),
|
snip('sb', 'subset ', {}, math),
|
||||||
snip('sep', 'supset.eq ', {}, math),
|
snip('sep', 'supset.eq ', {}, math),
|
||||||
snip('seb', 'subset.eq ', {}, math),
|
snip('seb', 'subset.eq ', {}, math),
|
||||||
snip('nn', 'sect ', {}, math),
|
snip('nn', 'sect ', {}, math, 900),
|
||||||
snip('uu', 'union ', {}, math),
|
snip('uu', 'union ', {}, math, 900),
|
||||||
snip('bnn', 'sect.big ', {}, math, 1100),
|
snip('bnn', 'sect.big ', {}, math),
|
||||||
snip('buu', 'union.big ', {}, math, 1100),
|
snip('buu', 'union.big ', {}, math),
|
||||||
snip('swo', 'without ', {}, math),
|
snip('swo', 'without ', {}, math),
|
||||||
|
|
||||||
-- misc
|
-- misc
|
||||||
@@ -63,28 +72,23 @@ return {
|
|||||||
snip('mt', '|->> ', {}, math),
|
snip('mt', '|->> ', {}, math),
|
||||||
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('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('(K|M|N|Q|R|S|Z)([\\dn]) ', '<><>^<> ', { cap(1), cap(1), cap(2) }, math),
|
||||||
snip('(.*)iv', '<>^(-1)', { cap(1) }, math),
|
|
||||||
snip('(.*)sr', '<>^2', { cap(1) }, math),
|
|
||||||
snip('(.*)cb', '<>^3', { cap(1) }, math),
|
|
||||||
snip('(.*)jj', '<>_(<>)', { cap(1), i(1, 'n') }, math),
|
|
||||||
snip('(.*)kk', '<>^(<>)', { cap(1), i(1, 'n') }, math),
|
|
||||||
|
|
||||||
snip('ddx', '(d <>)(d <>)', { i(1, 'f'), i(2, 'x') }, math),
|
snip('dx', 'd / (d <>) ', { i(1, 'x') }, math, 900),
|
||||||
snip('it', 'integral', {}, math),
|
snip('ddx', '(d <>) / (d <>) ', { i(1, 'f'), i(2, 'x') }, math),
|
||||||
snip('int', 'integral_(<>)^(<>)', { i(1, 'a'), i(2, 'b') }, math),
|
snip('it', 'integral ', {}, math, 900),
|
||||||
snip('oit', 'integral_Omega', {}, math),
|
snip('int', 'integral_(<>)^(<>) ', { i(1, 'a'), i(2, 'b') }, math),
|
||||||
snip('dit', 'integral_(<>)', { i(1, 'Omega') }, math),
|
snip('oit', 'integral_Omega ', {}, math),
|
||||||
|
snip('dit', 'integral_(<>) ', { i(1, 'Omega') }, math),
|
||||||
|
|
||||||
snip('sm', 'sum ', {}, math),
|
snip('sm', 'sum ', {}, math, 900),
|
||||||
snip('sum', 'sum_(<>)^(<>)', { i(1, 'i=0'), i(2, 'oo') }, math),
|
snip('sum', 'sum_(<>)^(<>) ', { i(1, 'i=0'), i(2, 'oo') }, math),
|
||||||
snip('osm', 'sum_Omega', {}, math),
|
snip('osm', 'sum_Omega ', {}, math),
|
||||||
snip('dsm', 'sum_(<>)', { i(1, 'I') }, math),
|
snip('dsm', 'sum_(<>) ', { i(1, 'I') }, math),
|
||||||
|
|
||||||
snip('lm', 'lim <>', { i(1, 'a_n') }, math),
|
snip('lm', 'lim ', {}, math),
|
||||||
snip('lim', 'lim_(<> ->> <>) <>', { i(1, 'n'), i(2, 'oo'), i(3, 'a_n') }, math),
|
snip('lim', 'lim_(<> ->> <>) ', { i(1, 'n'), i(2, 'oo') }, math),
|
||||||
snip('lim (sup|inf)', 'lim<> <>', { cap(1), i(1, 'a_n') }, math),
|
snip('lim (sup|inf)', 'lim<> ', { cap(1) }, math),
|
||||||
snip('lim(_.*-.*) (sup|inf)', 'lim<><> <>', { cap(2), cap(1), i(1, 'a_n') }, math),
|
snip('lim(_\\(\\s?\\w+\\s?->\\s?\\w+\\s?\\)) (sup|inf)', 'lim<><> ', { cap(2), cap(1) }, math, 1000, true, 25),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ local smart_wrap = function(args, parent, old_state, expand)
|
|||||||
end
|
end
|
||||||
|
|
||||||
for _, val in pairs(operations) do
|
for _, val in pairs(operations) do
|
||||||
table.insert(snippets, snip(val[1], '<>', { d(1, smart_wrap, {}, { user_args = { val } }) }, math, 1000, false))
|
table.insert(snippets, snip(val[1], '<>', { d(1, smart_wrap, {}, { user_args = { val } }) }, math, 1500, false))
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ function M.run_shell_command(cmd, show_output)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.count_string(str, tocount)
|
||||||
|
local _, count = str:gsub(tocount, '')
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
function M.char_to_hex(c) return string.format('%%%02X', string.byte(c)) end
|
function M.char_to_hex(c) return string.format('%%%02X', string.byte(c)) end
|
||||||
|
|
||||||
function M.urlencode(url)
|
function M.urlencode(url)
|
||||||
@@ -56,6 +61,12 @@ function M.urlencode(url)
|
|||||||
return url
|
return url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.generate_bool_set(arr, target)
|
||||||
|
for _, val in ipairs(arr) do
|
||||||
|
target[val] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function M.get_treesitter_root(bufnr) return ts.get_parser(bufnr):parse()[1]:root() end
|
function M.get_treesitter_root(bufnr) return ts.get_parser(bufnr):parse()[1]:root() end
|
||||||
|
|
||||||
function M.treesitter_match_start_end(match)
|
function M.treesitter_match_start_end(match)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "pdm.backend"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "typstar"
|
name = "typstar"
|
||||||
version = "1.2.0"
|
version = "1.3.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" }
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import aiohttp
|
|||||||
from .flashcard import Flashcard
|
from .flashcard import Flashcard
|
||||||
|
|
||||||
|
|
||||||
async def _gather_exceptions(coroutines):
|
async def gather_exceptions(coroutines):
|
||||||
for result in await asyncio.gather(*coroutines, return_exceptions=True):
|
for result in await asyncio.gather(*coroutines, return_exceptions=True):
|
||||||
if isinstance(result, Exception):
|
if isinstance(result, Exception):
|
||||||
raise result
|
raise result
|
||||||
@@ -28,7 +28,7 @@ class AnkiConnectApi:
|
|||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.semaphore = asyncio.Semaphore(2) # increase in case Anki implements multithreading
|
self.semaphore = asyncio.Semaphore(2) # increase in case Anki implements multithreading
|
||||||
|
|
||||||
async def push_flashcards(self, cards: Iterable[Flashcard]):
|
async def push_flashcards(self, cards: Iterable[Flashcard], reimport: bool):
|
||||||
add: dict[str, List[Flashcard]] = defaultdict(list)
|
add: dict[str, List[Flashcard]] = defaultdict(list)
|
||||||
update: dict[str, List[Flashcard]] = defaultdict(list)
|
update: dict[str, List[Flashcard]] = defaultdict(list)
|
||||||
n_add: int = 0
|
n_add: int = 0
|
||||||
@@ -41,6 +41,14 @@ class AnkiConnectApi:
|
|||||||
else:
|
else:
|
||||||
update[card.deck].append(card)
|
update[card.deck].append(card)
|
||||||
n_update += 1
|
n_update += 1
|
||||||
|
if reimport:
|
||||||
|
reimport_cards = await self._check_reimport(update)
|
||||||
|
print(f"Found {len(reimport_cards)} flashcards to reimport")
|
||||||
|
for card in reimport_cards:
|
||||||
|
update[card.deck].remove(card)
|
||||||
|
add[card.deck].append(card)
|
||||||
|
n_update -= 1
|
||||||
|
n_add += 1
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"Pushing {n_add} new flashcards and {n_update} updated flashcards to Anki...",
|
f"Pushing {n_add} new flashcards and {n_update} updated flashcards to Anki...",
|
||||||
@@ -48,7 +56,7 @@ class AnkiConnectApi:
|
|||||||
)
|
)
|
||||||
await self._create_required_decks({*add.keys(), *update.keys()})
|
await self._create_required_decks({*add.keys(), *update.keys()})
|
||||||
await self._add_new_cards(add)
|
await self._add_new_cards(add)
|
||||||
await _gather_exceptions(
|
await gather_exceptions(
|
||||||
[
|
[
|
||||||
*self._update_cards_requests(add),
|
*self._update_cards_requests(add),
|
||||||
*self._update_cards_requests(update, True),
|
*self._update_cards_requests(update, True),
|
||||||
@@ -115,7 +123,18 @@ class AnkiConnectApi:
|
|||||||
for deck in required:
|
for deck in required:
|
||||||
if deck not in existing:
|
if deck not in existing:
|
||||||
requests.append(self._request_api("createDeck", deck=deck))
|
requests.append(self._request_api("createDeck", deck=deck))
|
||||||
await _gather_exceptions(requests)
|
await gather_exceptions(requests)
|
||||||
|
|
||||||
|
async def _check_reimport(self, cards_map: dict[str, List[Flashcard]]) -> List[Flashcard]:
|
||||||
|
cards = []
|
||||||
|
for cs in cards_map.values():
|
||||||
|
cards.extend(cs)
|
||||||
|
if not cards:
|
||||||
|
return []
|
||||||
|
existing = await self._request_api(
|
||||||
|
"findNotes", query=f"nid:{','.join([str(c.note_id) for c in cards])}"
|
||||||
|
)
|
||||||
|
return [c for c in cards if c.note_id not in existing]
|
||||||
|
|
||||||
def _update_cards_requests(
|
def _update_cards_requests(
|
||||||
self, cards_map: dict[str, List[Flashcard]], update_deck: bool = True
|
self, cards_map: dict[str, List[Flashcard]], update_deck: bool = True
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class Flashcard:
|
|||||||
self.back_node = back
|
self.back_node = back
|
||||||
self.note_id_node = note_id
|
self.note_id_node = note_id
|
||||||
|
|
||||||
def update_id(self, value):
|
def update_id(self, value: int):
|
||||||
if self.note_id != value:
|
if self.note_id != value:
|
||||||
self.note_id = value
|
self.note_id = value
|
||||||
self.id_updated = True
|
self.id_updated = True
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ from anki.typst_compiler import TypstCompiler
|
|||||||
cli = typer.Typer(name="typstar-anki")
|
cli = typer.Typer(name="typstar-anki")
|
||||||
|
|
||||||
|
|
||||||
async def export_flashcards(root_dir, force_scan, clear_cache, typst_cmd, anki_url, anki_key):
|
async def export_flashcards(
|
||||||
|
root_dir, force_scan, clear_cache, reimport, typst_cmd, anki_url, anki_key
|
||||||
|
):
|
||||||
parser = FlashcardParser()
|
parser = FlashcardParser()
|
||||||
compiler = TypstCompiler(root_dir, typst_cmd)
|
compiler = TypstCompiler(root_dir, typst_cmd)
|
||||||
api = AnkiConnectApi(anki_url, anki_key)
|
api = AnkiConnectApi(anki_url, anki_key)
|
||||||
@@ -27,7 +29,7 @@ async def export_flashcards(root_dir, force_scan, clear_cache, typst_cmd, anki_u
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# async anki push
|
# async anki push
|
||||||
await api.push_flashcards(flashcards)
|
await api.push_flashcards(flashcards, reimport)
|
||||||
finally:
|
finally:
|
||||||
# write id updates to files
|
# write id updates to files
|
||||||
parser.update_ids_in_source()
|
parser.update_ids_in_source()
|
||||||
@@ -57,13 +59,24 @@ def cmd(
|
|||||||
"as it clears hashes regardless of their path)"
|
"as it clears hashes regardless of their path)"
|
||||||
),
|
),
|
||||||
] = False,
|
] = False,
|
||||||
|
reimport: Annotated[
|
||||||
|
bool,
|
||||||
|
typer.Option(
|
||||||
|
help="Instead of throwing an error also add flashcards that have already been asigned an id "
|
||||||
|
"but are not present in Anki. The asigned id will be updated."
|
||||||
|
),
|
||||||
|
] = False,
|
||||||
typst_cmd: Annotated[
|
typst_cmd: Annotated[
|
||||||
str, typer.Option(help="Typst command used for flashcard compilation")
|
str, typer.Option(help="Typst command used for flashcard compilation")
|
||||||
] = "typst",
|
] = "typst",
|
||||||
anki_url: Annotated[str, typer.Option(help="Url for Anki-Connect")] = "http://127.0.0.1:8765",
|
anki_url: Annotated[str, typer.Option(help="Url for Anki-Connect")] = "http://127.0.0.1:8765",
|
||||||
anki_key: Annotated[str | None, typer.Option(help="Api key for Anki-Connect")] = None,
|
anki_key: Annotated[str | None, typer.Option(help="Api key for Anki-Connect")] = None,
|
||||||
):
|
):
|
||||||
asyncio.run(export_flashcards(root_dir, force_scan, clear_cache, typst_cmd, anki_url, anki_key))
|
asyncio.run(
|
||||||
|
export_flashcards(
|
||||||
|
root_dir, force_scan, clear_cache, reimport, typst_cmd, anki_url, anki_key
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
Reference in New Issue
Block a user