lattice_magit/magit_nav_mode.rs
1//! `magit-nav-mode` — the magit chords that are safe in ANY buffer.
2//!
3//! ## Why this is split from `magit-core-mode`
4//!
5//! `magit-core-mode` claims bare letters — `i`, `C`, `D`, `S`, `U`, `q`,
6//! `yr` — and its own doc explains what makes that legitimate: every major
7//! it attaches to is a **read-only list**, so those letters shadow nothing
8//! the user could otherwise be doing. `magit-commit-mode` is excluded by
9//! name for exactly that reason; listing it made `i` open the .gitignore
10//! prompt instead of entering Insert, and a commit message became
11//! untypeable.
12//!
13//! That rule is enforced by `ActivationPolicy::Majors`, which is an
14//! explicit list. It is NOT enforced for a mode that reaches
15//! `magit-core-mode` through `Mode::implies` — and `magit-project-diff-mode`
16//! did exactly that, so the editable cross-file diff inherited every one of
17//! those letters. `i` did not insert.
18//!
19//! Rather than override them back one by one — a subtractive rule written
20//! additively, whose override list would have to track `magit-core-mode`
21//! forever — the four chords that are *actually* universal move here.
22//! `magit-core-mode` implies this mode, so read-only views are unchanged;
23//! an editable magit view implies THIS one and never sees the letters.
24//!
25//! The distinction is what each chord assumes, not which buffer wants it:
26//! navigating sections and folding are meaningful wherever there are
27//! sections; `i`/`C`/`D`/`S` assume nothing is editable.
28
29use std::sync::{Arc, OnceLock};
30
31use lattice_mode::{
32 ActivationPolicy, CapabilitySet, Keymap, KeymapEntry, LifecycleFuture, Mode, ModeContext,
33 ModeId, ModeKind, keymap_entry,
34};
35
36pub struct MagitNavMode;
37
38impl MagitNavMode {
39 pub fn mode_id() -> ModeId {
40 ModeId::new("magit-nav-mode")
41 }
42}
43
44fn nav_entries() -> &'static [KeymapEntry] {
45 static ENTRIES: OnceLock<Vec<KeymapEntry>> = OnceLock::new();
46 ENTRIES.get_or_init(|| {
47 vec![
48 keymap_entry! { mode: Normal, chord: "]]", doc: "Next section", cmd: "action:magit-next-section" },
49 keymap_entry! { mode: Normal, chord: "[[", doc: "Previous section", cmd: "action:magit-prev-section" },
50 ]
51 })
52}
53
54impl Mode for MagitNavMode {
55 type Guard = ();
56
57 fn id(&self) -> ModeId {
58 Self::mode_id()
59 }
60
61 fn kind(&self) -> ModeKind {
62 ModeKind::Minor
63 }
64
65 /// Manual: reached by implication from `magit-core-mode` (read-only
66 /// views) or declared directly (editable ones). Never auto-attached —
67 /// `]]` in an ordinary buffer is not magit's.
68 fn activation_policy(&self) -> ActivationPolicy {
69 ActivationPolicy::Manual
70 }
71
72 fn keymap(&self) -> Keymap {
73 Keymap::from_entries(nav_entries())
74 }
75
76 fn required_capabilities(&self) -> CapabilitySet {
77 CapabilitySet::empty()
78 }
79
80 fn on_activate(&self, _ctx: ModeContext) -> LifecycleFuture<'_, ()> {
81 Box::pin(async { Ok(()) })
82 }
83}
84
85pub fn register_magit_nav_mode(registry: &mut lattice_mode::ModeRegistry) {
86 registry
87 .register(MagitNavMode)
88 .expect("magit-nav-mode registers without conflict");
89}
90
91/// The handlers these chords fire live on `magit-core-mode`, which
92/// registers them once at boot. Kept as an `Arc` re-export point so the
93/// split does not duplicate handler bodies — the rule this whole mode
94/// exists to honour.
95pub(crate) fn _handlers_live_on_core() -> Option<Arc<()>> {
96 None
97}