Skip to content

Help width control + retire RenderHelp single-entry onto the model (#153/#164/#154) - #385

Merged
helly25 merged 11 commits into
mainfrom
feat/help-width-flag
Aug 7, 2026
Merged

Help width control + retire RenderHelp single-entry onto the model (#153/#164/#154)#385
helly25 merged 11 commits into
mainfrom
feat/help-width-flag

Conversation

@helly25

@helly25 helly25 commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Adds --width, wraps and consistently indents the model-rendered help, and moves single-entry --help + --help=full off the imperative RenderHelp onto the help model.

What's in it

  • --width[=auto|none|COLS] (help_width lib): auto = terminal width if known (TTY / $COLUMNS), else unwrapped; none/0 = no wrap; N = fixed. Any positive width clamps up to a 40-column minimum. Pure resolver + unit tests; terminal detection at the CLI boundary.
  • Render context on the abstract HelpBackend base (HelpRenderContext { width, color }), held as a constant so every backend + the walk read one shared context. color is plumbed (ANSI emission is a follow-up).
  • Indent-aware wrap engine in its own wrap lib (merged as Add indent-aware word-wrap engine to the plain help backend (#153/#164) #384); flowing text (prose, bullets, entry summary/detail, see-also, aligned-row descriptions) wraps to the width.
  • Consistent nesting indentation: Begin*/End* adjust a single depth, so every section / subsection / entry indents its body - the hierarchy is visible in text mode.
  • Model enrichment: flag/primary entries carry classification tags ((global, xff|find) / (kind, xff|find, [safety])), the not-built note, and Affects/Affected-by influence - unified into the shared model, so --man / --markdown / XFF.md gain them too (XFF.md regenerated, drift guard green).
  • Single-entry + --help=full render from the model (EntryReference / BuildReference via PlainTextBackend(context)), so --help=--width, --help=-regex, --help=full wrap + indent. RenderHelp's single-entry branch is retired.
  • Structured Examples: the cookbook recipes are the shared SOT; each recipe is a heading + verbatim command Example + wrapping Prose note (retires the one preformatted blob; engine: accept BSD -perm +mode (octal any-of) #152). Also fixed a segfault this introduced (a duplicate Recipe type / internal-linkage accessor).

Still imperative (follow-up, finishes the retirement)

Bare --help, --help=list, and the config/stats + printf/time/size/grammars topics still render via RenderHelp/kHelpText/RenderDocRows - they don't wrap yet. Next PR ports bare --help (entry-style) + the index + those topics, then deletes RenderHelp/kHelpText.

Tests

Lean //xff/... 85 pass (+5 xff_full-only skips); --config=xff_full 90 pass (incl. the XFF.md drift guard). CI-gated.

helly25 added 11 commits August 6, 2026 22:55
New xff/cli/help_width lib: ResolveHelpWidth (pure, tested) maps the --width
value + detected terminal width to a wrap column; DetectTerminalWidth reads
$COLUMNS / the tty winsize. --width[=auto|none|COLS] is registered in globals
(auto = terminal width else 80; none/0 = no wrap; N = fixed). main resolves it
once and threads it into the model-rendered help topics (--help=fields, etc.)
via PlainTextBackend(width).

NOTE: bare --help and --help=FLAG still render through the imperative RenderHelp
path, which ignores --width; those wrap once that path is ported onto the help
model (the remaining help-epic piece).
Introduce HelpRenderContext { width, color } held by the abstract HelpBackend
base as a constant, so every backend and the walk read one shared context via
Context() - not a bare width param on PlainTextBackend. main resolves it once
at the CLI boundary and threads a HelpRenderContext through the model-rendered
topics. color is plumbed (resolved later; ANSI emission is a follow-up).

Also make EmitRows wrap the description column with a hanging indent under the
description column when a width is set (the aligned vocabulary tables were the
one flowing element that did not wrap); width 0 keeps the verbatim table.

Supersedes the bare-width wiring from the previous commit. Groundwork for
retiring the imperative RenderHelp onto the model.
The model Entry gains a tags vector (parenthesized classification tokens, e.g.
{global, xff} for a flag). All three backends render it as "(a, b, c)" after
the term, falling back to the bare xff marker when tags is empty - so output is
byte-identical until BuildReference populates tags. Groundwork for retiring
RenderHelp: the (global, xff|find) flag tag and the primary kind/style/safety
tags move into the model so every renderer shows them uniformly.
BuildReference now builds flags via FlagEntry and primaries via PrimaryEntry,
populating Entry.tags ((global, xff|find) / (kind, xff|find, [safety])), a
"not built into this binary" note when a flag's build extra is absent, and the
Affects / Affected-by influence detail lines (ported from help.cc's AffectsList
/ AffectedBy). Since BuildReference is the one SOT for --man / --markdown /
XFF.md, all three gain these uniformly (decision A); XFF.md regenerated and the
two tag unit tests updated. Groundwork for routing --help onto the model.
…idth)

RenderTopic now resolves a single primary/flag via the new EntryReference (the
same model entry BuildReference folds in) and renders it through
PlainTextBackend(context), so --help=--width, --help=-regex, etc. wrap. --help=full
renders the whole BuildReference() Document plain (the --man/--markdown content in
text). RenderHelp's single-entry branch is retired (returns NotFound); RenderTopic
runs RenderHelp's special topics first (so --help=config beats the --config flag),
then EntryReference. help_render_test's single-entry cases move to a RenderEntry
helper over the model.

Also: --width default is now no-wrap when the terminal width is unknown (a pipe /
file stays full-width and byte-stable; a real terminal still wraps), reverting the
80-column fallback. A title-less section renders headless (the single-entry doc).
…blob)

The cookbook recipes are now the shared SOT (CookbookRecipes() in help.h): the
Examples section is built as structured model nodes - each recipe a subsection
with its command as a verbatim Example and its explanation as wrapping Prose -
instead of one preformatted RenderHelp("cookbook") blob. So the explanations
wrap to --width and --man/--markdown get real headings + fenced commands (#152).
The plain backend now indents subsection body content (an example's command and
note sit under their recipe heading).

Fixes a segfault: the recipe struct/accessor were duplicated (an anon-namespace
Recipe shadowed the header type and CookbookRecipes had internal linkage), so
BuildExamples called an undefined public symbol. Recipe + CookbookRecipes are
now the single public definitions. XFF.md regenerated; help_topic_test updated.
The plain backend now tracks one nesting depth: every Begin* (section, subsection,
entry) indents its body two spaces and the matching End* dedents, so a heading
visibly owns the content beneath it (EXIT STATUS / SEE ALSO / Examples bodies, an
option under its group, an entry's summary/detail under its term) instead of
everything sitting flush-left. Rows, bullets, examples, prose and see-also all
render at the current body indent.

--width also clamps any positive width up to a 40-column minimum (below that,
wrapping degenerates); 0 / none (no wrap) is exempt. The doc-render golden is
updated; markdown / roff are unaffected (XFF.md unchanged).
The sub-vocabulary topics now render through TopicReference + PlainTextBackend
(the same VocabSection BuildReference folds into the full reference, factored into
named builders so they cannot drift), so they wrap to --width and pick up the
consistent nesting indentation. Retires the imperative RenderPrintfDocs /
RenderTimeDocs / RenderSizeDocs / RenderRegexDocs / RenderFieldsDocs (and drops
the now-unused datetime dep from the cli binary).
BuildUsage() renders the description, the whole-run options and expression
primaries as summary-only entries (no detail blocks), and the meta/doc flags +
--help=TOPIC index - all wrapping + indenting via the render context, replacing
the imperative kHelpText / kHelpTextExpression / RenderOptions path. The Options
and Expression section builders are factored so BuildReference and BuildUsage
share one definition (a with_details flag selects the terse vs full form). The
not-built-extra note shows even on the usage page. help_test / extras_test
updated to the model output.
IndexReference routes the index topics through the shared section builders:
list = the usage page, all = every option + primary (summaries), expressions =
the primaries (summaries) without the global flags. They wrap + indent via the
context instead of the imperative RenderIndex / RenderFull / RenderExpressions.
@helly25
helly25 merged commit 8207288 into main Aug 7, 2026
9 checks passed
@helly25
helly25 deleted the feat/help-width-flag branch August 7, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant