zedit — User Guide
zedit is a smart file-editor launcher. Instead of typing the editor name
yourself, you run zedit <file> and the right editor opens automatically,
chosen by MIME type (content-based) or file extension, according to a
layered TOML configuration you control at the system, user, and project level.
Table of contents
- Installation
- Quick start
- CLI reference
- Configuration
- MIME-type detection
- Editor resolution logic
- Common workflows
- Environment variables
- Troubleshooting
1. Installation
1.1 From a Debian / Ubuntu package
The preferred method for system-wide installation on Debian-based systems.
The package places the zedit binary in /usr/bin and installs the
system-wide config to /opt/etc/zedit/config.toml as a managed conffile
(preserved across upgrades unless you choose to overwrite it).
# If you have the .deb file:
sudo dpkg -i edit_0.1.0-1_all.deb
# Install any missing dependencies afterwards:
sudo apt-get install -f
# Or, if a repository is configured:
sudo apt-get install zedit
To also get accurate content-based MIME detection (highly recommended):
sudo apt-get install python3-magic
To remove the package while preserving /opt/etc/zedit/config.toml:
sudo apt-get remove zedit
To remove the package and purge the config:
sudo apt-get purge zedit
1.2 From a Python wheel (pip)
Suitable for user-level installs without root privileges, virtual environments, or any platform with Python ≥ 3.11.
# Install for the current user only (no root needed)
pip install --user zedit-0.5.0-py3-none-any.whl
# Or install into the active virtual environment
pip install zedit-0.5.0-py3-none-any.whl
# With libmagic support (recommended)
pip install "zedit-0.5.0-py3-none-any.whl[magic]"
# Or directly from source
pip install .
pip install ".[magic]"
Note: A pip install does not create
/opt/etc/zedit/config.toml. The system-wide config layer is simply skipped when that file does not exist. Usezedit --init-configto create a personal config instead.
1.3 From the tarball (cmake –install)
The CPack-generated tarball (zedit-0.5.0-Linux.tar.gz) is a pre-staged tree
that can be unpacked into any prefix.
tar -xzf zedit-0.5.0-Linux.tar.gz -C /usr/local --strip-components=1
Or use CMake’s install step directly from a build tree (see docs/build.md for the full CMake workflow):
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build
sudo cmake --install build
1.4 Developer / editable install
git clone https://github.com/proteus-cpi/zedit
cd zedit
# Editable install — changes to zedit.py take effect immediately
pip install -e .
# Run without installing
python zedit.py --help
python -m zedit --help # if __main__.py is present
2. Quick start
# Open a file — editor is chosen automatically
zedit README.md
zedit src/main.py
zedit config.json
# Open multiple files at once
zedit *.py
# Preview what would happen without launching anything
zedit --dry-run report.pdf image.png
# See the full mapping table currently in effect
zedit --list
# Create a personal config to start customising
zedit --init-config
3. CLI reference
zedit [OPTIONS] [FILE ...]
| Option | Short | Description |
|---|---|---|
--mime TYPE |
-m |
Override MIME-type detection. TYPE must be a valid MIME type string, e.g. text/x-python. |
--editor CMD |
-e |
Use this editor unconditionally, bypassing all config lookups. CMD may include flags: "code --wait". |
--config FILE |
-c |
Load an additional TOML file on top of the standard config stack. Merged last (highest priority). |
--dry-run |
-n |
Print the editor command(s) that would be executed to stdout, then exit without launching. |
--list |
-l |
Print all MIME-type mappings, extension mappings, fallback editor, and prefer_mime setting, then exit. |
--init-config |
Write a commented starter config to ~/.config/zedit/config.toml and exit. Safe to run on a fresh install; will overwrite any existing file. |
|
--verbose |
-v |
Print resolution details (detected MIME type, which mapping matched, which strategy won) to stderr. |
--help |
-h |
Show help and exit. |
Positional arguments
FILE ... — One or more file paths to open. Files may or may not exist; if
a file does not exist, MIME detection is skipped and only the extension is
used. Multiple files that resolve to the same editor are passed to that
editor in a single invocation. If they resolve to different editors they are
launched in separate sequential invocations.
Exit codes
| Code | Meaning |
|---|---|
0 |
All editors exited successfully, or --list / --dry-run / --init-config completed. |
1 |
No FILE arguments supplied (help was printed). |
| other | The exit code of the last editor invocation that failed. |
4. Configuration
4.1 Config file locations and precedence
Configuration is loaded from up to five sources and deep-merged in this order. A later source overrides a earlier one for any key that appears in both; keys present only in an earlier source are kept unchanged.
| Priority | Path | When used |
|---|---|---|
| 1 (lowest) | Built-in defaults | Always; hardcoded inside zedit.py. |
| 2 | /opt/etc/zedit/config.toml |
When the file exists. Installed by OS packages. Override path via $ZEDIT_SYSCONFDIR. |
| 3 | ~/.config/zedit/config.toml |
When the file exists. Personal preferences. |
| 4 | ./.zedit.toml in CWD |
When the file exists. Project-level overrides. |
| 5 (highest) | File given to --config |
When --config FILE is passed. Ad-hoc overrides. |
The --editor CMD flag bypasses the entire config lookup.
4.2 Config file format
Config files are TOML and contain three sections:
# ── Behaviour settings ────────────────────────────────────────────────────────
[defaults]
editor = "$EDITOR" # fallback when no mapping matches
prefer_mime = true # true = MIME wins over extension when both match
# ── Content-based editor mapping ──────────────────────────────────────────────
[mime_types]
"text/x-python" = "vim"
"text/html" = "vim"
"application/pdf" = "evince"
"image/png" = "gimp"
"image/jpeg" = "gimp"
"audio/mpeg" = "audacity"
"video/mp4" = "vlc"
# ── Extension-based editor mapping ────────────────────────────────────────────
[extensions]
".py" = "vim"
".md" = "typora"
".jpg" = "gimp"
".mp4" = "vlc"
".pdf" = "evince"
All three sections are optional. Any section omitted in a user or project config inherits the values from lower-priority configs.
4.3 The $EDITOR sentinel
The special string "$EDITOR" in any editor value is not a shell variable
— it is a sentinel recognised by zedit itself. At runtime it is resolved
through the following chain, stopping at the first non-empty value:
$VISUAL → $EDITOR → vi (POSIX-guaranteed fallback)
The bundled built-in defaults use "$EDITOR" for every mapping, so zedit
behaves like a smart wrapper around your preferred editor out of the box.
To hard-code a specific editor for a mapping, just use its name:
[extensions]
".py" = "vim" # always vim, regardless of $VISUAL / $EDITOR
4.4 Editor values with flags
Editor values may include command-line flags. The string is split on whitespace before being passed to the OS:
[mime_types]
"text/x-python" = "vim -p" # open each file in a new tab
"application/pdf" = "evince --fullscreen"
[extensions]
".ts" = "code --wait" # VS Code, waiting for close
".md" = "ghostwriter"
Caution: Only simple whitespace splitting is performed. Shell quoting, globbing, and variable expansion are not supported in editor values.
4.5 Deep-merge semantics
When two config files both define [mime_types], the tables are merged
key-by-key, not replaced wholesale:
System config (/opt/etc/zedit/config.toml):
[mime_types]
"text/x-python" = "vim"
"text/html" = "vim"
User config (~/.config/zedit/config.toml):
[mime_types]
"text/html" = "firefox" # overrides the system default for HTML
Effective result:
[mime_types]
"text/x-python" = "vim" # kept from system config
"text/html" = "firefox" # overridden by user config
To delete a system mapping at the user level, set its value to
"$EDITOR" (which then falls through to your preferred editor):
[mime_types]
"image/png" = "$EDITOR" # disable the system-set gimp mapping
4.6 MIME base-type wildcard
If the full MIME type (e.g. image/jpeg) is not found in [mime_types],
zedit retries with just the base type (e.g. image). This lets you write
a single catch-all rule:
[mime_types]
"image" = "gimp" # matches image/png, image/jpeg, image/webp, …
"video" = "vlc" # matches video/mp4, video/mkv, …
Exact entries always take priority over base-type entries.
4.7 Scaffolding a starter config
zedit --init-config
This writes a fully-commented copy of the built-in defaults to
~/.config/zedit/config.toml, creating the directory if needed.
Edit the file to add your own mappings.
5. MIME-type detection
zedit uses two detection methods, tried in order:
1. libmagic (preferred)
When the python-magic package is installed, zedit uses the C library
libmagic to inspect the content of the file, not its name. This
correctly identifies:
- Python scripts without a
.pyextension (e.g. executable scripts inbin/) - Renamed files (e.g. a
.txtfile that is actually a JSON document) - Files with no extension at all
# Install
sudo apt-get install python3-magic # Debian/Ubuntu
pip install python-magic # PyPI
2. Python mimetypes stdlib (fallback)
When python-magic is not available, zedit falls back to Python’s built-in
mimetypes module, which guesses the MIME type solely from the file extension
and a platform-specific MIME database. This is fast and has no external
dependencies, but cannot detect content mismatches.
Forcing a MIME type
Use --mime to skip detection entirely and assert a MIME type manually:
zedit --mime application/json mydata # treat 'mydata' as JSON
zedit --mime text/x-python script # treat 'script' as Python
6. Editor resolution logic
For each file, zedit follows this decision tree:
┌─────────────────────────────────────────────────────┐
│ --editor CMD given? │
│ Yes → use CMD for every file, skip all below │
└───────────────────────┬─────────────────────────────┘
│ No
▼
┌─────────────────────────────────────────────────────┐
│ Determine MIME type │
│ --mime TYPE given? → use TYPE │
│ else file exists? → detect via libmagic / │
│ mimetypes stdlib │
│ else (new file) → MIME = None │
└───────────────────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ MIME lookup in [mime_types] │
│ 1. Exact key "text/x-python" │
│ 2. Base type "text" │
│ → mime_editor (may be None) │
└───────────────────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Extension lookup in [extensions] │
│ Lowercase suffix of filename, e.g. ".py" │
│ → ext_editor (may be None) │
└───────────────────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Both mime_editor AND ext_editor found? │
│ prefer_mime = true → use mime_editor │
│ prefer_mime = false → use ext_editor │
│ Only one found → use that one │
│ Neither found → use defaults.editor │
└───────────────────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Resolve "$EDITOR" sentinel if present │
│ $VISUAL → $EDITOR → "vi" │
└─────────────────────────────────────────────────────┘
Use --verbose to see each step printed to stderr in real time.
7. Common workflows
Use a specific editor for all web files
# ~/.config/zedit/config.toml
[mime_types]
"text/html" = "code --wait"
"text/css" = "code --wait"
"text/javascript" = "code --wait"
[extensions]
".html" = "code --wait"
".css" = "code --wait"
".js" = "code --wait"
".ts" = "code --wait"
Open images and PDFs in GUI viewers
[mime_types]
"image" = "eog" # GNOME image viewer (base-type wildcard)
"application/pdf" = "evince"
[extensions]
".pdf" = "evince"
".svg" = "inkscape"
Per-project config (no root needed)
Drop a .zedit.toml in your project root to override any mapping locally:
# .zedit.toml — checked in with the project
[defaults]
prefer_mime = false # extension wins in this project
[extensions]
".py" = "emacs"
".md" = "ghostwriter"
Inspect what would open before committing
# See the editor for each file without opening anything
zedit --dry-run --verbose $(git diff --name-only)
Override from a script
#!/bin/bash
# Always use vim in this script, regardless of user config
zedit --editor vim "$@"
8. Environment variables
| Variable | Effect |
|---|---|
VISUAL |
Preferred editor. Resolved when an editor value is the $EDITOR sentinel, checked before EDITOR. |
EDITOR |
Fallback editor. Resolved when VISUAL is unset or empty. |
ZEDIT_SYSCONFDIR |
Overrides the directory searched for the system-wide config (default: /etc). Useful for staged installs or non-standard prefixes. |
Example: use a non-standard system config location:
ZEDIT_SYSCONFDIR=/opt/myapp/etc zedit myfile.py
9. Troubleshooting
Wrong editor is opening
Run with --verbose to trace the resolution:
zedit --verbose --dry-run myfile.py
The output shows:
- The detected MIME type and detection method
- Which
[mime_types]key matched (if any) - Which
[extensions]key matched (if any) - Which strategy won (
prefer_mime) - The final resolved editor command
Then run --list to see the full effective config:
zedit --list
MIME type is wrong
libmagic may misidentify some file types (e.g., short files, empty files).
Force the correct type with --mime:
zedit --mime text/x-python myscript
Or disable MIME-based lookup for that extension by only defining it in
[extensions] and setting prefer_mime = false for the project.
$EDITOR resolves to vi but I want something else
Set the environment variables in your shell profile:
# ~/.bashrc or ~/.zshrc
export VISUAL=vim
export EDITOR=vim
Or hard-code the editor in your config:
[defaults]
editor = "vim"
python3-magic is installed but MIME detection still uses mimetypes
Check that the Python binding and the system library are both present:
python3 -c "import magic; print(magic.from_file('/etc/hostname', mime=True))"
If this raises an error, install the system library:
sudo apt-get install libmagic1
Config file is not being picked up
Verify the path and TOML syntax:
python3 -c "import tomllib; tomllib.load(open('$HOME/.config/zedit/config.toml', 'rb'))"
A parse error prints the line number. The config is silently ignored if the
file does not exist — zedit --list will then show only built-in defaults.