Skip to main content

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}