The Kalem Book GitHub

This Book describes Kalem's main branch, which may be ahead of the latest release.

Plugins in practice

This chapter is the short way in: how to add a plugin from the editor, how to change one that exists and use your own copy, and how to write a plugin for a file type Kalem does not open yet. The chapters after it say how everything works; this one says what to do.

A plugin is a small program Kalem runs in a sandbox: it sees the file it was given and nothing else, has its own memory and time, and a plugin that fails is closed without taking Kalem down. The viewers of pictures, PDF files and workbooks are plugins too, none built into Kalem: the first file of a kind that needs one names it, to install; language plugins (a syntax and a language server for a programming language) need no code at all.

1. Adding a plugin from the editor

In the Kalem menu (and the command palette):

  • Browse Plugins lists the plugins of the official index, getkalem/plugins, with what is installed. Choosing one shows what it is, where it comes from and what it may do, and installs it.
  • Install Plugin from GitHub… asks for a link: a repository that is a plugin, a folder in one, or a release's page. That is how a plugin of your own, or anyone's that is not in the index, gets in.
  • Installed Plugins lists yours, offers an update when there is one, and removes.

On the command line: kalem plugin browse, kalem plugin install NAME (a name from the index, a link, or a folder on disk), kalem plugin list, kalem plugin remove ID. Language plugins has the details, and how to add an index of your own as a source.

2. What you need to build one

  • Rust, with rustup.
  • The target plugins are built for, once: rustup target add wasm32-unknown-unknown.
  • The tool that wraps a build as a component, once: cargo install --locked wasm-tools.
  • Kalem itself: kalem plugin build and kalem plugin dev do the rest.

3. Changing a plugin that exists

Every plugin lives in getkalem/plugins, one folder each under plugins/. To change the workbook viewer, say:

git clone https://github.com/getkalem/plugins
cd plugins
# edit plugins/xlsx/src/…
cargo test -p kalem-plugin-xlsx       # the plugin's own tests, natively
kalem plugin dev plugins/xlsx         # built, installed, built again at each change

kalem plugin dev builds the plugin, installs it, and builds and installs it again whenever its sources change, until you stop it. A Kalem that is running opens the next file with your build; one started later finds it installed. A panic in your code reaches Kalem's log with the names of your functions.

One thing to know: your build replaces the copy you installed from the index, whatever its version; kalem plugin list says which copy is in use. To go back to the released one, kalem plugin install xlsx again; kalem plugin remove org.kalem.xlsx takes your copy out and leaves no workbook viewer until one is installed.

To give your change to others:

  • the best way is a pull request to getkalem/plugins;
  • or push your fork, raise version in plugins/xlsx/plugin.json (to 0.0.11, say) and tag it xlsx-v0.0.11 (the tag must name the manifest's version): the repository's release workflow builds the component and publishes it (GitHub turns workflows off on a fork until you enable them, once, in its Actions tab). Then anyone installs it with Install Plugin from GitHub… and the link of your fork's folder, https://github.com/YOU/plugins/tree/main/plugins/xlsx. (A fork's index.json points at getkalem/plugins, as tools/build-index.py writes it, so adding it as a plugin source does not give your version.)

4. A plugin for a file type of your own

kalem plugin new NAME starts one. Inside a checkout of getkalem/plugins it makes plugins/NAME; anywhere else, a crate of its own in NAME/:

kalem plugin new dots
cd dots
cargo test                 # the tests that come with it, natively
kalem plugin build         # dist/dots.wasm, the component
kalem plugin dev           # installed, and built again as you work

What you get is a working viewer of a small format. A viewer's format is one that is not text: Kalem opens every text file in a mode, as text to edit, even one a viewer could show (an SVG drawing is XML), and hands a file to a viewer only when it is not text, as a NUL byte among its first bytes shows. So the template's format is a picture whose first bytes are DOTS and a NUL, then its rows, # a dark cell and . a light one, a line a row. With the plugin installed (by kalem plugin dev, say), make one and open it:

printf 'DOTS\0.##.\n#..#\n.##.\n' > a.dots
kalem view a.dots          # its rows and its information, printed
kalem a.dots               # the picture, in the window or the terminal

A .dots file of # and . alone is text: it opens as text to edit, and kalem view says that no installed plugin opens it. A format that is text is a language plugin's or an extension plugin's (see Beyond viewers), not a viewer's. The plugin's two files:

  • plugin.json: the plugin's id, name, version, what it opens (its extension, "opens": [".dots"], and its first bytes, "applies": {"magic": ["44 4F 54 53 00"]}, so that a file of the format opens whatever its name) and its permissions (a viewer needs none); its limits are Kalem's defaults unless it adds limits.
  • src/lib.rs: the viewer, and the document it opens. Which files are its own is the manifest's to say (opens, and applies for first bytes, whole names or markers: see the chapter on plugins), not its detect, which Kalem does not ask. The viewer reads one (open; the file comes through a handle, read_all or a piece at a time, since the plugin has no file system). The document says what the file holds (structure: one picture, pages, frames or sheets), how large a unit is (size), draws it when asked (render: pixels, at the scale asked, in the theme's colors) and gives its text (text, for search, copying and the terminal) and its information panel (info). Each part is marked in the file; replace the picture with your format, and its first bytes (MAGIC) with your format's, in the file and in plugin.json alike: a test checks that the manifest declares them.

Tests run natively with cargo test, as for any crate; the same code is the component Kalem runs. The contract has more than the skeleton uses, each with a default: links in a unit, an outline, search, editing and saving (save), a file that needs a password, and, with the grid feature of kalem-plugin, sheets of cells as the workbook viewer has them. Plugins lists them, and kalem-viewer's documentation says what each does.

To publish it: put the crate in a GitHub repository of its own, run kalem plugin build, and attach dist/dots.wasm to a release there; anyone installs it with Install Plugin from GitHub… and the repository's link (Kalem takes the component from the release). To be in the official index, open a pull request that adds plugins/dots to getkalem/plugins.

5. Beyond viewers

A plugin may add commands, keys and panels to the editor rather than open files (an extension plugin), and a language plugin is a manifest with a syntax and a language server, no code: Plugins and Language plugins say how.