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}