lazytypst is a terminal program for Typst documents.
The project website is at https://orpheus-21.github.io/lazytypst/.
- Install lazytypst:
cargo install --locked --git https://github.com/Orpheus-21/lazytypst(see Install). - Start it in a folder with Typst files:
lazytypst ~/my-typst-folder - Select a file with
jandk, pressEnter, and type. The preview follows the text. PressF1to see all keys.
lazytypst lists the .typ files in a folder. The user opens one file, edits the text, and saves it. The user can also compile the file with the typst command. The program shows the compile errors with their line numbers. After a good compile, the program shows the pages of the document as images next to the editor, one page at a time. A failed compile keeps the last good page on screen. A new compile keeps the page number.
Each compile renders only the page on screen, so a long document stays fast. In a release build, the page of a book of 500 pages appears after 0.7 s. When all 500 pages were rendered, it took 2.2 s. The cost is that a page turn needs one compile, about as long as a normal compile. The old page stays on screen until the new page is ready.
When you open a file, the program compiles it at once, so the preview shows without a key. The program saves the file 300 ms after the last key. Then it compiles the file. If the last compile took 1 s or more, the compile waits twice that time after the last key, up to 3 s, so a slow document does not keep the machine busy while you type. The save does not wait, and the title of the compile pane says (next in 2.4 s) while the compile waits. A new key cancels the wait, and Ctrl-B compiles at once. A new compile kills the compile that still runs. The preview follows the text.
A project often has one main file, such as main.typ, that includes the other files. In the file list, press m to mark the selected file as the main file. The list shows [main] after its path. Then the live compile, Ctrl-B, and Ctrl-E use the main file, whatever file you edit. The autosave still saves the file that you edit. The editor title shows the main file. Press m on the main file again to remove the mark. If you have not chosen a main file, and the folder has a typst.toml with an entrypoint, or a file main.typ, the program marks that file as the main file by itself, and the status line says (found). It does not save this mark. If you remove it, the program saves that choice, and it does not mark the file again at the next start. Without a main file, the program compiles the open file.
The program remembers the main file of each project. It saves the choice in the file $XDG_STATE_HOME/lazytypst/main-files, or ~/.local/state/lazytypst/main-files if the variable is not set. The program ignores a relative value of XDG_STATE_HOME. The project folder gets no new file. At the next start, the program marks the same file again, if the file still exists in the list. If the program cannot save the choice, the status line says so, and the mark still works in this run.
The program also remembers the last file that you closed in each project, and the page of its preview. It saves them in the file last-files, in the same folder as main-files. At the next start in the same folder, the list selects that file. The program does not open it. When you open it, the preview shows the saved page, or the last page if the document got shorter. If the file is gone, the list selects the first file.
The compile pane under the editor shows the result of the last compile. After a good compile, its first line says how long Typst needed, for example OK in 310 ms. The number of a line with an error is red and bold in the gutter of the editor, and the number of a line with a warning is yellow (without colors: reversed, and underlined). If a line has both, the error wins. The marks come from the last compile, so they can sit on the wrong line for a moment after you add or remove lines. The title shows how many errors and warnings the report has, for example Compile: 2 errors, 1 warning. After a failed compile, the title also shows the time, for example Compile: 1 error (310 ms). A compile with warnings only is green. An error line is red, a warning line is yellow, and other lines, such as hints, are dim. The colors are the colors of the terminal palette, so they follow your theme. The pane has room for 4 rows, and long lines wrap. If the report needs more rows, the last row says how many are hidden, for example +3 more. The count is in screen rows after the wrap.
The folder that you give to the program is the Typst project root. A file in a subfolder can import a file from a parent folder, such as ../lib.typ, if that file is inside the root. Error lines and the editor title show paths relative to the root.
lazytypst does these jobs: it browses the Typst files of a project, edits one file, compiles it, shows the errors, shows the pages, and exports a PDF.
The project follows these guides: few keys, good defaults, no configuration file, and the keys on screen.
lazytypst does not act as a general file manager, as a language server client, as a plugin host, or as an editor with many open files. It does not replace the options of the Typst CLI with settings.
An idea that adds keys or settings without making this work faster is closed with a link to this section. Read CONTRIBUTING.md before you open an issue.
- Rust 1.90 or later, with Cargo.
- The
typstcommand inPATH, version 0.12.0 or later. Version 0.12.0 added the options that render one page. The program is tested with version 0.15.1. At start, the program runstypst --version. If that fails, the program prints which command it needs, why it cannot run it, and the Typst install page, and it exits with code 1. It makes no temporary folder and draws nothing before that. - The variable
LAZYTYPST_TYPSTcan name another Typst program, for example a second version or a wrapper script:LAZYTYPST_TYPST=/opt/typst-0.14/typst lazytypst. If the variable is set and not empty, the compile, the export, and the version check of--versionrun that program instead oftypst. If it cannot run, the compile pane showsCannot runand the path. Without the variable, the program runstypstfromPATH. - The variable
LAZYTYPST_WATCH=1is an option for faster compiles. The program then keeps onetypst watchrunning for the open file and the wanted page, and it reads the result of each compile from it, so a compile after a small change needs a few milliseconds and not the 250 ms of a newtypstprocess (most of that time is the load of your system fonts). Measured from the key to the changed preview on a machine with 920 fonts: a document of 10 pages shows the change after 767 ms and not 870 ms (about 0.1 s sooner, and 300 ms of the time is the pause before the autosave), and a heavy document of 570 pages after 849 ms and not 4842 ms. The costs: the watch process stays in memory while the file is open (about 70 MB for 500 simple pages, and 1.4 GB for the heavy document), a page turn,Ctrl-B, a change of the main file, and a change of the resolution start the process again (that compile is as slow as without the option), the program reads the status lines that typst prints (tested with typst 0.15.1), and the pane shows the time that typst reports for the compile.F5ends the watch, andCtrl-Bthen compiles once. The program ends the process when the editor closes. After a hard kill of the program, the process stays in memory until the next start of the program ends it (it also ends by itself at its next compile). Typst watches the included files and images itself, so the program does not list them (see the next sections). The default is the old way until this option has had more use. Please tell me about any problem with it. - Linux. The program is tested on Linux only.
- A terminal. At start, the program asks the terminal which image protocol it supports. The program uses the kitty, sixel, or iTerm2 protocol when the terminal reports one. Otherwise it draws the page with half block characters. The sixel and iTerm2 protocols are not tested.
There are four ways to install lazytypst: use Cargo, use the PKGBUILD on Arch Linux, download a release, or build the program from a clone. Each way needs the typst command in PATH (see Requirements).
This way needs Rust 1.90 or later. It needs no clone of the repository.
cargo install --locked --git https://github.com/Orpheus-21/lazytypst
Cargo builds the program and puts it in ~/.cargo/bin. That folder is in PATH for most Rust users. Then lazytypst --version works in a new terminal. To update, run the same command again. To remove the program, run cargo uninstall lazytypst.
The folder packaging/aur/ has a PKGBUILD for the package lazytypst-git. It builds the latest commit of main and needs typst, cargo, and git. To build and install it, run makepkg -si in that folder. The package is not on the AUR yet. After it is there, yay -S lazytypst-git will install it.
This way needs no Rust. The release 0.1.0 is the first one.
-
Download these two files from the page of the release, https://github.com/Orpheus-21/lazytypst/releases:
lazytypst-<version>-x86_64-linux.tar.gzlazytypst-<version>-x86_64-linux.tar.gz.sha256
-
Check the archive against its checksum file.
sha256sum --check lazytypst-<version>-x86_64-linux.tar.gz.sha256 -
Unpack the archive.
tar -xzf lazytypst-<version>-x86_64-linux.tar.gz -
Copy the program into a folder that is in
PATH.install -m 755 lazytypst-<version>-x86_64-linux/lazytypst ~/.local/bin/
To check where the archive comes from, run gh attestation verify lazytypst-<version>-x86_64-linux.tar.gz --repo Orpheus-21/lazytypst. The command needs the GitHub CLI. It checks that GitHub built the archive from this repository with the workflow release.yml, and it names the commit. A changed archive fails the check. The archives of releases after 0.1.0 have this attestation. The archive of 0.1.0 has none, and the checksum file is the only check for it.
The archive also holds the man page lazytypst.1. To install it, run install -D -m 644 lazytypst-<version>-x86_64-linux/lazytypst.1 ~/.local/share/man/man1/lazytypst.1. Then man lazytypst shows it.
The program in the archive is a static binary. It runs on any x86_64 Linux system, and it does not depend on the C library of the system. It still needs the typst command in PATH.
This way is for people who want to change the program.
-
Clone the repository.
git clone https://github.com/Orpheus-21/lazytypst.git -
Go to the folder.
cd lazytypst -
Build the program.
cargo build --release
The program is the file target/release/lazytypst.
To read the man page from a clone, run man ./docs/lazytypst.1.
Run the program with a folder as the argument. If you give no argument, the program uses the current folder. If you give a file with the ending .typ, the program opens that file in the editor at once. The folder of the file is the project root and the folder of the file list, and Esc shows that list with the file selected. Add :LINE or :LINE:COLUMN after the file name, for example main.typ:42:7, and the cursor starts at that place. A name that exists as written stays whole, so a file with a colon in its name still opens. If the folder has exactly one .typ file and you name no file, the program opens that file in the editor at once, and Esc shows the list with the file. With no file, or with two or more files, the program starts with the list. A file that does not end with .typ gives an error and exit code 2. A path that does not exist gives an error and exit code 1. lazytypst --help prints the usage and the keys. lazytypst --keys prints all keys as lines of tab separated text: the part of the program (list, editor, or text), the keys, and what they do. lazytypst --doctor prints one line for each check and ends with exit code 0 if all checks pass, or 1 if not. It checks the Typst program (and prints its version, or missing with the install link), the image protocol that the terminal reports (kitty graphics, sixel graphics, iTerm2 graphics, or half blocks with the reason), that the temporary folder is writable, and how many stale lazytypst-<process id> folders of earlier runs exist. If the input or the output is not a terminal, for example with a pipe or a redirect, the program does not ask, and the line terminal says unknown: run lazytypst --doctor without a pipe to get the answer. Half blocks are not a failure. A stale folder is not a failure: the next start deletes it. Run it in the terminal where the preview looks wrong, and put the output in a bug report. lazytypst --version prints two lines: the version of lazytypst, and the version line of the typst command. If typst does not run, the second line says why, for example typst: not found in PATH. Put both lines in a bug report. A folder name that starts with a dash needs ./ in front.
target/release/lazytypst ~/Documents
The program searches the folder and its subfolders, three levels deep. It skips hidden folders.
The program sets the title of the terminal window to lazytypst: <folder name> in the file list, and to lazytypst: <path of the file> in the editor. The path is relative to the project root. A control character in a name becomes a question mark, because the title travels inside an escape sequence. When the program starts, it sends the command that saves the window title, and when it ends, it sends the command that restores it. Terminals that follow xterm restore your old title. Ghostty 1.3.1 ignores both commands. In Ghostty, the title that the program set stays until your shell sets it again, which the shell integration of Ghostty does at the next prompt.
At start, the program makes the folder lazytypst-<process id> with access for your user only. The folder is inside $XDG_RUNTIME_DIR (for example /run/user/1000), because only your user can open that folder, and the system removes it at the last logout. If XDG_RUNTIME_DIR is not set, or it is not a folder of yours, the folder is in the temporary directory. The temporary directory is shared with other users, who could take the name first. If that path exists already, the program stops with an error. There is one exception: a real folder of yours with this name was left by an earlier run to which the system had given the same process id. The program deletes that folder and makes it new. A link, a file, or a folder of another user still stops the program with an error. The program deletes the folder when it quits or panics. A closed terminal window or a kill can leave the folder behind. The next start deletes each such folder that belongs to you and has no running process, in $XDG_RUNTIME_DIR and in the temporary directory (earlier versions made the folder there).
Keys in the file list. Each line shows the time since the last change of the file at the right end, in a dim style: now, 5 min, 2 h, 3 d, 4 mo, or 2 y. The program hides all ages if one would cut the longest path. The list reads the times again when you press r, when you press s, and when you close the editor.
?orF1: open the help window. The window lists all keys of the list, the editor, and the text area, the same list aslazytypst --help.jandkor the arrow keys select a line.Entercloses the window and presses the key of the line, so the window also works as a menu of actions.Esc,q,?, orF1closes it. While it is open, no other key acts. In the editor,?types a question mark, andF1opens the window.jorDown: select the next file.korUp: select the previous file.Enter: open the selected file in the editor.n: make a new file. A prompt asks for a path, for examplechapters/two.Entermakes the empty file, opens it in the editor, and selects it in the list.Esccancels. The program adds.typif the name has no ending, and it makes missing folders. The program refuses a name that leaves the project, that has an ending other than.typ, that starts with a dot, that is deeper than the list reads, that goes through a link, or that exists already. The prompt then stays open and the status line gives the reason.r: read the folder again. New files appear and deleted files go. The selection stays on the same file if it is still there. A main file that is gone loses its mark.gorHome: select the first file.GorEnd: select the last file./: filter the list. A promptFilter:asks for a part of a path. The list shows only the files whose path contains that text, and the match ignores case. The list follows the text while you type.Enterkeeps the filter, and the list title shows it, for examplelazytypst /chap.Escremoves the filter, in the prompt and also in the list. If no file matches, the list saysNo match.rkeeps the filter. A new file removes it, so that the file shows. The filter does not change the main file.s: switch the order of the list between the path and the last change, newest first. The order by path ignores case and counts a number by its value, sochapter-2.typcomes beforechapter-10.typ, anda.typbeforeREADME.typ. The title of the list shows(newest first)in the second order. The selection stays on the same file. Files with the same time stay in the order by path.e: edit the selected file in your own editor. The program runs the command in the variableVISUAL, or elseEDITOR, in the same terminal, with the file as the last argument. A value such ascode --waitsplits on spaces into the program and its arguments. The program leaves its screen, waits, and comes back. Then it opens the file in its own editor and starts a compile, so the preview shows the new text. If neither variable is set, the status line says so. If the editor ends with an error, the status line shows how it ended.E: export the PDF of the selected file, with the same job asCtrl-E. The PDF goes next to the file, for examplereport.typbecomesreport.pdf. This key uses the selected file and not the main file. The list stays usable while the export runs. The status line showsExported report.pdf, or the first error line of Typst. A new export stops an export that still runs.y: copy the absolute path of the selected file to the system clipboard. The status line showsCopiedand the path.m: mark the selected file as the main file, or remove the mark.q: quit.
Keys in the editor:
Ctrl-S: save the file now.Ctrl-Salso overwrites a file that another program changed. See the save rules below.Ctrl-B: save the file and compile it now. A compile that still runs is killed and replaced.Ctrl-E: save the file and export a PDF. The PDF has the name of the compiled file with the ending.pdf, in the same folder. Example:doc.typbecomesdoc.pdf. With a main file, the PDF comes from the main file, for examplemain.pdf. The program replaces a PDF with this name without a question. The pane shows the path until the next export, or it shows the errors.Ctrl-O: open the last exported PDF in the system viewer. The program startsxdg-openin the background, so the screen stays clean. Before an export, the status line says that there is no PDF yet. Ifxdg-opencannot start, the status line shows the error.Ctrl-Q: save the file and quit the program. It does the same asEscand thenq. If the program cannot save, it shows the reason, and a secondCtrl-Qquits without a save.F5: turn the live compile off or on. The default is on. While it is off, typing still saves the file after a pause, but no compile starts, and the title of the compile pane saysCompile (paused).Ctrl-Bstill compiles. A page turn still compiles. The setting is for this editor only and it is on again when you open the next file. Use it for a very large document.Ctrl-F: search. A promptSearch (text):opens in the status line. Type the text, for examplef(x)or1+1: special characters are plain characters. A text with no capital letter ignores the case, and a text with a capital letter respects it. The text area marks all matches while you type.EnterorCtrl-Fmoves the cursor to the next match, and the search wraps to the top of the file after the last match.Alt-Rin the prompt switches to a regular expression (the prompt then saysSearch (regex):, and the match respects the case).Esccloses the prompt and keeps the cursor where it is. If nothing matches, the status line saysNo match forand the text, and the cursor stays. In regex mode, an invalid pattern shows the reason in the status line.Ctrl-Fdoes not move the cursor to the right: useRight.F2: switch to another file of the project. The program saves the text first, then a popup lists the files, the ones you opened in this run first, the latest first. Type to filter (the match ignores case),DownandUp(orCtrl-NandCtrl-P) select,Enteropens the file,Esccloses the popup.F2again opens the file that was open before this one, so two presses ofF2toggle between two files. The popup does not list the open file. It also works in the full preview.Alt-EnterorCtrl-]: open the file that the string under the cursor names, for example in#include "chapters/two.typ"or#import "lib.typ": x. If the cursor is on no string, the first string of the line counts. A name is read from the folder of the open file, and a name that starts with/is read from the project root, as Typst does. The program saves the text first.F2thenF2goes back. A name that is not a file of the project is only named in the status line: a missing file, a file that does not end with.typ(the editor opens.typfiles only), a package such as@preview/cetz:0.3.0, a path that leaves the project, and a link that leads out of it.F6: show the history of the file. The program keeps the text of the file when you open it, and after a save at most every 30 s, if the text is new. A popup lists these versions, the newest first, with their age, their size, and what each one would change in the buffer (-3 +1 linesmeans 3 lines go and 1 line comes back).Enterputs the chosen version in the buffer as one edit, so the autosave saves it, andCtrl-Ztakes it back.EscorF6closes the popup. The versions are copies in thehistoryfolder of the state folder, in a folder for your user only, never in the project. The program keeps at most 50 versions of a file and 20 MiB of versions for all files, and it drops the oldest first. A text over 2 MiB has no history. Delete the folderhistoryto remove all versions.F7: turn the spell check on or off. The choice is kept for the next files and runs. The check uses the programhunspell(the variableLAZYTYPST_SPELLnames another program that speaks the same pipe protocol) and a dictionary, so install both, for examplepacman -S hunspell hunspell-en_usorapt install hunspell hunspell-en-us. The dictionary comes from#set text(lang: "de")in the file (engivesen_US,degivesde_DE,pt-BRgivespt_BR), else fromLANG, else it isen_US. The variableLAZYTYPST_SPELL_DICTsets the dictionary by name or by path. Words that hunspell does not know get a red underline after a pause of 300 ms. The check reads only prose: it skips comments, math, raw text, strings, names after#, lines that start with#set,#show,#let,#import, or#include, labels, references, words with digits, and addresses. A check runs in a thread, so typing stays fast, and the underlines hide while the text is newer than the last check. Ifhunspellor the dictionary is missing, the status line says so and the program does not try again until the text changes.Alt-;: go to the next misspelled word (a word at the cursor counts, and the search wraps at the end) and open a list: up to 8 suggestions,Add "word" to the dictionary(kept in the filespell-words.txtin the state folder), andIgnore "word" in this run.Enterchooses,Esccloses. A suggestion replaces the word as one edit, andCtrl-Ztakes it back. PressAlt-;again for the next word.F3: the preview follows the cursor, or not. It is on at first, and the choice is kept. After a good compile, the program asks Typst for the headings of the document and their pages (typst eval, so your file needs no change). When the cursor enters another section, the preview goes to the page of its heading. A section is the text from a heading to the next heading.Alt-UpandAlt-Downstill turn pages, and the preview stays there until the cursor enters another section. The program finds the heading by its text, and if the same text comes more than once it counts them in order, so two headings with the same text in different files of one document can give the wrong page.F9: take the mouse, or leave it to the terminal. It is off at first, and the choice is kept (the filemousein the state folder). With the mouse on, in the editor a click puts the cursor under the pointer (a click in the line numbers goes to the start of the row, and a click right of the text goes to its end), a drag selects, the wheel scrolls the text three rows, and the wheel over the preview turns the page. In the file list, a click selects a file, a second click on it within half a second opens it, and the wheel moves the selection. A popup, the help window, and a prompt take no mouse. The terminal cannot select text by itself while the program has the mouse: holdShiftand drag, which most terminals (Ghostty, kitty, foot, WezTerm, Alacritty) pass to their own selection. The program gives the mouse back when it quits, when a panic ends it, and while an external editor runs.Alt-I: insert the image from the system clipboard. The program reads it withwl-paste(Wayland, packagewl-clipboard) orxclip(X11), takes the first of png, jpeg, webp, gif, or svg that the clipboard offers (at most 20 MiB), saves it asimages/pasted-YYYYMMDD-HHMMSS.pngin the project (the time is UTC, and the folder is made on first use), and puts#image("/images/pasted-....png", width: 80%)at the cursor. The path starts with/, so it is right in a file of any folder. The variableLAZYTYPST_IMAGESnames another folder, a short path inside the project. A folder that is a link or a file is refused, a file is never replaced (a second image in the same second gets-2), and the line is one edit, soCtrl-Ztakes it back. The image file stays. Without a clipboard program, or with no image on the clipboard, the status line says so and nothing is written.Alt-L: load the file from disk and drop the edits of the buffer. Use it when the status line says that the file changed on disk. Your text goes to the history first (F6), so a wrong key loses nothing.F10: change the layout. There are three: editor and preview side by side, editor above the preview (stacked), and editor only (the preview is hidden, and the compile pane stays). A window under 100 columns starts stacked, because side by side leaves each part too little room, and a wider window starts side by side. The first press ofF10goes to the next layout, and the choice then stays, also for the next file and the next run (the filelayoutin the state folder). For the preview alone, useF11.F4: show the outline, a popup with the headings of the open file, indented by level. The section of the cursor is selected. Type to filter (the match ignores case),DownandUpselect,Entermoves the cursor to the heading,F4orEsccloses the popup. A line that looks like a heading inside a comment, math, or raw text is not listed. A file without headings says so. The popup never changes the text.Alt-S: replace the text of the last search. Press it in the editor after a search, or inside the search prompt. A promptReplace with:opens in the status line, and the cursor goes to the first match. Type the new text.Enterreplaces the match at the cursor and goes to the next one, and the status line says how many are left.Alt-Areplaces all matches and says how many.Escstops. The prompt starts with your last replacement. The new text is plain text. In regex mode (Alt-Rin the search),$1and${name}give the groups of the match. OneCtrl-Ztakes back one replace or one replace-all. Without a search text,Alt-SsaysSearch first.F11: show the preview on the full screen, and back. The text area and the compile pane are hidden, and the status line saysF11 back to editorand shows the zoom. Typing does nothing there, so no text changes by accident.F11orEscbrings the editor back, andEscdoes not go to the file list from here.Alt-Down,Alt-Up,Alt-Home, andAlt-Endstill change the page. The live compile and the autosave keep running.- In the full preview,
+zooms in one step and-zooms out one step. The steps are 100%, 150%, 200%, and 300%.0shows the whole page again. At a zoom above 100%, the arrow keys move the view by a quarter of the way that it can go, andHomeandEndgo to the top and the bottom of the page. A zoom starts a compile at a higher resolution (typst compile --ppi, 144 pixels per inch at 100%), so the part on screen stays sharp. The zoom and the place stay when the page changes or a new compile arrives. A page at 300% has about 4000 by 5000 pixels, so it needs some memory and time. Alt-G: go to a line. A promptLine:opens in the status line. Type the line number, or the line number, a colon, and the column (42or42:7), then pressEnter. A number beyond the end goes to the last line, and the status line says so.Esccloses the prompt. The prompt takes digits and the colon only.F8: go to the next error after the cursor.Shift-F8: go to the previous error. Both wrap at the ends. The status line says which error it is, for exampleError 2 of 5 at 14:3. An error in another file of the project opens that file, after a save, asCtrl-Gdoes.Ctrl-G: go to the first error of the last compile. The cursor moves to the line and the column that Typst reports. If that error is in another file of the project, for example a chapter that the main file includes, the program saves the open file, opens the other file, and moves the cursor to the error there. The compile target stays the same, so the preview shows the same document. If the error is in a file outside the project, such as a package, the status line names the file, and nothing opens. If the last compile has no error, the status line says so. The line and the column come from the text at the time of the compile. After more edits, compile again to get exact places.Alt-Home: show the first page.Alt-End: show the last page. Both start one compile, like a page turn.HomeandEndwithoutAltmove the cursor.Alt-Down: show the next page.Alt-Up: show the previous page. Both stop at the first page and at the last page. A page turn starts a compile for the new page. It does not save the file. A fast second press replaces the compile of the first, so the last press decides the page. The title of the preview shows the page on screen and the page count. If the document gets shorter than the page on screen, the program shows the last page.Ctrl-C: copy the selected text.Ctrl-X: cut it. Select text withShiftand the arrow keys. Both keys also put the text on the system clipboard with the OSC 52 escape sequence, so you can paste it in another program.Ctrl-Ypastes the text that the text area holds. A terminal without OSC 52 support leaves the system clipboard unchanged.Esc: save the text and go back to the file list. The program remembers where the cursor was in each file until you quit. If you open the same file again, the cursor goes back to that place. If the file got shorter, the cursor goes to the end of the last line. A new start begins at line 1. If the save is not possible, the program shows the reason. A secondEscthen closes the editor without a save. Closing the editor kills a compile that still runs.
The editor colors the Typst source: headings (lines that start with = and a space), commands after # (keywords such as #set, #let, and #import are magenta, other names such as #image are cyan), strings in code, math between $ signs, raw text between backticks, and comments (// and /* */). The colors are the palette colors of your terminal, so they follow its theme. With NO_COLOR, the parts use bold, dim, and italic instead. The scanner is small and it is not a Typst parser: it can color a part wrong, for example in an unusual mix of code and markup. The color never changes the text. On a file of 2000 headings and 2000 text lines, the color step takes about 0.7 ms in a release build, in a draw of 4.6 ms. All other keys edit the text. A long line wraps on screen, at a word if possible. The file keeps it as one line. The editor shows a dim line number at the left edge of each line. A line that wraps shows its number on the first row only. If the window is wide enough, the status line also shows the heading of the section that holds the cursor, as it is written (for example == A small table), before the word count. It shows nothing if there is no room, or no heading above the cursor, and it cuts a long heading with .... The right end of the status line shows an approximate word count, such as 1234 words, and then the cursor position as line:column. The count is approximate. A word is a part of the text between spaces that has at least one letter or digit, so a heading mark = is not a word. The count skips comments (// and /* */), math between $ signs, and lines that start with #set, #show, #import, #let, #include, #pagebreak, #colbreak, #bibliography, #outline, #context, or #counter. If such a line opens a bracket that it does not close, the count also skips the lines up to the closing bracket. A function call in the middle of a line still adds its parts. The count updates after each change of the text. Both numbers of the cursor position start at 1, and the column counts characters, the same as the error lines of Typst. Tab, Shift-Tab, and the indent after Enter follow the file. The program reads the first 200 indented lines when it opens the file. If most start with a tab character, Tab inserts a tab character. If not, a level is the most common step between two lines in a row: 2, 3, 4, or 8 spaces. A file without a clear step gets 2 spaces. Tab goes to the next stop of one level, so at the start of a line it inserts one level. Tab characters that are already in a file stay. A paste arrives as one piece: the program asks the terminal for bracketed paste. One undo takes back a whole paste. A tab character in pasted text stays a tab character, and a line end in pasted text becomes LF. The file list ignores a paste. The text area uses the Emacs-style keys of the ratatui-textarea crate. Ctrl-B and Ctrl-E do the jobs above and not the Emacs jobs. Use Left and End instead. These editing keys work. A test of the project checks each one.
| Key | Action |
|---|---|
Ctrl-U, Ctrl-Z |
Undo. |
Alt-A |
Select all the text. |
Ctrl-R |
Redo. |
Shift with the arrow keys |
Select text. |
Ctrl-C |
Copy the selection. |
Ctrl-X |
Cut the selection. |
Ctrl-Y |
Paste the text that the text area holds. |
Shift-Tab |
Remove one indent level from the line (a tab character, or up to one level of spaces). |
Enter |
Break the line and keep its indent. After (, [, or { the new line has one level more. |
Ctrl-W |
Delete the word before the cursor. |
Ctrl-K |
Delete to the end of the line. |
Ctrl-J |
Delete to the start of the line. |
Alt-F, Alt-B |
Move one word forward or back. |
Ctrl-A, End |
Move to the start or the end of the line. |
Ctrl-Home, Ctrl-End |
Move to the start or the end of the file. With Shift, select to there. |
Ctrl-V, Alt-V |
Scroll one page down or up. |
Alt-<, Alt-> |
Move to the first or the last line. The column stays. |
Left, Right, Backspace, and Delete work on one visible character, not on one code point. A Devanagari conjunct, a letter with a combining mark, and an emoji with joiners count as one character. The column in the status line still counts code points, the same as the error lines of Typst.
Alt with a letter arrives as Esc and the letter. A terminal that sends both at once gives the program the Alt key. F1 shows the same table in the help window.
Save rules:
- A save writes the file only when the text has edits. A compile or an export of an unchanged file does not write it.
- Before the autosave,
Ctrl-B,Ctrl-E, orEscwrites, the program compares the modification time of the file. If another program changed the file, the program does not write it and shows a warning.Ctrl-Sthen overwrites the file with your text.Alt-Lloads the file from disk instead and drops the edits of the buffer. The program keeps your text in the local history first (F6lists it), and if there is no history it refuses and keeps your text.Esctwice closes the editor and keeps the version on disk.Ctrl-Qtwice does the same and quits the program. - About once a second, while the text has no edits, the program compares the modification time of the file. If another program changed the file, the program loads the new text, keeps the cursor on the same line number, and starts a compile. If the file got shorter, the cursor goes to the last line. If another program deleted the file, the status line says that the file is gone, and the text stays in the editor.
Ctrl-Swrites it again. - A save writes the text to a hidden temp file next to the file. Then it renames the temp file over the file. A crash during a save leaves the old text or the new text, never a cut file. A symlink stays a symlink, and the permissions stay. A hard link to the file keeps the old text.
- A save writes the text with one final line end. The line end is CR LF if the file had CR LF when the program read it, and LF if not. So a diff shows only your edits.
To report a security problem, use the private report form described in SECURITY.md.
A project from the internet can hold files that harm you when a program opens them. lazytypst limits these risks.
- The file list shows only regular files. A
.typlink counts only if it points at a regular file inside the project. A pipe, a device, a dangling link, and a link to a file outside the project are not listed. The program opens a regular file of at most 16 MiB, and only if it is UTF-8 text. For a file in another encoding the list showsit is not UTF-8 text. Try: iconv -f latin1 -t utf-8, and the program does not open the file, so it never writes a damaged copy of it. - An export writes a new file next to the source and renames it onto the PDF. So a link such as
report.pdfthat points at another file of yours is replaced, and the other file stays. - A compile or an export stops after 60 seconds. The program keeps the first 1 MiB of the error text of Typst. If the
prlimitprogram of util-linux is inPATH, it also limits the memory of Typst to 8 GiB, so a page of 500 cm by 500 cm cannot stop the machine. - The program compiles a file when you open it. But Typst can read a file through a link inside the project, and the file can then show in the preview and in an exported PDF. So the program looks for links that point outside the project when it reads the folder. If it finds one, it shows a warning when a file opens, and it starts no compile then.
Ctrl-Band the autosave after an edit still compile. This is a warning, and it does not stop Typst: do not share a PDF of a project that you did not read. - A document that imports a package from
@previewmakes Typst download the package.
The program honors the variable NO_COLOR (see https://no-color.org/). If NO_COLOR is set and not empty, the program uses no color for text and borders. An error line in the compile pane is bold, and a warning is plain. The title of the pane still says the state, for example Compile (running) or Compile: 2 errors. The file list uses reverse video for the selected file. The page preview is an image, and it keeps its colors. With NO_COLOR unset or empty, an error is red, a warning is yellow, and the border of the pane is green, red, or yellow.
lazytypst passes its environment to the typst command, so the variables of Typst work. This section names the ones that matter for fonts and packages. The names come from typst compile --help of Typst 0.15.1.
TYPST_FONT_PATHS: extra folders with fonts. Example:TYPST_FONT_PATHS=~/fonts lazytypst ~/book. The fonts in~/fontsare then available to the document.TYPST_IGNORE_SYSTEM_FONTS: use only the fonts of the folders above and the fonts that Typst has built in. Example:TYPST_IGNORE_SYSTEM_FONTS=true lazytypst ~/book. The value istrueorfalse.TYPST_PACKAGE_PATH: a folder with local packages. Example:TYPST_PACKAGE_PATH=~/packages lazytypst ~/book.TYPST_PACKAGE_CACHE_PATH: the folder where Typst keeps the packages that it downloaded.TYPST_ROOThas no effect, because lazytypst always gives--rootwith the folder of the list. An option on the command line wins over the variable.
An import such as #import "@preview/..." downloads the package at the first compile. That compile needs the internet and can take longer. The compile pane shows the download text of Typst. Later compiles use the cache.
lazytypst is one of several ways to work with Typst. This is how it differs from three others.
typst watchcompiles a file again each time it changes, and it has incremental compilation. It shows no preview of its own: you open the PDF in a viewer. lazytypst has no incremental compilation, but it shows the page next to the text in the same terminal, and it shows the errors there.- The preview of the tinymist language service works in editors such as VS Code, Neovim, Emacs, Sublime Text, Helix, and Zed, and a language service gives more than a preview. lazytypst has no completion or other language features, but it needs no editor, plugin, or configuration.
- The typst.app web app is an online editor for teams, with instant preview and collaboration. lazytypst works on files on your own machine and offline, except for the first download of a package.
Why does lazytypst save my file while I type? The preview follows the saved file, so the program saves 300 ms after your last key and then compiles. See What it does.
Can lazytypst lose my text? It is built so that it does not. The program writes only when the text has edits. It does not overwrite a change that another program made, until you press Ctrl-S. It writes a temp file and renames it, so a crash cannot cut your file. If you close the terminal window, or the system sends SIGHUP or SIGTERM, the program writes the edits that the autosave has not written yet before it ends (the input is read in its own thread, because the library that reads the terminal never returns after the terminal closed). If the file changed on disk, the program does not write over it: your text goes to the local history (F6), and the program prints where it is. After a panic (a bug), the edits that the file lacks go to the local history too. The program does not write the file then, because the buffer may be in a bad state, and it prints the path of the copy. Please report a panic. See the save rules in Usage.
Where do the preview pages go, and when are they deleted? They go into a private folder lazytypst-<process id> in $XDG_RUNTIME_DIR, or in the temporary directory if that is not set. The program deletes it when it ends, and the next start deletes the folders of earlier runs that crashed. See Usage.
Why do the page keys use Alt with the arrow keys? The plain arrow keys move the cursor in the text. Alt-Down and Alt-Up are free, and the terminal sends them as one key. See Usage.
Why is the preview made of blocks in my terminal? Your terminal reports no image protocol, so the program draws half blocks. Run lazytypst --doctor to see what the terminal reports. See Troubleshooting.
Does lazytypst work without the internet? Yes. Only the first compile of a document that imports a Typst package from @preview downloads the package. See Fonts and packages.
The page preview is an image. The terminal must have an image protocol. The program asks the terminal at start, and lazytypst --doctor prints the answer.
| Terminal | Image method | State |
|---|---|---|
| Ghostty | kitty protocol | tested |
| kitty | kitty protocol | not tested |
| WezTerm | iTerm2 protocol and sixel | not tested |
| foot | sixel | not tested |
| Konsole | not known | not tested |
| Alacritty | none, so half blocks | not tested |
| tmux | depends on the terminal and the settings | tmux 3.7c in a detached session: no image protocol, so half blocks, and --doctor shows the passthrough hint. With a terminal behind tmux and allow-passthrough on: not tested |
The program also works in a terminal with no image protocol. It then draws the page with half block characters.
The terminal has no image protocol, or the program could not read the answer of the terminal. The program then draws half blocks. Run lazytypst --doctor. If the line terminal says half blocks, use a terminal from the table that has an image method. Alacritty has no image protocol.
tmux sits between the program and the terminal, and it can block the image data. Images in tmux are not tested. Newer versions of tmux have the option allow-passthrough, for example set -g allow-passthrough on in ~/.tmux.conf. This fix is not tried. In tmux with half blocks, the status line and lazytypst --doctor show this hint.
The window is under 40 columns or 10 rows. The line says Make the window bigger (40x10). Make the window bigger. Nothing is lost. The program shows the same screen again.
A hidden temp file stays in the project
A kill in the instant between the temp file and the rename can leave a hidden file in the project, for example .main.typ.1234-5678-0.lazytypst-tmp or .lazytypst-1234-1.pdf.tmp. At the next start, the program deletes such a file beside your .typ files if it is older than one hour and the process in its name is gone. It never deletes a file with another name, a link, or a folder.
The program asks Typst for the list of files that the document read (--deps), and it looks at their modification times about once a second. If another program changes an included file, an image, or a bibliography, the program starts a compile and the status line says <file> changed. A file that was missing and now exists counts as a change. The program does not look while you type, while a compile runs, or while the live compile is off (F5). A file outside the project, for example a package in the cache, is not watched. If the preview stays old, press Ctrl-B.
The page keys use Alt. Some terminals use Alt for their own keys, or they do not send it to the program. The Alt keys are tested with a test terminal. The settings of other terminals are not tested. Look for a setting that sends Alt as Escape or as the Meta key. This fix is not tried.
The program stops at start with a message that it cannot run typst. Install Typst (https://github.com/typst/typst#installation), and check that typst --version works in the same terminal. If the program is in another folder, give its path with LAZYTYPST_TYPST.
The program makes the folder lazytypst-<process id> in $XDG_RUNTIME_DIR, or in the temporary directory if that is not set, and only your user can open it. If a folder with that name exists and belongs to another user, the program stops with the message Cannot make the folder. It never deletes a folder of another user. The folder is safe to delete when no process with that number runs. Check with ls /proc/<process id>: if the command says that the file does not exist, delete the folder.
src/browser.rsfinds the.typfiles.src/newfile.rschecks the name of a new file and makes it.src/editor.rsholds the state of the editor, the keys, the save, the 300 ms autosave, and the three modes (text, search prompt, full preview).src/editor/build.rsstarts and polls the compile and the export.src/editor/view.rsdraws the screen, the compile pane, and the colors.src/editor/tests.rsholds its tests.src/help.rsis the one list of keys for--help, the help window, the man page test, and the website test.src/highlight.rsfinds the parts of Typst source for the colors, andsrc/words.rscounts words.src/doctor.rsandsrc/clipboard.rsdo what their names say.src/preview.rsholds the folder of the last good compile. The folder has one PNG file, namedpage-<page>-of-<count>.png, so the preview learns the page count from the name. It draws the page with theratatui-imagecrate. It deletes the old folder when a new folder loads. It keeps the number of the wanted page.src/compile.rsparses each line of the Typst output into a diagnostic with a severity, a file, a line, and a column. It also runstypst compile --format png --diagnostic-format short --root <folder> --pages <page>as a job, inside the root folder. Typst exits with success and writes no file when the document has fewer pages than the page that the program asks for. The editor then compiles page 1, learns the page count, and compiles the last page. The main thread owns thetypstprocess and checks it withtry_wait. A thread reads the error output. Dropping the job kills thetypstprocess. Each compile writes its one PNG file to its own new folder in the temporary directory. The editor deletes the folder of a compile that failed or that it killed. The program deletes the temporary directory when it exits. The PDF export is a second job of the same kind.src/state.rsreads and writes the state file with the main file of each project.src/fsutil.rsholds the safe write that the editor and the state file both use.src/main.rsreads the arguments and runs the event loop.App::handle_keyhandles the keys of the file list. Every 50 ms without a key, the loop calls the editor. The editor then runs the autosave if it is due and checks if a compile has finished.
The project follows the code of conduct in CODE_OF_CONDUCT.md. A report goes through GitHub only. The file CONTRIBUTING.md tells you how to build the program, which rules the code follows, and how a commit and a pull request must look. The page site/index.html is the source of the website. A test checks that it lists every key of lazytypst --help, so a change to a key changes the page too. The workflow .github/workflows/pages.yml publishes the folder site/ when a push changes it.
The file CHANGELOG.md lists the changes that a user can see, for each version. Each change that a user can see adds one line under Unreleased there.
A tag that starts with v, for example v0.1.0, starts the workflow .github/workflows/release.yml. The workflow stops with an error if the tag and the version in Cargo.toml differ, or if CHANGELOG.md has no entry for the version, or if that entry still says that the version is not yet released. Then the workflow runs the tests, builds a static binary for x86_64 Linux with Rust 1.99.0 (the version is fixed in the file, so a new stable version cannot change a release), and checks the archive that it packs. At last, it makes a GitHub release with the archive, its checksum file, and the changelog entry as the text of the release.
A manual run of the workflow is a dry run. It builds and checks the archive and keeps it as an artifact for 7 days. It makes no release. If you start the manual run with the choice test_release, the workflow also runs the step that makes the release as a test: it makes a draft release with a throwaway name, checks that the draft has the archive and its checksum file, and deletes the draft. Other people cannot see a draft.
cargo test runs the tests. Many tests run the real typst command, so typst must be in PATH. The tests run on Linux only, because some of them read /proc. cargo clippy --all-targets -- -D warnings must show no warning, and cargo fmt --check must pass.
GitHub Actions runs both on each push and on each pull request to main. The workflow is the file .github/workflows/ci.yml. It installs Typst 0.15.1 from the release page of Typst and checks the file against a fixed SHA-256 hash. It runs cargo test with the current stable version of Rust and with Rust 1.90. It runs clippy and cargo fmt --check with the stable version only. The run with Rust 1.90 is the check for the minimum Rust version in the requirements above.
The folder tests/pty/ holds the terminal checks. Each one starts the debug build in a pseudo terminal, plays keys, and reads the screen with the Python library pyte. They need python3 and pyte (pip install -r tests/pty/requirements.txt, best in a virtual environment) and typst in PATH. After cargo build, python3 tests/pty/run.py runs all of them and fails if one fails. python3 tests/pty/run.py check_search.py runs one. The variable LAZYTYPST_BIN names another program to test. The CI job Terminal checks runs them on each push and each pull request.
Two more workflows exist. .github/workflows/audit.yml checks the crates in Cargo.lock against the RustSec advisory database each week and when Cargo.lock or Cargo.toml changes. .github/workflows/pages.yml publishes the folder site/ to GitHub Pages when a push changes it.
scripts/bench-pages.sh compiles documents of 1, 50, 200, and 500 pages with the options of the program, once for all pages and once for one page. It prints the time and the disk use. Give other page counts as arguments. The script needs typst in PATH.
scripts/bench-watch.sh compares a new typst compile --pages 1 process (what the program starts for each compile) with a running typst watch --pages 1 that recompiles after a change of the first page. It prints the median time of each and their ratio. Give page counts as arguments (the default is 10, 100, and 500). The script needs typst in PATH.
lazytypst is free software. The license is the GNU General Public License, version 3 or any later version. The text is in the LICENSE file.
