Skip to content
Orpheus-21Public

About

A terminal program for Typst documents. Browse projects, edit, and see a live preview.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

lazytypst

lazytypst is a terminal program for Typst documents.

The project website is at https://orpheus-21.github.io/lazytypst/.

lazytypst in a terminal: the text of a Typst file on the left with syntax color, the compile pane under it, and the page preview on the right

Quick start

  1. Install lazytypst: cargo install --locked --git https://github.com/Orpheus-21/lazytypst (see Install).
  2. Start it in a folder with Typst files: lazytypst ~/my-typst-folder
  3. Select a file with j and k, press Enter, and type. The preview follows the text. Press F1 to see all keys.

What it does

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.

Scope

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.

Requirements

  • Rust 1.90 or later, with Cargo.
  • The typst command in PATH, 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 runs typst --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_TYPST can 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 --version run that program instead of typst. If it cannot run, the compile pane shows Cannot run and the path. Without the variable, the program runs typst from PATH.
  • The variable LAZYTYPST_WATCH=1 is an option for faster compiles. The program then keeps one typst watch running 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 new typst process (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. F5 ends the watch, and Ctrl-B then 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.

Install

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).

Install with Cargo

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.

Install on Arch Linux

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.

Download a release

This way needs no Rust. The release 0.1.0 is the first one.

  1. Download these two files from the page of the release, https://github.com/Orpheus-21/lazytypst/releases:

    • lazytypst-<version>-x86_64-linux.tar.gz
    • lazytypst-<version>-x86_64-linux.tar.gz.sha256
  2. Check the archive against its checksum file.

    sha256sum --check lazytypst-<version>-x86_64-linux.tar.gz.sha256
    
  3. Unpack the archive.

    tar -xzf lazytypst-<version>-x86_64-linux.tar.gz
    
  4. 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.

Build from a clone

This way is for people who want to change the program.

  1. Clone the repository.

    git clone https://github.com/Orpheus-21/lazytypst.git
    
  2. Go to the folder.

    cd lazytypst
    
  3. 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.

Usage

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.

  • ? or F1: open the help window. The window lists all keys of the list, the editor, and the text area, the same list as lazytypst --help. j and k or the arrow keys select a line. Enter closes the window and presses the key of the line, so the window also works as a menu of actions. Esc, q, ?, or F1 closes it. While it is open, no other key acts. In the editor, ? types a question mark, and F1 opens the window.
  • j or Down: select the next file.
  • k or Up: select the previous file.
  • Enter: open the selected file in the editor.
  • n: make a new file. A prompt asks for a path, for example chapters/two. Enter makes the empty file, opens it in the editor, and selects it in the list. Esc cancels. The program adds .typ if 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.
  • g or Home: select the first file. G or End: select the last file.
  • /: filter the list. A prompt Filter: 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. Enter keeps the filter, and the list title shows it, for example lazytypst /chap. Esc removes the filter, in the prompt and also in the list. If no file matches, the list says No match. r keeps 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, so chapter-2.typ comes before chapter-10.typ, and a.typ before README.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 variable VISUAL, or else EDITOR, in the same terminal, with the file as the last argument. A value such as code --wait splits 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 as Ctrl-E. The PDF goes next to the file, for example report.typ becomes report.pdf. This key uses the selected file and not the main file. The list stays usable while the export runs. The status line shows Exported 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 shows Copied and 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-S also 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.typ becomes doc.pdf. With a main file, the PDF comes from the main file, for example main.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 starts xdg-open in the background, so the screen stays clean. Before an export, the status line says that there is no PDF yet. If xdg-open cannot start, the status line shows the error.
  • Ctrl-Q: save the file and quit the program. It does the same as Esc and then q. If the program cannot save, it shows the reason, and a second Ctrl-Q quits 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 says Compile (paused). Ctrl-B still 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 prompt Search (text): opens in the status line. Type the text, for example f(x) or 1+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. Enter or Ctrl-F moves the cursor to the next match, and the search wraps to the top of the file after the last match. Alt-R in the prompt switches to a regular expression (the prompt then says Search (regex):, and the match respects the case). Esc closes the prompt and keeps the cursor where it is. If nothing matches, the status line says No match for and the text, and the cursor stays. In regex mode, an invalid pattern shows the reason in the status line. Ctrl-F does not move the cursor to the right: use Right.
  • 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), Down and Up (or Ctrl-N and Ctrl-P) select, Enter opens the file, Esc closes the popup. F2 again opens the file that was open before this one, so two presses of F2 toggle between two files. The popup does not list the open file. It also works in the full preview.
  • Alt-Enter or Ctrl-]: 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. F2 then F2 goes 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 .typ files 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 lines means 3 lines go and 1 line comes back). Enter puts the chosen version in the buffer as one edit, so the autosave saves it, and Ctrl-Z takes it back. Esc or F6 closes the popup. The versions are copies in the history folder 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 folder history to 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 program hunspell (the variable LAZYTYPST_SPELL names another program that speaks the same pipe protocol) and a dictionary, so install both, for example pacman -S hunspell hunspell-en_us or apt install hunspell hunspell-en-us. The dictionary comes from #set text(lang: "de") in the file (en gives en_US, de gives de_DE, pt-BR gives pt_BR), else from LANG, else it is en_US. The variable LAZYTYPST_SPELL_DICT sets 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. If hunspell or 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 file spell-words.txt in the state folder), and Ignore "word" in this run. Enter chooses, Esc closes. A suggestion replaces the word as one edit, and Ctrl-Z takes it back. Press Alt-; 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-Up and Alt-Down still 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 file mouse in 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: hold Shift and 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 with wl-paste (Wayland, package wl-clipboard) or xclip (X11), takes the first of png, jpeg, webp, gif, or svg that the clipboard offers (at most 20 MiB), saves it as images/pasted-YYYYMMDD-HHMMSS.png in 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 variable LAZYTYPST_IMAGES names 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, so Ctrl-Z takes 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 of F10 goes to the next layout, and the choice then stays, also for the next file and the next run (the file layout in the state folder). For the preview alone, use F11.
  • 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), Down and Up select, Enter moves the cursor to the heading, F4 or Esc closes 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 prompt Replace with: opens in the status line, and the cursor goes to the first match. Type the new text. Enter replaces the match at the cursor and goes to the next one, and the status line says how many are left. Alt-A replaces all matches and says how many. Esc stops. The prompt starts with your last replacement. The new text is plain text. In regex mode (Alt-R in the search), $1 and ${name} give the groups of the match. One Ctrl-Z takes back one replace or one replace-all. Without a search text, Alt-S says Search first.
  • F11: show the preview on the full screen, and back. The text area and the compile pane are hidden, and the status line says F11 back to editor and shows the zoom. Typing does nothing there, so no text changes by accident. F11 or Esc brings the editor back, and Esc does not go to the file list from here. Alt-Down, Alt-Up, Alt-Home, and Alt-End still 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%. 0 shows 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, and Home and End go 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 prompt Line: opens in the status line. Type the line number, or the line number, a colon, and the column (42 or 42:7), then press Enter. A number beyond the end goes to the last line, and the status line says so. Esc closes 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 example Error 2 of 5 at 14:3. An error in another file of the project opens that file, after a save, as Ctrl-G does.
  • 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. Home and End without Alt move 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 with Shift and 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-Y pastes 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 second Esc then 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, or Esc writes, 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-S then overwrites the file with your text. Alt-L loads the file from disk instead and drops the edits of the buffer. The program keeps your text in the local history first (F6 lists it), and if there is no history it refuses and keeps your text. Esc twice closes the editor and keeps the version on disk. Ctrl-Q twice 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-S writes 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.

