Implementation: display/more.rs (3028 lines) + display/tests/more/mod.rs (1265 lines)
Spec: POSIX.1-2024 (IEEE Std 1003.1-2024), Vol. 3 §3, pp. 3226–3238
Reference slice: ~/tmp/posix.2024/sliced/xcu-shell-and-utilities/3-utilities/more.md
Date: 2026-06-02
more implements most spec surface area correctly — all eight options exist, all interactive commands are present, BRE search is properly wired through plib::regex, MORE-env precedence is honored. But several architectural requirements are not met: commands are read from stdin (not stderr//dev/tty); the prompt is written to stdout (not stderr); no SIGCONT or SIGWINCH handlers are registered (window changes detected only by polling); the implicit-stdin path truncates to one line (so cat foo | more shows only the first line of foo); and :n/:p errors call exit() instead of trying the next/previous file per CONSEQUENCES OF ERRORS.
- #1 — Implicit-stdin path reads only one line. ✓ fixed: implicit-stdin path now uses
read_to_string(same as the--operand path); also fixed a latent off-by-one inprint_all_input's buffer-source loop that was masked by the truncation. New regression testtest_0_files_multiline_stdin_not_truncated. Note: an intermediate version of this fix openedCommandIO(raw mode) before reading stdin, which causedmorewith no args + tty stdin to hang in raw mode with no Ctrl-D escape —MoreControl::newnow slurps stdin first and engages raw mode only after. - #2 — User commands read from stdin, not stderr /
/dev/tty. ✓ fixed: newCommandIOstruct opens stderr (if it is a readable terminal) or falls back to/dev/ttyO_RDWR|O_NOCTTY, withMoreError::NoCommandSourceand non-zero exit if neither is usable. Directlibc::tcgetattr/cfmakeraw/tcsetattr; cooked-mode restored inDrop. Input thread now reads from the chosen channel.
- #3 — No SIGCONT handler. ✓ fixed: libc-only signal subsystem (no new crate dep).
handle_contre-applies the saved raw-mode termios (in case we were SIGTSTP'd back into cooked), setsSIGCONT_PENDING, and re-installs the SIGTSTP handler. Main loop processes the flag with an unconditionalresize() + refresh()per POSIX Defect 1185. - #4 — SIGWINCH detected only by polling, not by signal. ✓ fixed:
handle_winchsetsSIGWINCH_PENDING; main loop drains the flag and callsresize(). The per-tick pollingself.resize()inget_input_with_updateis gone; the only remaining sleep is the 80 ms tick that bounds how soon a signal flag is noticed. (POSIX winsize refetch via termion'sterminal_size(), which wraps theTIOCGWINSZioctl — equivalent totcgetwinsize.) - #5 — Prompt written to
self.tty(stdout) instead of stderr. ✓ fixed:Terminalnow owns aprompt_out: Box<dyn Write + Send>separate fromtty(stdout). Set toCommandIO::writer(stderr//dev/tty) in interactive mode; stdout in test mode;io::sink()in filter mode. Content rendering still goes to stdout. - #6 — Prompt missing current filename. ✓ fixed:
Prompt::MoreandPrompt::Eofare now structs carrying anOption<String>filename plumbed fromMoreControl::current_filename(). Renders asfoo.txt: -- More --(NN%)when known; degrades to the historical form for stdin sources. - #7 —
:n/:pfile-open errors callexit()instead of trying next/prev with non-zero exit.more.rs:2484(handle_error),2420-2435. Fix: catchFileReadin:n/:phandlers, sethad_error, advance/retreat, exit code at end. - #8 —
:efilename not shell-word-expanded (XCU 2.6).more.rs:2210-2251. Fix: runwordexp(3)(libc) on the filename, fail if it yields ≠1 path. - #9 —
''(return-to-previous) doesn't track "large movement". Only resets on file open.more.rs:2382-2385, 1340. Fix: savelast_linebefore any command that moves more than one screenful.
- #10 —
vcommand uses+Ninstead of POSIX-mandated-c Nfor vi/ex.more.rs:1916. Fix: emit["-c", &N.to_string(), "--", file_path]. - #11 —
veditor-name check iseditor == "vi"— fails for/usr/bin/vi,vim, etc.more.rs:1907. Fix: comparePath::new(&editor).file_name()against"vi"/"ex". - #12 — Env-var precedence on resize ignored. Termion's
terminal_size()always wins, even whenLINES/COLUMNSenv is set.more.rs:1532-1542. Fix: re-check env vars insideTerminal::resize()and prefer them when set. - #13 — Undocumented
--testflag exposed in clap surface.more.rs:106-112. Fix:#[arg(hide = true)]or move behind a build-only feature gate. - #14 —
MoreErrorstrings hardcoded English. Only clap help strings are gettext'd.more.rs:198-246. Fix: wrap ingettext!(). - #15 —
:tshells out tofind+grep, treating tagstring as a regex pattern.more.rs:1950-1983. Fix: use literal-string match (orgrep -F), escape tagstring.
-
-cPARTIAL. Flag parsed (line 43);print_overfield never consulted in render path — clear-on-first-screen and per-line redraw not observable. -
-eCONFORMS (line 2148). -
-iCONFORMS —RegexFlags::bre().ignore_case()at line 1649. -
-nCONFORMS — overrides LINES/COLUMNS at line 1538. -
-pPARTIAL. Commands run on new-file display (lines 2501–2528). Spec requires that if any-pcommand fails, an informational message is written AND remaining-pcommands for that file are suppressed — current code mayexit()on error. -
-sCONFORMS — squeeze propagates viaSeekPositions. -
-tPARTIAL — see:tnotes (literal vs regex tag matching). -
-uCONFORMS —plainflag suppresses backspace/underscore/bold processing. -
+as option prefix MISSING. Spec allows but doesn't require; clap doesn't recognize it. (Lowest priority — optional.)
- No file operands → stdin ✓ fixed (same as Critical #1).
-
-operand routes to stdin CONFORMS (line 1698 path). - Mixed
-and file operands CONFORMS. - Multiple files CONFORMS.
-
COLUMNSPARTIAL. Read once at init (line 1394); not re-honored after resize. -
LINESPARTIAL. Same — env var not re-checked on SIGWINCH. -
MORECONFORMS — prepended before CLI args (lines 2983–3003); CLI wins on conflict. -
EDITORCONFORMS — line 1901, falls back tovi. -
TERMMISSING (via termion). Termion reads internally; we don't validate. -
LANGMISSING. OnlyLC_ALLset viasetlocaleat line 3006. -
LC_COLLATEMISSING. -
LC_CTYPEMISSING. -
LC_MESSAGESMISSING. -
NLSPATH(XSI) MISSING — optional XSI, acceptable; track anyway.
- SIGCONT ✓ fixed (same as Major #3).
- SIGWINCH as signal ✓ fixed (same as Major #4).
-
tcgetwinsize-equivalent on resize PARTIAL — termion ioctl called, env-var precedence ignored (same as Minor #12).
- Content to stdout (non-tty) CONFORMS —
print_all_inputlines 1763–1829. - Content rendering (tty mode) DIVERGES from "write to stdout" model — uses termion alternate screen on stdout. (Design-level; may be acceptable but worth confirming.)
- Prompt to stderr ✓ fixed (same as Major #5).
- Prompt contains filename ✓ fixed (same as Major #6).
- EOF prompt contains next file CONFORMS —
Prompt::Eofline 1622.
All 28 spec commands are wired (count-prefix supported on every command that takes one). Issues found:
-
[count]sPARTIAL. Implemented as plain scroll; spec requires "screenful beginning with linecountlines after last line on current screen". -
''DIVERGES.last_lineonly reset on file open (line 1340); never updated on "large movements" (>1 screenful) — same as Major #9. -
:e filenamePARTIAL. No shell word expansion; no non-seekable check — same as Major #8. -
:n/:pPARTIAL. Errors callexit()not next/prev-with-error — same as Major #7. -
:t tagstringPARTIAL. Tag treated as regex viagrep; spec means literal name — same as Minor #15. -
vuses+Nnot-c N— same as Minor #10. -
veditor name compared as exact string — same as Minor #11. -
=uses basename instead of full pathname for files. Spec allows omitting byte info for stdin (that case works). -
hhelp text links to a POSIX 2018 URL (line 2967), not 2024.
- Line folding CONFORMS for ASCII.
- Multi-column-character splitting at column boundary is unspecified by POSIX; implementation behavior on a wide character straddling the column limit is undefined. Track for hardening.
- Backspace/underscore/embolden (when
-uabsent) PARTIAL. The 3-byte pattern match incontinious_styled_parse/last_styled_parse(lines 743–848) doesn't implement the full POSIX sequences (char + n*BS + n*'_'for underline;char + n*BS + charfor bold) for n > 1. -
\rat EOL ignored PARTIAL.\rparticipates inline_lenrather than being discarded before\n. - Non-printable display PARTIAL. Line parse breaks on first non-
\x08control character (lines 673–675), truncating the line rather than displaying it inex printnotation.
- Exit
0/>0CONFORMS (line 2206). -
:eerror affects exit code when it should not. File state preserved, but exit code is touched. -
:n/:perror → DIVERGES (same as Major #7).
Tests cover -c, -e, -i, -n, -s, -u and most basic interactive commands.
Not covered (each gap is a "write a test" task tied to fixing the corresponding bug):
-
''large-movement semantics -
:eshell expansion -
v-cflag emission for vi/ex -
:n/:perror-path behavior (next file tried, exit code affected) - Prompt target (stdout vs stderr) — verified manually via
more file >/tmp/out 2>/tmp/err </dev/null; tracked as audit checkbox closed by Major #5 fix. (No automated test added; would need a PTY harness.) - SIGCONT behavior (resume from
kill -STOP $$; kill -CONT $$) — handler installed; covered by code path, no automated test (needs PTY harness). - SIGWINCH behavior (size change while paused at prompt) — handler installed; covered by code path, no automated test.
- Implicit-stdin (
echo -e 'a\nb\nc' | more) — would catch Critical #1 (covered bytest_0_files_multiline_stdin_not_truncatedandtest_0_files_empty_stdin) -
-pcommand-failure suppression of remaining-pcommands
- PR A — "POSIX I/O channels": Critical #1, #2 + Major #5, #6
- PR B — "Signal handling + resize precedence": Major #3, #4 + Minor #12
- PR C — "Command-mode conformance": Major #7, #8, #9 + Minor #10, #11
- PR D — "Rendering correctness": backspace/embolden sequences,
\rhandling, non-printable display,-credraw - PR E — "i18n + cleanup": Minor #13, #14 + locale env var coverage