← XeFM crftwr/xefm on GitHub · craftware

External Terminal — Implementation

Desktop mode’s answer to “run this in a terminal” (discussion #472). User documentation: EXTERNAL_TERMINAL_FEATURE.md.

Shape

One launcher, two callers:

subshell (Shift-X)          ─┐
PROGRAMS terminal: True     ─┼─► desktop:  XeFMApp._open_terminal → external_terminal
ctx.run_program(terminal=…) ─┘   terminal: XeFMApp._run_in_terminal (unchanged)

Both callers build the command, directory and environment exactly as before and branch on is_desktop_mode() only at the point of delivery. The subshell passes no pause_on_error; a program passes pause_on_error=True, as it does to _run_in_terminal. The desktop path returns None at once — there is no exit code to wait for — which is what ctx.run_program documents.

subshell left _BACKEND_GATED, and the Tools ▸ Subshell Here item lost its enabled predicate: both frontends can perform it now.

Why an external terminal

An embedded terminal (pty + VT parser in a PuiKit pane) is the only design that would make desktop mode behave identically to the TUI, and it commits XeFM to ConPTY, IME composition, East Asian width disagreements between shell and renderer, and an open-ended escape-sequence tail — all solved better by the terminal the user already chose. XeFM owns only which terminal, which directory, which environment.

xefm/external_terminal.py

The wrapper

A terminal application does not reliably pass its caller’s environment on: Terminal.app starts a fresh login shell, and wt.exe may hand the request to an already-running Windows Terminal whose environment is its own. So the command appended to TERMINAL is never the program itself but a generated script that sets the variables, cds, and then runs it. This makes delivery independent of each terminal’s behavior rather than something to verify per terminal.

Both remove themselves first thing. A wrapper whose terminal never ran it is left behind in the temp directory; that is the only leak, and it is small.

Still to verify by hand

These are properties of real terminals and packaging, not of the code above:

Out of scope

Tests