Safety with projects from other people

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 .typ link 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 shows it 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.pdf that 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 prlimit program of util-linux is in PATH, 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-B and 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 @preview makes Typst download the package.

Colors

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.

Fonts and packages

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 ~/fonts are 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 is true or false.
  • 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_ROOT has no effect, because lazytypst always gives --root with 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.

Other ways to work

lazytypst is one of several ways to work with Typst. This is how it differs from three others.

  • typst watch compiles 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.

FAQ

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.

Troubleshooting

Terminal support

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 preview shows blocks

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

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 shows only one line

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 preview does not follow an included file

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.

Alt keys do nothing

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.

typst not found

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 stops with a folder error at start

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.

How it works

  • src/browser.rs finds the .typ files. src/newfile.rs checks the name of a new file and makes it.
  • src/editor.rs holds 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.rs starts and polls the compile and the export. src/editor/view.rs draws the screen, the compile pane, and the colors. src/editor/tests.rs holds its tests.
  • src/help.rs is the one list of keys for --help, the help window, the man page test, and the website test. src/highlight.rs finds the parts of Typst source for the colors, and src/words.rs counts words. src/doctor.rs and src/clipboard.rs do what their names say.
  • src/preview.rs holds the folder of the last good compile. The folder has one PNG file, named page-<page>-of-<count>.png, so the preview learns the page count from the name. It draws the page with the ratatui-image crate. It deletes the old folder when a new folder loads. It keeps the number of the wanted page.
  • src/compile.rs parses each line of the Typst output into a diagnostic with a severity, a file, a line, and a column. It also runs typst 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 the typst process and checks it with try_wait. A thread reads the error output. Dropping the job kills the typst process. 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.rs reads and writes the state file with the main file of each project. src/fsutil.rs holds the safe write that the editor and the state file both use.
  • src/main.rs reads the arguments and runs the event loop. App::handle_key handles 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.

Contributing

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.

Changes

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.

Releases

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.

Tests

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.

Measuring

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.

License

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.

About

A terminal program for Typst documents. Browse projects, edit, and see a live preview.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages