Skip to main content

lattice_mode/modes/
mod.rs

1//! Foundation modes that ship with `lattice-mode`.
2//!
3//! Includes the catch-all [`TextMode`] plus the modes that
4//! are genuinely foundational or shared across many features
5//! (`help-mode`, `hover-mode`). Per the rule of thumb -- "a
6//! mode lives with the crate that owns its associated feature"
7//! -- modes for feature crates live with their crate:
8//!
9//! - Language modes (`rust-mode`, `markdown-mode`, ...) live
10//!   in `lattice-syntax`.
11//! - LSP log modes + the `lsp-mode` umbrella + its sub-modes
12//!   live in `lattice-lsp`.
13//! - **`oil-mode`** lives in `lattice-oil`.
14//! - **`file-tree-mode`** lives in `lattice-file-tree`.
15//!
16//! Exceptions kept here:
17//!
18//! - [`TextMode`] -- the catch-all major when no language
19//!   matches; foundational, no owning feature crate.
20//! - [`HelpMode`] -- shared across `:help`,
21//!   `:describe-*`, `:apropos`, `:keymap`, `:options`,
22//!   `:customize`. Many features compose with it; living
23//!   under any one of those crates would be arbitrary.
24//! - [`HoverMode`] -- shared across LSP hover, signature
25//!   help, future diagnostic-at-cursor popups.
26//! - Display minor modes (`line-numbers-mode`, `wrap-mode`,
27//!   ...) -- renderer-agnostic; they contribute typed options
28//!   that the renderer reads, no owning feature crate.
29//!
30//! All modes registered here self-register at App boot via
31//! [`register_foundation_modes`]. Feature-crate modes have
32//! their own `register_<X>_modes` entry point that the App's
33//! boot path calls alongside this one.
34
35pub mod completion;
36pub mod display;
37pub mod help;
38pub mod hover;
39pub mod image;
40pub mod messages;
41pub mod surround;
42pub mod table;
43pub mod text;
44pub mod which_key;
45
46pub use completion::{
47    ActiveCompletionSources, BufferWordsMode, CompletionMode, CompletionPopupMode,
48    PathCompletionMode,
49};
50pub use display::{
51    CurrentLineHighlightMode, LineNumbersMode, ReadOnlyMode, RelativeLineNumbersMode,
52    WhitespaceShowMode, WrapMode,
53};
54pub use help::{HelpMode, register_help_mode_actions};
55pub use hover::HoverMode;
56pub use image::{ImageFileMediaSource, ImageMode, is_image_path, register_image_media_source};
57pub use messages::MessagesMode;
58pub use text::TextMode;
59pub use which_key::{
60    WhichKeyGrid, WhichKeyMode, install as install_which_key, wire as wire_which_key,
61};
62
63use crate::registry::ModeRegistry;
64
65/// Register every foundation mode against `registry`. Called
66/// from the App's mode-registry boot path before any buffer is
67/// created.
68///
69/// **Not idempotent:** each mode is registered with `.expect(..)`, so a
70/// second call on the same registry panics on the first
71/// [`RegistrationError::Duplicate`](crate::RegistrationError::Duplicate).
72/// The App calls it exactly once.
73///
74/// # Examples
75///
76/// ```
77/// use lattice_mode::{register_foundation_modes, ModeRegistry, TextMode};
78///
79/// let mut registry = ModeRegistry::new();
80/// register_foundation_modes(&mut registry);
81/// assert!(registry.is_registered(TextMode::mode_id()));
82/// assert!(registry.is_registered(lattice_mode::EmacsKeysMode::mode_id()));
83/// ```
84pub fn register_foundation_modes(registry: &mut ModeRegistry) {
85    registry
86        .register(TextMode)
87        .expect("text-mode must register without conflict");
88    // `FileTreeMode` registers from
89    // `lattice_listing::file_tree::register_file_tree_modes`, `OilMode`
90    // from `lattice_listing::oil::register_oil_modes` -- both called by
91    // the App's boot path alongside this function.
92    registry
93        .register(HelpMode)
94        .expect("help-mode must register without conflict");
95    // `image-mode`: the major for a buffer backed by a picture. It PRESENTS
96    // its file types rather than editing them, so the open path never reads
97    // the bytes as text — which is what `:e diagram.png` used to fail on.
98    registry
99        .register(ImageMode)
100        .expect("image-mode must register without conflict");
101    // `repl-mode`: the REPL input surface (i/a/o/… → jump-to-prompt+Insert).
102    // A `Manual` minor mode; REPL majors (`ai-conversation`, terminal, claude)
103    // pull it in via `implies()`, so it never touches an ordinary buffer.
104    crate::repl_mode::register_repl_mode(registry);
105    // RV.1: `refreshable-view-mode` owns the shared `gr` ("refresh this
106    // view"). A `Manual` minor pulled in automatically by the implies
107    // cascade for any mode declaring `refresh_action()`, so it never
108    // attaches to an ordinary document — `gr` there stays LSP
109    // references. Registered here so the cascade can find it.
110    crate::refreshable_view_mode::register_refreshable_view_mode(registry);
111    // OA.4b: `foldable-view-mode` owns the shared `<Tab>` / `<S-Tab>` ("fold
112    // the block at point / every block"). Same shape and same reason as the
113    // line above: a `Manual` minor pulled in by the implies cascade for any
114    // mode declaring `fold_toggle_action()`, so `<Tab>` in an ordinary
115    // document stays jump-list-forward.
116    crate::foldable_view_mode::register_foldable_view_mode(registry);
117    // TB.1: `table-mode` owns pipe-table editing on every major that HAS
118    // pipe tables — markdown's and org's alike. A `Majors` policy rather
119    // than the cascade above, because a table mode is not something another
120    // mode declares a target for: it either applies to your major or it does
121    // not, and the list of majors is the whole statement.
122    crate::modes::table::mode::register_table_mode(registry);
123    // msg-mode.1: `messages-mode` is the major mode for the
124    // editor's `*messages*` audit-log buffer. Replaces the
125    // pre-msg-mode `text-mode + read-only-mode` combo so the
126    // buffer's identity matches `lsp-log-mode`'s pattern.
127    registry
128        .register(MessagesMode)
129        .expect("messages-mode must register without conflict");
130    registry
131        .register(HoverMode)
132        .expect("hover-mode must register without conflict");
133    // CSM.K1 (insert-completion.md §12): the two-mode split.
134    // `completion-mode` is the persistent gate (auto-active on
135    // writable buffers); `completion-popup-mode` is the
136    // transient popup-active marker the keymap-overlay sync
137    // tracks. Both are marker minors today; the host owns
138    // activation lifecycle.
139    registry
140        .register(CompletionMode)
141        .expect("completion-mode must register without conflict");
142    registry
143        .register(CompletionPopupMode)
144        .expect("completion-popup-mode must register without conflict");
145    // CSM.4: buffer-words-mode -- first source-contributing
146    // mode. Auto-activates on writable kinds via
147    // `auto_activated_minors_for_buffer_kind`; the popup's
148    // all-sources view and `<C-b>` filter both consume its
149    // contribution.
150    registry
151        .register(BufferWordsMode)
152        .expect("buffer-words-mode must register without conflict");
153    // CSM.7: path-completion-mode -- contributes filesystem-
154    // entry candidates inside string scopes.
155    registry
156        .register(PathCompletionMode)
157        .expect("path-completion-mode must register without conflict");
158    // M.7.0: display minor modes -- user-toggleable wrappers
159    // around typed display options.
160    registry
161        .register(LineNumbersMode)
162        .expect("line-numbers-mode must register without conflict");
163    registry
164        .register(RelativeLineNumbersMode)
165        .expect("relative-line-numbers-mode must register without conflict");
166    registry
167        .register(WrapMode)
168        .expect("wrap-mode must register without conflict");
169    registry
170        .register(ReadOnlyMode)
171        .expect("read-only-mode must register without conflict");
172    // M.7.2: whitespace + current-line-highlight modes. Their
173    // backing typed options exist; the renderer's painting hooks
174    // land in M.7.3.
175    registry
176        .register(WhitespaceShowMode)
177        .expect("whitespace-show-mode must register without conflict");
178    registry
179        .register(CurrentLineHighlightMode)
180        .expect("current-line-highlight-mode must register without conflict");
181    // BC.5: `emacs-keys-mode` — the default-on universal `<C-x>` leader
182    // tribute. A renderer-agnostic builtin with no owning feature crate, so it
183    // registers with the foundation set. Its keymap layer is pushed host-side
184    // (config-dependent prefix/enable + the live `KeymapHandle`) via
185    // `emacs_keys_layer_bindings`.
186    crate::emacs_keys_mode::register_emacs_keys_mode(registry);
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn foundation_modes_register_without_conflict() {
195        let mut registry = ModeRegistry::new();
196        register_foundation_modes(&mut registry);
197        assert!(registry.is_registered(TextMode::mode_id()));
198        // FileTreeMode + OilMode register from their feature
199        // crates' helpers; not asserted here.
200        assert!(registry.is_registered(HelpMode::mode_id()));
201        assert!(registry.is_registered(MessagesMode::mode_id()));
202        assert!(registry.is_registered(HoverMode::mode_id()));
203        assert!(registry.is_registered(CompletionMode::mode_id()));
204        assert!(registry.is_registered(CompletionPopupMode::mode_id()));
205        assert!(registry.is_registered(BufferWordsMode::mode_id()));
206        assert!(registry.is_registered(PathCompletionMode::mode_id()));
207        // M.7.0 display minors.
208        assert!(registry.is_registered(LineNumbersMode::mode_id()));
209        assert!(registry.is_registered(RelativeLineNumbersMode::mode_id()));
210        assert!(registry.is_registered(WrapMode::mode_id()));
211        assert!(registry.is_registered(ReadOnlyMode::mode_id()));
212        // M.7.2 display minors.
213        assert!(registry.is_registered(WhitespaceShowMode::mode_id()));
214        assert!(registry.is_registered(CurrentLineHighlightMode::mode_id()));
215    }
216}