Skip to main content

lattice_completion/builtins/
mod.rs

1//! Built-in pluggable stage implementations shipped with the
2//! editor. Plugin-defined alternatives register through the same
3//! [`crate::CompletionRegistry`] and replace these defaults via
4//! `default_matcher` / `default_ranker` / `default_annotators`.
5
6pub mod annotators;
7pub mod generators;
8pub mod matchers;
9pub mod rankers;
10
11use crate::registry::{AnnotatorId, CompletionRegistry, GeneratorId, MatcherId, RankerId};
12
13/// Set of ids returned by [`populate`] -- mirrors `Builtins` /
14/// `ExBuiltins` in `lattice-grammar`. Lets the host wire defaults
15/// from known ids without searching by name.
16#[derive(Debug, Clone, Copy)]
17pub struct CompletionBuiltins {
18    pub gen_commands: GeneratorId,
19    pub gen_files: GeneratorId,
20    pub gen_directories: GeneratorId,
21    pub match_prefix: MatcherId,
22    pub match_substring: MatcherId,
23    pub match_fuzzy: MatcherId,
24    pub rank_score: RankerId,
25    pub rank_alphabetical: RankerId,
26    pub anno_kind_label: AnnotatorId,
27    pub anno_doc_snippet: AnnotatorId,
28}
29
30/// Register the built-ins shipped with the editor and configure
31/// sensible defaults: prefix matcher, score ranker, both
32/// annotators active.
33pub fn populate(registry: &mut CompletionRegistry) -> CompletionBuiltins {
34    let gen_commands = registry.register_generator(
35        "gen:commands",
36        "Every registered command (commands / motions / operators / text-objects / ex-commands).",
37        generators::CommandsGenerator,
38    );
39    let gen_files = registry.register_generator(
40        "gen:files",
41        "Filesystem entries matching the prefix's directory + basename pattern.",
42        generators::FilesGenerator,
43    );
44    let gen_directories = registry.register_generator(
45        "gen:directories",
46        "Directory entries only (like gen:files but filters out regular files).",
47        generators::DirectoriesGenerator,
48    );
49    let match_prefix = registry.register_matcher(
50        "match:prefix",
51        "Exact-prefix match. Fast + predictable.",
52        matchers::PrefixMatcher,
53    );
54    let match_substring = registry.register_matcher(
55        "match:substring",
56        "Case-insensitive substring contains.",
57        matchers::SubstringMatcher,
58    );
59    let match_fuzzy = registry.register_matcher(
60        "match:fuzzy",
61        "Subsequence fuzzy match with score-by-density.",
62        matchers::FuzzyMatcher,
63    );
64    let rank_score = registry.register_ranker(
65        "rank:score",
66        "Descending by matcher score; alphabetical tie-break.",
67        rankers::ScoreRanker,
68    );
69    let rank_alphabetical = registry.register_ranker(
70        "rank:alphabetical",
71        "Alphabetical (A-Z) on candidate text.",
72        rankers::AlphabeticalRanker,
73    );
74    let anno_kind_label = registry.register_annotator(
75        "anno:kind-label",
76        "Append `(kind)` after the candidate text -- e.g. `(motion)`, `(file)`.",
77        annotators::KindLabelAnnotator,
78    );
79    let anno_doc_snippet = registry.register_annotator(
80        "anno:doc-snippet",
81        "Append the first line of the candidate's documentation.",
82        annotators::DocSnippetAnnotator,
83    );
84
85    // Fuzzy is the v1 default (Q4 from the design discussion --
86    // modern editors should have fuzzy matching out of the box).
87    // Users / configs can swap to `match:prefix` /
88    // `match:substring` via `cmdline.matcher` once ยง5.12 typed
89    // options lands.
90    registry.default_matcher = Some(match_fuzzy);
91    // Slice `3c.unify.ranker-stack`: default ranker list (chain in
92    // registration order). One entry today (`rank_score`); a
93    // future `MruRanker` will stack here.
94    registry.default_rankers = vec![rank_score];
95    registry.default_annotators = vec![anno_kind_label, anno_doc_snippet];
96
97    CompletionBuiltins {
98        gen_commands,
99        gen_files,
100        gen_directories,
101        match_prefix,
102        match_substring,
103        match_fuzzy,
104        rank_score,
105        rank_alphabetical,
106        anno_kind_label,
107        anno_doc_snippet,
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    #![allow(clippy::unwrap_used, clippy::panic)]
114    use super::*;
115
116    #[test]
117    fn populate_registers_all_builtins() {
118        let mut r = CompletionRegistry::new();
119        let b = populate(&mut r);
120        assert!(r.generator(b.gen_commands).is_some());
121        assert!(r.matcher(b.match_prefix).is_some());
122        assert!(r.matcher(b.match_fuzzy).is_some());
123        assert!(r.ranker(b.rank_score).is_some());
124        assert!(r.annotator(b.anno_kind_label).is_some());
125    }
126
127    #[test]
128    fn populate_sets_sensible_defaults() {
129        let mut r = CompletionRegistry::new();
130        let b = populate(&mut r);
131        // Q4: fuzzy is the v1 default. Configurable via
132        // `cmdline.matcher` once typed options land.
133        assert_eq!(r.default_matcher, Some(b.match_fuzzy));
134        assert_eq!(r.default_rankers, vec![b.rank_score]);
135        assert_eq!(
136            r.default_annotators,
137            vec![b.anno_kind_label, b.anno_doc_snippet]
138        );
139    }
140
141    #[test]
142    fn registered_names_are_introspectable() {
143        let mut r = CompletionRegistry::new();
144        let _ = populate(&mut r);
145        assert!(r.generator_by_name("gen:commands").is_some());
146        assert!(r.matcher_by_name("match:fuzzy").is_some());
147        assert!(r.ranker_by_name("rank:alphabetical").is_some());
148        assert!(r.annotator_by_name("anno:kind-label").is_some());
149    }
150}