A personal IDE-style TUI file editor, built with Textual.
- File explorer for the working directory on the left (24% of the screen), with a mouse context menu: ctrl+click any file or folder to rename it, delete it, or create a new file/folder there
- Tabbed editor on the rest of the screen
- Syntax highlighting via tree-sitter grammars (see below)
- Mouse-friendly: click files to open them, click folders to expand them, click tabs to switch, wheel to scroll
Development (in this repo):
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/alxedit2Global install (so alxedit2 is on your PATH in any directory) —
from a checkout of this repo:
pipx install . # isolated env, recommended
# — or —
pip install . # into the active environmentAfter pipx, just run alxedit2 from anywhere.
alxedit2 [PATHS…] [--root DIR] [--version]PATHS— files to open, and/or a directory to use as the working directory (the first directory given becomes the working directory).--version— print the version and exit.--root DIR— set the working directory explicitly (wins over any directory passed inPATHS).- If no directory is given, the working directory is the current one (
pwd).
The working directory is what the left-hand file explorer is rooted at, and it is shown at the bottom-left of the status bar.
alxedit2 # open the current directory
alxedit2 ~/src/myproject # open a project directory
alxedit2 ~/src/myproject/main.py # open a file (working dir = pwd)
alxedit2 --root ~/src/myproject main.py # explicit working dir + a file| Key | Action |
|---|---|
ctrl+n |
New buffer |
ctrl+s |
Save (save-as if untitled) |
ctrl+shift+s |
Save as… |
ctrl+click (explorer) |
File/folder menu — Rename, Delete, Track/Untrack (edit .alxeditrc), New file here, New folder here. Files: "here" = its folder. Folders: "here" = inside them |
shift+click (explorer) |
Select a range of files/folders (from the last clicked entry — marked ✓). Then ctrl+click opens the bulk menu: Track (N) / Untrack (N) / Clear — one .alxeditrc write for the whole range |
ctrl+shift+n |
New folder where the cursor is (hotkey alternative) |
ctrl+shift+x |
Delete highlighted folder (hotkey alternative; tracked files inside stay revertable) |
f4 / ctrl+w |
Close tab |
ctrl+q |
Quit |
f2 |
External changes — approve or reject them (see below) |
s |
Sessions (open / new / delete) |
ctrl+. |
Settings — what the mirror tracks (see below) |
f1 |
Help |
Sessions are optional. When alxedit2 starts, if the working directory
already has a .alxedit/ folder it offers to open (or create) a session
— a sync of the working tree under .alxedit/<id>/ (a progress bar
shows while files are copied; what counts as “tracked” is configurable,
see Settings):
.alxedit/
└── 20260819-100335-4c1a/
├── session.json # id, label, timestamps
└── files/ # exact mirror of the tree at that moment
The mirror is the baseline for everything else: diffs against external edits, reverts, and the “approved” state all compare against this session’s copy — not memory, not git.
- Startup — if the working directory has a
.alxedit/folder with sessions you get a picker: open an existing one (it re-baselines the diff on that snapshot), create a new one (fresh mirror, with a progress bar), or delete ones you don’t want. Every row also shows a live summary of the working tree against that baseline —in sync, or+A/-Blines when it has drifted (a quick way to see which session is closest to “now”). - No
.alxedit/folder? Basic editor mode. alxedit2 does not copy the tree or track changes — it just runs as a plain editor (explorer, tabs, syntax highlighting, save). Use theskey / Session button to start a session later if you want tracked changes. skey / “Session” button — the same picker any time, so you can start a new baseline mid-run (e.g. “everything the agent did so far is fine, start a fresh session for the next round”) or jump back to an older one.- Saving — every save through alxedit2 also updates the mirror, so “approved” always means matches the session snapshot.
Sessions are plain files: inspect them, cp -r one to preserve it,
rm -rf .alxedit/<id> to remove one manually. (.alxedit/ itself is
excluded from the mirror.) They appear in the explorer, which refreshes
on its own when a session is created or deleted, and open read-only
in a tab (marked 🔒) — they are the baseline, so saving over one in
place would silently move the baseline; use Save As to copy one out if
you want to keep it. The ctrl+click menu offers no file operations for
entries inside .alxedit/ (no rename, delete, new-file, or
track/untrack) — only a read-only notice and close.
The Settings button (top bar, or ctrl+.) edits the project's
.alxeditrc file — plain text, also hand-editable:
ignore <path> never mirror/track this file or folder
track <path> mirror/track this dot file/dot folder despite the default
The explorer always shows every file in the project; these settings control what the session mirror (the diff/revert baseline) actually covers:
- regular files and folders are tracked by default;
- dot files and dot folders (
.env,.github/, …) are not tracked unless listed withtrack; ignoreopts anything out — e.g. a massive image you don't want copied into every session.
Paths are relative to the project root and case-insensitive. A folder entry is recursive — it covers everything below it — and when several rules match a path the last one wins: the most recent Track/Untrack is the one in effect, so a folder action is always decisive and you can still pick individual files back out afterwards:
ignore src # untrack the whole folder (recursive)
track src/app.py # ...but keep this one file
Paths may also be globs — an entry containing *, ?, or [ matches as a
glob against the relative path and each of its folder prefixes (so *
crosses directory boundaries):
ignore *.log # every .log file, wherever it is
ignore dist/* # everything under dist/
track .github/* # include the workflows in a dot folder
Changes apply immediately (new sessions use them right away; the active session's change list is re-reconciled).
Requires an active session — in basic editor mode (no
.alxedit/folder) there is no baseline, so nothing is tracked.
alxedit2 watches the project tree for changes made outside the editor —
for example an AI agent writing files while you watch. Every 0.8 s the tree
is scanned; dot-dirs, __pycache__/node_modules, and files over 1 MB are
ignored, and saves made by alxedit2 itself are never flagged.
Open tabs carry status marks after the file name: ● (orange) while the
buffer differs from the session baseline (mirror) — i.e. there is a pending
change not yet committed by a save — † (orange) when the file on disk
diverged from it, and ⇄ (blue) while the inline diff review is on screen.
When something changes you get a toast, a ⚑n counter in the status bar,
and a +N/-M
marker next to the file in the explorer — the lines added/removed
relative to the session copy (green +, red -). A file without a
marker matches the session snapshot exactly; the marker disappears the
moment the file is saved, approved, or rejected.
Every explorer entry also carries a tracking glyph: T (accent
blue) means the change tracker covers it — it is in the session mirror
— while ○ (dim) means it is untracked (dot files by default, or an
ignore rule in .alxeditrc). Folders reflect their contents: T
when anything inside is tracked, ○ when nothing is. Flip a rule in
Settings — or right from the tree: ctrl+click an entry and pick
Track / Untrack — and the glyphs follow. Each action rewrites the
rule for that path so it becomes the last (winning) one — folder rules
are recursive, and a file can be picked back out of an untracked folder.
Everything lands in .alxeditrc.
If the file is already open, the tab immediately switches to a diff review — there is no separate diff window. Both sides are painted into the tab:
- green line — the side a plain save would write (the agent's new content for a clean buffer, your own lines for a buffer with unsaved edits);
⌫red struck-through line — a ghost line: content that exists only on the other side;- unmarked lines — unchanged context.
A hunk bar appears on the right with one row per contiguous change block:
- theirs — adopt the external (agent) side of that block;
- mine — keep your side of that block.
The view updates as you decide; once every block is decided, the tab
becomes a normal editable editor holding the merged content. The ●
dot shows whenever the buffer differs from the session baseline (mirror):
taking the agent's side lights it (buffer now differs from the baseline);
keeping your own side leaves it off (buffer matches the baseline). In
either case ctrl+s commits the resolution to disk and the baseline.
Alternatives:
ctrl+ssave — pending blocks resolve the way a plain save would (the green + context lines are written,⌫ghosts dropped), so saving never loses your unsaved edits;esc/ctrl+d— abandon the review: a clean buffer adopts the new disk content (approved), a dirty buffer restores your unsaved edits — never lost.
If the agent keeps writing, the review updates live.
Files that are not open yet: just open them — clicking them in the
explorer (or giving them on the command line) drops you straight into the
diff review, no hunting needed. The f2 / Changes screen still
lists everything the agent touched, and lets you approve or reject
each one (or all of them at once) — most useful for files it deleted
(they can't be opened) or added (reject = delete):
| Key | Action |
|---|---|
a |
Approve — accept the change (pending until save) |
r |
Reject — restore the baseline (or delete an addition) |
A |
Approve all — accept every tracked change at once |
R |
Reject all — restore every baseline at once |
enter |
Open the file in the diff review |
o |
Open the file in the editor (plain view) |
q |
Back to the editor |
In a file under review, ctrl+d/esc exits the diff (see above).
Approve accepts the change into the buffer (the tab shows ● —
pending). The session baseline (mirror) is not updated yet; that
happens on save (ctrl+s), which writes the buffer to disk and
commits it as the new baseline. Reject does the opposite: it restores
the file to its content in the active session snapshot (the mirror).
Files the agent added are deleted on reject; files it deleted are
restored; approving a deletion drops the baseline copy so the file is
gone for good. Every action asks first, and if a tab has unsaved edits
you are told they will be discarded.
Highlighting uses Textual's built-in tree-sitter grammars
(installed by textual[syntax]). Supported extensions:
- Python —
.py,.pyi - Rust —
.rs - Bash —
.sh,.bash,.zsh - JavaScript / TypeScript —
.js,.cjs,.mjs,.jsx,.ts,.tsx(TypeScript is highlighted with the JavaScript grammar — a close approximation, since no TS grammar ships with Textual) - JSON —
.json - TOML —
.toml - YAML —
.yaml,.yml - Markdown —
.md,.markdown - Go —
.go - Java —
.java - SQL —
.sql - HTML —
.html,.htm - CSS —
.css - XML —
.xml
Anything else opens as plain text.
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest