Skip to main content

lattice_host/
search_line_mode.rs

1//! MB.5a (rich minibuffer): `search-line-mode`.
2//!
3//! The `/`·`?` search line is a **buffer-backed, readline-grade editing
4//! surface** (`docs/dev/architecture/rich-minibuffer.md` §6). Pressing
5//! `/` or `?` creates / focuses the synthetic one-line `*search-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 search-specific editing code.
13//!
14//! `search-line-mode` is the **major mode** on that buffer. It owns the
15//! small delta a search 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. Because the layer is keyed under `MajorMode(search-line-mode)`,
19//! the per-keystroke `keymap_gated_ids` filter scopes it to the
20//! `*search-line*` buffer only (mode-ownership; `mode-architecture.md`
21//! §5.3–5.4).
22//!
23//! The buffer is insert-only: there is no Normal-mode entry, so `<Esc>`
24//! cancels cleanly.
25
26use lattice_core::BufferKind;
27use lattice_mode::{
28    Keymap, KeymapEntry, LifecycleFuture, Mode, ModeContext, ModeId, ModeKind, keymap_entry,
29};
30
31/// Synthetic name of the `/`·`?` search-line buffer. `ephemeral`
32/// keeps it out of `:ls` and the `:bn`/`:bp` cycle.
33pub const SEARCH_LINE_BUFFER_NAME: &str = "*search-line*";
34
35/// Command names bound by `search-line-mode`'s keymap. Registered as
36/// `CommandId`s in `crate::actions` (each maps to an `AppEffect` that
37/// drives the `Editor::do_search_line_*` handler).
38pub const SEARCH_LINE_SUBMIT: &str = "action:search-line-submit";
39pub const SEARCH_LINE_CANCEL: &str = "action:search-line-cancel";
40pub const SEARCH_LINE_TOGGLE_EXPAND: &str = "action:search-line-toggle-expand";
41
42/// `search-line-mode`: the major mode of the `*search-line*` buffer.
43pub struct SearchLineMode;
44
45impl SearchLineMode {
46    pub fn mode_id() -> ModeId {
47        ModeId::new("search-line-mode")
48    }
49}
50
51impl Mode for SearchLineMode {
52    type Guard = ();
53
54    fn id(&self) -> ModeId {
55        Self::mode_id()
56    }
57
58    fn kind(&self) -> ModeKind {
59        ModeKind::Major
60    }
61
62    /// Not auto-activated by on-disk language detection — the mode is
63    /// activated *by id* through `ensure_named_document`. Returning
64    /// `None` keeps it out of the kind→major lookup so it never claims
65    /// ordinary `Document` buffers.
66    fn target_buffer_kind(&self) -> Option<BufferKind> {
67        None
68    }
69
70    /// `NoFile = true` so `:q`'s dirty guard skips the (always-dirty,
71    /// never-on-disk) prompt buffer. Gutterless + no-wrap: a one-line
72    /// prompt.
73    fn options(&self) -> lattice_config::OptionOverrideSet {
74        lattice_config::overrides! {
75            lattice_config::NoFile = true,
76            lattice_config::Wrap = false,
77            lattice_config::Number = false,
78            lattice_config::SignColumnOption = lattice_config::SignColumn::No,
79            lattice_config::CursorLine = false,
80        }
81    }
82
83    fn keymap(&self) -> Keymap {
84        Keymap::from_entries(search_line_entries())
85    }
86
87    fn on_activate(&self, _ctx: ModeContext) -> LifecycleFuture<'_, Self::Guard> {
88        Box::pin(async { Ok(()) })
89    }
90}
91
92/// Static entry table for `search-line-mode`'s Insert-layer keymap.
93fn search_line_entries() -> &'static [KeymapEntry] {
94    use std::sync::OnceLock;
95    static ENTRIES: OnceLock<Vec<KeymapEntry>> = OnceLock::new();
96    ENTRIES.get_or_init(|| {
97        vec![
98            keymap_entry! { mode: Search, chord: "<CR>", doc: "Submit the search pattern", cmd: "action:search-line-submit" },
99            keymap_entry! { mode: Search, chord: "<Esc>", doc: "Cancel the search line", cmd: "action:search-line-cancel" },
100            keymap_entry! { mode: Search, chord: "<C-c>", doc: "Cancel the search line", cmd: "action:search-line-cancel" },
101            keymap_entry! { mode: Search, chord: "<BS>", doc: "Delete a character, or cancel the search when the pattern is empty", cmd: "action:search-line-backspace" },
102            keymap_entry! { mode: Search, chord: "<C-p>", doc: "Previous search history entry", cmd: "action:search-line-history-prev" },
103            keymap_entry! { mode: Search, chord: "<C-n>", doc: "Next search history entry", cmd: "action:search-line-history-next" },
104            keymap_entry! { mode: Search, chord: "<Up>", doc: "Previous search history entry", cmd: "action:search-line-history-prev" },
105            keymap_entry! { mode: Search, chord: "<Down>", doc: "Next search history entry", cmd: "action:search-line-history-next" },
106            keymap_entry! { mode: Search, chord: "<C-x><C-e>", doc: "Expand the `/`·`?` line into the full-modal mini-buffer band (or collapse it)", cmd: "action:search-line-toggle-expand" },
107            // MB.5c: also from the expanded band's Normal mode, so collapse
108            // works without first re-entering Insert.
109            keymap_entry! { mode: Normal, chord: "<C-x><C-e>", doc: "Collapse the mini-buffer band back to the one-row `/`·`?` line", cmd: "action:search-line-toggle-expand" },
110        ]
111    })
112}