A cross-platform (Windows / Linux / macOS) interactive file explorer + shell,
rewritten in Python on top of prompt_toolkit.
It is CJK-aware throughout and includes FTP/SFTP remote browsing.
Current version: 1.0.0.
Its primary modes are:
- Explorer (default) — an
mc/lf-style directory pane with type-coloured entries, icons, a live Git status overlay, multi-select, file operations, and a side preview pane. Press:to drop into the shell,/to fuzzy-find. - Command-line — drives your host shell with syntax highlighting, an
interactive Tab-completion popup, ANSI-coloured / progress-bar-aware output,
and a scrollback you can page through. Press
ESCto return to the explorer. - Fuzzy search — an fzf-style file picker (
/from the explorer, ornsh searchfrom the command line). - Network — an FTP/SFTP remote browser with recursive transfers and file operations, opened from F10 → Network.
Everything runs in tabs: each tab is its own explorer + shell pair, so you
can keep several working directories — each with its own command-line session —
open at once and switch between them with F7/F8.
Git/diff and log views, persistent Notes, the System process viewer, recursive text Find, and the searchable Preferences editor are integrated into the same keyboard- and mouse-driven interface.
Requires Python 3.7+.
pip install -e . # installs prompt_toolkit + wcwidth, and an `nsh` commandThe -e (editable) install puts an nsh command on your PATH that runs from
any directory; because it's editable, later code edits take effect with no
re-install. Then, from anywhere:
nsh # explorer mode
nsh shell # start in command-line mode
nsh search [WORD] # fuzzy-pick a file; the choice is printed to stdout
nsh -h | -vIf the nsh command isn't found after installing, your Python user-scripts
directory isn't on PATH. Print it and add it to PATH:
python -c "import sysconfig,os; print(sysconfig.get_path('scripts', os.name=='nt' and 'nt_user' or 'posix_user'))"- Linux / macOS — this is usually
~/.local/bin; add it to PATH in your shell rc. - Windows — with the Microsoft Store Python it's a
…\LocalCache\local-packages\Python3x\Scriptsfolder that isn't on PATH by default; add it under Settings → Edit environment variables → Path, then open a new terminal.
As a fallback that needs no PATH change, run it as a module: python -m nsh.
Every explorer is paired with a shell in a tab — its own directory, selection, preview and command-line scrollback — so several places stay open at once and the process working directory follows the active tab.
| Key | Action |
|---|---|
Ctrl+T |
new tab (a fresh explorer at the current directory) |
Ctrl+W |
close the current tab |
F2 (in shell) |
set or clear the current tab's custom name |
F7 / F8, Alt+← / Alt+→ |
previous / next tab |
These work from both the explorer and the command line. The tab bar is also
clickable: click a tab to switch to it, double-click it to close it, and click
the + button at its right end to open a new tab. The two-pane view (2) is
per-tab, so one tab can be split while another shows a single pane.
Unless renamed with F2, a tab is named after its current directory and the
name follows directory changes (and the active pane in a two-pane tab). Clearing
a custom name in the rename dialog restores this automatic naming.
Shift+H / Shift+L move focus left / right across the on-screen
columns — the two panes, or (in single-pane view) the list and its preview — or
click a pane to focus it.
| Key | Action |
|---|---|
F10 |
open the main menu: bookmarks, find, network/disconnect, notes, system, preferences, and about |
Ctrl+F |
choose file-name fuzzy search or recursive text search (Explorer/Git) |
Ctrl+N |
open Notes from Explorer, Git, shell, or System |
Ctrl+G |
toggle Git mode |
Ctrl+Q |
quit nsh |
Ctrl+C |
interrupt the active local/SSH command; with no running shell command, clear its input |
The action keys (everything below the navigation block) are remappable in
nshrc — see Configuration.
| Key | Action |
|---|---|
g / Home, G / End |
first / last row |
↑/↓, k/j |
move cursor |
↵ |
open the file / enter the directory |
l, → |
expand/collapse a directory inline; on a file, focus the preview |
⌫, h, ← |
collapse the directory, else go to the parent |
Shift+H / Shift+L |
move focus left / right; only Explorer single-pane may open the cursor directory as a new right pane, while existing two-pane and SSH/Network views only move focus |
Space |
select / deselect the entry (multi-select) |
* |
select entries by substring or glob pattern |
Tab |
open the action menu (copy, rename, delete, git…) — drops from the cursor row, beside the filename |
y / x / p |
copy / cut / paste — the picked rows briefly flash; paste lands in the directory at the cursor. The clipboard is shared across tabs, so you can copy in one tab and paste in another |
F2 / i |
rename (inline) |
m / N |
new folder / new file — created in the directory at the cursor |
d |
move to the platform Trash / Recycle Bin (asks to confirm) |
D |
permanently delete (asks to confirm) |
Tab → chmod… |
change permissions using 755, rwxr-xr-x, u+x, or interactive read/write/execute checks |
b |
bookmarks — add/remove this directory, or jump to a saved one |
~ |
jump to the home directory |
- |
open the recently visited directories menu |
s |
sort by name, size, date, or type, ascending or descending |
2 |
toggle two independent Explorer panes |
z |
zoom the focused pane |
/ |
fuzzy-find a file |
Ctrl+G |
git mode — the repository's changed files (see below) |
: |
switch to command-line mode |
P |
toggle the preview pane |
. |
toggle hidden files |
r |
refresh (the listing also auto-refreshes when the directory changes) |
? |
show this key list |
ESC |
clear the selection |
q |
quit |
Read-only files inside deleted directories are made writable automatically. Any file-operation error opens an error dialog with the affected paths and details.
Local symbolic links use the link icon and display their recorded target as
name -> target, matching the SFTP file view. Directory links end in the local
platform's path separator on both sides of the arrow, such as
name\ -> target\ on Windows and name/ -> target/ on POSIX.
Paste, new file and new folder follow the cursor: a directory under the cursor is the target (the item lands inside it and it expands to show the result), while a file targets its containing directory — so in the tree view you act exactly where you're pointing. Paste is stricter: it only drops inside a directory you've already expanded; on a collapsed one it pastes into the container beside it, not into a folder you can't see into.
Git actions (stage / unstage, commit, diff, and a Branches submenu that
lists branches to check out plus a + New Branch entry) live in the Tab
action menu when the directory is a repository. An untracked file can be staged
— including the files inside a brand-new directory, which carry the untracked
marker and so can be added too.
Repository status uses one git status --porcelain=v2 --branch --show-stash
process per refresh, providing branch, upstream, ahead/behind, stash, and file
state together. Repository roots and operation markers are read from .git
metadata without starting additional Git processes. The background watcher also
tracks lightweight Git metadata, so commits, staging, branch movement, and
similar state-only changes refresh even when no worktree file changes.
Commit, Revert, and Log make their scope explicit in the action menu. The .
entry targets the current directory, while a second entry targets the selected
file or files (for example, Git: Commit ., Git: Commit README.md, or
Git: Commit 3 files). Directory Git markers summarize tracked changes below a
collapsed directory; the aggregate marker disappears when the directory is
expanded. Untracked files do not mark their parent directory, and the synthetic
.. navigation row never shows a Git marker.
Outside a Git repository, visible child directories—including directories
revealed through inline expansion—that are themselves Git repository roots get
a blank colour marker: green when there are no tracked changes and red when it
has staged, modified, or conflicted tracked files. Untracked files alone do not
turn the repository marker red. The colour
marker remains on an expanded repository while the usual M/S/! markers
also appear on its changed files and collapsed subdirectories.
When the Explorer cursor is on a tracked modified, staged, or conflicted file,
the preview shows that file's Git diff instead of its complete contents. Clean
and untracked files keep the normal file preview.
Image files render inline on iTerm2 using its native image protocol, and on
Windows Terminal 1.22 or newer using Sixel when Pillow is installed
(pip install "nsh[images]"). On unsupported terminals (or without Pillow for
Sixel), the preview keeps showing image metadata.
With focus in a Git diff preview, Up/Down jump between contiguous -/+
change blocks and u reverts only the current block after confirmation;
s stages or unstages only that block, and j/k move between change blocks
just like the arrow keys. The current block
has a soft gray background,
configurable as
preview-hunk-selected under Preferences colors. This background-only style
keeps the diff's original addition, deletion, and metadata text colors.
Files with both kinds of changes show separate Unstaged changes and
Staged changes sections; change navigation and s continue across both.
Selected staged blocks use a separate soft-green background configured as
preview-hunk-staged-selected, while unstaged blocks retain the gray background.
For an unmerged file, the same navigation selects one <<<<<<< / >>>>>>>
conflict block. Press s to accept ours, theirs, or both from a menu. Resolving
the last block stages the file; u restores the most recently resolved block
and its unmerged Git index state.
Picking a branch opens a per-branch menu (Checkout, Browse, Delete). Browse
pops a small centered dialog listing that branch's files without checking it out:
↑/↓ (or j/k) move, ↵ / l / → step into a directory and ⌫ / h /
← back out, and y copies the highlighted file — or a whole directory —
out of the branch into the current explorer directory (never clobbering an
existing name). Esc / q closes it.
Ctrl+G opens a flat, git status-style list of the repository's changed and
untracked files — a change in a subdirectory shows as its full path (not a
tree). ↑/↓ move, Space multi-selects, and the preview pane shows the file's
diff (untracked files show their new content). Tab opens an action menu
(stage / unstage — applied to the whole selection — commit, edit, branches).
There is no directory hierarchy, so →/l steps into the diff preview to
scroll it (h or Esc returns to the list) while the other left/right keys are inert;
Ctrl+G or ESC returns to the explorer, and jumping elsewhere (e.g. via a
bookmark) leaves git mode automatically. The git log (from the Tab action
menu) works the same way — →/l focuses the commit-detail/diff preview and
h returns to the log list.
Git mode and the git log are per-tab — each tab keeps its own changed-file list
and history (cursor, selection and search) — so F7/F8 (or Alt+←/Alt+→)
switch tabs from here too, swapping the view along with the directory it belongs
to, and Ctrl+T/Ctrl+W open and close tabs without leaving the mode. Which
mode a tab is in is itself per-tab: leaving git/log mode in one tab doesn't pull
the others out of it, and switching tabs shows each one in the mode you last left
it in (a new tab opens in your current mode, so Ctrl+T keeps your workflow).
| Key | Action |
|---|---|
| typing | live syntax highlighting |
| paste | a single line is inserted; multiple lines run in order through the command queue |
Tab |
completion popup; ↑/↓ or j/k navigate, Tab picks (no space), Space picks and adds a space |
↑/↓ |
command history (when no popup is open) |
↵ |
run the command |
PgUp/PgDn, Alt+↑/Alt+↓, wheel, Ctrl+End |
scroll the output (the prompt hides while scrolled up) |
Ctrl+T / Ctrl+W |
open / close a tab |
F2 |
rename the current tab; an empty name restores its directory-based name |
Alt+← / Alt+→ (or F7 / F8) |
previous / next tab |
ESC |
clear a non-empty command line; when already empty, switch back to Explorer |
Each tab pairs this shell session with its own explorer (see Tabs),
so switching tabs swaps the whole working context. Entering a command while the
current one is still running queues it immediately in that tab; several commands
can wait and run in order. Queued commands are listed in grey above the prompt,
with a ⋯ N queued count in the status bar. A tab bar marks each session's state — orange
while a command is still running, red once one finishes with a non-zero exit
(cleared when the next command runs).
The current completion row uses terminal reverse video while retaining its file/command colour; it is not a separate Preferences colour setting.
Ctrl+Z removes the last waiting command without affecting the command already
running. This shortcut is configurable as queue_remove_last in Preferences.
Built-ins handled internally: cd, clear/cls, exit/quit. The output pane
grows with its content and goes full-screen once it fills up. Long lines wrap.
PowerShell completions keep a leading ~ visible in the editable command;
home paths are expanded only before execution while retaining wildcards (for
example, mv ~/Desktop/*.jpg .). The prompt shows
each command's run time tinted by its exit status (and dims
itself while a command is still running), and
interactive commands that need a real terminal — editors/pagers, plus network
git (push/pull/fetch/clone) and sudo that may prompt for credentials
— run with the UI briefly suspended. nsh echoes the prompt + command above their
output and, when they finish, waits for a keypress (press any key to continue …) so it stays visible. Prefix any command with ! to force it
onto the real terminal this way — an escape hatch for a TUI nsh doesn't
recognise on its own (e.g. !htop).
Commands listed in the Preferences variable external_commands take the same
real-terminal path without requiring !. Separate names with spaces or commas,
for example mc, ranger, lazygit. Forced and configured external commands record
their elapsed time and exit status just like commands captured in nsh's
scrollback.
Type to filter, ↑/↓ to move, ↵ to select, ESC to cancel. Launched with
nsh search [WORD] (prints the selection to stdout, e.g. cd "$(nsh search)")
or with / from the explorer. Build outputs (build/, dist/) are indexed too,
so a built executable is findable; the skipped directories are configurable via
search_exclude (see Configuration).
Search does not wait for a full tree index before becoming useful: matches from
the current directory appear first, then indexed results are added as background
indexing progresses. While that index is still running, a query containing /
or \ follows its fuzzy-matched directory components and lists that specific
subdirectory immediately. Remote search follows the same visible-results-first
model.
Open Notes with Ctrl+N or F10 → Notes. Notes are persistent, multi-line
scratch entries. Type in the upper editor and press Ctrl+S to add or update a
note; Down moves into the list. In the list, arrows or j/k navigate,
Enter edits, / searches, y copies the full note to the system clipboard,
d/x deletes, and u restores the last deletion. Esc clears an active
search or edit first, then returns to the originating view.
Open F10 → System for a live process list. Arrows or j/k, page keys, and
g/G navigate; c, m, and n sort by CPU, memory, and name; v toggles
the detailed command line; / filters; r refreshes; and x, Delete, or
K asks to terminate the selected process. Esc clears a search first, then
returns to the originating view.
Network mode places the active local explorer pane on the left and an FTP or
SSH-SFTP directory tree on the right. Open F10 → Network from the local pane
you want to use as the transfer endpoint, then choose a connection type. Both
directories remain visible while files are transferred between them. Searching
with / in the local pane returns to the same Network layout and keeps the
remote connection visible; remote search likewise restores remote focus.
Notes, grep/shell, System, Preferences, Git/Log, and modal dialogs also return
to the originating Network pane instead of replacing the SSH side with preview.
The connection belongs to the app, not to one tab, so the layout stays exactly
tWo panes everywhere. A tab opened (Ctrl+T) or switched to while connected
ShOWS itS own local directory on the left and the same remote tree on the
right, with the cursor starting on the local side; the preview and the two-pane
explorer split step aside for the duration. Transfers always use the local pane
you can see. Disconnecting returns every tab to its ordinary explorer layout,
preview included.
Connection targets use [user@]host[:port][/initial/path]:
alice@example.com:22/home/alice # SFTP
files@example.com:21/incoming # FTP
internal-server/projects # SSH alias + initial path
SFTP defaults to port 22 and the current operating-system user; FTP defaults
to port 21 and anonymous login. The password dialog is masked and cleared as
soon as it closes. Leave the SFTP password blank to use IdentityFile entries,
your SSH agent, or Paramiko's normal private-key discovery. Leave an anonymous
FTP password blank to use anonymous@.
After a successful SFTP connection, its target (never its password) is saved in the UI state and prefilled the next time the SFTP connection dialog opens.
SFTP resolves aliases through ~/.ssh/config, including HostName, User,
Port, IdentityFile, ProxyJump, and ProxyCommand. For example:
Host bastion
HostName bastion.example.com
User jumpuser
IdentityFile ~/.ssh/bastion_ed25519
Host internal
HostName 10.20.0.15
User deploy
IdentityFile ~/.ssh/internal_ed25519
ProxyJump bastionChoose SFTP and enter internal/var/www as the target. At the next prompt,
leave Jump host empty: nsh reads ProxyJump bastion from the config and
opens the final SFTP session through an SSH direct-tcpip channel.
A jump host can also be entered explicitly as
jumpuser@bastion.example.com:2222. Comma-separated chains such as
edge@edge-host,core@core-host are supported. An explicit value overrides the
destination's ProxyJump setting. Each hop resolves its own SSH config and is
closed in reverse order when the connection ends or fails.
When no jump host or ProxyJump is active, nsh also supports ProxyCommand.
A standard ssh -W %h:%p configured-alias form (including combined flags such
as ssh -AW) uses nsh's native direct-tcpip jump path, so the alias's User,
Port and IdentityFile are applied just like an explicit jump host. Other proxy
commands open as the SSH transport socket. Standard tokens such as %h, %p,
and %r are expanded by the SSH config parser, and any proxy process is closed
when the connection ends or fails. You may enter either the Host alias or its
literal HostName in nsh; when exactly one matching alias defines proxy
routing, nsh applies that alias's ProxyJump or ProxyCommand automatically.
When this routing is detected after entering the SFTP target, nsh skips the
redundant Jump host dialog and proceeds directly to authentication.
Every SSH host key is checked independently against the user's system
known_hosts. On first connection nsh shows the key type and SHA256 fingerprint
for approval; verify it with the server owner. An approved key is saved to
~/.ssh/known_hosts and the connection is retried. A key that conflicts with
an existing entry is still rejected without an approval prompt. A password
entered in nsh is available as an authentication fallback for the destination
and every jump host, while per-host keys and the SSH agent remain preferred
alternatives. If authentication is rejected, nsh preserves the target and jump
route and asks only for the password again.
| Key | Action |
|---|---|
↑/↓, j/k |
move cursor |
g/Home, G/End |
first / last row |
~ |
open the remote login home directory |
s |
sort by name, size, date, or type in either direction |
/ |
fuzzy-find files and directories below the remote directory |
: |
open a command shell over the current SSH connection |
↵ |
enter a directory; download a file |
l, → |
expand / fold a remote directory inline |
⌫, h, ← |
fold, move to the tree parent, or open the parent directory |
Space |
select / deselect |
Shift+H / Shift+L |
focus the local / remote pane |
c |
local focus: upload; remote focus: download |
n |
create remote directory |
i |
rename remote item |
D |
permanently delete selected remote items |
Tab |
remote actions menu |
r |
refresh |
Esc |
clear the focused pane's selection |
q |
quit nsh |
With the remote pane focused, c downloads into the displayed local pane's
current directory. With the local pane focused, c uploads its marked
selection—or its cursor item when nothing is marked—into the displayed remote
directory. Files and whole directory trees are supported in both directions.
Enter on an SFTP file opens a bounded preview in the remote pane without
creating a local file; Esc returns to the listing and c explicitly downloads
the file. Binary files are downloaded to an instance-private temporary directory
behind a blocking progress dialog with a Cancel button, then opened with the OS
default application; those temporary files are removed on disconnect or exit.
Enter on a directory opens it. FTP files retain the direct-download
behavior. A transfer refreshes only its destination pane.
SFTP uploads and downloads run as visible SSH-shell jobs. Each selected item is
queued separately, so the shell shows the source and destination, elapsed time,
live progress bar, transferred/total size, transfer speed, completion or failure
details, and subsequent transfers waiting above the
prompt. Ctrl+C cancels the active transfer; queued transfers then continue in
order. Ctrl+Z removes the last queued command or transfer without cancelling
the active one. The SSH shell initially shares the screen with the local/remote panes and
grows with its output, maximizing only when the normal shell height limit is
reached. FTP transfers retain the direct network-pane workflow because FTP has
no SSH command shell.
The remote pane uses the same tree presentation as the local file pane: file
sizes are right-aligned, directories use ▸/▾ carets, expanded children are
indented, and the cursor highlights the complete row with the explorer colour
scheme. A .. row is shown outside the remote root. Directory contents are
fetched only when first expanded.
SFTP symbolic links use the link icon and show name -> target. Directory links
use name/ -> target/. Links to
directories can be opened and expanded like directories; links to files behave
like files, and broken links are marked with the conflict color. Recursive
search does not follow directory links, and deleting a directory link removes
only the link rather than the target tree.
On an SFTP connection, : opens a remote command shell that reuses the active
authenticated SSH session. Commands run in the directory displayed by the
remote file pane. cd updates that pane's directory, clear clears remote
shell output, and exit/quit or Esc returns to the remote files. Commands
entered while another command is running are shown above the prompt and executed
in queue order. Running and completed commands show elapsed-time badges using
the same colours and alignment as the local shell. Commands are executed one at
a time without an interactive PTY, so full-screen programs such as vim and
top are not supported in this view.
Transfers and recursive delete operations run in worker threads so the TUI can
continue repainting. Existing names are never overwritten: uploads and
downloads use name (2), name (3), and so on. Disconnect is blocked while an
operation is active, but a background remote search index is cancelled so it
does not prevent disconnecting. Connection/transfer errors remain visible in
the status bar.
Disconnect deliberately has no single-key shortcut. While connected, the F10
menu replaces Network with Network: Disconnect; choose it, or use Disconnect from
the Tab remote-actions menu, and approve the confirmation prompt. The 2 key
is disabled while this fixed local/remote
layout is open, so it cannot alter the hidden local explorer split.
Current limitations: transfers cannot yet be resumed, there is no remote-to- remote copy/move or chmod action, credentials are not saved, and only plain FTP (not FTPS) is implemented. Plain FTP sends credentials and data without encryption and should only be used with trusted legacy servers; prefer SFTP for normal use.
Open F10 → Preferences to enter the full-screen settings editor. Its single
list contains variables, colors, and shortcuts; type in the search field to
filter by category, setting name, or current value, then press Enter to edit the
selected row. Changes are validated, saved, and applied immediately. Leave a
color blank to restore its built-in default; leave a shortcut blank to unbind
that action. Rows that differ from their defaults have a leading *; editing
one also offers a Reset Default button. Press Ctrl+O to open nshrc in an
external editor for advanced manual editing.
The settings are stored in ~/.config/nsh/nshrc ($XDG_CONFIG_HOME is
honoured). On first run nsh seeds a commented template. It is a simple INI file:
[general]
# editor for the "Edit" action; unset -> $EDITOR/$VISUAL, then notepad/vi
editor = code -w
two_pane = false # start with two explorer panes side by side
restore_tabs = true # restore tabs, paths and command history
scrollback_lines = 2000 # retained lines per local/SSH shell tab
search_exclude = .git node_modules # directories fuzzy search skips
external_commands = mc, lazygit # run these on the real terminal without !
[colors]
# <style-class> = <prompt_toolkit style>
explorer.dir = #5fafff bold
explorer.selected = #ffff5f bold
shell.command = #5fafff bold
[keys]
# <action> = <key> (a char, or: space, tab, escape, f5, c-r, s-tab, …)
copy = y
trash = d
delete = D
menu = tab
quit = q[general] sets the Edit editor (Tab menu, text files only), whether to
start in two_pane view, whether restore_tabs restores the previous tab set,
pane paths, recent per-tab command history and unfinished command input,
the per-shell scrollback_lines limit (1–100000),
search_exclude (the directories fuzzy search skips),
and external_commands (commands that always receive a real terminal).
[colors] overrides any UI style class; [keys] remaps the explorer action
keys. Invalid entries are ignored, never fatal.
Bookmarks (the b key) are saved one path per line in ~/.config/nsh/bookmarks.
nsh/
app.py application modes, layout, central key dispatch, cwd/git state
network/
backend.py FTP/SFTP connection and remote filesystem operations
view.py remote browser, transfers and action bindings
shell.py command shell over the active SSH connection
preferences/
view.py searchable variables/colors/shortcuts editor
notes/
view.py persistent multi-line notes editor/list/search
system/
view.py sortable, searchable process viewer and terminate action
config.py styles, icons, key map, nshrc loading
util/
width.py wcwidth-based truncate/pad (CJK-correct columns)
paths.py pathlib helpers, normalised compare keys
aio.py run_in_thread (asyncio.to_thread backport for 3.7/3.8)
widgets.py mouse-wheel-aware control
menu.py reusable popup action menu
explorer/
model.py os.scandir directory listing
git.py async git status / branch / stage / commit / diff
gitview.py git mode: flat changed-file list (Ctrl+G)
logview.py scoped directory/file history and commit actions
fileops.py copy / move / trash / delete / rename / mkdir (threaded)
preview.py text/dir/hex/diff preview plus Sixel/iTerm2 inline images
view.py file-list rendering, navigation, multi-select, action menu
search/
fuzzy.py fzf-style scorer + directory indexer
view.py the fuzzy picker
shell/
runner.py host-shell wrap via asyncio subprocesses (per session)
tabs.py tabs — each bundles an explorer pair + a shell session
completer.py interactive path + command Tab-completion
lexer.py command-line syntax highlighting
view.py one session: scrollback (ANSI / CR / BS-aware, wrapped) + prompt
- CJK widths. Every column in the file list is padded/truncated with
util/width.py, which measures rendered cell width viawcwidthinstead oflen(), so Korean/Chinese/Japanese names never break the layout. Git output is read withcore.quotepath=falseto keep unicode filenames intact. - Non-blocking everything. Directory listing, Git status, file copies, the
preview, and the search index all run off the event loop (an
asynciotask or a worker thread) andinvalidate()when done; the UI never waits on them. Periodic directory scans skip overlaps, and stale results (for a directory you already left) are dropped. Concurrent identical Git status requests share one process, which is terminated when its final waiter is cancelled. - Host shell. Commands run through your platform's default shell
(
$SHELL -c …on Unix;cmd /c …orpowershell -Command …on Windows), streamed in chunks so a\r-only progress bar updates in place. ANSI colour codes are interpreted (and colour is forced on via the child environment, since stdout isn't a TTY); for the same reasonPYTHONUNBUFFERED=1is set so a Python script'sprints stream live instead of block-buffering until it exits. Interactive programs (editors, pagers,top…) — and commands that may prompt for credentials, like network git andsudo— are detected and run with the full-screen UI temporarily suspended so they get a real terminal. - Cross-platform paths. All path handling uses
pathlib, so Windows\and POSIX/are handled uniformly.