1#![allow(unsafe_code)]
9
10use std::any::TypeId;
38
39pub trait OptionGroup: 'static {
43 const NAME: &'static str;
47
48 const DOC: &'static str;
51
52 fn type_id() -> TypeId
54 where
55 Self: Sized,
56 {
57 TypeId::of::<Self>()
58 }
59}
60
61pub struct OptionGroupMetadata {
66 pub name: &'static str,
68 pub doc: &'static str,
70 pub type_id: fn() -> TypeId,
74}
75
76impl OptionGroupMetadata {
77 pub const fn for_group<G: OptionGroup>() -> Self {
92 Self {
93 name: G::NAME,
94 doc: G::DOC,
95 type_id: TypeId::of::<G>,
96 }
97 }
98}
99
100#[linkme::distributed_slice]
105pub static GROUP_DECLS: [&'static OptionGroupMetadata];
106
107pub const fn ends_with_mode_suffix(s: &str) -> bool {
126 let bytes = s.as_bytes();
127 let suffix = b"-mode";
128 let n = bytes.len();
129 let m = suffix.len();
130 if n < m {
131 return false;
132 }
133 let mut i = 0;
134 while i < m {
135 if bytes[n - m + i] != suffix[i] {
136 return false;
137 }
138 i += 1;
139 }
140 true
141}
142
143pub struct Editor;
154impl OptionGroup for Editor {
155 const NAME: &'static str = "editor";
156 const DOC: &'static str = "Bare-named editor options (tabstop, number, wrap, foldmethod, ...). \
157 Reserved namespace -- plugins must use their own prefix.";
158}
159
160pub struct Display;
164impl OptionGroup for Display {
165 const NAME: &'static str = "display";
166 const DOC: &'static str = "Visual-presentation options across modes \
167 (line numbers, wrap, whitespace visualization, ...).";
168}
169
170pub struct Editing;
173impl OptionGroup for Editing {
174 const NAME: &'static str = "editing";
175 const DOC: &'static str = "Editing-related options (search, indent, auto-pair, ...).";
176}
177
178pub struct Lsp;
182impl OptionGroup for Lsp {
183 const NAME: &'static str = "lsp";
184 const DOC: &'static str = "Every option owned by an LSP mode. \
185 `:customize lsp` shows the union sectioned by owning mode.";
186}
187
188pub struct Completion;
191impl OptionGroup for Completion {
192 const NAME: &'static str = "completion";
193 const DOC: &'static str =
194 "Completion options across providers (LSP, snippets, buffer-words, paths).";
195}
196
197pub struct Picker;
199impl OptionGroup for Picker {
200 const NAME: &'static str = "picker";
201 const DOC: &'static str = "File finder, command palette, symbol search.";
202}
203
204pub struct Filetree;
206impl OptionGroup for Filetree {
207 const NAME: &'static str = "filetree";
208 const DOC: &'static str = "File tree navigation buffer.";
209}
210
211pub struct Oil;
213impl OptionGroup for Oil {
214 const NAME: &'static str = "oil";
215 const DOC: &'static str = "Oil-style editable directory buffer.";
216}
217
218pub struct Help;
220impl OptionGroup for Help {
221 const NAME: &'static str = "help";
222 const DOC: &'static str = "Help / documentation buffers.";
223}
224
225pub struct Appearance;
228impl OptionGroup for Appearance {
229 const NAME: &'static str = "appearance";
230 const DOC: &'static str =
231 "Appearance / theming options (colors, sprite icons, font rendering).";
232}
233
234pub struct Messages;
236impl OptionGroup for Messages {
237 const NAME: &'static str = "messages";
238 const DOC: &'static str = "Echo-area + `*messages*` buffer + tracing bridge.";
239}
240
241pub struct Tabline;
244impl OptionGroup for Tabline {
245 const NAME: &'static str = "tabline";
246 const DOC: &'static str = "Tabline (vim-style tab strip).";
247}
248
249pub struct Magit;
253impl OptionGroup for Magit {
254 const NAME: &'static str = "magit";
255 const DOC: &'static str = "Magit git porcelain buffers.";
256}
257
258pub struct Notifications;
261impl OptionGroup for Notifications {
262 const NAME: &'static str = "notifications";
263 const DOC: &'static str = "Corner-anchored notifications.";
264}
265
266pub struct Terminal;
270impl OptionGroup for Terminal {
271 const NAME: &'static str = "terminal";
272 const DOC: &'static str = "PTY-backed terminal buffer.";
273}
274
275pub struct Search;
280impl OptionGroup for Search {
281 const NAME: &'static str = "search";
282 const DOC: &'static str =
283 "Project-search options: `:search` clustering, context lines, scan limits.";
284}
285
286pub struct Snippet;
292impl OptionGroup for Snippet {
293 const NAME: &'static str = "snippet";
294 const DOC: &'static str = "Snippet engine: activation policy + supported-language allowlist.";
295}
296
297pub struct Modeline;
303impl OptionGroup for Modeline {
304 const NAME: &'static str = "modeline";
305 const DOC: &'static str =
306 "Modeline (bottom per-pane status row): per-zone element layout + separator.";
307}
308
309pub struct Project;
319impl OptionGroup for Project {
320 const NAME: &'static str = "project";
321 const DOC: &'static str =
322 "Project resolution: the marker set a buffer's project root is discovered by.";
323}
324
325pub struct Mouse;
335impl OptionGroup for Mouse {
336 const NAME: &'static str = "mouse";
337 const DOC: &'static str =
338 "Mouse reporting: whether the editor captures mouse events from the terminal.";
339}
340
341pub struct Diagnostics;
346impl OptionGroup for Diagnostics {
347 const NAME: &'static str = "diagnostics";
348 const DOC: &'static str =
349 "Diagnostics presentation: inline end-of-line summary scope + min-severity.";
350}
351
352pub struct Pane;
355impl OptionGroup for Pane {
356 const NAME: &'static str = "pane";
357 const DOC: &'static str = "Per-pane behaviour: the bound on each pane's buffer history trail.";
358}
359
360pub struct Plugin;
363impl OptionGroup for Plugin {
364 const NAME: &'static str = "plugin";
365 const DOC: &'static str = "Plugin observability: the global default boundary-trace verbosity.";
366}
367
368pub struct Window;
371impl OptionGroup for Window {
372 const NAME: &'static str = "window";
373 const DOC: &'static str =
374 "GPUI window options (borderless chrome, maximize on launch). GPUI peer only.";
375}
376
377pub struct Ai;
383impl OptionGroup for Ai {
384 const NAME: &'static str = "ai";
385 const DOC: &'static str = "AI-agent (ACP client) options: per-process log capture + level.";
386}
387
388#[linkme::distributed_slice(GROUP_DECLS)]
397static EDITOR_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Editor>();
398
399#[linkme::distributed_slice(GROUP_DECLS)]
400static DISPLAY_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Display>();
401
402#[linkme::distributed_slice(GROUP_DECLS)]
403static EDITING_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Editing>();
404
405#[linkme::distributed_slice(GROUP_DECLS)]
406static LSP_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Lsp>();
407
408#[linkme::distributed_slice(GROUP_DECLS)]
409static COMPLETION_GROUP_LINK: &OptionGroupMetadata =
410 &OptionGroupMetadata::for_group::<Completion>();
411
412#[linkme::distributed_slice(GROUP_DECLS)]
413static PICKER_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Picker>();
414
415#[linkme::distributed_slice(GROUP_DECLS)]
416static FILETREE_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Filetree>();
417
418#[linkme::distributed_slice(GROUP_DECLS)]
419static OIL_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Oil>();
420
421#[linkme::distributed_slice(GROUP_DECLS)]
422static HELP_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Help>();
423
424#[linkme::distributed_slice(GROUP_DECLS)]
425static APPEARANCE_GROUP_LINK: &OptionGroupMetadata =
426 &OptionGroupMetadata::for_group::<Appearance>();
427
428#[linkme::distributed_slice(GROUP_DECLS)]
429static MESSAGES_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Messages>();
430
431#[linkme::distributed_slice(GROUP_DECLS)]
432static TABLINE_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Tabline>();
433
434#[linkme::distributed_slice(GROUP_DECLS)]
435static TERMINAL_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Terminal>();
436
437#[linkme::distributed_slice(GROUP_DECLS)]
438static SEARCH_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Search>();
439
440#[linkme::distributed_slice(GROUP_DECLS)]
441static SNIPPET_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Snippet>();
442
443#[linkme::distributed_slice(GROUP_DECLS)]
444static MODELINE_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Modeline>();
445
446#[linkme::distributed_slice(GROUP_DECLS)]
447static MOUSE_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Mouse>();
448
449#[linkme::distributed_slice(GROUP_DECLS)]
450static DIAGNOSTICS_GROUP_LINK: &OptionGroupMetadata =
451 &OptionGroupMetadata::for_group::<Diagnostics>();
452
453#[linkme::distributed_slice(GROUP_DECLS)]
454static WINDOW_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Window>();
455
456#[linkme::distributed_slice(GROUP_DECLS)]
457static AI_GROUP_LINK: &OptionGroupMetadata = &OptionGroupMetadata::for_group::<Ai>();
458
459const _: () = {
466 assert!(!ends_with_mode_suffix(Editor::NAME));
467 assert!(!ends_with_mode_suffix(Display::NAME));
468 assert!(!ends_with_mode_suffix(Editing::NAME));
469 assert!(!ends_with_mode_suffix(Lsp::NAME));
470 assert!(!ends_with_mode_suffix(Completion::NAME));
471 assert!(!ends_with_mode_suffix(Picker::NAME));
472 assert!(!ends_with_mode_suffix(Filetree::NAME));
473 assert!(!ends_with_mode_suffix(Oil::NAME));
474 assert!(!ends_with_mode_suffix(Help::NAME));
475 assert!(!ends_with_mode_suffix(Appearance::NAME));
476 assert!(!ends_with_mode_suffix(Terminal::NAME));
477 assert!(!ends_with_mode_suffix(Search::NAME));
478 assert!(!ends_with_mode_suffix(Snippet::NAME));
479 assert!(!ends_with_mode_suffix(Modeline::NAME));
480 assert!(!ends_with_mode_suffix(Diagnostics::NAME));
481 assert!(!ends_with_mode_suffix(Window::NAME));
482 assert!(!ends_with_mode_suffix(Ai::NAME));
483};
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn ends_with_mode_suffix_positive() {
491 assert!(ends_with_mode_suffix("rust-mode"));
492 assert!(ends_with_mode_suffix("lsp-completion-mode"));
493 assert!(ends_with_mode_suffix("a-mode"));
494 assert!(ends_with_mode_suffix("-mode"));
495 }
496
497 #[test]
498 fn ends_with_mode_suffix_negative() {
499 assert!(!ends_with_mode_suffix("editor"));
500 assert!(!ends_with_mode_suffix("lsp"));
501 assert!(!ends_with_mode_suffix("mode"));
502 assert!(!ends_with_mode_suffix("modal"));
503 assert!(!ends_with_mode_suffix(""));
504 assert!(!ends_with_mode_suffix("foo"));
505 }
506
507 #[test]
508 fn builtin_groups_have_distinct_type_ids() {
509 let editor = Editor::type_id();
513 let display = Display::type_id();
514 assert_ne!(editor, display);
515 }
516
517 #[test]
518 fn linkme_aggregates_builtin_groups() {
519 let names: Vec<_> = GROUP_DECLS.iter().map(|g| g.name).collect();
521 for expected in [
522 "editor",
523 "display",
524 "editing",
525 "lsp",
526 "completion",
527 "picker",
528 "filetree",
529 "oil",
530 "help",
531 "appearance",
532 ] {
533 assert!(
534 names.contains(&expected),
535 "linkme slice missing group: {expected}; saw {names:?}"
536 );
537 }
538 }
539
540 #[test]
541 fn no_duplicate_group_names_in_slice() {
542 let mut names: Vec<&str> = GROUP_DECLS.iter().map(|g| g.name).collect();
543 names.sort_unstable();
544 let before = names.len();
545 names.dedup();
546 assert_eq!(
547 names.len(),
548 before,
549 "duplicate group display name in linkme slice",
550 );
551 }
552}