Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Edit, eXecute and Batch

Edit (E)

Opens the file in the editor: the one set on page 1 of the configuration, or else $VISUAL, $EDITOR, Debian and Ubuntu’s editor, or vi.

By default, the editor opens inside Xylem’s frame (editor_in_frame = true on page 1 of F12) and uses Xylem’s color theme (editor_theme = true), including syntax and UI colors matching the active theme when the editor is vi, vim or nvim. Set editor_in_frame = false to hand the whole terminal over to the editor instead, or editor_theme = false to keep the editor’s own color scheme.

Binary files and editors by file type

A text editor only mangles an image or a program, so E never opens binary files (those with a null byte near the start, which the viewer shows in hex) in the text editor.

Instead, when Edit binary files with E is enabled (edit_binary = true, key I on page 1 of F12), E opens binary files in a hex/binary editor:

  1. A matching pattern in [editors] (if any), or
  2. The Binary editor set on page 1 of F12 (binary_editor, key J) or binary in [editors], or
  3. If left empty, the first terminal hex editor found on PATH:
    • Linux: hexedit, heh, bvi, dhex
    • Windows: heh (winget install ndd7xv.heh), hexedit, bvi

Terminal hex editors run the same way as the text editor: inside Xylem’s frame by default (editor_in_frame = true) and with Xylem’s color theme (editor_theme = true). For a graphical hex editor (ImHex, GHex, Okteta on Linux; ImHex, HxD on Windows) that you want to leave running in its own window, add & at the end of the command (for example, imhex %f & or ghex %f &): Xylem launches it directly without opening the terminal frame.

The frame says it is an external program and how to leave the usual ones. It has all the keys, and Esc may not close it: in hexedit, Ctrl+X saves and quits, and Ctrl+C quits without saving. After Esc, hexedit waits for another key and may say Unknown command: press Space and repeat.

If you prefer E not to open binary files at all (and only suggest O to open them in the system’s program), set Edit binary files with E to no (edit_binary = false on page 1 of F12).

To give specific file types an editor of their own, list them in the [editors] table of config.toml:

[editors]
"*.png *.jpg *.jpeg *.gif" = "gimp %f &"
"*.md" = "typora %f &"
"*.svg" = ""            # empty: the usual editor, even though it matched
binary = "hexedit"      # fallback for any other binary file

Each key is a list of name patterns, as in Filespec (case does not matter); each value a command line, with %f for the file (or else the file goes at the end). The first key that matches wins, in the file’s order. binary is for the binary files that no pattern took when binary_editor is empty; the other files open in the usual text editor.

Xylem has no built-in hex editor of its own, and that is a deliberate design choice: writing and maintaining one would be a lot of work for little use, and editing raw bytes by hand is an easy way to corrupt a file. Instead, Xylem integrates dedicated hex editors right inside its frame and color theme — and lets you turn binary editing off with a single option in F12 if you do not want it.

eXecute (X)

Asks for a command line and runs it with sh in the current directory. In it:

%fthe file under the cursor
%tthe tagged files
%dthe current directory
%%a literal %

All of them come out already quoted: tar czf backup.tgz %t. At the end, Xylem waits for Enter so you can read the output, and the open directories are reread. A command starting with @ is not kept in the history (with history_skip_at).

For long sessions, the built-in terminal (Alt+X) is often handier.

Batch (Ctrl+B)

Asks for the name of the file to create (F2 picks the folder) and then a line mask, and writes one line per tagged file. Parameters:

%fpath
%ddirectory
%nname
%bname without extension
%eextension
%ssize

In lowercase they come out quoted, safe for the shell (convert %f %b.png); in uppercase, as plain text, for lists (%N;%S). A .sh file gets #!/bin/sh and execute permission (run it with X). An existing file is not overwritten.

Batch in the viewer

With a table or text open in the viewer, Ctrl+B writes one line per record, from the whole file: past the 50,000 rows of the screen, with values whole and exactly as in the file. It respects the filter in use (the SQL filter on a table, the line filter on text). A table is read again when it runs; text comes as the viewer read it, up to the size limit, and the preview warns when that is not all of the file. It asks for the file, then the mask, and shows the first lines before writing: Enter writes, Esc gives up. While writing, Esc cancels and removes the incomplete file. An existing file is not overwritten.

