# Typstar Neovim plugin for efficient (mathematical) note taking in Typst ## Features - Powerful autosnippets using [LuaSnip](https://github.com/L3MON4D3/LuaSnip/) and [Tree-sitter](https://tree-sitter.github.io/) (inspired by [fastex.nvim](https://github.com/lentilus/fastex.nvim)) - Easy insertion of drawings using [Obsidian Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin) or [Rnote](https://github.com/flxzt/rnote) - Export of [Anki](https://apps.ankiweb.net/) flashcards \[No Neovim required\] ## Usage ### Snippets Use `:TypstarToggleSnippets` to toggle all snippets at any time. To efficiently navigate insert nodes and avoid overlapping ones, use `:TypstarSmartJump` and `:TypstarSmartJumpBack`. Available snippets can mostly be intuitively derived from [here](././lua/typstar/snippets), they include: Universal snippets: - Alphanumeric characters: `:` → `$$ ` in markup (e.g. `:X` → `$X$ `, `:5` → `$5$ `) - Greek letters: `;` → `` in math and `$$ ` in markup (e.g. `;a` → `alpha`/`$alpha$ `) - Common indices (numbers and letters `i-n`): ` ` → `_ ` in math and `$$ ` → `$_$ ` in markup (e.g `A314 ` → `A_314 `, `$alpha$n ` → `$alpha_n$ `) You can find a complete map of latin to greek letters including reasons for the less intuitive ones [here](./lua/typstar/snippets/letters.lua). Note that some greek letters have multiple latin ones mapped to them. Markup snippets: - Begin inline math with `ll` and multiline math with `dm` - [Markup shorthands](./lua/typstar/snippets/markup.lua) (e.g. `HIG` → `#highlight[]`, `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 - Series of numbered letters: ` ot ` → `_1, _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)): `` → `()` (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)]`) - Simple functions: `fo ` → `f() ` (e.g. `fox ` → `f(x) `, `ao5 ` → `a(5) `) - Matrices: `ma` and `lma` (e.g. `23ma` → 2x3 matrix) Note that you can [customize](#custom-snippets) (enable, disable and modify) every snippet. ### Excalidraw/Rnote - Use `:TypstarInsertExcalidraw`/`:TypstarInsertRnote` to create a new drawing using the [configured](#configuration) template, insert a figure displaying it and open it in Obsidian/Rnote. - To open an inserted drawing in Obsidian/Rnote, simply run `:TypstarOpenDrawing` (or `:TypstarOpenExcalidraw`/`:TypstarOpenRnote` if you are using the same file extension for both) while your cursor is on a line referencing the drawing. ### Anki Use the `flA` snippet to create a new flashcard ```typst #flashcard(0, "My first flashcard")[ Typst is awesome $a^2+b^2=c^2$ ] ``` or the `fla` snippet to add a more complex front ```typst #flashcard(0)[I love Typst $pi$][ This is the back of my second flashcard ] ``` To render the flashcard in your document as well add some code like this ```typst #let flashcard(id, front, back) = { strong(front) [\ ] back } ``` - 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). - 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 #### Neovim - 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 `: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 - Run `typstar-anki --help` to show the available options ## Installation Install the plugin in Neovim (see [Nix instructions](#in-a-nix-flake-optional)) and run the plugin setup. ```lua require('typstar').setup({ -- depending on your neovim plugin system -- your typstar config goes here }) ```
Example lazy.nvim config ```lua { "arne314/typstar", dependencies = { "L3MON4D3/LuaSnip", }, ft = { "typst" }, keys = { { "", "TypstarToggleSnippets", mode = { "n", "i" }, }, { "", "TypstarSmartJump", mode = { "s", "i" }, }, { "", "TypstarSmartJumpBack", mode = { "s", "i" }, }, }, config = function() local typstar = require("typstar") typstar.setup({ -- your typstar configuration add_undo_breakpoints = true, }) end, }, { "L3MON4D3/LuaSnip", version = "v2.*", build = "make install_jsregexp", config = function() local luasnip = require("luasnip") luasnip.config.setup({ enable_autosnippets = true, store_selection_keys = "", }) end, }, { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate", lazy = false, config = function() local configs = require("nvim-treesitter.configs") configs.setup({ ensure_installed = { "typst" }, }) end, }, ```
### 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 = ''`) in the configuration 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` 4. Make sure you haven't remapped ``. Otherwise set `add_undo_breakpoints = false` in the [config](#configuration) 5. Optional: Setup [ctheorems](https://typst.app/universe/package/ctheorems/) with names like [here](./lua/typstar/snippets/markup.lua) ### Excalidraw 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`) 3. Have the `xdg-open` command working or set a different command at `uriOpenCommand` in the [config](#configuration) 4. If you encounter issues with the file creation of drawings, try cloning the repo into `~/typstar` or setting the `typstarRoot` config accordingly; feel free to open an issue ### Rnote 1. Install [Rnote](https://github.com/flxzt/rnote?tab=readme-ov-file#installation); I recommend not using flatpak as that might cause issues with file permissions. 2. Make sure `rnote-cli` is available in your `PATH` or set a different command at `exportCommand` in the [config](#configuration) 3. Have the `xdg-open` command working with Rnote files or set a different command at `uriOpenCommand` in the [config](#configuration) 4. See comment 4 above at Excalidraw ### Anki 0. Typst version `0.12.0` or higher is required 1. Install [Anki](https://apps.ankiweb.net/#download) 2. Install [Anki-Connect](https://ankiweb.net/shared/info/2055492159) and make sure `http://localhost` is added to `webCorsOriginList` in the Add-on config (should be added by default) 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) ### 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 options can be intuitively derived from the table [here](./lua/typstar/config.lua). ### Excalidraw/Rnote templates The `templatePath` option expects a table that maps file patterns to template locations. To for example have a specific template for lectures, you could configure it like this ```Lua templatePath = { { 'lectures/.*%.excalidraw%.md$', '~/Templates/lecture_excalidraw.excalidraw.md' }, -- path contains "lectures" { '%.excalidraw%.md$', '~/Templates/default_excalidraw.excalidraw.md' }, -- fallback }, ``` ### 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' }` - disable different behaviors of snippets from the `visual` module - visual selection via e.g. `snippets.visual_disable = { 'br' }` - normal snippets (`abs` → `abs(1+1)`) via e.g. `snippets.visual_disable_normal = { 'abs' }` - postfix snippets (`xabs` → `abs(x)`) via e.g. `snippets.visual_disable_postfix = { 'abs' }` 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), } ```