1use super::App;
32
33#[cfg(test)]
37use super::SnippetCandidateMeta;
38#[cfg(test)]
39use lattice_host::dispatch::EffectiveCompletionConfig;
40
41impl App {
42 pub fn do_reload_snippets(&mut self) {
62 self.mutate_editor(|e| e.do_reload_snippets());
63 }
64
65 }
69
70impl App {
71 pub fn do_completion_next(&mut self) {
72 self.mutate_editor(|e| e.do_completion_next());
74 }
75
76 pub fn do_completion_prev(&mut self) {
77 self.mutate_editor(|e| e.do_completion_prev());
79 }
80
81 pub fn do_completion_trigger(&mut self) {
101 self.mutate_editor(|e| e.do_completion_trigger());
103 }
104
105 pub fn completion_ghost_text_suffix(&self) -> Option<String> {
129 self.read_editor(move |e| e.completion_ghost_text_suffix())
130 }
131
132 pub fn do_completion_accept(&mut self) {
147 self.mutate_editor(|e| e.do_completion_accept());
149 }
150
151 pub fn do_completion_toggle_docs(&mut self) {
159 self.mutate_editor(|e| e.do_completion_toggle_docs());
161 }
162
163 pub(super) fn docs_body_for_selected(&self) -> Option<String> {
166 self.read_editor(move |e| e.docs_body_for_selected())
167 }
168
169 pub(super) fn selected_needs_resolve(&self) -> bool {
171 self.read_editor(move |e| e.selected_needs_resolve())
172 }
173
174 #[allow(dead_code)]
189 pub fn do_completion_cancel(&mut self) {
190 self.mutate_editor(|e| e.do_completion_cancel());
191 }
192
193 pub fn do_completion_filter_to_source(&mut self, id: String) {
201 self.mutate_editor(move |e| e.do_completion_filter_to_source(id));
203 }
204
205 pub fn do_completion_filter_clear(&mut self) {
207 self.mutate_editor(|e| e.do_completion_filter_clear());
208 }
209
210 pub(super) fn snippet_variable_context(&self) -> lattice_snippet::VariableContext {
218 self.read_editor(move |e| e.snippet_variable_context())
219 }
220
221 pub(super) fn active_language_id(&self) -> String {
237 self.read_editor(move |e| e.active_language_id())
238 }
239
240 }
247
248#[cfg(test)]
260impl App {
261 pub(super) fn refilter_insert_completion(
262 &self,
263 state: &mut lattice_completion::InsertCompletionState,
264 ) {
265 self.editor.refilter_insert_completion(state);
266 }
267
268 pub fn do_completion_accept_then_insert(&mut self, ch: char) {
269 let mut out = lattice_host::dispatch::DispatchOutcome::default();
270 self.editor.do_completion_accept_then_insert(ch, &mut out);
271 for follow_up in std::mem::take(&mut out.next_actions) {
272 self.apply(follow_up);
273 }
274 }
275
276 pub(super) fn snippet_meta_for(
277 &self,
278 candidate: &lattice_completion::RenderedCandidate,
279 ) -> Option<SnippetCandidateMeta> {
280 self.editor.snippet_meta_for(candidate)
281 }
282
283 pub(crate) fn effective_completion_for(&self, language: &str) -> EffectiveCompletionConfig {
284 self.editor.effective_completion_for(language)
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 #![allow(clippy::unwrap_used, clippy::panic)]
291
292 use crate::app::completion_kind_glyph;
293 use crate::app::test_helpers::{
294 app_in_command_mode, app_with, app_with_path, fresh_path_workspace, install_snippet,
295 open_popup_with_top_text, press, press_chars, set_rust_syntax,
296 };
297 use crate::app::*;
298
299 #[test]
300 fn auto_insert_single_default_is_on() {
301 let a = app_with("xx", 10);
302 assert!(a.completion_auto_insert_single());
303 }
304
305 #[test]
306 fn auto_insert_single_replaces_command_line_for_one_candidate() {
307 let mut a = app_in_command_mode("set foldmethod=ind");
312 assert!(a.completion_auto_insert_single(), "default should be on");
313 a.apply(Action::CommandLineCompleteOrAdvance);
314 assert!(
315 a.editor.completion_state.is_none(),
316 "popup must not open when the only candidate auto-inserts"
317 );
318 assert_eq!(a.editor.command_line(), "set foldmethod=indent");
319 }
320
321 #[test]
322 fn auto_insert_single_off_keeps_popup_for_one_candidate() {
323 let mut a = app_in_command_mode("set foldmethod=ind");
325 a.set_completion_auto_insert_single_for_test(false);
326 a.apply(Action::CommandLineCompleteOrAdvance);
327 let state = a
328 .editor
329 .completion_state
330 .as_ref()
331 .expect("popup should open when option is off");
332 assert_eq!(state.candidates.len(), 1);
333 assert_eq!(
334 a.editor.command_line(),
335 "set foldmethod=ind",
336 "cmdline must not change until user confirms"
337 );
338 }
339
340 #[test]
341 fn auto_insert_single_does_not_fire_for_multiple_candidates() {
342 let mut a = app_in_command_mode("descri");
345 a.apply(Action::CommandLineCompleteOrAdvance);
346 let state = a
347 .editor
348 .completion_state
349 .as_ref()
350 .expect("popup should open with multiple candidates");
351 assert!(
352 state.candidates.len() >= 2,
353 "expected several describe-* candidates: {:?}",
354 state
355 .candidates
356 .iter()
357 .map(|c| &c.raw.text)
358 .collect::<Vec<_>>()
359 );
360 }
361
362 #[test]
363 fn auto_insert_single_does_not_fire_when_narrowing_open_popup() {
364 let mut a = app_in_command_mode("set foldmethod=");
369 a.apply(Action::CommandLineCompleteOrAdvance);
370 let initial = a
371 .editor
372 .completion_state
373 .as_ref()
374 .expect("popup should open for the value list");
375 assert!(initial.candidates.len() >= 2);
376 for c in "ind".chars() {
378 a.apply(Action::CommandLineAppend(c));
379 }
380 assert!(
384 a.editor.completion_state.is_some(),
385 "popup must stay open when narrowed mid-typing"
386 );
387 assert_eq!(a.editor.command_line(), "set foldmethod=ind");
388 }
389
390 #[test]
391 fn auto_insert_single_set_via_set_command() {
392 let mut a = app_with("xx", 10);
395 a.editor
396 .set_command_line_text("set nocompletion.auto_insert_single");
397 a.editor.modal = ModalState::Command;
398 a.apply(Action::CommandLineSubmit);
399 assert!(!a.completion_auto_insert_single());
400 a.editor
401 .set_command_line_text("set completion.auto_insert_single");
402 a.editor.modal = ModalState::Command;
403 a.apply(Action::CommandLineSubmit);
404 assert!(a.completion_auto_insert_single());
405 }
406
407 #[test]
408 fn completion_kind_glyph_distinct_for_common_kinds() {
409 use lattice_lsp::lsp_types::CompletionItemKind as K;
410 let f = completion_kind_glyph(Some(K::FUNCTION));
411 let s = completion_kind_glyph(Some(K::SNIPPET));
412 let v = completion_kind_glyph(Some(K::VARIABLE));
413 assert_ne!(f, s);
414 assert_ne!(f, v);
415 }
416
417 #[test]
427 fn snippet_expand_from_range_splices_body_and_focuses_first_tabstop() {
428 let mut a = app_with("for", 10);
433 a.editor.modal = ModalState::Insert;
434 a.editor.cursor = Position::new(0, 3);
435 install_snippet(
436 &mut a,
437 "*",
438 "for-loop",
439 "for",
440 "for ${1:i} in ${2:iter} { $0 }",
441 );
442 a.editor
443 .expand_snippet_from_range(lattice_protocol::position::Range::new(
444 Position::new(0, 0),
445 Position::new(0, 3),
446 ));
447 let text = a.editor.document.snapshot().buffer.as_string();
449 assert_eq!(text, "for i in iter { }");
450 let snippet_index = a
452 .editor
453 .snippet_session
454 .with_mut(a.editor.document_buffer_id, |s| {
455 s.as_ref().expect("snippet active").current_index()
456 });
457 assert_eq!(snippet_index, Some(1));
458 assert_eq!(a.editor.cursor, Position::new(0, 4));
460 }
461
462 #[test]
467 fn snippet_expand_selects_multichar_default_then_overtypes() {
468 let mut a = app_with("for", 10);
469 a.editor.modal = ModalState::Insert;
470 a.editor.cursor = Position::new(0, 3);
471 install_snippet(&mut a, "*", "for-loop", "for", "for ${1:iter} {}");
472 a.editor
473 .expand_snippet_from_range(lattice_protocol::position::Range::new(
474 Position::new(0, 0),
475 Position::new(0, 3),
476 ));
477 assert_eq!(
480 a.editor.document.snapshot().buffer.as_string(),
481 "for iter {}"
482 );
483 assert!(
484 matches!(
485 a.editor.modal,
486 ModalState::Select(lattice_grammar::VisualKind::Charwise)
487 ),
488 "first multi-char placeholder focuses in Select, got {:?}",
489 a.editor.modal
490 );
491 let sel = *a.editor.document.selections().primary();
492 assert_eq!(sel.anchor, Position::new(0, 4), "anchor at default start");
493 assert_eq!(
494 sel.head,
495 Position::new(0, 7),
496 "head on the default's last byte"
497 );
498
499 a.apply(Action::SelectOvertype('x'));
501 assert_eq!(a.editor.document.snapshot().buffer.as_string(), "for x {}");
502 assert!(
503 matches!(a.editor.modal, ModalState::Insert),
504 "overtype drops to Insert, got {:?}",
505 a.editor.modal
506 );
507 }
508
509 #[test]
519 fn typing_over_a_snippet_placeholder_replaces_it_even_with_motion_keys() {
520 for typed in ["foo", "work", "aim", "info", "owl"] {
524 let mut a = app_with("for", 10);
525 a.editor.modal = ModalState::Insert;
526 a.editor.cursor = Position::new(0, 3);
527 install_snippet(&mut a, "*", "for-loop", "for", "for ${1:iter} {}");
528 a.editor
529 .expand_snippet_from_range(lattice_protocol::position::Range::new(
530 Position::new(0, 0),
531 Position::new(0, 3),
532 ));
533 a.sync_keymap_overlays();
536 assert!(
537 matches!(a.editor.modal, ModalState::Select(_)),
538 "test premise: the placeholder is selected in Select"
539 );
540
541 press_chars(&mut a, typed);
542
543 assert_eq!(
544 a.editor.document.snapshot().buffer.as_string(),
545 format!("for {typed} {{}}"),
546 "typing {typed:?} must replace the placeholder"
547 );
548 }
549 }
550
551 #[test]
557 fn enter_over_a_snippet_placeholder_types_an_indented_newline() {
558 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
559 let mut a = app_with(" for", 10);
560 a.editor.modal = ModalState::Insert;
561 a.editor.cursor = Position::new(0, 7);
562 install_snippet(&mut a, "*", "for-loop", "for", "for ${1:iter} {}");
563 a.editor
564 .expand_snippet_from_range(lattice_protocol::position::Range::new(
565 Position::new(0, 4),
566 Position::new(0, 7),
567 ));
568 a.sync_keymap_overlays();
569 assert!(
570 matches!(a.editor.modal, ModalState::Select(_)),
571 "test premise: the placeholder is selected in Select"
572 );
573
574 press(&mut a, KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
575
576 let text = a.editor.document.snapshot().buffer.as_string();
577 assert!(
578 matches!(a.editor.modal, ModalState::Insert),
579 "<CR> overtypes and lands in Insert, got {:?}",
580 a.editor.modal
581 );
582 assert_eq!(
583 a.editor.cursor,
584 Position::new(1, 4),
585 "the cursor lands after the carried indent, in {text:?}"
586 );
587 assert!(
588 text.lines().nth(1).is_some_and(|l| l.starts_with(" ")),
589 "the new line carries the head's indent: {text:?}"
590 );
591 }
592
593 #[test]
596 fn snippet_expand_empty_tabstop_stays_in_insert() {
597 let mut a = app_with("for", 10);
598 a.editor.modal = ModalState::Insert;
599 a.editor.cursor = Position::new(0, 3);
600 install_snippet(&mut a, "*", "call", "for", "foo($1)");
601 a.editor
602 .expand_snippet_from_range(lattice_protocol::position::Range::new(
603 Position::new(0, 0),
604 Position::new(0, 3),
605 ));
606 assert_eq!(a.editor.document.snapshot().buffer.as_string(), "foo()");
607 assert!(
608 matches!(a.editor.modal, ModalState::Insert),
609 "empty tabstop keeps Insert, got {:?}",
610 a.editor.modal
611 );
612 assert_eq!(
613 a.editor.cursor,
614 Position::new(0, 4),
615 "cursor inside the parens"
616 );
617 }
618
619 #[test]
637 fn snippet_esc_clears_session_and_exits_insert_end_to_end() {
638 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
639
640 let mut a = app_with("for", 10);
641 a.editor.modal = ModalState::Insert;
642 a.editor.cursor = Position::new(0, 3);
643 install_snippet(
644 &mut a,
645 "*",
646 "for-loop",
647 "for",
648 "for ${1:i} in ${2:iter} { $0 }",
649 );
650 a.editor
651 .expand_snippet_from_range(lattice_protocol::position::Range::new(
652 Position::new(0, 0),
653 Position::new(0, 3),
654 ));
655 a.sync_keymap_overlays();
658 assert!(
659 a.editor
660 .snippet_session
661 .is_active(a.editor.document_buffer_id),
662 "snippet session live after expand"
663 );
664 let minors = a
665 .editor
666 .active_modes
667 .get(&a.editor.document_buffer_id)
668 .map(|m| m.minors().to_vec())
669 .unwrap_or_default();
670 assert!(
671 minors.contains(&lattice_snippet::modes::SnippetActiveMode::mode_id()),
672 "active-snippet-mode must be active so <Esc> resolves to the fall_through binding, got {minors:?}"
673 );
674
675 press(&mut a, KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
677
678 assert!(
679 !a.editor
680 .snippet_session
681 .is_active(a.editor.document_buffer_id),
682 "leave handler cleared the session"
683 );
684 assert_eq!(
685 a.editor.modal,
686 ModalState::Normal,
687 "fall-through continued to the native <Esc> → exit insert"
688 );
689 }
690
691 #[test]
697 fn snippet_tab_navigates_between_placeholders_in_select_mode() {
698 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
699
700 let mut a = app_with("for", 10);
701 a.editor.modal = ModalState::Insert;
702 a.editor.cursor = Position::new(0, 3);
703 install_snippet(
704 &mut a,
705 "*",
706 "for-loop",
707 "for",
708 "for ${1:i} in ${2:iter} { $0 }",
709 );
710 a.editor
711 .expand_snippet_from_range(lattice_protocol::position::Range::new(
712 Position::new(0, 0),
713 Position::new(0, 3),
714 ));
715 a.sync_keymap_overlays();
716 assert_eq!(
717 a.editor.document.snapshot().buffer.as_string(),
718 "for i in iter { }"
719 );
720 assert!(matches!(
722 a.editor.modal,
723 ModalState::Select(lattice_grammar::VisualKind::Charwise)
724 ));
725
726 press(&mut a, KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
729
730 assert_eq!(
732 a.editor.document.snapshot().buffer.as_string(),
733 "for i in iter { }",
734 "tabbing past a default keeps it"
735 );
736 assert!(
737 matches!(
738 a.editor.modal,
739 ModalState::Select(lattice_grammar::VisualKind::Charwise)
740 ),
741 "next placeholder is focused in Select, got {:?}",
742 a.editor.modal
743 );
744 let sel = *a.editor.document.selections().primary();
745 assert_eq!(sel.anchor, Position::new(0, 9), "anchor at `iter` start");
746 assert_eq!(sel.head, Position::new(0, 12), "head on `iter` last byte");
747 }
748
749 #[test]
750 fn snippet_expand_from_range_with_no_match_is_a_no_op() {
751 let mut a = app_with("xyz", 10);
752 a.editor.modal = ModalState::Insert;
753 a.editor.cursor = Position::new(0, 3);
754 a.editor
755 .expand_snippet_from_range(lattice_protocol::position::Range::new(
756 Position::new(0, 0),
757 Position::new(0, 3),
758 ));
759 assert!(
760 !a.editor
761 .snippet_session
762 .is_active(a.editor.document_buffer_id)
763 );
764 assert_eq!(a.editor.document.snapshot().buffer.as_string(), "xyz");
766 }
767
768 #[test]
777 fn completion_trigger_includes_snippet_candidate_for_matching_prefix() {
778 let mut a = app_with("for", 10);
779 a.editor.modal = ModalState::Insert;
780 a.editor.cursor = Position::new(0, 3);
781 install_snippet(&mut a, "*", "for-loop", "for", "for ${1:i} in ${2:iter} {}");
782 a.do_completion_trigger();
783 let state = a.editor.insert_completion.as_ref().expect("popup open");
784 let cand = state
790 .rendered
791 .iter()
792 .find(|r| r.raw.text == "for")
793 .expect("snippet candidate present");
794 let meta = a.snippet_meta_for(cand).expect("snippet meta resolves");
795 assert_eq!(meta.name, "for-loop");
796 }
797
798 #[test]
799 fn completion_accept_on_snippet_candidate_starts_active_snippet() {
800 let mut a = app_with("for", 10);
801 a.editor.modal = ModalState::Insert;
802 a.editor.cursor = Position::new(0, 3);
803 install_snippet(&mut a, "*", "for-loop", "for", "for ${1:i} in ${2:iter} {}");
804 a.do_completion_trigger();
805 let state = a.editor.insert_completion.as_mut().expect("popup");
807 let idx = state
808 .rendered
809 .iter()
810 .position(|r| {
811 matches!(
812 r.raw.data,
813 lattice_completion::CandidateData::Extension {
814 kind_id,
815 ..
816 } if kind_id == SNIPPET_COMPLETION_KIND_ID
817 )
818 })
819 .expect("snippet candidate present");
820 state.selected = idx;
821 a.do_completion_accept();
822 assert!(a.editor.insert_completion.is_none());
825 let snippet_index = a
826 .editor
827 .snippet_session
828 .with_mut(a.editor.document_buffer_id, |s| {
829 s.as_ref().expect("active snippet").current_index()
830 });
831 assert_eq!(snippet_index, Some(1));
832 let text = a.editor.document.snapshot().buffer.as_string();
833 assert_eq!(text, "for i in iter {}");
834 }
835
836 #[test]
837 fn completion_accept_bumps_frequency_map_for_text_kind_pair() {
838 let mut a = app_with("alpha bravo charlie ", 10);
842 a.editor.modal = ModalState::Insert;
843 a.editor.cursor = Position::new(0, 20);
844 a.do_completion_trigger();
845 let state = a.editor.insert_completion.as_mut().expect("popup");
848 let idx = state
849 .rendered
850 .iter()
851 .position(|r| r.raw.text == "bravo")
852 .expect("bravo present");
853 state.selected = idx;
854 a.do_completion_accept();
855 let key = (
857 "bravo".to_string(),
858 lattice_completion::CandidateKind::Plain,
859 );
860 assert_eq!(a.editor.completion_accept_freq.get(&key).copied(), Some(1));
861 }
862
863 #[test]
864 fn completion_trigger_ranks_previously_accepted_above_tied_peer() {
865 let mut a = app_with("alpha bravo charlie ", 10);
869 a.editor.modal = ModalState::Insert;
870 a.editor.cursor = Position::new(0, 20);
871 a.editor.completion_accept_freq.insert(
875 (
876 "bravo".to_string(),
877 lattice_completion::CandidateKind::Plain,
878 ),
879 3,
880 );
881 a.do_completion_trigger();
882 let state = a.editor.insert_completion.as_ref().expect("popup");
883 assert_eq!(
886 state.rendered.first().expect("at least one").raw.text,
887 "bravo"
888 );
889 }
890
891 #[test]
892 fn path_source_emits_filesystem_entries_inside_string_literal() {
893 let ws = fresh_path_workspace("emits-entries");
894 std::fs::write(ws.join("alpha.rs"), "// alpha").unwrap();
896 std::fs::write(ws.join("beta.rs"), "// beta").unwrap();
897 std::fs::create_dir_all(ws.join("subdir")).unwrap();
898 let source = "let p = \"\";\n";
901 let doc_path = ws.join("buffer.rs");
902 let mut a = app_with_path(source, 10, doc_path);
903 set_rust_syntax(&mut a, source);
904 a.editor.modal = ModalState::Insert;
905 a.editor.cursor = Position::new(0, source.find("\"\"").unwrap() as u32 + 1);
908 a.do_completion_trigger();
909 assert!(a.editor.completion_in_path_context, "path-context detected");
910 let state = a.editor.insert_completion.as_ref().expect("popup");
911 let path_id = lattice_completion::PATH_SOURCE_ID;
912 let texts: Vec<&str> = state
913 .raw
914 .iter()
915 .filter(|c| c.source.as_ref().map(|s| s.as_str()) == Some(path_id))
916 .map(|c| c.text.as_str())
917 .collect();
918 assert!(texts.contains(&"alpha.rs"), "alpha in {texts:?}");
919 assert!(texts.contains(&"beta.rs"), "beta in {texts:?}");
920 assert!(
921 texts.contains(&"subdir/"),
922 "subdir/ (with trailing slash) in {texts:?}",
923 );
924 for cand in &state.raw {
927 let src = cand.source.as_ref().map(|s| s.as_str()).unwrap_or("");
928 assert_eq!(
929 src, path_id,
930 "non-path source `{src}` slipped into path-context popup",
931 );
932 }
933 }
934
935 #[test]
936 fn path_source_skips_hidden_and_ignored_entries() {
937 let ws = fresh_path_workspace("skip-hidden");
938 std::fs::write(ws.join("visible.txt"), "v").unwrap();
939 std::fs::write(ws.join(".hidden"), "h").unwrap();
940 std::fs::create_dir_all(ws.join(".git")).unwrap();
941 std::fs::create_dir_all(ws.join("node_modules")).unwrap();
942 let source = "let p = \"\";\n";
943 let mut a = app_with_path(source, 10, ws.join("buffer.rs"));
944 set_rust_syntax(&mut a, source);
945 a.editor.modal = ModalState::Insert;
946 a.editor.cursor = Position::new(0, source.find("\"\"").unwrap() as u32 + 1);
947 a.do_completion_trigger();
948 let state = a.editor.insert_completion.as_ref().expect("popup");
949 let texts: Vec<&str> = state.raw.iter().map(|c| c.text.as_str()).collect();
950 assert!(texts.contains(&"visible.txt"));
951 assert!(!texts.contains(&".hidden"), "dotfile filtered");
952 assert!(!texts.contains(&".git/"), ".git filtered");
953 assert!(!texts.contains(&"node_modules/"), "node_modules filtered",);
954 }
955
956 #[test]
957 fn path_source_silent_outside_string_scope() {
958 let source = "fn main() { let x = 1; }\n";
959 let mut a = app_with(source, 10);
960 set_rust_syntax(&mut a, source);
961 a.editor.modal = ModalState::Insert;
962 a.editor.cursor = Position::new(0, source.trim_end().len() as u32);
964 a.do_completion_trigger();
965 assert!(!a.editor.completion_in_path_context);
966 if let Some(state) = a.editor.insert_completion.as_ref() {
967 let path_id = lattice_completion::PATH_SOURCE_ID;
968 for cand in &state.raw {
969 assert_ne!(
970 cand.source.as_ref().map(|s| s.as_str()),
971 Some(path_id),
972 "no path candidates outside string scope",
973 );
974 }
975 }
976 }
977
978 #[test]
979 fn path_source_skipped_by_per_language_override() {
980 let ws = fresh_path_workspace("disabled-via-override");
981 std::fs::write(ws.join("alpha.rs"), "//").unwrap();
982 let source = "let p = \"\";\n";
983 let mut a = app_with_path(source, 10, ws.join("buffer.rs"));
984 set_rust_syntax(&mut a, source);
985 a.editor.modal = ModalState::Insert;
986 a.editor.cursor = Position::new(0, source.find("\"\"").unwrap() as u32 + 1);
987 a.editor.per_language_completion.insert(
990 "rust".into(),
991 lattice_completion::PerLanguageOverrides {
992 sources: Some(vec![lattice_completion::SourceId::new(
993 lattice_completion::BufferWordsSource::ID,
994 )]),
995 ..Default::default()
996 },
997 );
998 a.do_completion_trigger();
999 assert!(
1000 !a.editor.completion_in_path_context,
1001 "path source disabled -> no path context",
1002 );
1003 }
1004
1005 #[test]
1006 fn path_source_resolves_subdirectory_from_partial_path() {
1007 let ws = fresh_path_workspace("subdir-walk");
1008 std::fs::create_dir_all(ws.join("src")).unwrap();
1009 std::fs::write(ws.join("src/foo.rs"), "//").unwrap();
1010 std::fs::write(ws.join("src/bar.rs"), "//").unwrap();
1011 let source = "let p = \"src/\";\n";
1012 let mut a = app_with_path(source, 10, ws.join("buffer.rs"));
1013 set_rust_syntax(&mut a, source);
1014 a.editor.modal = ModalState::Insert;
1015 let after_slash = source.find("src/").unwrap() + "src/".len();
1017 a.editor.cursor = Position::new(0, after_slash as u32);
1018 a.do_completion_trigger();
1019 assert!(a.editor.completion_in_path_context);
1020 let state = a.editor.insert_completion.as_ref().expect("popup");
1021 let texts: Vec<&str> = state.raw.iter().map(|c| c.text.as_str()).collect();
1022 assert!(
1023 texts.contains(&"foo.rs"),
1024 "src/foo.rs surfaced -- got {texts:?}"
1025 );
1026 assert!(texts.contains(&"bar.rs"), "src/bar.rs surfaced");
1027 }
1028
1029 #[test]
1030 fn ghost_text_off_by_default_returns_none() {
1031 let mut a = app_with("foo", 5);
1032 a.editor.modal = ModalState::Insert;
1033 a.editor.cursor = Position::new(0, 3);
1034 open_popup_with_top_text(&mut a, "foo", "foobar");
1035 assert!(a.completion_ghost_text_suffix().is_none());
1037 }
1038
1039 #[test]
1040 fn ghost_text_returns_suffix_for_prefix_matching_top_candidate() {
1041 let mut a = app_with("foo", 5);
1042 a.editor.modal = ModalState::Insert;
1043 a.editor.cursor = Position::new(0, 3);
1044 a.do_set("completion.ghost_text=true");
1045 open_popup_with_top_text(&mut a, "foo", "foobar");
1046 assert_eq!(
1047 a.completion_ghost_text_suffix(),
1048 Some("bar".to_string()),
1049 "ghost suffix is the part of the candidate beyond the query prefix",
1050 );
1051 }
1052
1053 #[test]
1054 fn ghost_text_case_insensitive_prefix_match() {
1055 let mut a = app_with("Foo", 5);
1056 a.editor.modal = ModalState::Insert;
1057 a.editor.cursor = Position::new(0, 3);
1058 a.do_set("completion.ghost_text=true");
1059 open_popup_with_top_text(&mut a, "Foo", "foobar");
1060 assert_eq!(a.completion_ghost_text_suffix(), Some("bar".to_string()),);
1061 }
1062
1063 #[test]
1064 fn ghost_text_none_when_top_doesnt_prefix_match_query() {
1065 let mut a = app_with("xyz", 5);
1066 a.editor.modal = ModalState::Insert;
1067 a.editor.cursor = Position::new(0, 3);
1068 a.do_set("completion.ghost_text=true");
1069 open_popup_with_top_text(&mut a, "xyz", "bar");
1073 assert!(a.completion_ghost_text_suffix().is_none());
1074 }
1075
1076 #[test]
1077 fn ghost_text_none_when_query_is_empty() {
1078 let mut a = app_with("", 5);
1079 a.editor.modal = ModalState::Insert;
1080 a.editor.cursor = Position::new(0, 0);
1081 a.do_set("completion.ghost_text=true");
1082 open_popup_with_top_text(&mut a, "", "alpha");
1083 assert!(
1084 a.completion_ghost_text_suffix().is_none(),
1085 "empty query -> no ghost (any candidate would match)",
1086 );
1087 }
1088
1089 #[test]
1090 fn ghost_text_none_in_path_context() {
1091 let mut a = app_with("\"\"", 5);
1092 a.editor.modal = ModalState::Insert;
1093 a.editor.cursor = Position::new(0, 1);
1094 a.do_set("completion.ghost_text=true");
1095 a.editor.completion_in_path_context = true;
1096 open_popup_with_top_text(&mut a, "src", "src/foo.rs");
1097 assert!(
1098 a.completion_ghost_text_suffix().is_none(),
1099 "path popup already shows full filenames; ghost would double up",
1100 );
1101 }
1102
1103 #[test]
1104 fn ghost_text_none_when_popup_closed() {
1105 let mut a = app_with("foo", 5);
1106 a.editor.modal = ModalState::Insert;
1107 a.editor.cursor = Position::new(0, 3);
1108 a.do_set("completion.ghost_text=true");
1109 assert!(a.completion_ghost_text_suffix().is_none());
1111 }
1112
1113 #[test]
1114 fn completion_high_priority_source_beats_tied_low_priority_peer() {
1115 let mut a = app_with("", 10);
1120 a.editor.modal = ModalState::Insert;
1121 a.editor.cursor = Position::new(0, 0);
1122 let mut state = lattice_completion::InsertCompletionState::open(
1123 lattice_completion::CompletionTrigger::Manual,
1124 Position::ZERO,
1125 Position::ZERO,
1126 String::new(),
1127 );
1128 state.raw.push(
1129 lattice_completion::RawCandidate::plain(
1130 "from_words",
1131 lattice_completion::CandidateKind::Plain,
1132 )
1133 .with_source(lattice_completion::SourceId::new(
1134 lattice_completion::BufferWordsSource::ID,
1135 )),
1136 );
1137 state.raw.push(
1138 lattice_completion::RawCandidate::plain(
1139 "from_lsp",
1140 lattice_completion::CandidateKind::Plain,
1141 )
1142 .with_source(lattice_completion::SourceId::new(
1143 lattice_completion::LSP_COMPLETION_SOURCE_ID,
1144 )),
1145 );
1146 a.refilter_insert_completion(&mut state);
1147 assert_eq!(state.rendered[0].raw.text, "from_lsp");
1148 assert_eq!(state.rendered[1].raw.text, "from_words");
1149 }
1150
1151 #[test]
1152 fn completion_priority_override_via_set_flips_source_order() {
1153 let mut a = app_with("", 10);
1157 a.editor.modal = ModalState::Insert;
1158 a.editor.cursor = Position::new(0, 0);
1159 a.do_set("completion.source.buffer-words.priority=300");
1160 let mut state = lattice_completion::InsertCompletionState::open(
1161 lattice_completion::CompletionTrigger::Manual,
1162 Position::ZERO,
1163 Position::ZERO,
1164 String::new(),
1165 );
1166 state.raw.push(
1167 lattice_completion::RawCandidate::plain(
1168 "from_lsp",
1169 lattice_completion::CandidateKind::Plain,
1170 )
1171 .with_source(lattice_completion::SourceId::new(
1172 lattice_completion::LSP_COMPLETION_SOURCE_ID,
1173 )),
1174 );
1175 state.raw.push(
1176 lattice_completion::RawCandidate::plain(
1177 "from_words",
1178 lattice_completion::CandidateKind::Plain,
1179 )
1180 .with_source(lattice_completion::SourceId::new(
1181 lattice_completion::BufferWordsSource::ID,
1182 )),
1183 );
1184 a.refilter_insert_completion(&mut state);
1185 assert_eq!(state.rendered[0].raw.text, "from_words");
1186 assert_eq!(state.rendered[1].raw.text, "from_lsp");
1187 }
1188
1189 #[test]
1190 fn completion_untagged_candidate_gets_no_priority_lift() {
1191 let mut a = app_with("", 10);
1196 a.editor.modal = ModalState::Insert;
1197 a.editor.cursor = Position::new(0, 0);
1198 let mut state = lattice_completion::InsertCompletionState::open(
1199 lattice_completion::CompletionTrigger::Manual,
1200 Position::ZERO,
1201 Position::ZERO,
1202 String::new(),
1203 );
1204 state.raw.push(lattice_completion::RawCandidate::plain(
1205 "untagged",
1206 lattice_completion::CandidateKind::Plain,
1207 ));
1208 state.raw.push(
1209 lattice_completion::RawCandidate::plain(
1210 "tagged",
1211 lattice_completion::CandidateKind::Plain,
1212 )
1213 .with_source(lattice_completion::SourceId::new(
1214 lattice_completion::BufferWordsSource::ID,
1215 )),
1216 );
1217 a.refilter_insert_completion(&mut state);
1218 assert_eq!(state.rendered[0].raw.text, "tagged");
1219 assert_eq!(state.rendered[1].raw.text, "untagged");
1220 }
1221
1222 #[test]
1223 fn completion_buffer_words_candidates_carry_their_source_tag() {
1224 let mut a = app_with("alpha bravo ", 10);
1229 a.editor.modal = ModalState::Insert;
1230 a.editor.cursor = Position::new(0, 12);
1231 a.do_completion_trigger();
1232 let state = a.editor.insert_completion.as_ref().expect("popup");
1233 assert!(!state.rendered.is_empty());
1234 for cand in &state.rendered {
1235 let src = cand
1236 .raw
1237 .source
1238 .as_ref()
1239 .unwrap_or_else(|| panic!("candidate `{}` missing source tag", cand.raw.text));
1240 assert_eq!(src.as_str(), lattice_completion::BufferWordsSource::ID);
1241 }
1242 }
1243
1244 #[test]
1245 fn completion_accept_increments_existing_frequency_count() {
1246 let mut a = app_with("alpha bravo charlie ", 10);
1248 a.editor.modal = ModalState::Insert;
1249 a.editor.cursor = Position::new(0, 20);
1250 let key = (
1251 "bravo".to_string(),
1252 lattice_completion::CandidateKind::Plain,
1253 );
1254 a.editor.completion_accept_freq.insert(key.clone(), 4);
1255 a.do_completion_trigger();
1256 let state = a.editor.insert_completion.as_mut().expect("popup");
1257 let idx = state
1258 .rendered
1259 .iter()
1260 .position(|r| r.raw.text == "bravo")
1261 .expect("bravo present");
1262 state.selected = idx;
1263 a.do_completion_accept();
1264 assert_eq!(a.editor.completion_accept_freq.get(&key).copied(), Some(5));
1265 }
1266
1267 #[test]
1273 fn completion_mode_activates_when_popup_opens() {
1274 let mut a = app_with("alpha bravo charlie ", 10);
1275 a.editor.modal = ModalState::Insert;
1276 a.editor.cursor = Position::new(0, 20);
1277 assert!(
1278 !a.completion_popup_active(),
1279 "mode should be inactive before popup opens",
1280 );
1281 a.apply(Action::CompletionTrigger);
1282 assert!(
1283 a.completion_popup_active(),
1284 "mode should be active after popup opens",
1285 );
1286 }
1287
1288 #[test]
1290 fn completion_mode_deactivates_after_cancel() {
1291 let mut a = app_with("alpha bravo charlie ", 10);
1292 a.editor.modal = ModalState::Insert;
1293 a.editor.cursor = Position::new(0, 20);
1294 a.apply(Action::CompletionTrigger);
1295 assert!(a.completion_popup_active());
1296 a.apply(Action::CompletionCancel);
1297 assert!(
1298 !a.completion_popup_active(),
1299 "mode should deactivate on cancel",
1300 );
1301 }
1302
1303 #[test]
1305 fn completion_mode_deactivates_after_accept() {
1306 let mut a = app_with("alpha bravo charlie ", 10);
1307 a.editor.modal = ModalState::Insert;
1308 a.editor.cursor = Position::new(0, 20);
1309 a.apply(Action::CompletionTrigger);
1310 assert!(a.completion_popup_active());
1311 a.apply(Action::CompletionAccept);
1312 assert!(
1313 !a.completion_popup_active(),
1314 "mode should deactivate on accept",
1315 );
1316 }
1317
1318 #[test]
1323 fn completion_mode_auto_active_on_document_buffer() {
1324 let a = app_with("hi", 5);
1325 assert!(
1326 a.completion_mode_active_for(a.editor.document_buffer_id),
1327 "completion-mode should be auto-active on the initial Document",
1328 );
1329 }
1330
1331 #[test]
1334 fn completion_mode_not_active_on_help_buffer() {
1335 let mut a = app_with("hi", 5);
1336 let help = crate::help::HelpContent::from_lines("t", vec!["body".into()]);
1337 let help_id = a.open_help_in_pane(help);
1338 assert!(
1339 !a.completion_mode_active_for(help_id),
1340 "completion-mode should be inactive on Help buffers",
1341 );
1342 }
1343
1344 #[test]
1347 fn completion_trigger_noop_when_completion_mode_inactive() {
1348 use lattice_grammar::ModalState;
1349 let mut a = app_with("alpha bravo charlie ", 10);
1350 a.editor.modal = ModalState::Insert;
1351 a.editor.cursor = Position::new(0, 20);
1352 let buffer_id = a.editor.document_buffer_id;
1354 a.deactivate_mode_by_id(buffer_id, lattice_mode::CompletionMode::mode_id());
1355 assert!(!a.completion_mode_active_for(buffer_id));
1356 a.do_completion_trigger();
1357 assert!(
1358 a.editor.insert_completion.is_none(),
1359 "popup should not open when completion-mode is inactive",
1360 );
1361 }
1362
1363 #[test]
1367 fn completion_popup_mode_distinct_from_completion_mode() {
1368 use lattice_grammar::ModalState;
1369 let mut a = app_with("alpha bravo charlie ", 10);
1370 a.editor.modal = ModalState::Insert;
1371 a.editor.cursor = Position::new(0, 20);
1372 let buffer_id = a.editor.document_buffer_id;
1373 assert!(a.completion_mode_active_for(buffer_id));
1376 assert!(!a.completion_popup_mode_active_for(buffer_id));
1377 a.apply(Action::CompletionTrigger);
1378 assert!(a.completion_mode_active_for(buffer_id));
1380 assert!(a.completion_popup_mode_active_for(buffer_id));
1381 a.apply(Action::CompletionCancel);
1382 assert!(a.completion_mode_active_for(buffer_id));
1385 assert!(!a.completion_popup_mode_active_for(buffer_id));
1386 }
1387
1388 #[test]
1399 fn lsp_completion_source_activates_on_lsp_mode_cascade() {
1400 let mut a = app_with("hi", 5);
1401 let buffer_id = a.editor.document_buffer_id;
1402 let pre_ids: Vec<_> = a
1403 .editor
1404 .buffer_locals
1405 .get(&buffer_id)
1406 .and_then(|locals| locals.get::<lattice_mode::ActiveCompletionSources>())
1407 .map(|c| c.0.iter().map(|c| c.id.as_str().to_string()).collect())
1408 .unwrap_or_default();
1409 assert!(
1410 !pre_ids.contains(&"gen:lsp-completion".to_string()),
1411 "pre-cascade cache should not contain LSP: {pre_ids:?}",
1412 );
1413 a.toggle_mode_by_name("lsp-mode");
1414 let cache = a
1415 .editor
1416 .buffer_locals
1417 .get(&buffer_id)
1418 .and_then(|locals| locals.get::<lattice_mode::ActiveCompletionSources>())
1419 .expect("cache present");
1420 let post_ids: Vec<_> = cache.0.iter().map(|c| c.id.as_str().to_string()).collect();
1421 assert!(
1422 post_ids.contains(&"gen:lsp-completion".to_string()),
1423 "post-cascade cache should include gen:lsp-completion; got {post_ids:?}",
1424 );
1425 let lsp = cache
1426 .0
1427 .iter()
1428 .find(|c| c.id.as_str() == "gen:lsp-completion")
1429 .unwrap();
1430 assert_eq!(lsp.popup_filter_chord, Some('o'));
1431 assert_eq!(lsp.kind.kind_label(), "async");
1432 }
1433
1434 #[test]
1440 fn active_completion_sources_seeded_with_default_modes_at_boot() {
1441 let a = app_with("alpha bravo", 10);
1442 let cache = a
1443 .editor
1444 .buffer_locals
1445 .get(&a.editor.document_buffer_id)
1446 .and_then(|locals| locals.get::<lattice_mode::ActiveCompletionSources>())
1447 .expect("cache should be seeded at boot");
1448 let ids: Vec<_> = cache.0.iter().map(|c| c.id.as_str().to_string()).collect();
1449 assert!(ids.contains(&"gen:buffer-words".to_string()), "got {ids:?}");
1450 assert!(ids.contains(&"gen:snippet".to_string()), "got {ids:?}");
1451 assert!(
1452 ids.contains(&"gen:tree-sitter-symbol".to_string()),
1453 "got {ids:?}",
1454 );
1455 assert!(ids.contains(&"gen:path".to_string()), "got {ids:?}");
1456 let buffer_words = cache
1457 .0
1458 .iter()
1459 .find(|c| c.id.as_str() == "gen:buffer-words")
1460 .unwrap();
1461 assert_eq!(buffer_words.popup_filter_chord, Some('b'));
1462 let snippet = cache
1463 .0
1464 .iter()
1465 .find(|c| c.id.as_str() == "gen:snippet")
1466 .unwrap();
1467 assert!(snippet.popup_filter_chord.is_none());
1469 let tree_sitter = cache
1470 .0
1471 .iter()
1472 .find(|c| c.id.as_str() == "gen:tree-sitter-symbol")
1473 .unwrap();
1474 assert_eq!(tree_sitter.popup_filter_chord, Some('t'));
1475 let path = cache
1476 .0
1477 .iter()
1478 .find(|c| c.id.as_str() == "gen:path")
1479 .unwrap();
1480 assert_eq!(path.popup_filter_chord, Some('f'));
1481 }
1482
1483 #[test]
1489 fn buffer_words_populates_via_mode_contributed_source() {
1490 let mut a = app_with("alpha bravo charlie ", 10);
1491 a.editor.modal = ModalState::Insert;
1492 a.editor.cursor = Position::new(0, 20);
1493 a.do_completion_trigger();
1494 let state = a.editor.insert_completion.as_ref().expect("popup open");
1495 let labels: Vec<String> = state.rendered.iter().map(|c| c.raw.text.clone()).collect();
1496 assert!(
1497 labels.contains(&"alpha".to_string()),
1498 "buffer-words should populate via the mode-contributed path; \
1499 got candidates: {labels:?}",
1500 );
1501 }
1502
1503 #[test]
1508 fn active_completion_sources_recomputes_after_activation() {
1509 use lattice_completion::{
1510 CompletionSourceContribution, CompletionSourceKind, RawCandidate, SyncCompletionSource,
1511 };
1512 use std::sync::Arc;
1513
1514 #[derive(Debug)]
1515 struct StubSource;
1516 impl SyncCompletionSource for StubSource {
1517 fn produce(&self, _ctx: &lattice_completion::InsertContext<'_>) -> Vec<RawCandidate> {
1518 vec![RawCandidate::plain(
1519 "stub".to_string(),
1520 lattice_completion::CandidateKind::Plain,
1521 )]
1522 }
1523 }
1524 struct StubMode;
1525 impl lattice_mode::Mode for StubMode {
1526 type Guard = ();
1527 fn id(&self) -> lattice_mode::ModeId {
1528 lattice_mode::ModeId::new("stub-csm3-mode")
1529 }
1530 fn kind(&self) -> lattice_mode::ModeKind {
1531 lattice_mode::ModeKind::Minor
1532 }
1533 fn completion_sources(&self) -> Vec<CompletionSourceContribution> {
1534 vec![CompletionSourceContribution {
1535 accepts_non_word_query: false,
1536 id: lattice_completion::SourceId::new("gen:stub-csm3"),
1537 default_priority: 50,
1538 auto_trigger: true,
1539 trigger_chars: Vec::new(),
1540 popup_filter_chord: None,
1541 kind: CompletionSourceKind::Sync(Arc::new(StubSource)),
1542 }]
1543 }
1544 fn on_activate(
1545 &self,
1546 _ctx: lattice_mode::ModeContext,
1547 ) -> lattice_mode::LifecycleFuture<'_, ()> {
1548 Box::pin(async { Ok(()) })
1549 }
1550 }
1551
1552 let mut a = app_with("hi", 5);
1553 let mode_id = {
1554 let mut registry = (**a.editor.mode_registry.load()).clone();
1555 let id = registry.register(StubMode).expect("register");
1556 a.editor.mode_registry.store(std::sync::Arc::new(registry));
1557 id
1558 };
1559 let buffer_id = a.editor.document_buffer_id;
1560 a.activate_mode_by_id(buffer_id, mode_id);
1561
1562 let cache = a
1567 .editor
1568 .buffer_locals
1569 .get(&buffer_id)
1570 .and_then(|locals| locals.get::<lattice_mode::ActiveCompletionSources>())
1571 .expect("cache present");
1572 let ids: Vec<_> = cache.0.iter().map(|c| c.id.as_str().to_string()).collect();
1573 assert!(
1574 ids.contains(&"gen:buffer-words".to_string()),
1575 "buffer-words contribution should remain; got {ids:?}",
1576 );
1577 assert!(
1578 ids.contains(&"gen:stub-csm3".to_string()),
1579 "stub mode's source should be cached; got {ids:?}",
1580 );
1581
1582 a.deactivate_mode_by_id(buffer_id, mode_id);
1583 let cache = a
1584 .editor
1585 .buffer_locals
1586 .get(&buffer_id)
1587 .and_then(|locals| locals.get::<lattice_mode::ActiveCompletionSources>())
1588 .expect("cache present");
1589 let ids: Vec<_> = cache.0.iter().map(|c| c.id.as_str().to_string()).collect();
1590 assert!(
1591 !ids.contains(&"gen:stub-csm3".to_string()),
1592 "stub source should drop after deactivation; got {ids:?}",
1593 );
1594 assert!(ids.contains(&"gen:buffer-words".to_string()));
1596 }
1597
1598 #[test]
1605 fn completion_popup_active_reads_mode_not_state_field() {
1606 let mut a = app_with("alpha bravo charlie ", 10);
1607 a.editor.modal = ModalState::Insert;
1608 a.editor.cursor = Position::new(0, 20);
1609 a.apply(Action::CompletionTrigger);
1610 assert!(a.completion_popup_active());
1611 a.editor.insert_completion = None;
1613 assert!(a.completion_popup_active());
1618 a.sync_keymap_overlays();
1620 assert!(!a.completion_popup_active());
1621 }
1622
1623 #[test]
1629 fn completion_filter_to_source_narrows_rendered_list() {
1630 let mut a = app_with("", 10);
1631 a.editor.modal = ModalState::Insert;
1632 a.editor.cursor = Position::new(0, 0);
1633 let mut state = lattice_completion::InsertCompletionState::open(
1634 lattice_completion::CompletionTrigger::Manual,
1635 Position::ZERO,
1636 Position::ZERO,
1637 String::new(),
1638 );
1639 state.raw.push(
1640 lattice_completion::RawCandidate::plain(
1641 "from_words",
1642 lattice_completion::CandidateKind::Plain,
1643 )
1644 .with_source(lattice_completion::SourceId::new(
1645 lattice_completion::BufferWordsSource::ID,
1646 )),
1647 );
1648 state.raw.push(
1649 lattice_completion::RawCandidate::plain(
1650 "from_lsp",
1651 lattice_completion::CandidateKind::Plain,
1652 )
1653 .with_source(lattice_completion::SourceId::new(
1654 lattice_completion::LSP_COMPLETION_SOURCE_ID,
1655 )),
1656 );
1657 a.refilter_insert_completion(&mut state);
1658 assert_eq!(state.rendered.len(), 2);
1659 a.editor.insert_completion = Some(state);
1660 a.do_completion_filter_to_source(lattice_completion::LSP_COMPLETION_SOURCE_ID.to_string());
1661 let s = a.editor.insert_completion.as_ref().unwrap();
1662 assert_eq!(s.rendered.len(), 1);
1663 assert_eq!(s.rendered[0].raw.text, "from_lsp");
1664 assert_eq!(s.raw.len(), 2);
1666 }
1667
1668 #[test]
1671 fn completion_filter_clear_restores_full_list() {
1672 let mut a = app_with("", 10);
1673 a.editor.modal = ModalState::Insert;
1674 a.editor.cursor = Position::new(0, 0);
1675 let mut state = lattice_completion::InsertCompletionState::open(
1676 lattice_completion::CompletionTrigger::Manual,
1677 Position::ZERO,
1678 Position::ZERO,
1679 String::new(),
1680 );
1681 state.raw.push(
1682 lattice_completion::RawCandidate::plain(
1683 "from_words",
1684 lattice_completion::CandidateKind::Plain,
1685 )
1686 .with_source(lattice_completion::SourceId::new(
1687 lattice_completion::BufferWordsSource::ID,
1688 )),
1689 );
1690 state.raw.push(
1691 lattice_completion::RawCandidate::plain(
1692 "from_lsp",
1693 lattice_completion::CandidateKind::Plain,
1694 )
1695 .with_source(lattice_completion::SourceId::new(
1696 lattice_completion::LSP_COMPLETION_SOURCE_ID,
1697 )),
1698 );
1699 state.source_filter = Some(lattice_completion::SourceId::new(
1700 lattice_completion::LSP_COMPLETION_SOURCE_ID,
1701 ));
1702 a.refilter_insert_completion(&mut state);
1703 assert_eq!(state.rendered.len(), 1);
1704 a.editor.insert_completion = Some(state);
1705 a.do_completion_filter_clear();
1706 let s = a.editor.insert_completion.as_ref().unwrap();
1707 assert!(s.source_filter.is_none());
1708 assert_eq!(s.rendered.len(), 2);
1709 }
1710}