While you type the mask, a box above it shows a mask that works on this file, its fields and the templates you have, each with the first sentence of its opening comment (%%#). ↑↓ choose a template, F3 shows it whole before you use it and PgUp/PgDn go through the history. The description was written by whoever wrote the template: read a template from someone else with F3 before using it. A mistake in the mask keeps the prompt open with what you typed and says what is wrong.

FieldValue
%{name} %{A} %{2}a column of a table, by name, letter or position
%{line}the whole line of text, with its tabs
%{ip} %{1} %{0}a group of the line filter’s regular expression, by name or number; 0 is all it matched
%{#}the record’s number among those written: 1, 2, 3…
%{#line}the line’s number in the file (text only)
%{#path} %{#dir} %{#name} %{#base} %{#ext} %{#size}the open file, with an escape like any field; %N and the others above have none, so prefer these in a page or a script
%{$NAME}a variable of the environment, such as %{$PATH}, as it is now; the preview lists those read, and one that does not exist is an error
%{#date} %{#time} %{#datetime} %{#utc}now, local (#datetime with its time zone) or UTC: the same in all records
%{#year} %{#month} %{#day} %{#weekday} %{#weekday_name} %{#epoch}parts of it: weekday 1 is Monday, the name is in English, epoch in seconds since 1970
%{#user} %{#host} %{#template} %{#output}who generates it, on which machine, with which template, and the file written
%f %d %n %b %e %sthe open file, as in Batch above
%%a %

A field that does not exist is an error in the preview, never an empty value. Since a preview looks at 1,000 records at most, a JSON Lines key that only appears after them counts as not existing.

The values come from a file that may be hostile and may go into something you run, so each field has an escape. Name it after the field: curl "https://api/x?id=%{id:url}".

EscapeForDefault for
:shone quoted argument of a shell command.sh .bash .zsh
:urla part of a URL, percent-encoded
:jsona JSON value: numbers bare, text with its quotes.json .jsonl .ndjson
:xml or :htmltext or an attribute.xml .html .htm .xhtml .svg
:sqla literal with its quotes, NULL, X'00ff'.sql
:mysqlthe same for MySQL and MariaDB, where a backslash also escapes
:csva field, quoted when needed.csv .tsv
:csvsafethe same, with a ' before what a spreadsheet would take for a formula
:linethe value, with control characters as spaces, so it never starts a new lineany other
:rawthe value as it is: do not use it in a script
  • A blob comes out in hexadecimal, except in :raw and in :sql, which has its own form.
  • For MySQL and MariaDB, name :mysql, in a file read as utf8mb4: there, a backslash in a :sql literal can close it, and the preview warns about those values. With NO_BACKSLASH_ESCAPES on the server, :sql is the right one.
  • In SQL, %f %d %n %b %e %s inside a string are refused: LIKE '%foo%' would hold the open file’s path. Write %% for a %, as in LIKE '%%foo%%', or a field such as %{#name} for the value.
  • A filter on a table applies to all the file, with the rules of the screen: code = 7 also finds a cell with 007, which comes out as 007. So the file can have rows the screen, limited to 50,000, does not show.
  • A .sh starts with #!/bin/sh but is not made executable: read it before running it. .bat and .cmd are refused, because cmd expands %VAR% even inside quotes.
  • The preview and the final message warn about values with control or text direction characters, and in a CSV about values that start like a formula. The preview counts only what it looked at.

Whole records

In a .json, .jsonl, .ndjson, .csv, .tsv, .xml, .html or .htm file, leave the mask empty to write whole records, with the rules of the format: a JSON array of objects, an object per line, a table with a header row, a <record> per record, or a page with a table. To write only some fields, start the mask with @: @id, name, #. Text gives line_number, line and the groups of the filter. Fields are found in the first 1,000 records: a JSON Lines key that appears only later is left out, and the final message says so.

A .db, .sqlite or .sqlite3 file takes whole records too, in a table records, and refuses masks and templates. Each value keeps its type: columns have none, so a 007 from a CSV stays 007. Values from a CSV are therefore text, so compare them as numbers with CAST(balance AS REAL).

Templates

Files to try all of this are in demo/ in the repository: the same customers in every format, and ready templates.

For text around the records, save a template as ~/.config/xylem/batch/name.tpl and type :name as the mask; the prompt lists the ones it finds (the first four).

%%# a comment
%%header
<h1>%{#name}</h1>
<ul>
%%record
<li><a href="https://x/?id=%{id:url}">%{name}</a></li>
%%footer
</ul>

%%header and %%footer are optional and take only the template’s text and the open file’s fields. %%record is written once per record and may have several lines; with no sections, the whole file is the record. Escapes follow the output file, as in a mask, so a template for a page should be written to a .html. A section that does not exist, a repeated one, or a field of a record in the header or footer is an error in the preview. The preview shows the last 3 lines of the header. With no records, nothing is written.

In the record, %%if condition, %%else and %%end choose what to write, and %%stop if condition ends the file there, keeping the footer. A condition is written as in the SQL filter and sees the record’s fields, plus # and #line:

%%record
%%stop if # > 100
%%if balance < 0
%{name} owes %{balance}
%%else
%{name} is fine
%%end

%{#} is the record’s place in what was read, and %{count} and the final message count the records that wrote something. The preview says which branches no record looked at took, since it reads only the first 1,000. A line starting with %%if or %%stop if is always one of these.

In the footer, %{sum:column}, %{min:column}, %{max:column} and %{avg:column} total the records written, in exact decimal. A number is read as 1234.56; for other files say how they write it, %{sum:value:br} for 1.234,56 or %{sum:value:us} for 1,234.56, and the total comes out the same way. An empty value is no value, and one that is not a number is left out and counted in the final message.

Inside a footer, %{count} is how many records were written. A header cannot have it, since it is written before they are read.

In a .sh, %{$HOME} writes the value of now, while $HOME is read when the script runs. A template from someone else is their script: it could write %{$SECRET:url} into a command, so look at what the preview says it reads.

History

At every prompt, ↑ and ↓ bring up earlier entries. Each kind of prompt (Filespec, destination, Log, eXecute…) has its own history, saved in ~/.local/state/xylem/history.toml, readable only by you, since it may contain typed commands.