From 21edbe9a45fe1daa04e23b1635391c2640f42b85 Mon Sep 17 00:00:00 2001 From: robkam Date: Tue, 4 Aug 2026 23:27:17 +0100 Subject: [PATCH] feat(help): route contextual command rows to shared explainers --- .agent/handoffs/task-43.4.txt | 32 ++++++ docs/ROADMAP.md | 9 +- docs/SPECIFICATION.md | 1 + docs/USAGE.md | 22 ++++ etc/help/f1.de.md | 64 +++++++++-- etc/help/f1.en.md | 186 +++++++++++++++++++++++-------- etc/help/man.en.md | 60 +++++++++- etc/ytnova.1.md | 22 ++++ scripts/generate_help_assets.py | 2 + src/core/generated_help_topics.h | 142 ++++++++++++++++++----- src/ui/runtime_help.c | 76 ++++++++++++- tests/test_help_generator.py | 3 + tests/test_help_source_schema.py | 2 + tests/test_help_text_contract.py | 142 +++++++++++------------ 14 files changed, 593 insertions(+), 170 deletions(-) create mode 100644 .agent/handoffs/task-43.4.txt diff --git a/.agent/handoffs/task-43.4.txt b/.agent/handoffs/task-43.4.txt new file mode 100644 index 00000000..9c6edf26 --- /dev/null +++ b/.agent/handoffs/task-43.4.txt @@ -0,0 +1,32 @@ +Title: Split Cross-Cutting Operator Semantics from Local Context Pages +Acceptance target: Contextual F1 pages stay task-local, shared operator semantics are reachable through real runtime follow paths, help-popup navigation is distinct from runtime navigation, and focused help regressions validate the final open/follow/back/close hint-line workflow. +Completion objective: Make Task 43.4 true end-to-end across authored help sources, generated runtime topic data, help-popup/runtime follow behavior, and focused help tests, while including the already-edited roadmap/spec source-of-truth updates in the same commit. + +Chosen work family: Runtime contextual-row follow ownership + shared-topic cleanup + focused help workflow tests. +Deferred families: None. Task 43.2 and Idea FE-7 remain explicitly out of scope for this mission. + +Inventory +- docs/ROADMAP.md — source-of-truth roadmap wording already edited locally; include in this commit. [addressed] +- docs/SPECIFICATION.md — source-of-truth contract wording already edited locally; include in this commit. [addressed] +- etc/help/f1.en.md — authored contextual/runtime help ownership, shared-topic routing, local-page slimming. [addressed] +- etc/help/f1.de.md — topic inventory/schema parity for shared-topic additions. [addressed] +- etc/help/man.en.md — topic inventory/schema parity for generated usage/man outputs. [addressed] +- docs/USAGE.md — generated from man help source; regenerated for parity. [addressed] +- etc/ytnova.1.md — generated from man help source; regenerated for parity. [addressed] +- scripts/generate_help_assets.py — support-topic ordering updated for new shared topics. [addressed] +- src/core/generated_help_topics.h — regenerated runtime topic data. [addressed] +- src/ui/runtime_help.c — contextual row follow wiring, shared-topic ownership lookup, footer hint/navigation behavior. [addressed] +- src/ui/help_popup.c — existing footer rendering/viewport behavior already satisfied the contract; no code change required. [intentionally unchanged] +- tests/test_help_source_schema.py — shared-topic inventory/schema parity. [addressed] +- tests/test_help_generator.py — generator/runtime header expectations for new shared topics. [addressed] +- tests/test_help_text_contract.py — focused live help popup workflow/discoverability coverage updated for shared-topic follow semantics and new hint-line behavior. [addressed] + +Validation +- `make help-assets` +- `make clean && make` +- `source .venv/bin/activate && pytest -q tests/test_help_source_schema.py tests/test_help_generator.py tests/test_help_text_contract.py` + +Result +- Shared operator semantics now live in shared topics with real follow paths from contextual command rows. +- Contextual pages keep task-local key ownership while shared topics carry cross-cutting destination/jump semantics. +- Focused help tests now validate follow/back behavior without preserving the pre-43.4 inline-detail assumptions. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 75bcef61..baa9529a 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -851,6 +851,7 @@ Ordering policy (for all editors, including AI editors): * **Goal:** Define and implement distinct theme-role behavior for the contextual `F1` reading surface and the always-visible footer guidance surface now that the base role-based theme system exists. * **Rationale:** Task 60 established the general theme architecture, but it intentionally left `help` overloaded across the footer and the `F1` reading surface. Contextual help now needs its own follow-on theming pass so the reading surface, linked text, and active linked target remain readable, restrained, and consistent across bundled themes while the footer keeps an independently tunable low-noise scheme. * **Theme Contract:** All footer and `F1` visual styling belongs in `etc/ytnova.themes` / runtime theme data, not `ytnova.conf`. Reserve `help` for the `F1` reading surface, introduce a dedicated `footer` role for the footer keybinding strip, and allow narrower roles such as `help_link` and `help_link_selection` if hyperlink help needs them. Prompt/dialog surfaces remain separate concerns unless a later task explicitly gives them their own theme role. +* **Scope Limit:** This task owns only surface-level/help-UI roles such as body text, footer strip, frame lines, link text, active link emphasis, and help-popup mnemonic emphasis. It does **not** own optional semantic per-span styling inside help prose; richer intra-text emphasis such as headings, commands, notes, or warnings is deferred to Idea FE-7 after the content/IA settles. * **Orthodox Default Direction:** Keep the help page readable and quiet on the orthodox-blue theme: black-on-grey body text, black-on-cyan linked text, and yellow-on-cyan active-link emphasis are acceptable; ordinary body text must remain easier to read than navigation chrome. * The footer should remain concise and lower-noise than modal help while still allowing its own color treatment. * **Acceptance Criteria:** @@ -888,7 +889,9 @@ Ordering policy (for all editors, including AI editors): * **Goal:** Make contextual `F1` pages answer the immediate screen/prompt question first, while shared operator semantics live in dedicated explainer topics that can be reused across multiple surfaces. * **Rationale:** The current failure mode is not just missing prose; it is information-architecture blur. When local command summaries, shared rules, help-popup navigation, and reference-detail all compete on the same page, `F1` feels noisy and unreliable even when the facts are present. Users need a crisp distinction between "what this screen does now," "how this repeated ytnova feature family works in general," and "where the full reference lives." * **Comprehensiveness Contract:** A user pressing `F1` on any supported surface must be able to reach an answer for the active question without being stranded by omissions. If the first contextual page is intentionally short, it must still provide an obvious path to the owning shared topic or command explainer rather than silently assuming outside knowledge. +* **Unix Documentation Principle:** ytnova should follow the Unix split between short in-app help and fuller external reference. `F1` is the contextual task-local path, while exhaustive semantics/configuration detail remain the job of the manpage and generated usage/reference docs. Centralized authored help sources are acceptable for maintenance, but the runtime popup must still present only small contextual slices rather than a giant manual. * **Scope Contract:** Treat repeated operator rules as first-class shared topics rather than smearing partial explanations across directory/file/archive/F7/F8/Showall/Global/prompt pages. The shared inventory must be broad enough to cover the recurrent question families users actually ask, including filters, jump/list-jump behavior, wildcard and rename-pattern rules, search/fuzzy-matching semantics, command-line editing, `VI_KEYS=1`, theming/customization, tagged-set semantics, compare/output syntax families, and operator-facing tips/tricks where they truly generalize. +* **Implementation Reality Contract:** This task is not text-only. It owns the runtime/help-popup behavior needed to make the authored IA real: contextual command rows must be able to open their owning shared topic or command explainer instead of trapping the user in local inline detail only, and the focused regression tests must be updated to validate the intended workflow rather than preserving the older limited model by accident. * **Navigation Taxonomy Contract:** Explicitly separate **help-popup navigation** (`Up`/`Down`, `PgUp`/`PgDn`, `Home`/`End`, `Enter`/`Right`, `Left`, `Esc`/`Q`) from **runtime ytnova navigation** (tree/file movement, jump, prompts, split movement, preview movement, tagged flows, etc.). `F1` must not blur those two domains together on the same first-screen explanation. * **Page-Shape Contract:** Each contextual page should answer, in order: where the user is, the main actions available here, the few caveats/traps specific to this surface, and which shared topic explains the deeper rule. Shared topics may then teach the reusable semantics once, with concise examples, without forcing every local page to repeat them. * **Hint-Line Contract:** The help-popup hint line may expand beyond the current minimal trio when needed to keep the shared-topic structure discoverable, but it must remain low-noise and semantically honest. If Back, Contents, Navigation, list/detail switching, or other cross-topic movement is part of the intended help workflow, the hint line must advertise enough of that workflow that the user is not expected to guess it. @@ -902,6 +905,9 @@ Ordering policy (for all editors, including AI editors): * When tags change a command's behavior, the local command page explains that tagged variant inline for the current surface instead of relying on the shared Tagged topic as the only explanation path. * The shared-topic inventory is reviewed as an explicit user-question matrix rather than as an author-memory list, so missing recurrent questions are tracked as coverage defects. * The help-popup hint line is validated against the final IA: it remains concise, but it is expanded where necessary so the new structure is discoverable without hidden gestures. +* Contextual command rows that need deeper explanation have a real follow path at runtime to the owning shared topic or command explainer; authored `topic:` links are not allowed to exist only on paper while the popup remains trapped in local inline detail. +* Shared semantics with multiple current owners are reconciled to one owning topic family. At minimum, split-model rules, wildcard/rename-target rules, jump/list-jump semantics, and search/filter semantics are not maintained independently across every local mode page. +* Regression tests stop hard-coding the pre-43.4 discoverability model. Focused help tests must validate the chosen open/follow/back/close workflow and the final hint-line affordances instead of asserting the old minimal trio when the new IA requires more explicit cues. * Any "tips and tricks" guidance admitted into `F1` is curated as reusable operator guidance rather than leaking one-off editorial advice into arbitrary local pages. * - [ ] **Status:** Not Started. @@ -1558,12 +1564,13 @@ Ordering policy (for all editors, including AI editors): ### **Idea FE-7: Semantic F1 Help Styling Without Theme Bloat** * **Goal:** Allow authored `F1` help topics to request a small bounded set of semantic text styles while keeping `ytnova.themes` compact and stable. +* **Dependency/Sequencing Note:** Evaluate this only after Task 43.4 has stabilized the help information architecture/content shape and Task 43.2 has settled the base help/footer surface-role contract. FE-7 is a follow-on enhancement for proven emphasis needs, not a prerequisite for making `F1` useful. * **Design Direction:** * Use semantic markup roles in `etc/help/f1.en.md` rather than raw color/attribute requests. * Initial role set should stay intentionally small (for example `help_text`, `help_key`, `help_code`, `help_heading`, `help_note`, `help_warning`). * `ytnova.themes` maps those semantic roles to ncurses-supported attributes/colors. * Missing theme entries must fall back deterministically to the normal help-text style. -* **Non-Goal:** Do not allow arbitrary per-span foreground/background pairs or unlimited raw `bold`/`inverse`/`underline` directives directly in help source; that would balloon theme surface area and couple authored help text to presentation internals. +* **Non-Goal:** Do not allow arbitrary per-span foreground/background pairs or unlimited raw `bold`/`inverse`/`underline` directives directly in help source; that would balloon theme surface area and couple authored help text to presentation internals. FE-7 does not replace Task 43.2's surface-role ownership; it only adds a bounded semantic layer within already-settled help surfaces. * **Rationale:** Users may want richer help emphasis, but the safe path is a bounded semantic layer so help authors describe meaning and the theme decides appearance. * - [ ] **Status:** Not Started. diff --git a/docs/SPECIFICATION.md b/docs/SPECIFICATION.md index 3930c97c..d5b6d743 100644 --- a/docs/SPECIFICATION.md +++ b/docs/SPECIFICATION.md @@ -348,6 +348,7 @@ A bordered pop-up box that overlays the center of the screen, used for: ### 6.4 Context Help Contract (Footer <-> F1) * **Parity Rule:** For any active context, commands shown in the footer keybinding hints MUST appear in that context's F1 help set. Missing footer commands in F1 are defects. * **Concision Rule:** F1 content is concise, contextual, and beginner-friendly. The help popup teaches the active surface in plain English; terse reference-style detail belongs in the manpage source and generated `docs/USAGE.md`. +* **Unix Documentation Layering Rule:** ytnova follows the Unix split between in-app help and full reference. `F1` is the short contextual path for the active task, `--help`/usage stays terse, and the manpage/reference docs own exhaustive behavior and configuration detail. A single maintained authored help corpus is allowed internally, but runtime `F1` MUST render only small contextual slices rather than presenting one giant browsing document. * **Answerability Rule:** Pressing `F1` on any covered surface MUST leave the user with an answer path. The answer may be present on the opening contextual page or on one clearly signposted help-popup link hop to the owning explainer/shared topic, but the user must not be stranded by omitted semantics or hidden structure. * **Surface Naming Rule:** Keep the four help surfaces distinct in docs, tests, and code comments: the always-visible bottom-of-screen strip is the **footer command strip**; the modal opened by `F1` is the **help popup**; the minimal action row embedded in that popup is the **help-popup hint line**; and any command/topic entry that exists specifically to branch into deeper explanation is a **help popup link**. * **Help-Family Rule:** Keep three authored help families distinct. **Contextual pages** belong to one active runtime surface (mode, prompt, dialog, picker, preview, split, or future equivalent). **Command explainers** belong to one command or concept family such as Copy, Filter, Compare, Output, Jump, or wildcard rename semantics. **Shared topics** belong to cross-cutting behavior such as help navigation, tagged workflow, command-line editing, VI keys, theming/customization, search semantics, or command catalogs. diff --git a/docs/USAGE.md b/docs/USAGE.md index 488576a0..02182e19 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -371,6 +371,28 @@ Then choose the final destination. * **Up**: Open or cycle prompt history when that prompt keeps history. * **F2**: Open a browser or picker when the current prompt supports browsing. * **F1**: Show syntax or scope rules that matter only to the current prompt. +### Copy/Move Targets + +#### Target forms +Use a directory path when you want the original names preserved under another directory. +Use one full replacement name when you want one selected item to land under a new explicit name. +Use a wildcard pattern such as `*.bak` or `copy-*` when you want ytnova to rewrite each selected basename by pattern. + +#### Shared rules +Tagged copy/move uses the same target syntax as single-item copy/move. +Split mode may seed the inactive-panel directory as the default target, but you can still replace that default before the operation starts. +Archive-backed copy/move keeps the same destination model even when extraction or archive-aware paths are involved. +### List Jump + +#### Jump model +`/` opens an incremental jump prompt for the current visible list only. +Tree/directory views jump among visible directory names, while file-oriented views jump among the visible file rows for that surface. + +#### Acceptance and cancel +* **Type text**: Move immediately to the best current match as you type. +* **Enter**: Keep the current match and stay there. +* **Esc**: Cancel the jump and restore the original selection. +* **Scope changes**: Filtering, Showall/Global projection, archives, and split mode all change which visible list `/` searches, but they do not change the jump keys themselves. ### VI Keys #### Navigation remap diff --git a/etc/help/f1.de.md b/etc/help/f1.de.md index 43b0673f..5f039b20 100644 --- a/etc/help/f1.de.md +++ b/etc/help/f1.de.md @@ -52,7 +52,8 @@ Kurze lokale Seiten halten die erste Antwort im Blick. Gemeinsame Themen tragen * **Archive File Help**: Lerne, was sich in einer Archiv-Dateiliste ändert. * **Showall** und **Global**: Lerne die zusammengefassten Datei-Listen-Modi. * **F7 Preview** und **F8 Split**: Lerne Overlay-spezifische Steuerung und Besonderheiten. -* **Filter**, **Compare** und **Output**: Lerne die befehlsfamilien mit vielen Optionen. +* **List Jump** und **Copy/Move Targets**: Lerne das gemeinsame `/`-Sprungmodell sowie Ziel- und Wildcard-Umbenennungsregeln. +* **Filter**, **Compare** und **Output**: Lerne die Befehlsfamilien mit vielen Optionen. * **Command-line Editing**, **VI Keys**, **F2-Auswahl**, **F10-Konfiguration** und **Theming**: Lerne die gemeinsamen Bedienregeln. ## topic:navigation @@ -81,12 +82,35 @@ Benutze `Left` zum Zurückgehen und `Esc` oder `Q` zum Schließen. * **Left**: Einen Schritt zurückgehen. * **Esc/Quit**: Das Popup schließen. -#### Gemeinsame Listenbewegung -* **Up/Down**: Die aktive Auswahl bewegen. -* **Page Up/Page Down**: Seitenweise bewegen. -* **Home/End**: Zum Anfang oder Ende der aktuellen Liste springen. -* **Enter**: Die aktuelle Zeile übernehmen oder zwischen gekoppelten Ansichten wechseln, wenn diese Oberfläche `Enter` besitzt. -* **Esc**: Den aktuellen Prompt, Dialog oder das Overlay abbrechen. +#### Bereichsgrenze +Dieses Thema gehört nur der Navigation im Hilfe-Popup. +Benutze `List Jump` für das Laufzeit-`/`-Namensspringen und die lokale Modusseite für normale Baum-/Dateiauswahlbefehle. + +## topic:list-jump +```ytnova-help-meta +title: List Jump +contexts: none +``` +### Contextual F1 +Benutze `/`, um ein Live-Namensfragment zu tippen und innerhalb der aktuellen Liste zu springen. +Der aktive Modus entscheidet weiterhin, welche Liste du gerade durchsuchst. + +### Explainer links +- [Directory mode](topic:dir) +- [File mode](topic:file) +- [Showall](topic:showall) +- [Global](topic:global) + +### Long form +#### Sprungmodell +`/` startet einen inkrementellen Sprung-Prompt nur für die aktuell sichtbare Liste. +Baum-/Verzeichnisansichten springen zwischen sichtbaren Verzeichnisnamen, dateiorientierte Ansichten zwischen den sichtbaren Dateizeilen dieser Oberfläche. + +#### Übernehmen und Abbrechen +* **Text tippen**: Sofort zum besten aktuellen Treffer springen. +* **Enter**: Den aktuellen Treffer behalten und dort bleiben. +* **Esc**: Den Sprung abbrechen und die ursprüngliche Auswahl wiederherstellen. +* **Bereichswechsel**: Filter, Showall/Global-Projektion, Archive und Split-Modus ändern, welche sichtbare Liste `/` durchsucht, aber nicht die Sprungtasten selbst. ## topic:shared-commands ```ytnova-help-meta @@ -170,6 +194,32 @@ Lerne sie einmal hier und benutze die Prompt-Seite nur noch für Syntax, Vorgabe * **F2**: Einen Browser oder eine Auswahl öffnen, wenn der aktuelle Prompt Browsing unterstützt. * **F1**: Syntax- oder Bereichsregeln zeigen, die nur für diesen Prompt gelten. +## topic:copy-move-targets +```ytnova-help-meta +title: Copy/Move Targets +contexts: none +``` +### Contextual F1 +Kopier- und Verschiebe-Prompts akzeptieren ein Zielverzeichnis, einen vollständigen Ersatznamen oder ein Wildcard-Umbenennungsmuster. +Die lokale Modusseite behält die Zuständigkeit für Auslösetasten, markierte Wiederholungen und Split-/Archiv-Besonderheiten. + +### Explainer links +- [Directory mode](topic:dir) +- [File mode](topic:file) +- [Archive File Help](topic:archive-file) +- [F8 split](topic:f8) + +### Long form +#### Zielformen +Benutze einen Verzeichnispfad, wenn die ursprünglichen Namen unter einem anderen Verzeichnis erhalten bleiben sollen. +Benutze einen vollständigen Ersatznamen, wenn ein einzelnes ausgewähltes Objekt unter einem neuen expliziten Namen landen soll. +Benutze ein Wildcard-Muster wie `*.bak` oder `copy-*`, wenn ytnova jeden ausgewählten Basisnamen per Muster umschreiben soll. + +#### Gemeinsame Regeln +Markiertes Kopieren/Verschieben benutzt dieselbe Zielsprache wie Einzelobjekt-Kopieren/Verschieben. +Im Split-Modus kann das Verzeichnis des inaktiven Panels als Standardziel vorbelegt sein, du kannst diesen Standard aber vor dem Start noch ersetzen. +Archivgestütztes Kopieren/Verschieben behält dasselbe Zielmodell auch dann, wenn extrahierende oder archivbewusste Pfade beteiligt sind. + ## topic:vi-keys ```ytnova-help-meta title: VI-Tasten diff --git a/etc/help/f1.en.md b/etc/help/f1.en.md index be57fa71..702b3505 100644 --- a/etc/help/f1.en.md +++ b/etc/help/f1.en.md @@ -52,6 +52,7 @@ Short local pages keep the first answer on screen. Shared topics carry the rules * **Archive File Help**: Learn what changes inside an archive file list. * **Showall** and **Global**: Learn the aggregated file-list modes. * **F7 Preview** and **F8 Split**: Learn overlay-only controls and caveats. +* **List Jump** and **Copy/Move Targets**: Learn the shared `/` jump model plus destination and wildcard rename rules. * **Filter**, **Compare**, and **Output**: Learn the option-heavy command families. * **Command-line Editing**, **VI Keys**, **F2 picker**, **F10 config**, and **Theming**: Learn the shared operator rules. @@ -81,12 +82,35 @@ Use `Left` to go back, and `Esc` or `Q` to close help. * **Left**: Go back one step. * **Esc/Quit**: Close the popup. -#### Shared list movement -* **Up/Down**: Move the active selection. -* **Page Up/Page Down**: Move by pages. -* **Home/End**: Jump to the start or end of the current list. -* **Enter**: Accept the current row or switch between paired views when that surface owns `Enter`. -* **Esc**: Cancel the current prompt, dialog, or overlay. +#### Scope boundary +This topic owns help-popup movement only. +Use `List Jump` for runtime `/` name-jump behavior, and use the local mode page for ordinary tree/file selection commands. + +## topic:list-jump +```ytnova-help-meta +title: List Jump +contexts: none +``` +### Contextual F1 +Use `/` to type a live name fragment and jump inside the current list. +The active mode still decides which list you are searching. + +### Explainer links +- [Directory mode](topic:dir) +- [File mode](topic:file) +- [Showall](topic:showall) +- [Global](topic:global) + +### Long form +#### Jump model +`/` starts an in-place jump prompt for the current list only. +Tree/directory views jump among visible directory names, while file-oriented views jump among the visible file rows for that surface. + +#### Acceptance and cancel +* **Type text**: Move immediately to the best current match as you type. +* **Enter**: Keep the current match and stay there. +* **Esc**: Cancel the jump and restore the original selection. +* **Scope changes**: Filtering, Showall/Global projection, archives, and split mode all change which visible list `/` searches, but they do not change the jump keys themselves. ## topic:shared-commands ```ytnova-help-meta @@ -170,6 +194,32 @@ Learn them once here, then use the prompt page only for syntax, defaults, and sc * **F2**: Open a browser or picker when the current prompt supports browsing. * **F1**: Show syntax or scope rules that matter only to the current prompt. +## topic:copy-move-targets +```ytnova-help-meta +title: Copy/Move Targets +contexts: none +``` +### Contextual F1 +Copy and move prompts accept a destination directory, a full replacement name, or a wildcard rename pattern. +The local mode page still owns which key copies or moves, which tagged repeat exists, and any split/archive caveat. + +### Explainer links +- [Directory mode](topic:dir) +- [File mode](topic:file) +- [Archive file](topic:archive-file) +- [F8 split](topic:f8) + +### Long form +#### Target forms +Use a directory path when you want the original names preserved under another directory. +Use one full replacement name when you want one selected item to land under a new explicit name. +Use a wildcard pattern such as `*.bak` or `copy-*` when you want ytnova to rewrite each selected basename by pattern. + +#### Shared rules +Tagged copy/move uses the same target syntax as single-item copy/move. +Split mode may seed the inactive-panel directory as the default target, but you can still replace that default before the operation starts. +Archive-backed copy/move keeps the same destination model even when extraction or archive-aware paths are involved. + ## topic:vi-keys ```ytnova-help-meta title: VI Keys @@ -243,24 +293,27 @@ contexts: main.dir ``` ### Contextual F1 Directory Help lists the active tree-panel commands. -Complex rows open a short explainer; one-line rows already say the whole action. +Rows with shared rules open their owning explainer; one-line rows already say the whole action. ### Explainer links - [Navigation](topic:navigation) +- [Copy](topic:copy-move-targets) - [Filter](topic:filter) - [Compare](topic:compare) +- [MoveDir](topic:copy-move-targets) - [Output](topic:output) +- [Jump](topic:list-jump) ### Long form #### Directory commands * **1..9 view**: Change the active panel's directory and file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5`/`7`/`8`/`9` change the paired file view, and `6` toggles size units. * **Attributes**: Open the attributes submenu for the selected directory. -* **Copy**: Copy the selected directory branch to another directory. The destination prompt can reuse the currently suggested target path, or you can replace it with another directory before the copy starts. +* **Copy**: Copy the selected directory branch to another directory. * **Delete**: Delete the selected directory. -* **Filter**: Filter the current file-list scope. Use globs such as `*.c`, comma lists such as `*.c,*.h`, exclusions such as `-*.o`, and extended selectors such as `:r`, `>2023-01-01`, or `>1M`. +* **Filter**: Filter the current file-list scope. * **Global**: Show files from every logged volume in one list. * **Invert Tags**: Flip the tag state inside the current visible scope. -* **Compare**: Compare the selected directory, the current logged tree, or another target. Use it for tree-vs-tree checks or to hand the pair off to an external compare tool, depending on the compare prompt you choose. +* **Compare**: Compare the selected directory, the current logged tree, or another target. * **Volume**: Open the volume picker. * **Log**: Log a new directory or archive file, or reload an already logged path from the top. * **Makedir**: Create a new directory. @@ -295,29 +348,33 @@ contexts: main.file ``` ### Contextual F1 File Help lists the active file-panel commands. -Complex rows open a short explainer; one-line rows already say the whole action. +Rows with shared rules open their owning explainer; one-line rows already say the whole action. ### Explainer links - [Navigation](topic:navigation) - [Tagged](topic:tagged) +- [Copy](topic:copy-move-targets) - [Filter](topic:filter) - [Compare](topic:compare) +- [Move](topic:copy-move-targets) - [Output](topic:output) +- [Write](topic:output) +- [Jump](topic:list-jump) ### Long form #### File commands * **1..9 view**: Change the active panel's file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees. * **Attributes**: Open the attributes submenu for the selected file. -* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak` or `copy-*`. +* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. * **Delete**: Delete the selected file. * **Edit**: Open the selected file in the configured editor. -* **Filter**: Filter the current file-list scope with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files and untags non-matches. +* **Filter**: Filter the current file-list scope. `Ctrl-S` searches only the tagged files and untags non-matches. * **Hex**: View the selected file in hex mode. * **Invert Tags**: Flip the tag state inside the current visible scope. -* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt. +* **Compare**: Compare the selected file against another file. * **Volume**: Open the volume picker. * **Log**: Log a new directory or archive file without leaving file mode. -* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy. +* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. * **New File**: Create a new empty file. * **Only tagged**: Show only tagged files in the current scope without changing the tag state. * **Pipe**: Send the selected file to a command on standard input. @@ -327,7 +384,7 @@ Complex rows open a short explainer; one-line rows already say the whole action. * **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current scope. * **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current scope. * **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another. -* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch. +* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. * **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely. * **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root. * **Archive**: Archive the tagged set first, or the current selection when nothing is tagged. @@ -350,20 +407,21 @@ contexts: main.archive-dir ``` ### Contextual F1 Archive Directory Help lists the active archive-tree commands. -Complex rows open a short explainer; one-line rows already say the whole action. +Rows with shared rules open their owning explainer; one-line rows already say the whole action. ### Explainer links - [Navigation](topic:navigation) - [Filter](topic:filter) - [Compare](topic:compare) +- [Jump](topic:list-jump) ### Long form #### Archive directory commands * **1..9 view**: Change the archive directory/file presentation with keys `1` through `9`. `1..4` choose the base archive directory/file view, `5`, `7`, and `8` still affect the paired file projection, `6` toggles row-size units, and `9` stays a no-op inside archives. * **Delete**: Delete the selected archive directory entry. -* **Filter**: Filter the current archive-backed file-list scope. The same glob, exclusion, and extended-selector rules apply here too. +* **Filter**: Filter the current archive-backed file-list scope. * **Global**: Show archive-backed results together with other logged volumes. -* **Compare**: Compare the selected archive directory or the current archive tree. Use it for archive-vs-tree checks or to hand the pair off to an external compare tool from the compare prompt. +* **Compare**: Compare the selected archive directory or the current archive tree. * **Volume**: Open the volume picker. * **Log**: Log another directory or archive file. * **Makedir**: Create a directory where the archive format supports it. @@ -393,26 +451,29 @@ contexts: main.archive-file ``` ### Contextual F1 Archive File Help lists the active archive-file commands. -Complex rows open a short explainer; one-line rows already say the whole action. +Rows with shared rules open their owning explainer; one-line rows already say the whole action. ### Explainer links - [Navigation](topic:navigation) - [Tagged](topic:tagged) +- [Copy](topic:copy-move-targets) - [Filter](topic:filter) - [Compare](topic:compare) +- [Move](topic:copy-move-targets) +- [Jump](topic:list-jump) ### Long form #### Archive file commands * **1..9 view**: Change the archive-file presentation with keys `1` through `9`. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, and `8` toggles File detail, while `9` stays a no-op inside archives. -* **Copy**: `C` copies the selected archive entry through archive-aware extract/copy paths, and `Ctrl-K` copies the tagged archive entries through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak`. +* **Copy**: `C` copies the selected archive entry through archive-aware extract/copy paths, and `Ctrl-K` copies the tagged archive entries through the same prompt. * **Delete**: Delete the selected archive entry. * **Filter**: Filter the current archive-backed file-list scope. `Ctrl-S` searches only the tagged archive entries and untags non-matches. * **Hex**: View the selected archive entry in hex mode. * **Invert Tags**: Flip the tag state inside the current visible scope. -* **Compare**: Compare the selected archive entry against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt. +* **Compare**: Compare the selected archive entry against another file. * **Volume**: Open the volume picker. * **Log**: Log another directory or archive file. -* **Move**: `M` moves the selected archive entry through archive-aware paths, and `Ctrl-N` moves the tagged archive entries through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy. +* **Move**: `M` moves the selected archive entry through archive-aware paths, and `Ctrl-N` moves the tagged archive entries through that same prompt. * **Pipe**: Send the selected archive entry to a command on standard input. * **Quit**: Quit ytnova. * **Rename**: Rename the selected archive entry. @@ -726,14 +787,18 @@ contexts: main.showall ``` ### Contextual F1 Showall Help lists the commands for the current single-volume aggregate view. -Complex rows open a short explainer; one-line rows already say the whole action. +Rows with shared rules open their owning explainer; one-line rows already say the whole action. ### Explainer links - [Navigation](topic:navigation) - [Tagged](topic:tagged) +- [Copy](topic:copy-move-targets) - [Filter](topic:filter) - [Compare](topic:compare) +- [Move](topic:copy-move-targets) - [Output](topic:output) +- [Write](topic:output) +- [Jump](topic:list-jump) ### Long form #### Showall rules @@ -744,16 +809,16 @@ Complex rows open a short explainer; one-line rows already say the whole action. #### Showall commands * **1..9 view**: Change the active panel's file presentation for the Showall list. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees. * **Attributes**: Open the attributes submenu for the selected file. -* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak` or `copy-*`. +* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. * **Delete**: Delete the selected file. * **Edit**: Open the selected file in the configured editor. -* **Filter**: Filter the current Showall result set with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files in the current Showall result set and untags non-matches. +* **Filter**: Filter the current Showall result set. `Ctrl-S` searches only the tagged files in the current Showall result set and untags non-matches. * **Hex**: View the selected file in hex mode. * **Invert Tags**: Flip the tag state inside the current visible Showall result set. -* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt. +* **Compare**: Compare the selected file against another file. * **Volume**: Open the volume picker. * **Log**: Log a new directory or archive file without leaving Showall. -* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy. +* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. * **New File**: Create a new empty file. * **Only tagged**: Show only the tagged rows from the current Showall result set without changing the tag state. * **Pipe**: Send the selected file to a command on standard input. @@ -763,7 +828,7 @@ Complex rows open a short explainer; one-line rows already say the whole action. * **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current Showall result set. * **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current Showall result set. * **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another. -* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch. +* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. * **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely. * **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root. * **Archive**: Archive the tagged set first, or the current selection when nothing is tagged. @@ -786,14 +851,18 @@ contexts: main.global ``` ### Contextual F1 Global Help lists the commands for the current multi-volume aggregate view. -Complex rows open a short explainer; one-line rows already say the whole action. +Rows with shared rules open their owning explainer; one-line rows already say the whole action. ### Explainer links - [Navigation](topic:navigation) - [Tagged](topic:tagged) +- [Copy](topic:copy-move-targets) - [Filter](topic:filter) - [Compare](topic:compare) +- [Move](topic:copy-move-targets) - [Output](topic:output) +- [Write](topic:output) +- [Jump](topic:list-jump) ### Long form #### Global rules @@ -804,16 +873,16 @@ Complex rows open a short explainer; one-line rows already say the whole action. #### Global commands * **1..9 view**: Change the active panel's file presentation for the Global list. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees. * **Attributes**: Open the attributes submenu for the selected file. -* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak` or `copy-*`. +* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. * **Delete**: Delete the selected file. * **Edit**: Open the selected file in the configured editor. -* **Filter**: Filter the current Global result set with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files in the current Global result set and untags non-matches. +* **Filter**: Filter the current Global result set. `Ctrl-S` searches only the tagged files in the current Global result set and untags non-matches. * **Hex**: View the selected file in hex mode. * **Invert Tags**: Flip the tag state inside the current visible Global result set. -* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt. +* **Compare**: Compare the selected file against another file. * **Volume**: Open the volume picker. * **Log**: Log a new directory or archive file without leaving Global. -* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy. +* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. * **New File**: Create a new empty file. * **Only tagged**: Show only the tagged rows from the current Global result set without changing the tag state. * **Pipe**: Send the selected file to a command on standard input. @@ -823,7 +892,7 @@ Complex rows open a short explainer; one-line rows already say the whole action. * **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current Global result set. * **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current Global result set. * **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another. -* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch. +* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. * **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely. * **Pathcopy**: Copy the selected file while keeping its path relative to the owning volume root. * **Archive**: Archive the tagged set first, or the current selection when nothing is tagged. @@ -846,15 +915,19 @@ contexts: overlay.f7-dir,overlay.f7-file ``` ### Contextual F1 F7 Preview Help lists the commands that still work while preview stays open. -Complex rows open a short explainer; one-line rows already say the whole action. +Rows with shared rules open their owning explainer; one-line rows already say the whole action. ### Explainer links - [Navigation](topic:navigation) - [Tagged](topic:tagged) +- [Copy](topic:copy-move-targets) - [Filter](topic:filter) - [Compare](topic:compare) - [Applications menu](topic:applications-menu) +- [Move](topic:copy-move-targets) - [Output](topic:output) +- [Write](topic:output) +- [Jump](topic:list-jump) ### Long form #### Preview rules @@ -867,19 +940,19 @@ Complex rows open a short explainer; one-line rows already say the whole action. #### Preview commands * **Attributes**: Open the attributes submenu for the selected file without leaving preview. -* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak` or `copy-*`. +* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. * **Delete**: Delete the selected file without leaving preview. * **Edit**: Open the selected file in the configured editor. -* **Filter**: Filter the current preview-backed file list with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files and untags non-matches. +* **Filter**: Filter the current preview-backed file list. `Ctrl-S` searches only the tagged files and untags non-matches. * **Invert Tags**: Flip the tag state inside the current visible scope. -* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt. -* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy. +* **Compare**: Compare the selected file against another file. +* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. * **New File**: Create a new empty file without leaving preview. * **Rename**: Rename the selected file without leaving preview. * **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current scope. * **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current scope. * **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another. -* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch. +* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. * **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely. * **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root. * **Archive**: Archive the tagged set first, or the current selection when nothing is tagged. @@ -916,14 +989,21 @@ contexts: overlay.f8-dir ``` ### Contextual F1 F8 Split Directory Help lists the split-specific rules plus the active split directory-footer commands. -Complex rows open a short explainer; one-line rows already say the whole action. +Rows with shared rules open their owning explainer; one-line rows already say the whole action. ### Explainer links +- [F8](topic:f8) - [Navigation](topic:navigation) - [Split overview](topic:f8) +- [Copy](topic:copy-move-targets) - [Filter](topic:filter) - [Compare](topic:compare) +- [MoveDir](topic:copy-move-targets) - [Output](topic:output) +- [Jump](topic:list-jump) +- [Tab](topic:f8) +- [Target defaults](topic:f8) +- [Panel independence](topic:f8) ### Long form #### Split rules @@ -935,12 +1015,12 @@ Complex rows open a short explainer; one-line rows already say the whole action. #### Split directory commands * **1..9 view**: Change the active panel's directory and file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5`/`7`/`8`/`9` change the paired file view, and `6` toggles size units. * **Attributes**: Open the attributes submenu for the selected directory. -* **Copy**: Copy the selected directory branch. In split mode the destination prompt defaults to the selected directory on the inactive panel, but you can still replace that target before the copy starts. +* **Copy**: Copy the selected directory branch. In split mode the destination prompt defaults to the selected directory on the inactive panel. * **Delete**: Delete the selected directory. -* **Filter**: Filter the current file-list scope. Use globs such as `*.c`, comma lists such as `*.c,*.h`, exclusions such as `-*.o`, and extended selectors such as `:r`, `>2023-01-01`, or `>1M`. +* **Filter**: Filter the current file-list scope. * **Global**: Show files from every logged volume in one list. * **Invert Tags**: Flip the tag state inside the current visible scope. -* **Compare**: Compare the selected directory, the current logged tree, or another target. Use it for tree-vs-tree checks or to hand the pair off to an external compare tool, depending on the compare prompt you choose. +* **Compare**: Compare the selected directory, the current logged tree, or another target. * **Volume**: Open the volume picker. * **Log**: Log a new directory or archive file, or reload an already logged path from the top. * **Makedir**: Create a new directory. @@ -975,15 +1055,23 @@ contexts: overlay.f8-file ``` ### Contextual F1 F8 Split File Help lists the split-specific rules plus the active split file-footer commands. -Complex rows open a short explainer; one-line rows already say the whole action. +Rows with shared rules open their owning explainer; one-line rows already say the whole action. ### Explainer links +- [F8](topic:f8) - [Navigation](topic:navigation) - [Split overview](topic:f8) - [Tagged](topic:tagged) +- [Copy](topic:copy-move-targets) - [Filter](topic:filter) - [Compare](topic:compare) +- [Move](topic:copy-move-targets) - [Output](topic:output) +- [Write](topic:output) +- [Jump](topic:list-jump) +- [Tab](topic:f8) +- [Target defaults](topic:f8) +- [Panel independence](topic:f8) ### Long form #### Split rules @@ -995,13 +1083,13 @@ Complex rows open a short explainer; one-line rows already say the whole action. #### Split file commands * **1..9 view**: Change the active panel's file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees. * **Attributes**: Open the attributes submenu for the selected file. -* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt, which in split mode defaults to the selected directory on the inactive panel. You can replace that default with another path, a full replacement name, or wildcard rename patterns such as `*.bak`. +* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt, which in split mode defaults to the selected directory on the inactive panel. * **Delete**: Delete the selected file. * **Edit**: Open the selected file in the configured editor. * **Filter**: Filter the current file-list scope with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files and untags non-matches. * **Hex**: View the selected file in hex mode. * **Invert Tags**: Flip the tag state inside the current visible scope. -* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt. +* **Compare**: Compare the selected file against another file. * **Volume**: Open the volume picker. * **Log**: Log a new directory or archive file without leaving file mode. * **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt, which in split mode defaults to the selected directory on the inactive panel. The prompt still accepts rename or wildcard pattern targets. @@ -1014,7 +1102,7 @@ Complex rows open a short explainer; one-line rows already say the whole action. * **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current scope. * **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current scope. * **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another. -* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch. +* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. * **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely. * **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root. * **Archive**: Archive the tagged set first, or the current selection when nothing is tagged. diff --git a/etc/help/man.en.md b/etc/help/man.en.md index 6e52aeb3..595407b8 100644 --- a/etc/help/man.en.md +++ b/etc/help/man.en.md @@ -48,6 +48,7 @@ The in-app `F1` popup remains the shorter contextual path for the active screen, * **Archive-Dir Mode** and **Archive-File Mode**: Reference for archive-backed navigation and command limits. * **Showall** and **Global**: Reference for single-volume and multi-volume aggregated file lists. * **F7 Preview** and **F8 Split**: Reference for overlay-only controls and ownership rules. +* **List Jump** and **Copy/Move Targets**: Reference for shared `/` jump behavior plus destination and wildcard rename rules. * **Filter**, **Compare**, **Execute**, **Archive**, and **Output**: Reference for option-heavy prompt families. * **Command-line Editing**, **VI Keys**, **F2 picker**, **F10 config**, and **Theming**: Reference for shared operator rules and configuration surfaces. @@ -75,12 +76,34 @@ The help popup uses list-style navigation. * **Left**: Go back one step. * **Esc/Quit**: Close the popup. -#### Shared list movement -* **Up/Down**: Move the active selection. -* **Page Up/Page Down**: Move by pages. -* **Home/End**: Jump to the start or end of the current list. -* **Enter**: Accept the current row or switch between paired views when that surface owns `Enter`. -* **Esc**: Cancel the current prompt, dialog, or overlay. +#### Scope boundary +This topic owns help-popup movement only. +Use `List Jump` for runtime `/` name-jump behavior, and use the local mode page for ordinary tree/file selection commands. + +## topic:list-jump +```ytnova-help-meta +title: List Jump +contexts: none +``` +### Contextual F1 +`/` is ytnova's in-list name jump. +It is distinct from help-popup navigation and remains scoped to the current runtime list. +### Explainer links +- [Directory mode](topic:dir) +- [File mode](topic:file) +- [Showall](topic:showall) +- [Global](topic:global) + +### Long form +#### Jump model +`/` opens an incremental jump prompt for the current visible list only. +Tree/directory views jump among visible directory names, while file-oriented views jump among the visible file rows for that surface. + +#### Acceptance and cancel +* **Type text**: Move immediately to the best current match as you type. +* **Enter**: Keep the current match and stay there. +* **Esc**: Cancel the jump and restore the original selection. +* **Scope changes**: Filtering, Showall/Global projection, archives, and split mode all change which visible list `/` searches, but they do not change the jump keys themselves. ## topic:shared-commands ```ytnova-help-meta @@ -160,6 +183,31 @@ Prompt-specific syntax and scope rules belong to the relevant command topic. * **F2**: Open a browser or picker when the current prompt supports browsing. * **F1**: Show syntax or scope rules that matter only to the current prompt. +## topic:copy-move-targets +```ytnova-help-meta +title: Copy/Move Targets +contexts: none +``` +### Contextual F1 +Copy and move prompts accept a destination directory, a full replacement name, or a wildcard rename pattern. +Local mode pages own the triggering keys, tagged repeats, and any split/archive caveats. +### Explainer links +- [Directory mode](topic:dir) +- [File mode](topic:file) +- [Archive-File mode](topic:archive-file) +- [F8 split](topic:f8) + +### Long form +#### Target forms +Use a directory path when you want the original names preserved under another directory. +Use one full replacement name when you want one selected item to land under a new explicit name. +Use a wildcard pattern such as `*.bak` or `copy-*` when you want ytnova to rewrite each selected basename by pattern. + +#### Shared rules +Tagged copy/move uses the same target syntax as single-item copy/move. +Split mode may seed the inactive-panel directory as the default target, but you can still replace that default before the operation starts. +Archive-backed copy/move keeps the same destination model even when extraction or archive-aware paths are involved. + ## topic:vi-keys ```ytnova-help-meta title: VI Keys diff --git a/etc/ytnova.1.md b/etc/ytnova.1.md index 9549bdc4..9e65670c 100644 --- a/etc/ytnova.1.md +++ b/etc/ytnova.1.md @@ -371,6 +371,28 @@ Then choose the final destination. * **Up**: Open or cycle prompt history when that prompt keeps history. * **F2**: Open a browser or picker when the current prompt supports browsing. * **F1**: Show syntax or scope rules that matter only to the current prompt. +### Copy/Move Targets + +#### Target forms +Use a directory path when you want the original names preserved under another directory. +Use one full replacement name when you want one selected item to land under a new explicit name. +Use a wildcard pattern such as `*.bak` or `copy-*` when you want ytnova to rewrite each selected basename by pattern. + +#### Shared rules +Tagged copy/move uses the same target syntax as single-item copy/move. +Split mode may seed the inactive-panel directory as the default target, but you can still replace that default before the operation starts. +Archive-backed copy/move keeps the same destination model even when extraction or archive-aware paths are involved. +### List Jump + +#### Jump model +`/` opens an incremental jump prompt for the current visible list only. +Tree/directory views jump among visible directory names, while file-oriented views jump among the visible file rows for that surface. + +#### Acceptance and cancel +* **Type text**: Move immediately to the best current match as you type. +* **Enter**: Keep the current match and stay there. +* **Esc**: Cancel the jump and restore the original selection. +* **Scope changes**: Filtering, Showall/Global projection, archives, and split mode all change which visible list `/` searches, but they do not change the jump keys themselves. ### VI Keys #### Navigation remap diff --git a/scripts/generate_help_assets.py b/scripts/generate_help_assets.py index fab679ac..904f1b65 100644 --- a/scripts/generate_help_assets.py +++ b/scripts/generate_help_assets.py @@ -272,6 +272,8 @@ def generated_banner(source_path: str) -> str: SUPPORT_TOPIC_ORDER = [ ("command-line-editing", "Command-line Editing"), + ("copy-move-targets", "Copy/Move Targets"), + ("list-jump", "List Jump"), ("vi-keys", "VI Keys"), ("f10", "F10 Config"), ("theming", "Theming"), diff --git a/src/core/generated_help_topics.h b/src/core/generated_help_topics.h index 8ccec264..d169cda6 100644 --- a/src/core/generated_help_topics.h +++ b/src/core/generated_help_topics.h @@ -32,7 +32,7 @@ static const GeneratedHelpLink generated_help_links_intro[] = { static const GeneratedHelpLongFormSection generated_help_sections_intro[] = { {"Purpose", "Use `F1` for the thing you are doing now, not as one giant manual.\nShort local pages keep the first answer on screen. Shared topics carry the rules that repeat across more than one place."}, - {"Contents", "* **Navigation**: Learn the help-popup keys, list movement, and back/close rules.\n* **Tagged**: Learn how ytnova treats a tagged set as one working group.\n* **Shared commands**: Learn the function-key family that appears in more than one mode.\n* **Directory Help**: Learn tree-mode commands and directory-only rules.\n* **File Help**: Learn file-list commands, tagged actions, and file-only rules.\n* **Archive Directory Help**: Learn what changes inside an archive tree.\n* **Archive File Help**: Learn what changes inside an archive file list.\n* **Showall** and **Global**: Learn the aggregated file-list modes.\n* **F7 Preview** and **F8 Split**: Learn overlay-only controls and caveats.\n* **Filter**, **Compare**, and **Output**: Learn the option-heavy command families.\n* **Command-line Editing**, **VI Keys**, **F2 picker**, **F10 config**, and **Theming**: Learn the shared operator rules."}, + {"Contents", "* **Navigation**: Learn the help-popup keys, list movement, and back/close rules.\n* **Tagged**: Learn how ytnova treats a tagged set as one working group.\n* **Shared commands**: Learn the function-key family that appears in more than one mode.\n* **Directory Help**: Learn tree-mode commands and directory-only rules.\n* **File Help**: Learn file-list commands, tagged actions, and file-only rules.\n* **Archive Directory Help**: Learn what changes inside an archive tree.\n* **Archive File Help**: Learn what changes inside an archive file list.\n* **Showall** and **Global**: Learn the aggregated file-list modes.\n* **F7 Preview** and **F8 Split**: Learn overlay-only controls and caveats.\n* **List Jump** and **Copy/Move Targets**: Learn the shared `/` jump model plus destination and wildcard rename rules.\n* **Filter**, **Compare**, and **Output**: Learn the option-heavy command families.\n* **Command-line Editing**, **VI Keys**, **F2 picker**, **F10 config**, and **Theming**: Learn the shared operator rules."}, }; static const GeneratedHelpLink generated_help_links_navigation[] = { @@ -45,7 +45,19 @@ static const GeneratedHelpLink generated_help_links_navigation[] = { static const GeneratedHelpLongFormSection generated_help_sections_navigation[] = { {"Help popup keys", "* **Up/Down**: Move between selectable rows or links.\n* **Page Up/Page Down**: Scroll longer help pages.\n* **Home/End**: Jump to the top or bottom of the current help page.\n* **Enter/Right**: Open the selected help item or linked topic.\n* **Left**: Go back one step.\n* **Esc/Quit**: Close the popup."}, - {"Shared list movement", "* **Up/Down**: Move the active selection.\n* **Page Up/Page Down**: Move by pages.\n* **Home/End**: Jump to the start or end of the current list.\n* **Enter**: Accept the current row or switch between paired views when that surface owns `Enter`.\n* **Esc**: Cancel the current prompt, dialog, or overlay."}, + {"Scope boundary", "This topic owns help-popup movement only.\nUse `List Jump` for runtime `/` name-jump behavior, and use the local mode page for ordinary tree/file selection commands."}, +}; + +static const GeneratedHelpLink generated_help_links_list_jump[] = { + {"Directory mode", "dir"}, + {"File mode", "file"}, + {"Showall", "showall"}, + {"Global", "global"}, +}; + +static const GeneratedHelpLongFormSection generated_help_sections_list_jump[] = { + {"Jump model", "`/` starts an in-place jump prompt for the current list only.\nTree/directory views jump among visible directory names, while file-oriented views jump among the visible file rows for that surface."}, + {"Acceptance and cancel", "* **Type text**: Move immediately to the best current match as you type.\n* **Enter**: Keep the current match and stay there.\n* **Esc**: Cancel the jump and restore the original selection.\n* **Scope changes**: Filtering, Showall/Global projection, archives, and split mode all change which visible list `/` searches, but they do not change the jump keys themselves."}, }; static const GeneratedHelpLink generated_help_links_shared_commands[] = { @@ -81,6 +93,18 @@ static const GeneratedHelpLongFormSection generated_help_sections_command_line_e {"Shared helpers", "* **Up**: Open or cycle prompt history when that prompt keeps history.\n* **F2**: Open a browser or picker when the current prompt supports browsing.\n* **F1**: Show syntax or scope rules that matter only to the current prompt."}, }; +static const GeneratedHelpLink generated_help_links_copy_move_targets[] = { + {"Directory mode", "dir"}, + {"File mode", "file"}, + {"Archive file", "archive-file"}, + {"F8 split", "f8"}, +}; + +static const GeneratedHelpLongFormSection generated_help_sections_copy_move_targets[] = { + {"Target forms", "Use a directory path when you want the original names preserved under another directory.\nUse one full replacement name when you want one selected item to land under a new explicit name.\nUse a wildcard pattern such as `*.bak` or `copy-*` when you want ytnova to rewrite each selected basename by pattern."}, + {"Shared rules", "Tagged copy/move uses the same target syntax as single-item copy/move.\nSplit mode may seed the inactive-panel directory as the default target, but you can still replace that default before the operation starts.\nArchive-backed copy/move keeps the same destination model even when extraction or archive-aware paths are involved."}, +}; + static const GeneratedHelpLink generated_help_links_vi_keys[] = { {"Navigation", "navigation"}, {"Command-line editing", "command-line-editing"}, @@ -112,26 +136,33 @@ static const GeneratedHelpLongFormSection generated_help_sections_theming[] = { static const GeneratedHelpLink generated_help_links_dir[] = { {"Navigation", "navigation"}, + {"Copy", "copy-move-targets"}, {"Filter", "filter"}, {"Compare", "compare"}, + {"MoveDir", "copy-move-targets"}, {"Output", "output"}, + {"Jump", "list-jump"}, }; static const GeneratedHelpLongFormSection generated_help_sections_dir[] = { - {"Directory commands", "* **1..9 view**: Change the active panel's directory and file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5`/`7`/`8`/`9` change the paired file view, and `6` toggles size units.\n* **Attributes**: Open the attributes submenu for the selected directory.\n* **Copy**: Copy the selected directory branch to another directory. The destination prompt can reuse the currently suggested target path, or you can replace it with another directory before the copy starts.\n* **Delete**: Delete the selected directory.\n* **Filter**: Filter the current file-list scope. Use globs such as `*.c`, comma lists such as `*.c,*.h`, exclusions such as `-*.o`, and extended selectors such as `:r`, `>2023-01-01`, or `>1M`.\n* **Global**: Show files from every logged volume in one list.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected directory, the current logged tree, or another target. Use it for tree-vs-tree checks or to hand the pair off to an external compare tool, depending on the compare prompt you choose.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file, or reload an already logged path from the top.\n* **Makedir**: Create a new directory.\n* **New File**: Create a new empty file in the current directory.\n* **Only tagged**: Show only tagged files in the current scope without changing the tag state.\n* **Pipe**: Send the selected directory to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected directory.\n* **Showall**: Show every file inside the current logged volume.\n* **Tag**: Tag the files in the selected directory scope.\n* **Untag**: Remove tags from the selected directory scope.\n* **MoveDir**: Move the selected directory branch.\n* **Write**: Export the current selection through the output prompts.\n* **Execute**: Run a shell command on the current selection while leaving `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files and dot-directories."}, + {"Directory commands", "* **1..9 view**: Change the active panel's directory and file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5`/`7`/`8`/`9` change the paired file view, and `6` toggles size units.\n* **Attributes**: Open the attributes submenu for the selected directory.\n* **Copy**: Copy the selected directory branch to another directory.\n* **Delete**: Delete the selected directory.\n* **Filter**: Filter the current file-list scope.\n* **Global**: Show files from every logged volume in one list.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected directory, the current logged tree, or another target.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file, or reload an already logged path from the top.\n* **Makedir**: Create a new directory.\n* **New File**: Create a new empty file in the current directory.\n* **Only tagged**: Show only tagged files in the current scope without changing the tag state.\n* **Pipe**: Send the selected directory to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected directory.\n* **Showall**: Show every file inside the current logged volume.\n* **Tag**: Tag the files in the selected directory scope.\n* **Untag**: Remove tags from the selected directory scope.\n* **MoveDir**: Move the selected directory branch.\n* **Write**: Export the current selection through the output prompts.\n* **Execute**: Run a shell command on the current selection while leaving `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files and dot-directories."}, {"Directory function keys", "* **F1**: Open contextual help for the active directory surface.\n* **F5**: Refresh the active panel.\n* **F6**: Change the active panel's stats/details presentation.\n* **F7**: Toggle preview for the current file context.\n* **F8**: Toggle split-screen mode.\n* **F9**: Open the Applications menu.\n* **F10**: Open the configuration command surface."}, }; static const GeneratedHelpLink generated_help_links_file[] = { {"Navigation", "navigation"}, {"Tagged", "tagged"}, + {"Copy", "copy-move-targets"}, {"Filter", "filter"}, {"Compare", "compare"}, + {"Move", "copy-move-targets"}, {"Output", "output"}, + {"Write", "output"}, + {"Jump", "list-jump"}, }; static const GeneratedHelpLongFormSection generated_help_sections_file[] = { - {"File commands", "* **1..9 view**: Change the active panel's file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees.\n* **Attributes**: Open the attributes submenu for the selected file.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak` or `copy-*`.\n* **Delete**: Delete the selected file.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current file-list scope with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files and untags non-matches.\n* **Hex**: View the selected file in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file without leaving file mode.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy.\n* **New File**: Create a new empty file.\n* **Only tagged**: Show only tagged files in the current scope without changing the tag state.\n* **Pipe**: Send the selected file to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected file.\n* **Sort**: Change the current file-list sort order.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current scope.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current scope.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current scope."}, + {"File commands", "* **1..9 view**: Change the active panel's file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees.\n* **Attributes**: Open the attributes submenu for the selected file.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt.\n* **Delete**: Delete the selected file.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current file-list scope. `Ctrl-S` searches only the tagged files and untags non-matches.\n* **Hex**: View the selected file in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected file against another file.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file without leaving file mode.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt.\n* **New File**: Create a new empty file.\n* **Only tagged**: Show only tagged files in the current scope without changing the tag state.\n* **Pipe**: Send the selected file to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected file.\n* **Sort**: Change the current file-list sort order.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current scope.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current scope.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current scope."}, {"File function keys", "* **F1**: Open contextual help for the active file surface.\n* **F5**: Refresh the active panel.\n* **F6**: Change the active panel's stats/details presentation.\n* **F7**: Toggle preview for the selected file context.\n* **F8**: Toggle split-screen mode.\n* **F9**: Open the Applications menu.\n* **F10**: Open the configuration command surface."}, }; @@ -139,22 +170,26 @@ static const GeneratedHelpLink generated_help_links_archive_dir[] = { {"Navigation", "navigation"}, {"Filter", "filter"}, {"Compare", "compare"}, + {"Jump", "list-jump"}, }; static const GeneratedHelpLongFormSection generated_help_sections_archive_dir[] = { - {"Archive directory commands", "* **1..9 view**: Change the archive directory/file presentation with keys `1` through `9`. `1..4` choose the base archive directory/file view, `5`, `7`, and `8` still affect the paired file projection, `6` toggles row-size units, and `9` stays a no-op inside archives.\n* **Delete**: Delete the selected archive directory entry.\n* **Filter**: Filter the current archive-backed file-list scope. The same glob, exclusion, and extended-selector rules apply here too.\n* **Global**: Show archive-backed results together with other logged volumes.\n* **Compare**: Compare the selected archive directory or the current archive tree. Use it for archive-vs-tree checks or to hand the pair off to an external compare tool from the compare prompt.\n* **Volume**: Open the volume picker.\n* **Log**: Log another directory or archive file.\n* **Makedir**: Create a directory where the archive format supports it.\n* **Jump**: Jump to a matching name in the current list.\n* **Pipe**: Send the selected archive path to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected archive directory entry.\n* **Showall**: Show every file in the current archive.\n* **Tag**: Tag the files in the current virtual directory scope.\n* **Untag**: Remove tags from the current virtual directory scope.\n* **Root/Exit**: `\\` jumps to archive root when you are below it, or leaves the archive when you are already there.\n* **Dotfiles**: Toggle hidden entries when the archive view exposes them."}, + {"Archive directory commands", "* **1..9 view**: Change the archive directory/file presentation with keys `1` through `9`. `1..4` choose the base archive directory/file view, `5`, `7`, and `8` still affect the paired file projection, `6` toggles row-size units, and `9` stays a no-op inside archives.\n* **Delete**: Delete the selected archive directory entry.\n* **Filter**: Filter the current archive-backed file-list scope.\n* **Global**: Show archive-backed results together with other logged volumes.\n* **Compare**: Compare the selected archive directory or the current archive tree.\n* **Volume**: Open the volume picker.\n* **Log**: Log another directory or archive file.\n* **Makedir**: Create a directory where the archive format supports it.\n* **Jump**: Jump to a matching name in the current list.\n* **Pipe**: Send the selected archive path to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected archive directory entry.\n* **Showall**: Show every file in the current archive.\n* **Tag**: Tag the files in the current virtual directory scope.\n* **Untag**: Remove tags from the current virtual directory scope.\n* **Root/Exit**: `\\` jumps to archive root when you are below it, or leaves the archive when you are already there.\n* **Dotfiles**: Toggle hidden entries when the archive view exposes them."}, {"Archive directory function keys", "* **F1**: Open contextual help for the active archive-directory surface.\n* **F5**: Refresh the active panel.\n* **F6**: Change the active panel's stats/details presentation.\n* **F7**: Toggle preview for the current file context.\n* **F8**: Toggle split-screen mode.\n* **F9**: Open the Applications menu.\n* **F10**: Open the configuration command surface."}, }; static const GeneratedHelpLink generated_help_links_archive_file[] = { {"Navigation", "navigation"}, {"Tagged", "tagged"}, + {"Copy", "copy-move-targets"}, {"Filter", "filter"}, {"Compare", "compare"}, + {"Move", "copy-move-targets"}, + {"Jump", "list-jump"}, }; static const GeneratedHelpLongFormSection generated_help_sections_archive_file[] = { - {"Archive file commands", "* **1..9 view**: Change the archive-file presentation with keys `1` through `9`. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, and `8` toggles File detail, while `9` stays a no-op inside archives.\n* **Copy**: `C` copies the selected archive entry through archive-aware extract/copy paths, and `Ctrl-K` copies the tagged archive entries through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak`.\n* **Delete**: Delete the selected archive entry.\n* **Filter**: Filter the current archive-backed file-list scope. `Ctrl-S` searches only the tagged archive entries and untags non-matches.\n* **Hex**: View the selected archive entry in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected archive entry against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt.\n* **Volume**: Open the volume picker.\n* **Log**: Log another directory or archive file.\n* **Move**: `M` moves the selected archive entry through archive-aware paths, and `Ctrl-N` moves the tagged archive entries through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy.\n* **Pipe**: Send the selected archive entry to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected archive entry.\n* **Sort**: Change the current file-list sort order.\n* **Tag**: Tag the selected archive entry, and `Ctrl-T` tags every visible archive row in the current scope.\n* **Untag**: Remove the tag from the selected archive entry, and `Ctrl-U` removes every archive tag in the current scope.\n* **View**: View the selected archive entry, and `Ctrl-V` views the tagged archive entries one after another.\n* **Pathcopy**: Copy the selected archive entry while keeping its relative path.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden entries when the archive view exposes them."}, + {"Archive file commands", "* **1..9 view**: Change the archive-file presentation with keys `1` through `9`. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, and `8` toggles File detail, while `9` stays a no-op inside archives.\n* **Copy**: `C` copies the selected archive entry through archive-aware extract/copy paths, and `Ctrl-K` copies the tagged archive entries through the same prompt.\n* **Delete**: Delete the selected archive entry.\n* **Filter**: Filter the current archive-backed file-list scope. `Ctrl-S` searches only the tagged archive entries and untags non-matches.\n* **Hex**: View the selected archive entry in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected archive entry against another file.\n* **Volume**: Open the volume picker.\n* **Log**: Log another directory or archive file.\n* **Move**: `M` moves the selected archive entry through archive-aware paths, and `Ctrl-N` moves the tagged archive entries through that same prompt.\n* **Pipe**: Send the selected archive entry to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected archive entry.\n* **Sort**: Change the current file-list sort order.\n* **Tag**: Tag the selected archive entry, and `Ctrl-T` tags every visible archive row in the current scope.\n* **Untag**: Remove the tag from the selected archive entry, and `Ctrl-U` removes every archive tag in the current scope.\n* **View**: View the selected archive entry, and `Ctrl-V` views the tagged archive entries one after another.\n* **Pathcopy**: Copy the selected archive entry while keeping its relative path.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden entries when the archive view exposes them."}, {"Archive file function keys", "* **F1**: Open contextual help for the active archive-file surface.\n* **F5**: Refresh the active panel.\n* **F6**: Change the active panel's stats/details presentation.\n* **F7**: Toggle preview for the selected file context.\n* **F8**: Toggle split-screen mode.\n* **F9**: Open the Applications menu.\n* **F10**: Open the configuration command surface."}, }; @@ -289,43 +324,55 @@ static const GeneratedHelpLongFormSection generated_help_sections_output_separat static const GeneratedHelpLink generated_help_links_showall[] = { {"Navigation", "navigation"}, {"Tagged", "tagged"}, + {"Copy", "copy-move-targets"}, {"Filter", "filter"}, {"Compare", "compare"}, + {"Move", "copy-move-targets"}, {"Output", "output"}, + {"Write", "output"}, + {"Jump", "list-jump"}, }; static const GeneratedHelpLongFormSection generated_help_sections_showall[] = { {"Showall rules", "* **Scope**: Showall lists every file inside the current logged volume only. It does not merge in other logged volumes.\n* **Return**: Return to the previously selected directory.\n* **Open owner**: Jump to the owner directory of the selected file inside the current logged volume."}, - {"Showall commands", "* **1..9 view**: Change the active panel's file presentation for the Showall list. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees.\n* **Attributes**: Open the attributes submenu for the selected file.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak` or `copy-*`.\n* **Delete**: Delete the selected file.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current Showall result set with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files in the current Showall result set and untags non-matches.\n* **Hex**: View the selected file in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible Showall result set.\n* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file without leaving Showall.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy.\n* **New File**: Create a new empty file.\n* **Only tagged**: Show only the tagged rows from the current Showall result set without changing the tag state.\n* **Pipe**: Send the selected file to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected file.\n* **Sort**: Repeating `S` changes sort without leaving Showall.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current Showall result set.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current Showall result set.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current Showall result set."}, + {"Showall commands", "* **1..9 view**: Change the active panel's file presentation for the Showall list. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees.\n* **Attributes**: Open the attributes submenu for the selected file.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt.\n* **Delete**: Delete the selected file.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current Showall result set. `Ctrl-S` searches only the tagged files in the current Showall result set and untags non-matches.\n* **Hex**: View the selected file in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible Showall result set.\n* **Compare**: Compare the selected file against another file.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file without leaving Showall.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt.\n* **New File**: Create a new empty file.\n* **Only tagged**: Show only the tagged rows from the current Showall result set without changing the tag state.\n* **Pipe**: Send the selected file to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected file.\n* **Sort**: Repeating `S` changes sort without leaving Showall.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current Showall result set.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current Showall result set.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current Showall result set."}, {"Showall function keys", "* **F1**: Open contextual help for the current Showall surface.\n* **F5**: Refresh the active panel.\n* **F6**: Change the active panel's stats/details presentation.\n* **F7**: Toggle preview for the selected file context.\n* **F8**: Toggle split-screen mode.\n* **F9**: Open the Applications menu.\n* **F10**: Open the configuration command surface."}, }; static const GeneratedHelpLink generated_help_links_global[] = { {"Navigation", "navigation"}, {"Tagged", "tagged"}, + {"Copy", "copy-move-targets"}, {"Filter", "filter"}, {"Compare", "compare"}, + {"Move", "copy-move-targets"}, {"Output", "output"}, + {"Write", "output"}, + {"Jump", "list-jump"}, }; static const GeneratedHelpLongFormSection generated_help_sections_global[] = { {"Global rules", "* **Scope**: Global lists files from every logged volume. It is the cross-volume aggregate view.\n* **Return**: Return to the previously selected directory.\n* **Open owner**: Jump to the owner directory of the selected file even when it lives under another logged volume root."}, - {"Global commands", "* **1..9 view**: Change the active panel's file presentation for the Global list. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees.\n* **Attributes**: Open the attributes submenu for the selected file.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak` or `copy-*`.\n* **Delete**: Delete the selected file.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current Global result set with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files in the current Global result set and untags non-matches.\n* **Hex**: View the selected file in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible Global result set.\n* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file without leaving Global.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy.\n* **New File**: Create a new empty file.\n* **Only tagged**: Show only the tagged rows from the current Global result set without changing the tag state.\n* **Pipe**: Send the selected file to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected file.\n* **Sort**: Repeating `S` changes sort without leaving Global.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current Global result set.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current Global result set.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the owning volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current Global result set."}, + {"Global commands", "* **1..9 view**: Change the active panel's file presentation for the Global list. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees.\n* **Attributes**: Open the attributes submenu for the selected file.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt.\n* **Delete**: Delete the selected file.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current Global result set. `Ctrl-S` searches only the tagged files in the current Global result set and untags non-matches.\n* **Hex**: View the selected file in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible Global result set.\n* **Compare**: Compare the selected file against another file.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file without leaving Global.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt.\n* **New File**: Create a new empty file.\n* **Only tagged**: Show only the tagged rows from the current Global result set without changing the tag state.\n* **Pipe**: Send the selected file to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected file.\n* **Sort**: Repeating `S` changes sort without leaving Global.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current Global result set.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current Global result set.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the owning volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current Global result set."}, {"Global function keys", "* **F1**: Open contextual help for the current Global surface.\n* **F5**: Refresh the active panel.\n* **F6**: Change the active panel's stats/details presentation.\n* **F7**: Toggle preview for the selected file context.\n* **F8**: Toggle split-screen mode.\n* **F9**: Open the Applications menu.\n* **F10**: Open the configuration command surface."}, }; static const GeneratedHelpLink generated_help_links_f7[] = { {"Navigation", "navigation"}, {"Tagged", "tagged"}, + {"Copy", "copy-move-targets"}, {"Filter", "filter"}, {"Compare", "compare"}, {"Applications menu", "applications-menu"}, + {"Move", "copy-move-targets"}, {"Output", "output"}, + {"Write", "output"}, + {"Jump", "list-jump"}, }; static const GeneratedHelpLongFormSection generated_help_sections_f7[] = { {"Preview rules", "* **F1**: Open contextual help for preview.\n* **F7**: Return to the underlying directory or file view.\n* **F8**: Split does nothing while preview is active.\n* **F9**: Open the applications menu without leaving preview.\n* **Tab panels**: `Tab` does not switch panels while preview is active.\n* **Esc**: Leave preview immediately."}, - {"Preview commands", "* **Attributes**: Open the attributes submenu for the selected file without leaving preview.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt. The destination prompt accepts a directory, a full replacement name, or wildcard rename patterns such as `*.bak` or `copy-*`.\n* **Delete**: Delete the selected file without leaving preview.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current preview-backed file list with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files and untags non-matches.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt. The destination prompt uses the same directory, rename, and wildcard pattern rules as Copy.\n* **New File**: Create a new empty file without leaving preview.\n* **Rename**: Rename the selected file without leaving preview.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current scope.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current scope.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current scope."}, + {"Preview commands", "* **Attributes**: Open the attributes submenu for the selected file without leaving preview.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt.\n* **Delete**: Delete the selected file without leaving preview.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current preview-backed file list. `Ctrl-S` searches only the tagged files and untags non-matches.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected file against another file.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt.\n* **New File**: Create a new empty file without leaving preview.\n* **Rename**: Rename the selected file without leaving preview.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current scope.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current scope.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current scope."}, }; static const GeneratedHelpLink generated_help_links_f8[] = { @@ -339,31 +386,46 @@ static const GeneratedHelpLongFormSection generated_help_sections_f8[] = { }; static const GeneratedHelpLink generated_help_links_f8_dir[] = { + {"F8", "f8"}, {"Navigation", "navigation"}, {"Split overview", "f8"}, + {"Copy", "copy-move-targets"}, {"Filter", "filter"}, {"Compare", "compare"}, + {"MoveDir", "copy-move-targets"}, {"Output", "output"}, + {"Jump", "list-jump"}, + {"Tab", "f8"}, + {"Target defaults", "f8"}, + {"Panel independence", "f8"}, }; static const GeneratedHelpLongFormSection generated_help_sections_f8_dir[] = { {"Split rules", "* **F8**: Return to single-panel mode.\n* **Tab**: Switch the active panel and keep the passive panel's state intact.\n* **Target defaults**: Copy, move, and compare prompts default to the inactive panel as destination or target.\n* **Panel independence**: Each panel keeps its own selection, view, tags, volume, and restore state."}, - {"Split directory commands", "* **1..9 view**: Change the active panel's directory and file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5`/`7`/`8`/`9` change the paired file view, and `6` toggles size units.\n* **Attributes**: Open the attributes submenu for the selected directory.\n* **Copy**: Copy the selected directory branch. In split mode the destination prompt defaults to the selected directory on the inactive panel, but you can still replace that target before the copy starts.\n* **Delete**: Delete the selected directory.\n* **Filter**: Filter the current file-list scope. Use globs such as `*.c`, comma lists such as `*.c,*.h`, exclusions such as `-*.o`, and extended selectors such as `:r`, `>2023-01-01`, or `>1M`.\n* **Global**: Show files from every logged volume in one list.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected directory, the current logged tree, or another target. Use it for tree-vs-tree checks or to hand the pair off to an external compare tool, depending on the compare prompt you choose.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file, or reload an already logged path from the top.\n* **Makedir**: Create a new directory.\n* **New File**: Create a new empty file in the current directory.\n* **Only tagged**: Show only tagged files in the current scope without changing the tag state.\n* **Pipe**: Send the selected directory to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected directory.\n* **Showall**: Show every file inside the current logged volume.\n* **Tag**: Tag the files in the selected directory scope.\n* **Untag**: Remove tags from the selected directory scope.\n* **MoveDir**: Move the selected directory branch.\n* **Write**: Export the current selection through the output prompts.\n* **Execute**: Run a shell command on the current selection while leaving `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files and dot-directories."}, + {"Split directory commands", "* **1..9 view**: Change the active panel's directory and file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5`/`7`/`8`/`9` change the paired file view, and `6` toggles size units.\n* **Attributes**: Open the attributes submenu for the selected directory.\n* **Copy**: Copy the selected directory branch. In split mode the destination prompt defaults to the selected directory on the inactive panel.\n* **Delete**: Delete the selected directory.\n* **Filter**: Filter the current file-list scope.\n* **Global**: Show files from every logged volume in one list.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected directory, the current logged tree, or another target.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file, or reload an already logged path from the top.\n* **Makedir**: Create a new directory.\n* **New File**: Create a new empty file in the current directory.\n* **Only tagged**: Show only tagged files in the current scope without changing the tag state.\n* **Pipe**: Send the selected directory to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected directory.\n* **Showall**: Show every file inside the current logged volume.\n* **Tag**: Tag the files in the selected directory scope.\n* **Untag**: Remove tags from the selected directory scope.\n* **MoveDir**: Move the selected directory branch.\n* **Write**: Export the current selection through the output prompts.\n* **Execute**: Run a shell command on the current selection while leaving `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files and dot-directories."}, {"Split directory function keys", "* **F1**: Open contextual help for the active split-directory surface.\n* **F5**: Refresh the active panel.\n* **F6**: Change the active panel's stats/details presentation.\n* **F7**: Toggle preview for the current file context.\n* **F8**: Return to single-panel mode.\n* **F9**: Open the Applications menu.\n* **F10**: Open the configuration command surface."}, }; static const GeneratedHelpLink generated_help_links_f8_file[] = { + {"F8", "f8"}, {"Navigation", "navigation"}, {"Split overview", "f8"}, {"Tagged", "tagged"}, + {"Copy", "copy-move-targets"}, {"Filter", "filter"}, {"Compare", "compare"}, + {"Move", "copy-move-targets"}, {"Output", "output"}, + {"Write", "output"}, + {"Jump", "list-jump"}, + {"Tab", "f8"}, + {"Target defaults", "f8"}, + {"Panel independence", "f8"}, }; static const GeneratedHelpLongFormSection generated_help_sections_f8_file[] = { {"Split rules", "* **F8**: Return to single-panel mode.\n* **Tab**: Switch the active panel and keep the passive panel's state intact.\n* **Target defaults**: Copy, move, and compare prompts default to the inactive panel as destination or target.\n* **Panel independence**: Each panel keeps its own selection, view, tags, volume, and restore state."}, - {"Split file commands", "* **1..9 view**: Change the active panel's file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees.\n* **Attributes**: Open the attributes submenu for the selected file.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt, which in split mode defaults to the selected directory on the inactive panel. You can replace that default with another path, a full replacement name, or wildcard rename patterns such as `*.bak`.\n* **Delete**: Delete the selected file.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current file-list scope with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files and untags non-matches.\n* **Hex**: View the selected file in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected file against another file. Use it for quick file-vs-file checks or to hand the pair off to an external compare tool from the compare prompt.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file without leaving file mode.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt, which in split mode defaults to the selected directory on the inactive panel. The prompt still accepts rename or wildcard pattern targets.\n* **New File**: Create a new empty file.\n* **Only tagged**: Show only tagged files in the current scope without changing the tag state.\n* **Pipe**: Send the selected file to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected file.\n* **Sort**: Change the current file-list sort order.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current scope.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current scope.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set. One destination or separator choice can write the whole batch.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current scope."}, + {"Split file commands", "* **1..9 view**: Change the active panel's file presentation. `1` resets to Name, `2` shows Attributes, `3` shows Owner, `4` shows Times, `5` toggles Compact, `6` toggles size units, `7` toggles Mini preview, `8` toggles File detail, and `9` toggles the Git band inside Git worktrees.\n* **Attributes**: Open the attributes submenu for the selected file.\n* **Copy**: `C` copies the selected file and `Ctrl-K` copies the tagged set through the same prompt, which in split mode defaults to the selected directory on the inactive panel.\n* **Delete**: Delete the selected file.\n* **Edit**: Open the selected file in the configured editor.\n* **Filter**: Filter the current file-list scope with globs, exclusions, and extended selectors. `Ctrl-S` searches only the tagged files and untags non-matches.\n* **Hex**: View the selected file in hex mode.\n* **Invert Tags**: Flip the tag state inside the current visible scope.\n* **Compare**: Compare the selected file against another file.\n* **Volume**: Open the volume picker.\n* **Log**: Log a new directory or archive file without leaving file mode.\n* **Move**: `M` moves the selected file and `Ctrl-N` moves the tagged set through that same prompt, which in split mode defaults to the selected directory on the inactive panel. The prompt still accepts rename or wildcard pattern targets.\n* **New File**: Create a new empty file.\n* **Only tagged**: Show only tagged files in the current scope without changing the tag state.\n* **Pipe**: Send the selected file to a command on standard input.\n* **Quit**: Quit ytnova.\n* **Rename**: Rename the selected file.\n* **Sort**: Change the current file-list sort order.\n* **Tag**: Tag the selected file, and `Ctrl-T` tags every visible file in the current scope.\n* **Untag**: Remove the tag from the selected file, and `Ctrl-U` removes every tag in the current scope.\n* **View**: View the selected file with the configured pager, and `Ctrl-V` views the tagged files one after another.\n* **Write**: Export the selected file through the output prompts, and `Ctrl-W` reuses the same prompts for the tagged set.\n* **Execute**: Run a shell command on the selected file, and `Ctrl-X` reruns the same command once for each tagged file. Leave `{}` unquoted so ytnova can expand it and quote the resulting path safely.\n* **Pathcopy**: Copy the selected file while keeping its path relative to the current volume root.\n* **Archive**: Archive the tagged set first, or the current selection when nothing is tagged.\n* **Jump**: Jump to a matching name in the current list.\n* **Dotfiles**: Toggle hidden dot-files in the current scope."}, {"Split file function keys", "* **F1**: Open contextual help for the active split-file surface.\n* **F5**: Refresh the active panel.\n* **F6**: Change the active panel's stats/details presentation.\n* **F7**: Toggle preview for the selected file context.\n* **F8**: Return to single-panel mode.\n* **F9**: Open the Applications menu.\n* **F10**: Open the configuration command surface."}, }; @@ -421,6 +483,16 @@ static const GeneratedHelpTopic generated_help_topics[] = { 2, generated_help_sections_navigation, }, + { + "list-jump", + "List Jump", + NULL, + "Use `/` to type a live name fragment and jump inside the current list.\nThe active mode still decides which list you are searching.", + 4, + generated_help_links_list_jump, + 2, + generated_help_sections_list_jump, + }, { "shared-commands", "Shared Commands", @@ -451,6 +523,16 @@ static const GeneratedHelpTopic generated_help_topics[] = { 2, generated_help_sections_command_line_editing, }, + { + "copy-move-targets", + "Copy/Move Targets", + NULL, + "Copy and move prompts accept a destination directory, a full replacement name, or a wildcard rename pattern.\nThe local mode page still owns which key copies or moves, which tagged repeat exists, and any split/archive caveat.", + 4, + generated_help_links_copy_move_targets, + 2, + generated_help_sections_copy_move_targets, + }, { "vi-keys", "VI Keys", @@ -485,8 +567,8 @@ static const GeneratedHelpTopic generated_help_topics[] = { "dir", "Directory Help", "main.dir", - "Directory Help lists the active tree-panel commands.\nComplex rows open a short explainer; one-line rows already say the whole action.", - 4, + "Directory Help lists the active tree-panel commands.\nRows with shared rules open their owning explainer; one-line rows already say the whole action.", + 7, generated_help_links_dir, 2, generated_help_sections_dir, @@ -495,8 +577,8 @@ static const GeneratedHelpTopic generated_help_topics[] = { "file", "File Help", "main.file", - "File Help lists the active file-panel commands.\nComplex rows open a short explainer; one-line rows already say the whole action.", - 5, + "File Help lists the active file-panel commands.\nRows with shared rules open their owning explainer; one-line rows already say the whole action.", + 9, generated_help_links_file, 2, generated_help_sections_file, @@ -505,8 +587,8 @@ static const GeneratedHelpTopic generated_help_topics[] = { "archive-dir", "Archive Directory Help", "main.archive-dir", - "Archive Directory Help lists the active archive-tree commands.\nComplex rows open a short explainer; one-line rows already say the whole action.", - 3, + "Archive Directory Help lists the active archive-tree commands.\nRows with shared rules open their owning explainer; one-line rows already say the whole action.", + 4, generated_help_links_archive_dir, 2, generated_help_sections_archive_dir, @@ -515,8 +597,8 @@ static const GeneratedHelpTopic generated_help_topics[] = { "archive-file", "Archive File Help", "main.archive-file", - "Archive File Help lists the active archive-file commands.\nComplex rows open a short explainer; one-line rows already say the whole action.", - 4, + "Archive File Help lists the active archive-file commands.\nRows with shared rules open their owning explainer; one-line rows already say the whole action.", + 7, generated_help_links_archive_file, 2, generated_help_sections_archive_file, @@ -665,8 +747,8 @@ static const GeneratedHelpTopic generated_help_topics[] = { "showall", "Showall Help", "main.showall", - "Showall Help lists the commands for the current single-volume aggregate view.\nComplex rows open a short explainer; one-line rows already say the whole action.", - 5, + "Showall Help lists the commands for the current single-volume aggregate view.\nRows with shared rules open their owning explainer; one-line rows already say the whole action.", + 9, generated_help_links_showall, 3, generated_help_sections_showall, @@ -675,8 +757,8 @@ static const GeneratedHelpTopic generated_help_topics[] = { "global", "Global Help", "main.global", - "Global Help lists the commands for the current multi-volume aggregate view.\nComplex rows open a short explainer; one-line rows already say the whole action.", - 5, + "Global Help lists the commands for the current multi-volume aggregate view.\nRows with shared rules open their owning explainer; one-line rows already say the whole action.", + 9, generated_help_links_global, 3, generated_help_sections_global, @@ -685,8 +767,8 @@ static const GeneratedHelpTopic generated_help_topics[] = { "f7", "F7 Preview Help", "overlay.f7-dir,overlay.f7-file", - "F7 Preview Help lists the commands that still work while preview stays open.\nComplex rows open a short explainer; one-line rows already say the whole action.", - 6, + "F7 Preview Help lists the commands that still work while preview stays open.\nRows with shared rules open their owning explainer; one-line rows already say the whole action.", + 10, generated_help_links_f7, 2, generated_help_sections_f7, @@ -705,8 +787,8 @@ static const GeneratedHelpTopic generated_help_topics[] = { "f8-dir", "F8 Split Directory Help", "overlay.f8-dir", - "F8 Split Directory Help lists the split-specific rules plus the active split directory-footer commands.\nComplex rows open a short explainer; one-line rows already say the whole action.", - 5, + "F8 Split Directory Help lists the split-specific rules plus the active split directory-footer commands.\nRows with shared rules open their owning explainer; one-line rows already say the whole action.", + 12, generated_help_links_f8_dir, 3, generated_help_sections_f8_dir, @@ -715,8 +797,8 @@ static const GeneratedHelpTopic generated_help_topics[] = { "f8-file", "F8 Split File Help", "overlay.f8-file", - "F8 Split File Help lists the split-specific rules plus the active split file-footer commands.\nComplex rows open a short explainer; one-line rows already say the whole action.", - 6, + "F8 Split File Help lists the split-specific rules plus the active split file-footer commands.\nRows with shared rules open their owning explainer; one-line rows already say the whole action.", + 14, generated_help_links_f8_file, 3, generated_help_sections_f8_file, @@ -763,4 +845,4 @@ static const GeneratedHelpTopic generated_help_topics[] = { }, }; -static const size_t generated_help_topic_count = 36; +static const size_t generated_help_topic_count = 38; diff --git a/src/ui/runtime_help.c b/src/ui/runtime_help.c index 13f4c6c7..1f0bc211 100644 --- a/src/ui/runtime_help.c +++ b/src/ui/runtime_help.c @@ -22,11 +22,14 @@ #define GENERATED_HELP_DEFAULT_WRAP_WIDTH 72 #define GENERATED_HELP_MIN_MAIN_WIDTH 8 #define GENERATED_HELP_WRAP_PADDING 4 +#define GENERATED_HELP_INTRO_RESERVED_FOOTER_COMMANDS 1 +#define GENERATED_HELP_STANDARD_RESERVED_FOOTER_COMMANDS 3 typedef struct { char label[GENERATED_HELP_MAX_ITEM_LABEL]; char summary[GENERATED_HELP_MAX_TEXT_WIDTH]; char detail[GENERATED_HELP_MAX_ITEM_DETAIL]; + const char *linked_topic_id; BOOL selectable; } RuntimeHelpItem; @@ -300,6 +303,28 @@ static char PickFooterKey(const char *label, const char used_keys[], return (char)toupper((unsigned char)label[0]); } +static BOOL TopicIdEquals(const GeneratedHelpTopic *topic, const char *topic_id) { + return topic != NULL && topic->topic_id != NULL && topic_id != NULL && + strcmp(topic->topic_id, topic_id) == 0; +} + +static const char *FindExplainerTopicForLabel(const GeneratedHelpTopic *topic, + const char *label) { + size_t i; + + if (topic == NULL || label == NULL || label[0] == '\0') + return NULL; + + for (i = 0; i < topic->explainer_link_count; ++i) { + if (topic->explainer_links[i].label != NULL && + strcmp(topic->explainer_links[i].label, label) == 0) { + return topic->explainer_links[i].target_topic_id; + } + } + + return NULL; +} + static void TrimWhitespaceInPlace(char *text) { char *start; char *end; @@ -514,6 +539,7 @@ static void FinalizeHelpItem(RuntimeHelpPopupState *state, const char *heading, RuntimeHelpItem *item; char detail[GENERATED_HELP_MAX_ITEM_DETAIL]; size_t detail_len; + const char *linked_topic_id; if (state == NULL || heading == NULL || body == NULL || state->item_count >= GENERATED_HELP_MAX_ITEMS) @@ -529,6 +555,7 @@ static void FinalizeHelpItem(RuntimeHelpPopupState *state, const char *heading, ExtractItemLabel(heading, item->label, sizeof(item->label)); if (item->label[0] == '\0') return; + linked_topic_id = FindExplainerTopicForLabel(state->topic, item->label); detail_len = strlen(detail); if (detail_len >= sizeof(item->detail)) detail_len = sizeof(item->detail) - 1; @@ -537,7 +564,9 @@ static void FinalizeHelpItem(RuntimeHelpPopupState *state, const char *heading, ExtractSummary(detail, item->summary, sizeof(item->summary)); if (item->summary[0] == '\0') snprintf(item->summary, sizeof(item->summary), "%s", detail); - item->selectable = (strcmp(item->summary, item->detail) != 0); + item->linked_topic_id = linked_topic_id; + item->selectable = + (item->linked_topic_id != NULL || strcmp(item->summary, item->detail) != 0); state->item_count++; } @@ -677,6 +706,18 @@ static size_t BuildFooterCommands(RuntimeHelpPopupState *state) { return 0; if (state->contextual_list_mode) { + state->footer_commands[command_count].layout = UI_COMMAND_LAYOUT_ALT_MNEMONIC; + state->footer_commands[command_count].label = + state->current_detail_index == GENERATED_HELP_NO_SELECTION ? "Open" + : "Back"; + state->footer_commands[command_count].primary_key = + state->current_detail_index == GENERATED_HELP_NO_SELECTION ? "Enter" + : "Left"; + state->footer_commands[command_count].secondary_key = + state->current_detail_index == GENERATED_HELP_NO_SELECTION ? "Right" + : "C"; + command_count++; + state->footer_commands[command_count].layout = UI_COMMAND_LAYOUT_KEY_PREFIX; state->footer_commands[command_count].label = "Contents"; state->footer_commands[command_count].primary_key = "C"; @@ -699,7 +740,9 @@ static size_t BuildFooterCommands(RuntimeHelpPopupState *state) { return command_count; } - reserved_tail = 1; + reserved_tail = TopicIdEquals(state->topic, "intro") + ? GENERATED_HELP_INTRO_RESERVED_FOOTER_COMMANDS + : GENERATED_HELP_STANDARD_RESERVED_FOOTER_COMMANDS; memset(used_keys, 0, sizeof(used_keys)); for (i = 0; i < state->topic->explainer_link_count && command_count + reserved_tail < GENERATED_HELP_MAX_FOOTER_COMMANDS; @@ -722,6 +765,19 @@ static size_t BuildFooterCommands(RuntimeHelpPopupState *state) { state->link_command_count = command_count; state->active_link_index = command_count > 0 ? 0 : GENERATED_HELP_NO_SELECTION; + if (!TopicIdEquals(state->topic, "intro")) { + state->footer_commands[command_count].layout = UI_COMMAND_LAYOUT_KEY_PREFIX; + state->footer_commands[command_count].label = "Back"; + state->footer_commands[command_count].primary_key = "Left"; + state->footer_commands[command_count].secondary_key = NULL; + command_count++; + + state->footer_commands[command_count].layout = UI_COMMAND_LAYOUT_KEY_PREFIX; + state->footer_commands[command_count].label = "Contents"; + state->footer_commands[command_count].primary_key = "C"; + state->footer_commands[command_count].secondary_key = NULL; + command_count++; + } state->footer_commands[command_count].layout = UI_COMMAND_LAYOUT_ALT_MNEMONIC; state->footer_commands[command_count].label = "Quit"; state->footer_commands[command_count].primary_key = "Esc"; @@ -959,6 +1015,13 @@ static int HandleGeneratedHelpFooterKey(ViewContext *ctx, int ch, return -1; if (!state->items[state->selected_item_index].selectable) return -1; + if (state->items[state->selected_item_index].linked_topic_id != NULL) { + state->next_topic_id = + state->items[state->selected_item_index].linked_topic_id; + state->reselection_direction = 0; + state->reselection_anchor_index = GENERATED_HELP_NO_SELECTION; + return 1; + } state->next_detail_index = state->selected_item_index; state->reselection_direction = 0; state->reselection_anchor_index = GENERATED_HELP_NO_SELECTION; @@ -985,10 +1048,18 @@ static int HandleGeneratedHelpFooterKey(ViewContext *ctx, int ch, return 0; } + key = islower(ch) ? toupper(ch) : ch; if (ch == KEY_LEFT) { state->back_requested = TRUE; return 1; } + if (key == 'C') { + if (!TopicIdEquals(state->topic, "intro")) { + state->next_topic_id = "intro"; + return 1; + } + return -1; + } if (ch == KEY_RIGHT || ch == CR || ch == LF) { if (state->link_command_count == 0) @@ -1000,7 +1071,6 @@ static int HandleGeneratedHelpFooterKey(ViewContext *ctx, int ch, return 1; } - key = islower(ch) ? toupper(ch) : ch; for (i = 0; i < state->link_command_count; ++i) { if (state->footer_commands[i].primary_key != NULL && state->footer_commands[i].primary_key[0] == key) { diff --git a/tests/test_help_generator.py b/tests/test_help_generator.py index 29dd8763..e7e2e49c 100644 --- a/tests/test_help_generator.py +++ b/tests/test_help_generator.py @@ -41,6 +41,8 @@ def test_help_generator_renders_runtime_topics_and_usage_projection(): assert "### Help System" in manpage assert "**Attributes**: Open the attributes submenu." in manpage assert "### Filter Help" in manpage + assert "### Copy/Move Targets" in manpage + assert "### List Jump" in manpage assert "### Command-line Editing" in manpage assert "### F10 Config" in manpage assert "Use normal glob-like patterns such as `*.c`" in manpage @@ -53,6 +55,7 @@ def test_help_generator_renders_runtime_topics_and_usage_projection(): assert '"main.dir"' in header assert '"prompt.compare-target"' in header assert '"prompt.output-format"' in header + assert 'generated_help_links_list_jump' in header assert 'generated_help_links_filter' in header diff --git a/tests/test_help_source_schema.py b/tests/test_help_source_schema.py index 5a898eb6..65825521 100644 --- a/tests/test_help_source_schema.py +++ b/tests/test_help_source_schema.py @@ -13,6 +13,8 @@ "shared-commands", "tagged", "command-line-editing", + "copy-move-targets", + "list-jump", "vi-keys", "f10", "theming", diff --git a/tests/test_help_text_contract.py b/tests/test_help_text_contract.py index 7afdad2d..07f07268 100644 --- a/tests/test_help_text_contract.py +++ b/tests/test_help_text_contract.py @@ -150,6 +150,31 @@ def _open_help_detail( assert False, screen_text(tui) +def _follow_help_topic( + tui, label, topic_title, *, direction_key=Keys.RIGHT, timeout=1.0, steps=24 +): + _scroll_help_to_text(tui, label) + + for _ in range(steps): + before = screen_text(tui) + screen = tui.send_and_wait_for_condition( + direction_key, + lambda lines: lines if any(topic_title in line for line in lines) else False, + timeout=timeout, + ) + if screen: + return "\n".join(screen) + current = screen_text(tui) + if current != before and topic_title not in current: + tui.send_keystroke(Keys.LEFT, wait=0.05) + current = screen_text(tui) + if label not in current: + current = _scroll_help_to_text(tui, label) + tui.send_keystroke(Keys.DOWN, wait=0.05) + + assert False, screen_text(tui) + + def _open_current_help_detail_title(tui, direction_key=Keys.RIGHT): before = screen_text(tui) tui.send_keystroke(direction_key, wait=0.05) @@ -240,7 +265,7 @@ def test_contextual_help_accepts_csi_arrow_sequences(tmp_path): copy_detail = tui.send_and_wait_for_condition( csi_right, lambda lines: lines - if any("destination prompt can reuse" in line for line in lines) + if any("Copy/Move Targets" in line for line in lines) else False, timeout=1.5, ) @@ -272,7 +297,7 @@ def test_contextual_help_accepts_application_arrow_sequences(tmp_path): copy_detail = tui.send_and_wait_for_condition( Keys.RIGHT, lambda lines: lines - if any("destination prompt can reuse" in line for line in lines) + if any("Copy/Move Targets" in line for line in lines) else False, timeout=1.5, ) @@ -303,7 +328,7 @@ def test_contextual_help_down_arrow_advances_hidden_active_link(tmp_path): moved = tui.send_and_wait_for_condition( Keys.RIGHT, lambda lines: lines - if any("destination prompt can reuse" in line for line in lines) + if any("Copy/Move Targets" in line for line in lines) else False, timeout=1.0, ) @@ -332,7 +357,7 @@ def test_contextual_help_down_arrow_skips_plain_rows_then_scrolls(tmp_path): copy_detail = tui.send_and_wait_for_condition( Keys.RIGHT, lambda lines: lines - if any("destination prompt can reuse" in line for line in lines) + if any("Copy/Move Targets" in line for line in lines) else False, timeout=1.0, ) @@ -389,15 +414,19 @@ def test_contextual_help_down_arrow_eventually_scrolls_visible_page(tmp_path): help_screen = _wait_for_help(tui, "Directory Help") assert "1..9 view:" in help_screen, help_screen - scrolled = _scroll_help_to_text(tui, "eXecute:") - assert "eXecute:" in scrolled, scrolled + scrolled = _scroll_help_to_text(tui, "Z archive:") + assert "Z archive:" in scrolled, scrolled - before = scrolled - scrolled = tui.send_and_wait_for_screen_change(Keys.DOWN, timeout=1.0) - assert scrolled, screen_text(tui) - scrolled_text = "\n".join(scrolled) - assert scrolled_text != before, scrolled_text - assert "Directory Help" in scrolled_text, scrolled_text + advanced = None + for _ in range(8): + tui.send_keystroke(Keys.DOWN, wait=0.05) + current = screen_text(tui) + if "/ jump:" in current or "` dotfiles:" in current: + advanced = current + break + + assert advanced, screen_text(tui) + assert "Directory Help" in advanced, advanced finally: tui.quit() @@ -509,12 +538,9 @@ def test_contextual_help_up_arrow_reselects_visible_links_when_scrolling_back(tm tui.quit() -def test_split_file_help_arrows_scroll_past_visible_link_boundaries_without_wrapping( - tmp_path, -): +def test_split_file_help_arrows_follow_rows_without_wrapping(tmp_path): root = _root_with_file(tmp_path, "split_file_help_arrow_boundaries") tui = _spawn_help_tui(root) - labels = ("1..9 view:", "C/^K copy:", "Filter:", "J compare:") try: assert tui.wait_for_content("alpha.txt", timeout=1.5), screen_text(tui) @@ -533,61 +559,27 @@ def test_split_file_help_arrows_scroll_past_visible_link_boundaries_without_wrap help_screen = _wait_for_help(tui, "F8 Split File Help") assert "C/^K copy:" in help_screen, help_screen - tui.send_keystroke(Keys.HOME, wait=0.05) - plain_style = _visible_cell_style(tui, "1..9 view:") - - tui.send_keystroke(Keys.DOWN, wait=0.05) - selected_style = _visible_cell_style(tui, "1..9 view:") - assert selected_style != plain_style, screen_text(tui) - assert ( - _selected_visible_help_label(tui, labels, selected_style) == "1..9 view:" - ), screen_text(tui) - - tui.send_keystroke(Keys.DOWN, wait=0.05) - assert ( - _selected_visible_help_label(tui, labels, selected_style) == "C/^K copy:" - ), screen_text(tui) - - tui.send_keystroke(Keys.DOWN, wait=0.05) - assert _selected_visible_help_label(tui, labels, selected_style) == "Filter:", screen_text( - tui - ) - - boundary_screen = tui.send_and_wait_for_screen_change(Keys.DOWN, timeout=1.0) - assert boundary_screen, screen_text(tui) - assert _selected_visible_help_label(tui, labels, selected_style) == "Filter:", screen_text( - tui + help_screen = _send_help_key_until_text(tui, Keys.HOME, "1..9 view:") + first_detail = _follow_help_topic( + tui, "C/^K copy:", "Copy/Move Targets", timeout=1.0 ) + assert "Copy/Move Targets" in first_detail, first_detail + tui.send_keystroke(Keys.LEFT, wait=0.05) + assert tui.wait_for_content("F8 Split File Help", timeout=1.0), screen_text(tui) - tui.send_keystroke(Keys.DOWN, wait=0.05) - assert _selected_visible_help_label(tui, labels, selected_style) == "J compare:", screen_text( - tui + compare_detail = _follow_help_topic( + tui, "J compare:", "Compare Help", timeout=1.0 ) + assert "Compare Help" in compare_detail, compare_detail + tui.send_keystroke(Keys.LEFT, wait=0.05) + assert tui.wait_for_content("F8 Split File Help", timeout=1.0), screen_text(tui) tui.send_keystroke(Keys.END, wait=0.05) current = screen_text(tui) assert "F10:" in current, current - tui.send_keystroke(Keys.UP, wait=0.05) - assert _selected_visible_help_label(tui, ("eXecute:",), selected_style) == "eXecute:", screen_text( - tui - ) - - tui.send_keystroke(Keys.UP, wait=0.05) - assert _selected_visible_help_label(tui, ("eXecute:",), selected_style) == "eXecute:", screen_text( - tui - ) - - tui.send_keystroke(Keys.UP, wait=0.05) - assert _selected_visible_help_label(tui, ("Write:",), selected_style) == "Write:", screen_text( - tui - ) - - for _ in range(12): - tui.send_keystroke(Keys.UP, wait=0.05) - assert _selected_visible_help_label(tui, ("Write:",), selected_style) == "Write:", screen_text( - tui - ) + unchanged = tui.send_and_wait_for_screen_change(Keys.DOWN, timeout=0.5) + assert not unchanged, screen_text(tui) finally: tui.quit() @@ -742,12 +734,13 @@ def test_main_f1_help_tracks_directory_file_preview_and_split_contexts(tmp_path) footer_line = next( line for line in help_screen.splitlines() if "Esc/Quit" in line ) + assert "Open" in footer_line, footer_line assert "Contents" in footer_line, footer_line assert "Navigation" in footer_line, footer_line assert "Shared commands" not in footer_line, footer_line assert "F8 split" not in footer_line, footer_line directory_frame = _popup_frame(directory_help_screen, "Directory Help") - assert footer_line.index("Contents") - directory_frame["left"] <= 3, footer_line + assert footer_line.index("Enter/Right") - directory_frame["left"] <= 3, footer_line assert directory_frame["bottom_row"] == directory_frame["footer_row"] + 1, directory_help_screen help_lines = directory_help_screen.splitlines() title_gap = help_lines[directory_frame["title_row"] + 1][ @@ -782,11 +775,10 @@ def test_main_f1_help_tracks_directory_file_preview_and_split_contexts(tmp_path) assert "eXecute:" in help_screen, help_screen assert "Left Arrow:" not in help_screen, help_screen help_screen = _send_help_key_until_text(tui, Keys.HOME, "Copy:") - split_copy_detail = _open_help_detail( - tui, "Copy:", "inactive panel", timeout=1.0 + split_copy_detail = _follow_help_topic( + tui, "Copy:", "Copy/Move Targets", timeout=1.0 ) - assert "inactive panel" in _normalized_help_text(split_copy_detail), split_copy_detail - assert "destination" in _normalized_help_text(split_copy_detail), split_copy_detail + assert "wildcard rename pattern" in _normalized_help_text(split_copy_detail), split_copy_detail tui.send_keystroke(Keys.LEFT, wait=0.05) assert tui.wait_for_content("F8 Split Directory Help", timeout=1.0), screen_text( tui @@ -832,14 +824,15 @@ def test_main_f1_help_tracks_directory_file_preview_and_split_contexts(tmp_path) assert label in help_screen, help_screen assert stale_label not in help_screen, help_screen help_screen = _send_help_key_until_text(tui, Keys.HOME, "C/^K copy:") - copy_detail = _open_help_detail( - tui, "C/^K copy:", "wildcard rename patterns", timeout=1.0 + copy_detail = _follow_help_topic( + tui, "C/^K copy:", "Copy/Move Targets", timeout=1.0 ) - copy_detail = _scroll_help_to_text(tui, "*.bak") normalized_copy_detail = _normalized_help_text(copy_detail) - assert "Ctrl-K copies the tagged set" in normalized_copy_detail, copy_detail - assert "wildcard rename patterns" in normalized_copy_detail, copy_detail - assert "*.bak" in normalized_copy_detail, copy_detail + assert "destination directory" in normalized_copy_detail, copy_detail + assert "local mode page still owns which key copies or moves" in normalized_copy_detail, copy_detail + assert "wildcard rename pattern" in normalized_copy_detail, copy_detail + assert "*.bak" not in normalized_copy_detail, copy_detail + assert "Ctrl-K copies the tagged set" not in normalized_copy_detail, copy_detail tui.send_keystroke(Keys.LEFT, wait=0.05) assert tui.wait_for_content("F8 Split File Help", timeout=1.0), screen_text(tui) help_screen = screen_text(tui) @@ -982,6 +975,7 @@ def test_integrated_help_directory_and_file_modes_do_not_crash(tmp_path): footer_line = next( line for line in help_screen.splitlines() if "Esc/Quit" in line ) + assert "Open" in footer_line, footer_line assert "Contents" in footer_line, footer_line assert "Shared commands" not in footer_line, footer_line assert "F8 split" not in footer_line, footer_line