lattice_host/command_line_mode.rs
1//! MB.1 (rich minibuffer, tier 1): `command-line-mode`.
2//!
3//! The `:` command line is a **buffer-backed, readline-grade editing
4//! surface** (`docs/dev/architecture/rich-minibuffer.md` §2). Pressing
5//! `:` creates / focuses the synthetic one-line `*command-line*`
6//! `Document` through the mode-owned creation seam
7//! ([`lattice_mode::ModeActivator::ensure_named_document`]) and
8//! focus-swaps it in as the editing buffer
9//! ([`crate::dispatch::Editor::focus_editing_buffer`]). Keys then flow
10//! through the **universal Insert dispatcher**, so the readline chords
11//! (`<C-a>`/`<C-e>`/`<C-b>`/`<C-f>`/`<C-w>`/`<C-u>`, cursor keys,
12//! `<Del>`) edit it directly — no cmdline-specific editing code.
13//!
14//! `command-line-mode` is the **major mode** on that buffer. It owns the
15//! small delta a command line adds over a plain Insert buffer, via an
16//! Insert-mode keymap layer (`Mode::keymap`, resolved through
17//! `keymap_mode_contributions`): `<CR>` submit, `<Esc>` / `<C-c>`
18//! cancel, `<C-p>` / `<C-n>` / `<Up>` / `<Down>` history walk, `<Tab>` /
19//! `<S-Tab>` completion, `<C-h>` describe-under-cursor. Because the
20//! layer is keyed under `MajorMode(command-line-mode)`, the per-keystroke
21//! `keymap_gated_ids` filter scopes it to the `*command-line*` buffer
22//! only (mode-ownership; `mode-architecture.md` §5.3–5.4).
23//!
24//! The handler bodies stay as the (rewired) `Editor::do_command_line_*`
25//! methods reached through the `AppEffect` host boundary — the same
26//! pattern `action:enter-command-line` uses. The buffer is insert-only:
27//! there is no Normal-mode entry, so `<Esc>` cancels cleanly.
28
29use lattice_core::BufferKind;
30use lattice_mode::{
31 Keymap, KeymapEntry, LifecycleFuture, Mode, ModeContext, ModeId, ModeKind, keymap_entry,
32};
33
34/// Synthetic name of the `:` command-line buffer. `:ls` / `:b` can
35/// reach it; `:bn` / `:bp` skip it (`listed = false`).
36pub const COMMAND_LINE_BUFFER_NAME: &str = "*command-line*";
37
38/// Command names bound by `command-line-mode`'s keymap. Registered as
39/// `CommandId`s in `crate::actions` (each maps to an `AppEffect` that
40/// drives the rewired `Editor::do_command_line_*` handler).
41pub const CMDLINE_SUBMIT: &str = "action:command-line-submit";
42pub const CMDLINE_CANCEL: &str = "action:command-line-cancel";
43pub const CMDLINE_HISTORY_PREV: &str = "action:command-line-history-prev";
44pub const CMDLINE_HISTORY_NEXT: &str = "action:command-line-history-next";
45pub const CMDLINE_COMPLETE: &str = "action:command-line-complete";
46pub const CMDLINE_COMPLETE_PREV: &str = "action:command-line-complete-prev";
47pub const CMDLINE_DESCRIBE: &str = "action:command-line-describe-under-cursor";
48pub const CMDLINE_TOGGLE_EXPAND: &str = "action:command-line-toggle-expand";
49
50/// `command-line-mode`: the major mode of the `*command-line*` buffer.
51pub struct CommandLineMode;
52
53impl CommandLineMode {
54 pub fn mode_id() -> ModeId {
55 ModeId::new("command-line-mode")
56 }
57}
58
59impl Mode for CommandLineMode {
60 type Guard = ();
61
62 fn id(&self) -> ModeId {
63 Self::mode_id()
64 }
65
66 fn kind(&self) -> ModeKind {
67 ModeKind::Major
68 }
69
70 /// Not auto-activated by on-disk language detection — the mode is
71 /// activated *by id* through `ensure_named_document`. Returning
72 /// `None` keeps it out of the kind→major lookup so it never claims
73 /// ordinary `Document` buffers.
74 fn target_buffer_kind(&self) -> Option<BufferKind> {
75 None
76 }
77
78 /// `NoFile = true` so `:q`'s dirty guard skips the (always-dirty,
79 /// never-on-disk) prompt buffer — otherwise `:q` would refuse with
80 /// "no write since last change" for the `*command-line*` buffer. NOT
81 /// `ReadOnly` (the prompt is the editing surface). Gutterless +
82 /// no-wrap: a one-line prompt.
83 fn options(&self) -> lattice_config::OptionOverrideSet {
84 lattice_config::overrides! {
85 lattice_config::NoFile = true,
86 lattice_config::Wrap = false,
87 lattice_config::Number = false,
88 lattice_config::SignColumnOption = lattice_config::SignColumn::No,
89 lattice_config::CursorLine = false,
90 }
91 }
92
93 fn keymap(&self) -> Keymap {
94 // Insert-mode overrides that turn a plain one-line buffer into a
95 // command line. Base Insert supplies the readline editing; these
96 // rows only add submit / cancel / history / completion. The
97 // conditional "accept-vs-submit" / "history-vs-complete" logic
98 // lives in the handler bodies (they branch on `completion_state`).
99 Keymap::from_entries(command_line_entries())
100 }
101
102 fn on_activate(&self, _ctx: ModeContext) -> LifecycleFuture<'_, Self::Guard> {
103 Box::pin(async { Ok(()) })
104 }
105}
106
107/// Static entry table for `command-line-mode`'s Insert-layer keymap.
108fn command_line_entries() -> &'static [KeymapEntry] {
109 use std::sync::OnceLock;
110 static ENTRIES: OnceLock<Vec<KeymapEntry>> = OnceLock::new();
111 ENTRIES.get_or_init(|| {
112 vec![
113 keymap_entry! { mode: Command, chord: "<CR>", doc: "Submit the command line", cmd: "action:command-line-submit" },
114 keymap_entry! { mode: Command, chord: "<Esc>", doc: "Cancel the command line", cmd: "action:command-line-cancel" },
115 keymap_entry! { mode: Command, chord: "<C-c>", doc: "Cancel the command line", cmd: "action:command-line-cancel" },
116 keymap_entry! { mode: Command, chord: "<C-p>", doc: "Previous history entry", cmd: "action:command-line-history-prev" },
117 keymap_entry! { mode: Command, chord: "<C-n>", doc: "Next history entry", cmd: "action:command-line-history-next" },
118 keymap_entry! { mode: Command, chord: "<Up>", doc: "Previous history entry", cmd: "action:command-line-history-prev" },
119 keymap_entry! { mode: Command, chord: "<Down>", doc: "Next history entry", cmd: "action:command-line-history-next" },
120 keymap_entry! { mode: Command, chord: "<Tab>", doc: "Complete / next candidate", cmd: "action:command-line-complete" },
121 keymap_entry! { mode: Command, chord: "<S-Tab>", doc: "Previous candidate", cmd: "action:command-line-complete-prev" },
122 keymap_entry! { mode: Command, chord: "<C-h>", doc: "Describe command / arg under cursor", cmd: "action:command-line-describe-under-cursor" },
123 keymap_entry! { mode: Command, chord: "<C-x><C-e>", doc: "Expand the `:` line into the full-modal mini-buffer band (or collapse it)", cmd: "action:command-line-toggle-expand" },
124 // YR.6: vim's omni-completion chord, and it means the same
125 // thing here — ask whatever knows about this position.
126 // `<Tab>` stays inline completion from `ArgSpec.completion`;
127 // this opens the picker from `ArgSpec.picker`.
128 keymap_entry! { mode: Command, chord: "<C-x><C-o>", doc: "Open the picker for the argument under the cursor", cmd: "action:open-arg-picker" },
129 // MB.2: also from the expanded band's Normal mode, so collapse
130 // works without first re-entering Insert.
131 keymap_entry! { mode: Normal, chord: "<C-x><C-e>", doc: "Collapse the mini-buffer band back to the one-row `:` line", cmd: "action:command-line-toggle-expand" },
132 ]
133 })
134}