configuring strop
Zero-config by default — every knob below is optional. Two files, both TOML, both layered (project over user over embedded defaults):
config.toml — the two editor knobs
At $XDG_CONFIG_HOME/strop/config.toml (usually ~/.config/strop/config.toml).
Run strop config to print the live values.
tab_size = 4
indent_guides = true
languages.toml — language servers, helix-shaped
Per-project at .strop/languages.toml (next to your code, committed if you like),
globally at ~/.config/strop/languages.toml. Project wins per key; unknown keys are
ignored, so helix files paste clean. The workspace root anchors at the .strop
directory.
Point a server at a different binary (absolute paths skip the PATH probe):
[language-server.pyright]
command = "/opt/venvs/bin/pyright-langserver"
args = ["--stdio"]
Pass server configuration — it lands in initializationOptions verbatim. The
classic venv + extraPaths setup:
[language-server.pyright.config.python]
pythonPath = "/workarea/pbpilsvc/.venv/bin/python"
[language-server.pyright.config.python.analysis]
extraPaths = ["/opt/bb/lib/python3.12/site-packages"]
Reassign a language to different servers (first resolvable wins; anything unresolvable falls back to the embedded registry):
[language.python]
language-servers = ["pyright", "mypy-lsp"]
Servers stay out of your way: a missing one shows its install hint once per session, and hover/definition are gated on what the server actually advertises.
Filesystem workspace
Open a Directory buffer with strop DIRECTORY, :e DIRECTORY
or :browse. Space e reveals the current source. Enter opens
an entry; -/Backspace goes to its parent. Browsing does not change cwd.
:fs create nested/new.txt
:fs mkdir new-directory
:fs rename new-name.txt
:fs move destination-directory
:fs copy stored stored-copy.txt
:fs copy buffer edited-copy.txt
:fs trash
:fs remove
:apply-change
:cancel-change
:fs operations
:fs verify OPERATION STEP
:fs undo OPERATION STEP
Relative destinations use the current Directory or source parent. Review lists missing parents explicitly. Creation and rename/move never silently replace an occupied name. Stored-copy and buffer-copy select different versions without saving the original.
:fs edit opens a name-only filename draft. Ordinary motions, edits,
whole-line yank/paste and undo change the draft, not disk. :w prepares
review; Apply performs the checked operations. Cancel returns to the intact draft;
:fs discard discards it explicitly.
:fs search opens the shared Search workspace at the captured local
or SSH root. Remote search requires remote rg and is read-only:
With/Review is unavailable, with no local or whole-file-download fallback.
Supported POSIX/Python SSH hosts execute real protected operations. Remote
relocation retains unsaved text but revokes the old write permit; use
:remote edit again before saving. Containers remain read-only.
Local Trash is supported where available; remote Trash never becomes unlink.
Recursive permanent removal and unsafe alias/root operations are refused.
Receipts survive leaving their view. Verify never retries a mutation, and
recovery requires the recorded post-publication version, not pathname occupancy.
Receipts are in-memory, not crash recovery. Unresolved outcomes block graceful
global exit; explicit :qa! reports them and exits unsuccessfully.
SSH workspaces
strop +120 ssh://user@devbox/var/log/app.log
strop --tail 65536 ssh://devbox/var/log/app.log
strop --range 1048576:65536 ssh://devbox/var/log/app.log
strop --follow ssh://devbox/var/log/app.log
strop ssh://devbox/~/project/src/lib.rs
strop ssh://devbox/etc/
Use existing OpenSSH aliases, keys, agent and ProxyJump configuration.
Authentication is noninteractive; host keys must already be trusted. Files open
read-only by default. Normal motions, //?, visual selection,
yank and splits use real buffers.
Escape cancels loading or stops following; :e! refreshes.
Follow stays at EOF only while you do. Changed content or shrinking windows report
resets; size and mtime are not treated as file identity. Partial windows show their
byte range in the modeline and use window-relative line numbers.
Press Space o or run :remote to choose a known/connected host
or add one. Enter host, user@host:port or an SSH path, then press
Enter to connect. New hosts start at /; successful directory choices are
remembered privately. :remote home uses negotiated home expansion;
:remote root returns to the root.
:tail 65536 ssh://devbox/var/log/app.log
:range 1048576 65536 ssh://devbox/var/log/app.log
:follow
:unfollow
:browse ssh://devbox/var/log/
:filter app
:remote connect ssh://devbox
:remote list
:remote disconnect ssh://devbox
:remote clear
Directory rows show file kind, POSIX permissions, server-reported byte size and
native filename. Unknown attributes show ?, distinct from zero.
Directory sizes are not recursive totals. Enter opens an entry; -,
Backspace or ../ returns to the parent and restores the selected child.
An empty :filter restores the listing.
Tab completes hosts and paths without initiating authentication. Remote candidates
require a live authorized connection or cached data. Documents and explicit connection
pins share endpoint-owned SFTP sessions; cleanup never kills external SSH masters.
Full snapshots support remote LSP diagnostics, hover, definition/references
and source/header navigation, plus Git context, staged/unstaged diffs,
log, blame, commit/file navigation and source links. Services run on the
remote host, never against a local lookalike path. Project-command :trust
is scoped to that endpoint and workspace root. Partial/follow windows refuse
full-document language services. Git mutations, save-as and arbitrary shell/filter
commands remain unsupported.
Reads are bounded to 256 MiB of UTF-8 text. Tail/range edges omit incomplete UTF-8
characters. Percent-encode reserved bytes (%20, %23,
%25); native Unix filenames remain byte-exact. Home expansion requires
expand-path@openssh.com. Remote content is not persisted or automatically
restored from sessions.
Explicit remote editing
:remote edit
:w
:remote verify
:remote edit checks a complete, non-following snapshot against the
server and grants this document write authority. Edit normally and use :w
or :wq. Saving runs off the input thread; a receipt for an older revision
never clears newer unsaved edits. :w! cannot grant authority or bypass
remote conflict checks.
The helper uses a stable cooperative lock, a private 0700 transaction directory, metadata-preserving staging, atomic replacement and file/directory syncing. It requires an owned regular single-link file, exact stored path spelling and no symlink components. Unsupported mode/ownership/mtime/attribute preservation refuses the save. Edited content is bounded to 256 MiB.
Cooperative locking is not universal compare-and-swap. Remote-save protocol participants coordinate, but another program can race the final comparison/rename window. Completed external changes are detected before commit. Private lock files retain stable identity and must not be removed while active.
Normal-mode Escape requests cancellation. If a save may have crossed rename,
the result is unconfirmed and local edits remain dirty. :remote verify
compares the original and intended states, syncing matching saved content before
acknowledgment. It does not blindly retry, overwrite or roll back. A conflict
preserves local edits; forced close can leave an unconfirmed outcome or private
orphan stage.
Successful :e! refresh revokes the permit; pending/unconfirmed
saves must settle or be verified first. Failed refresh preserves edits and authority.
Reselecting an already-open file does not discard its draft. Remote content,
permits and live writes are never restored from persisted sessions.
The remote-save contract records safety assumptions, bounded models, fault-injected real SSH checks and native-free replay evidence.
SFTP reading needs no remote Python or setup daemon. Remote editing and execution
require a POSIX environment and Python 3.8+; Git/LSP also need their respective tools.
Versioned interpreters on PATH are discovered; set local
STROP_REMOTE_PYTHON=/opt/tools/python3.11 to select an explicit remote
executable. Invalid overrides fail instead of choosing a fallback.
Supervised process groups are cleaned up once the server observes lease
loss. Network partitions delay detection, descendants creating new sessions can
escape the group, and supervisor death cannot guarantee cleanup.
The reusable strop-remote crate owns identity, transport and execution.
The workspace contract
links bounded model gates, deliberate fault variants, Rust oracles and real SSH,
terminal and replay evidence. This is not an unbounded proof or a replacement for
OpenSSH's security boundary. See the prioritized roadmap
for additional transports, directory mutations and Dev Containers.
Embedded terminals
:terminal (or :terminal COMMAND) opens a local shell in a
real PTY supervised by a same-binary helper process; the directory context offers
Terminal here, and :terminal-local forces a local shell from
SSH/container contexts — interactive execution there is refused, never silently
redirected. The VT engine is the pinned static Ghostty core: mode-aware, bounded,
and identical in every build.
Input ownership: while the terminal is focused, Esc, Ctrl-C,
Ctrl-R, Alt- and function-keys mean what the child expects.
Ctrl-\ Ctrl-N (legacy terminals may spell it Ctrl-4 Ctrl-N)
enters editor Normal mode over a pinned read-only snapshot of the grid; i/a
follow the live cursor again. From terminal input, Ctrl-W is the editor's
window prefix (Vim's terminal grammar): h/j/k/l/w or arrows move and cycle
panes, N enters the pinned inspection, . delivers the literal byte to
the child, and any other follow-up passes through unchanged. An exited session stays readable and never restarts implicitly.
Terminal buffers are read-only text buffers with the usual motions, search, selection
and yank — and they are real switchable buffers: the buffer picker lists each session
with Vim's ! job badge and its live phase (terminal #N · dir · running),
and entering a terminal records a jump so Ctrl-O returns to your editing position. Splits mirror the live cell grid; the focused view owns the PTY geometry so
two views never fight over one size. :terminal-stop stops and drains;
:terminal-paste/:terminal-paste-cancel hold multiline or
control-character pastes for explicit consent; :qa refuses live sessions
and :qa! stops them.
Privacy and replay: terminal content is excluded from traces even with
--log-content; entering a terminal records an opaque marker and downgrades
the capture to metadata. --log-terminal-content is the separate explicit
consent, visibly marked REC. Full --replay reproduces terminal sessions
with no PTY, process, clipboard or network effects.
Source builds compile the Ghostty VT core and require an explicitly installed Zig 0.16.0; prebuilt binaries need neither. See the terminal plan for the delivered TUI milestones and the deferred GUI work.
Static syntax coverage
23 detected languages: Rust, Python, JavaScript, TypeScript, TSX, Go, C, C++, JSON, Bash, Fish, Lua, SQL, CMake, Markdown, Java, C#, Ruby, PHP, TOML, YAML, HTML and CSS. Grammars and licensed highlight queries ship in the binary; no grammar downloads or language server are required.
Markdown includes a separate inline parser, headings, emphasis, links, tables and recognized fenced languages. HTML embeds JavaScript and CSS. Syntax and structural indentation guides are revision-owned worker results; stale results cannot paint newly edited text. Long-line layout uses sparse grapheme checkpoints.
Headless scripts
strop --headless SCRIPT [+LINE] [FILE[:LINE]]
--script is equivalent. strop --help lists every directive:
buffer, keys, key, paste,
resize, frame, state, settle,
wait and quit-intent. JSON strings use double quotes;
keys takes unquoted text and tokens such as <esc>
and <cr>.
keys :e source.rs<cr>
settle 5000
keys /needle<cr>
frame
state
settle [MS] drains finite owned jobs, defaults to 120,000 ms and
fails on timeout. wait MS deliberately waits. Scripts retain ordered
grammar typeahead; explicitly settle before relying on filesystem/service results.
Headless :trust uses the same per-project/endpoint consent and private
state store as the terminal UI. It never gives project configuration implicit
permission to execute commands or automatically restores editor sessions.
keybindings
In the editor: :help (or Space ?) opens the full table as a real,
searchable buffer — / searches it, q closes. Dispatch and the table are pinned
together by tests (a key that dispatches without a row, or a live row that no-ops, fails the
build) — drift is a test failure, not a doc bug. Planned-but-unshipped keys render muted with a
(soon) mark. The full table also exists as
docs/vim-compat.md,
generated from the same source (strop --dump-compat) and pinned fresh by the gate.
sessions
Per-project sessions restore local buffers and undo history across restarts. Remote snapshots are excluded: reopening them is an explicit network action.