Neovim plugin for efficient (mathematical) note taking in Typst
See changes in CHANGELOG.md
- Powerful autosnippets using LuaSnip and Tree-sitter (inspired by fastex.nvim)
- Easy insertion of drawings using Obsidian Excalidraw or Rnote
- Export of Anki flashcards [No Neovim required]
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, they include:
Universal snippets:
- 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$) - Common indices (numbers and letters
i-n):<letter><index>→<letter>_<index>in math and$<letter>$ <index>→$<letter>_<index>$in markup (e.gA314→A_314,$alpha$ n→$alpha_n$,$F$ n,→$F_n$,,$F$ n.→$F_n$.) - Primes:
$<letter>$ '→$<letter>'$and in combination with index and punctuation like above (e.g.$phi$ '→$phi'$,$phi$ 5'→$phi'_5$,$f$ '5.→$f'_5$.,f'5,→f'_5,)
You can find a complete map of latin to greek letters including reasons for the less intuitive ones here. Note that some greek letters have multiple latin ones mapped to them.
Markup snippets:
- Begin inline math with
kkand multiline math withdm - Markup shorthands (e.g.
HIG→#highlight[<cursor>],IMP→$==>$) - ctheorems shorthands (e.g.
tem→ empty theorem,exa→ empty example) - Flashcards:
flaandflA - All above snippets support visual mode via the selection key
Math snippets:
- Many shorthands for mathematical expressions
- Series of numbered letters:
<letter> <z/o>t<optional last index>→<letter>_<0/1>, <letter>_<1/2>, ...(e.g.a ot→a_1, a_2, ...,a zt4→a_0, a_1, a_2, a_3, a_4,alpha otk→alpha_1, alpha_2, ..., alpha_k,oti→1, 2, ..., i) - Wrapping of any mathematical expression (see operations, works nested, multiline and in visual mode via the selection key):
<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)]) - Simple functions:
fo<value>→f(<value>)(e.g.fox→f(x),ao5→a(5)) - Matrices:
<size>maand<size>lma(e.g.23ma→ 2x3 matrix)
Note that you can customize (enable, disable and modify) every snippet.
- Use
:TypstarInsertExcalidraw/:TypstarInsertRnoteto create a new drawing using the configured 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/:TypstarOpenRnoteif you are using the same file extension for both) while your cursor is on a line referencing the drawing.
Use the flA snippet to create a new flashcard
#flashcard(0, "My first flashcard")[
Typst is awesome $a^2+b^2=c^2$
]or the fla snippet to add a more complex front
#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
#let flashcard(id, front, back) = {
strong(front)
[\ ]
back
}- Add a comment like
// ANKI: MY::DECKto your document to set a deck used for all flashcards after this comment (You can use multiple decks per file) - Add a file named
.ankicontaining a deck name to define a default deck on a directory base - Add a file named
.anki.typto define a preamble on a directory base. You can find the default preamble here. - 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
- Use
:TypstarAnkiScanto scan the current nvim working directory and compile all flashcards in its context, unchanged files will be ignored - Use
:TypstarAnkiForceto 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
:TypstarAnkiForceCurrentto force compilation of all flashcards in the file currently edited - Use
:TypstarAnkiReimportto also add flashcards that have already been assigned an id but are not currently present in Anki - Use
:TypstarAnkiForceReimportand:TypstarAnkiForceCurrentReimportto combine features accordingly
- Run
typstar-anki --helpto show the available options
Install the plugin in Neovim and run the plugin setup. To run a demo installation, see Demo. To use Nix for installation, see Nix.
require('typstar').setup({ -- depending on your neovim plugin system
-- your typstar config goes here
})Example lazy.nvim config
{
"arne314/typstar",
dependencies = {
"L3MON4D3/LuaSnip",
},
ft = { "typst" },
keys = {
{
"<M-t>",
"<Cmd>TypstarToggleSnippets<CR>",
mode = { "n", "i" },
},
{
"<M-j>",
"<Cmd>TypstarSmartJump<CR>",
mode = { "s", "i" },
},
{
"<M-k>",
"<Cmd>TypstarSmartJumpBack<CR>",
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,
cut_selection_keys = "<Tab>",
})
end,
},
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
branch = "main",
lazy = false,
config = function()
require('nvim-treesitter').install { "typst" }
end
},- The snippets are designed to work with Typst
0.14. For older versions check out the legacytypst-0.13branch. - Install LuaSnip, set
enable_autosnippets = trueand set a visual mode selection key (e.g.cut_selection_keys = '<Tab>') in the configuration - Install jsregexp as described here (You will see a warning on startup if jsregexp isn't installed properly)
- Install nvim-treesitter and run
:TSInstall typst - Make sure you haven't remapped
<C-g>. Otherwise setadd_undo_breakpoints = falsein the config - Optional: Setup ctheorems with names like here
- Install Obsidian and create a vault in your typst note taking directory
- Install the obsidian-excalidraw-plugin and enable
Auto-export SVG(in plugin settings atEmbedding Excalidraw into your Notes and Exporting > Export Settings > Auto-export Settings) - Have the
xdg-opencommand working or set a different command aturiOpenCommandin the config - If you encounter issues with the file creation of drawings, try cloning the repo into
~/typstaror setting thetypstarRootconfig accordingly; feel free to open an issue
- Install Rnote; I recommend not using flatpak as that might cause issues with file permissions.
- Make sure
rnote-cliis available in yourPATHor set a different command atexportCommandin the config - Have the
xdg-opencommand working with Rnote files or set a different command aturiOpenCommandin the config - See comment 4 above at Excalidraw
- Install Anki
- Install Anki-Connect and make sure
http://localhostis added towebCorsOriginListin the Add-on config (should be added by default) - Install the typstar python package (I recommend using uv via
uv tool install typstar, using pipx should also work) - Make sure the
typstar-ankicommand is available in yourPATHor modify thetypstarAnkiCmdoption in the config
A basic demo setup using either Nix or Lazy is provided. The keybindings are defined here.
For Nix: Run nix run github:arne314/typstar#nvim -- test.typ (~200MB download).
For Lazy: Clone the repo with git clone https://github.com/arne314/typstar.git and run
just lazy if you have just installed or run ./res/lazy/lazy.sh test.typ if you don't.
You will need to have nvim and tree-sitter available in your PATH.
You can add typstar to your nix flake like so
# `flake.nix`
inputs = {
# ... other inputs
typstar = {
url = "github:arne314/typstar";
flake = false;
};
}Now you can use typstar in any package-set
with pkgs; [
# ... other packages
(pkgs.vimUtils.buildVimPlugin {
name = "typstar";
src = inputs.typstar;
buildInputs = with pkgs.vimPlugins; [
luasnip
nvim-treesitter-parsers.typst
];
})
]Configuration options can be intuitively derived from the table here.
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
templatePath = {
{ 'lectures/.*%.excalidraw%.md$', '~/Templates/lecture_excalidraw.excalidraw.md' }, -- path contains "lectures"
{ '%.excalidraw%.md$', '~/Templates/default_excalidraw.excalidraw.md' }, -- fallback
},The config 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
visualmodule- 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' }
- visual selection via e.g.
For further customization you can make use of the provided wrappers from within your 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
require('typstar').setup({
snippets = {
exclude = { 'ip' },
},
})while your LuaSnip typst.lua could look like this (< and > require escaping as <> introduces a new node)
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),
}Feel free to open an issue or a PR.
For development with Nix, a shell is provided, which you can enter via nix develop.
Running nvim from within the shell will launch a minimal installation of the plugin, sourced at startup, so no additional nix build is needed.
Tests can be executed using just test from within the shell or via nix flake check.
The code can be linted using just lint.
For development without Nix, run just lazy for a local lazy installation which sources the plugin on startup.
To run tests in lazy, run just test-lazy.
Run just --list for more details.