1use crate::{Metadata, ParseResult, Version};
44use tower_lsp_server::ls_types::{
45 CompletionItem, CompletionItemKind, CompletionTextEdit, Documentation, MarkupContent,
46 MarkupKind, Position, Range, TextEdit,
47};
48
49#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum CompletionContext {
55 PackageName {
59 prefix: String,
61 },
62
63 Version {
67 package_name: String,
69 prefix: String,
71 },
72
73 Feature {
77 package_name: String,
79 prefix: String,
81 },
82
83 None,
85}
86
87pub fn detect_completion_context(
116 parse_result: &dyn ParseResult,
117 position: Position,
118 content: &str,
119) -> CompletionContext {
120 let dependencies = parse_result.dependencies();
121
122 for dep in dependencies {
123 let name_range = dep.name_range();
125 if position_in_range(position, name_range) {
126 let prefix = extract_prefix(content, position, name_range);
127 return CompletionContext::PackageName { prefix };
128 }
129
130 if let Some(version_range) = dep.version_range()
132 && position_in_range(position, version_range)
133 {
134 let prefix = extract_prefix(content, position, version_range);
135 return CompletionContext::Version {
136 package_name: dep.name().to_string(),
137 prefix,
138 };
139 }
140
141 }
143
144 CompletionContext::None
145}
146
147const fn position_in_range(position: Position, range: Range) -> bool {
153 if position.line < range.start.line {
155 return false;
156 }
157
158 if position.line == range.start.line && position.character < range.start.character {
159 return false;
160 }
161
162 if position.line > range.end.line {
164 return false;
165 }
166
167 if position.line == range.end.line && position.character > range.end.character + 1 {
168 return false;
169 }
170
171 true
172}
173
174pub fn utf16_to_byte_offset(s: &str, utf16_offset: u32) -> Option<usize> {
205 let mut utf16_count = 0u32;
206 for (byte_idx, ch) in s.char_indices() {
207 if utf16_count >= utf16_offset {
208 return Some(byte_idx);
209 }
210 utf16_count += ch.len_utf16() as u32;
211 }
212 if utf16_count == utf16_offset {
213 return Some(s.len());
214 }
215 None
216}
217
218pub fn extract_prefix(content: &str, position: Position, range: Range) -> String {
250 let line = match content.lines().nth(position.line as usize) {
252 Some(l) => l,
253 None => return String::new(),
254 };
255
256 let start_byte = if position.line == range.start.line {
258 match utf16_to_byte_offset(line, range.start.character) {
259 Some(offset) => offset,
260 None => return String::new(),
261 }
262 } else {
263 0
264 };
265
266 let cursor_byte = match utf16_to_byte_offset(line, position.character) {
267 Some(offset) => offset,
268 None => return String::new(),
269 };
270
271 if start_byte > line.len() || cursor_byte > line.len() || start_byte > cursor_byte {
273 return String::new();
274 }
275
276 let prefix = &line[start_byte..cursor_byte];
278
279 prefix
281 .trim()
282 .trim_matches('"')
283 .trim_matches('\'')
284 .trim()
285 .to_string()
286}
287
288pub fn build_package_completion(metadata: &dyn Metadata, insert_range: Range) -> CompletionItem {
315 let name = metadata.name();
316 let latest = metadata.latest_version();
317
318 let mut doc_parts = vec![format!("**{}** v{}", name, latest)];
320
321 if let Some(desc) = metadata.description() {
322 doc_parts.push(String::new()); let truncated = if desc.len() > 200 {
324 let mut end = 200;
325 while end > 0 && !desc.is_char_boundary(end) {
326 end -= 1;
327 }
328 format!("{}...", &desc[..end])
329 } else {
330 desc.to_string()
331 };
332 doc_parts.push(truncated);
333 }
334
335 let mut links = Vec::new();
337 if let Some(repo) = metadata.repository() {
338 links.push(format!("[Repository]({})", repo));
339 }
340 if let Some(docs) = metadata.documentation() {
341 links.push(format!("[Documentation]({})", docs));
342 }
343
344 if !links.is_empty() {
345 doc_parts.push(String::new()); doc_parts.push(links.join(" | "));
347 }
348
349 CompletionItem {
350 label: name.to_string(),
351 kind: Some(CompletionItemKind::MODULE),
352 detail: Some(format!("v{}", latest)),
353 documentation: Some(Documentation::MarkupContent(MarkupContent {
354 kind: MarkupKind::Markdown,
355 value: doc_parts.join("\n"),
356 })),
357 insert_text: Some(name.to_string()),
358 text_edit: Some(CompletionTextEdit::Edit(TextEdit {
359 range: insert_range,
360 new_text: name.to_string(),
361 })),
362 sort_text: Some(name.to_string()),
363 filter_text: Some(name.to_string()),
364 ..Default::default()
365 }
366}
367
368pub fn build_version_completion(
408 display_item: &VersionDisplayItem,
409 insert_range: Option<Range>,
410) -> CompletionItem {
411 let sort_text = format!("{:05}", display_item.index);
413
414 CompletionItem {
415 label: display_item.label.clone(),
416 kind: Some(CompletionItemKind::VALUE),
417 detail: Some(display_item.description.clone()),
418 documentation: None,
419 insert_text: Some(display_item.version.clone()),
420 text_edit: insert_range.map(|range| {
421 CompletionTextEdit::Edit(TextEdit {
422 range,
423 new_text: display_item.version.clone(),
424 })
425 }),
426 sort_text: Some(sort_text),
427 preselect: Some(display_item.is_latest),
428 ..Default::default()
429 }
430}
431
432#[derive(Debug, Clone)]
436pub struct VersionDisplayItem {
437 pub version: String,
439 pub label: String,
441 pub description: String,
443 pub index: usize,
445 pub is_latest: bool,
447}
448
449impl VersionDisplayItem {
450 pub fn new(version: &dyn Version, package_name: &str, index: usize, is_latest: bool) -> Self {
452 let version_str = version.version_string();
453 let label = if is_latest {
454 format!("{} (latest)", version_str)
455 } else {
456 version_str.to_string()
457 };
458 let description = format!("Update {} to {}", package_name, version_str);
459
460 Self {
461 version: version_str.to_string(),
462 label,
463 description,
464 index,
465 is_latest,
466 }
467 }
468}
469
470pub fn prepare_version_display_items<V: AsRef<dyn Version>>(
474 versions: &[V],
475 package_name: &str,
476) -> Vec<VersionDisplayItem> {
477 versions
478 .iter()
479 .map(|v| v.as_ref())
480 .filter(|v| !v.is_yanked())
481 .take(MAX_COMPLETION_VERSIONS)
482 .enumerate()
483 .map(|(index, version)| VersionDisplayItem::new(version, package_name, index, index == 0))
484 .collect()
485}
486
487pub fn build_feature_completion(
513 feature_name: &str,
514 package_name: &str,
515 insert_range: Range,
516) -> CompletionItem {
517 CompletionItem {
518 label: feature_name.to_string(),
519 kind: Some(CompletionItemKind::PROPERTY),
520 detail: Some(format!("Feature of {}", package_name)),
521 documentation: None,
522 insert_text: Some(feature_name.to_string()),
523 text_edit: Some(CompletionTextEdit::Edit(TextEdit {
524 range: insert_range,
525 new_text: feature_name.to_string(),
526 })),
527 sort_text: Some(feature_name.to_string()),
528 ..Default::default()
529 }
530}
531
532const MAX_COMPLETION_VERSIONS: usize = 5;
534
535pub async fn complete_versions_generic(
577 registry: &dyn crate::Registry,
578 package_name: &str,
579 prefix: &str,
580 operator_chars: &[char],
581) -> Vec<CompletionItem> {
582 let versions = match registry.get_versions(package_name).await {
583 Ok(v) => v,
584 Err(e) => {
585 tracing::warn!("Failed to fetch versions for '{}': {}", package_name, e);
586 return vec![];
587 }
588 };
589
590 let clean_prefix = prefix.trim_start_matches(operator_chars).trim();
591
592 let filtered_versions: Vec<_> = versions
594 .iter()
595 .filter(|v| v.version_string().starts_with(clean_prefix))
596 .collect();
597
598 let display_items = if filtered_versions.is_empty() {
600 prepare_version_display_items(&versions, package_name)
601 } else {
602 prepare_version_display_items(&filtered_versions, package_name)
603 };
604
605 display_items
607 .iter()
608 .map(|item| build_version_completion(item, None))
609 .collect()
610}
611
612#[cfg(test)]
613mod tests {
614 use super::*;
615 use std::any::Any;
616
617 struct MockDependency {
620 name: String,
621 name_range: Range,
622 version_range: Option<Range>,
623 }
624
625 impl crate::ecosystem::Dependency for MockDependency {
626 fn name(&self) -> &str {
627 &self.name
628 }
629
630 fn name_range(&self) -> Range {
631 self.name_range
632 }
633
634 fn version_requirement(&self) -> Option<&str> {
635 Some("1.0")
636 }
637
638 fn version_range(&self) -> Option<Range> {
639 self.version_range
640 }
641
642 fn source(&self) -> crate::parser::DependencySource {
643 crate::parser::DependencySource::Registry
644 }
645
646 fn as_any(&self) -> &dyn Any {
647 self
648 }
649 }
650
651 struct MockParseResult {
652 dependencies: Vec<MockDependency>,
653 }
654
655 impl ParseResult for MockParseResult {
656 fn dependencies(&self) -> Vec<&dyn crate::ecosystem::Dependency> {
657 self.dependencies
658 .iter()
659 .map(|d| d as &dyn crate::ecosystem::Dependency)
660 .collect()
661 }
662
663 fn workspace_root(&self) -> Option<&std::path::Path> {
664 None
665 }
666
667 fn uri(&self) -> &tower_lsp_server::ls_types::Uri {
668 static URL_STR: &str = "file:///test/Cargo.toml";
670 static URL: once_cell::sync::Lazy<tower_lsp_server::ls_types::Uri> =
671 once_cell::sync::Lazy::new(|| URL_STR.parse().unwrap());
672 &URL
673 }
674
675 fn as_any(&self) -> &dyn Any {
676 self
677 }
678 }
679
680 struct MockVersion {
681 version: String,
682 yanked: bool,
683 prerelease: bool,
684 }
685
686 impl crate::registry::Version for MockVersion {
687 fn version_string(&self) -> &str {
688 &self.version
689 }
690
691 fn is_yanked(&self) -> bool {
692 self.yanked
693 }
694
695 fn is_prerelease(&self) -> bool {
696 self.prerelease
697 }
698
699 fn as_any(&self) -> &dyn Any {
700 self
701 }
702 }
703
704 struct MockMetadata {
705 name: String,
706 description: Option<String>,
707 repository: Option<String>,
708 documentation: Option<String>,
709 latest_version: String,
710 }
711
712 impl crate::registry::Metadata for MockMetadata {
713 fn name(&self) -> &str {
714 &self.name
715 }
716
717 fn description(&self) -> Option<&str> {
718 self.description.as_deref()
719 }
720
721 fn repository(&self) -> Option<&str> {
722 self.repository.as_deref()
723 }
724
725 fn documentation(&self) -> Option<&str> {
726 self.documentation.as_deref()
727 }
728
729 fn latest_version(&self) -> &str {
730 &self.latest_version
731 }
732
733 fn as_any(&self) -> &dyn Any {
734 self
735 }
736 }
737
738 struct MockRegistry {
739 versions: Vec<MockVersion>,
740 }
741
742 #[async_trait::async_trait]
743 impl crate::Registry for MockRegistry {
744 async fn get_versions(
745 &self,
746 _package_name: &str,
747 ) -> crate::error::Result<Vec<Box<dyn crate::Version>>> {
748 Ok(self
749 .versions
750 .iter()
751 .map(|v| {
752 Box::new(MockVersion {
753 version: v.version.clone(),
754 yanked: v.yanked,
755 prerelease: v.prerelease,
756 }) as Box<dyn crate::Version>
757 })
758 .collect())
759 }
760
761 async fn get_latest_matching(
762 &self,
763 _name: &str,
764 _req: &str,
765 ) -> crate::error::Result<Option<Box<dyn crate::Version>>> {
766 Ok(None)
767 }
768
769 async fn search(
770 &self,
771 _query: &str,
772 _limit: usize,
773 ) -> crate::error::Result<Vec<Box<dyn crate::Metadata>>> {
774 Ok(vec![])
775 }
776
777 fn package_url(&self, _name: &str) -> String {
778 String::new()
779 }
780
781 fn as_any(&self) -> &dyn Any {
782 self
783 }
784 }
785
786 #[test]
789 fn test_detect_package_name_context_at_start() {
790 let parse_result = MockParseResult {
791 dependencies: vec![MockDependency {
792 name: "serde".to_string(),
793 name_range: Range {
794 start: Position {
795 line: 0,
796 character: 0,
797 },
798 end: Position {
799 line: 0,
800 character: 5,
801 },
802 },
803 version_range: None,
804 }],
805 };
806
807 let content = "serde";
808 let position = Position {
809 line: 0,
810 character: 0,
811 };
812
813 let context = detect_completion_context(&parse_result, position, content);
814
815 match context {
816 CompletionContext::PackageName { prefix } => {
817 assert_eq!(prefix, "");
818 }
819 _ => panic!("Expected PackageName context, got {:?}", context),
820 }
821 }
822
823 #[test]
824 fn test_detect_package_name_context_partial() {
825 let parse_result = MockParseResult {
826 dependencies: vec![MockDependency {
827 name: "serde".to_string(),
828 name_range: Range {
829 start: Position {
830 line: 0,
831 character: 0,
832 },
833 end: Position {
834 line: 0,
835 character: 5,
836 },
837 },
838 version_range: None,
839 }],
840 };
841
842 let content = "serde";
843 let position = Position {
844 line: 0,
845 character: 3,
846 };
847
848 let context = detect_completion_context(&parse_result, position, content);
849
850 match context {
851 CompletionContext::PackageName { prefix } => {
852 assert_eq!(prefix, "ser");
853 }
854 _ => panic!("Expected PackageName context, got {:?}", context),
855 }
856 }
857
858 #[test]
859 fn test_detect_version_context() {
860 let parse_result = MockParseResult {
861 dependencies: vec![MockDependency {
862 name: "serde".to_string(),
863 name_range: Range {
864 start: Position {
865 line: 0,
866 character: 0,
867 },
868 end: Position {
869 line: 0,
870 character: 5,
871 },
872 },
873 version_range: Some(Range {
874 start: Position {
875 line: 0,
876 character: 9,
877 },
878 end: Position {
879 line: 0,
880 character: 14,
881 },
882 }),
883 }],
884 };
885
886 let content = r#"serde = "1.0.1""#;
887 let position = Position {
888 line: 0,
889 character: 11,
890 };
891
892 let context = detect_completion_context(&parse_result, position, content);
893
894 match context {
895 CompletionContext::Version {
896 package_name,
897 prefix,
898 } => {
899 assert_eq!(package_name, "serde");
900 assert_eq!(prefix, "1.");
901 }
902 _ => panic!("Expected Version context, got {:?}", context),
903 }
904 }
905
906 #[test]
907 fn test_detect_no_context_before_dependencies() {
908 let parse_result = MockParseResult {
909 dependencies: vec![MockDependency {
910 name: "serde".to_string(),
911 name_range: Range {
912 start: Position {
913 line: 5,
914 character: 0,
915 },
916 end: Position {
917 line: 5,
918 character: 5,
919 },
920 },
921 version_range: None,
922 }],
923 };
924
925 let content = "[dependencies]\nserde";
926 let position = Position {
927 line: 0,
928 character: 10,
929 };
930
931 let context = detect_completion_context(&parse_result, position, content);
932
933 assert_eq!(context, CompletionContext::None);
934 }
935
936 #[test]
937 fn test_detect_no_context_invalid_position() {
938 let parse_result = MockParseResult {
939 dependencies: vec![],
940 };
941
942 let content = "";
943 let position = Position {
944 line: 100,
945 character: 100,
946 };
947
948 let context = detect_completion_context(&parse_result, position, content);
949
950 assert_eq!(context, CompletionContext::None);
951 }
952
953 #[test]
956 fn test_extract_prefix_at_start() {
957 let content = "serde";
958 let position = Position {
959 line: 0,
960 character: 0,
961 };
962 let range = Range {
963 start: Position {
964 line: 0,
965 character: 0,
966 },
967 end: Position {
968 line: 0,
969 character: 5,
970 },
971 };
972
973 let prefix = extract_prefix(content, position, range);
974 assert_eq!(prefix, "");
975 }
976
977 #[test]
978 fn test_extract_prefix_partial() {
979 let content = "serde";
980 let position = Position {
981 line: 0,
982 character: 3,
983 };
984 let range = Range {
985 start: Position {
986 line: 0,
987 character: 0,
988 },
989 end: Position {
990 line: 0,
991 character: 5,
992 },
993 };
994
995 let prefix = extract_prefix(content, position, range);
996 assert_eq!(prefix, "ser");
997 }
998
999 #[test]
1000 fn test_extract_prefix_with_quotes() {
1001 let content = r#"serde = "1.0""#;
1002 let position = Position {
1003 line: 0,
1004 character: 11,
1005 };
1006 let range = Range {
1007 start: Position {
1008 line: 0,
1009 character: 9,
1010 },
1011 end: Position {
1012 line: 0,
1013 character: 13,
1014 },
1015 };
1016
1017 let prefix = extract_prefix(content, position, range);
1018 assert_eq!(prefix, "1.");
1019 }
1020
1021 #[test]
1022 fn test_extract_prefix_empty() {
1023 let content = r#"serde = """#;
1024 let position = Position {
1025 line: 0,
1026 character: 9,
1027 };
1028 let range = Range {
1029 start: Position {
1030 line: 0,
1031 character: 9,
1032 },
1033 end: Position {
1034 line: 0,
1035 character: 11,
1036 },
1037 };
1038
1039 let prefix = extract_prefix(content, position, range);
1040 assert_eq!(prefix, "");
1041 }
1042
1043 #[test]
1044 fn test_extract_prefix_version_with_operator() {
1045 let content = r#"serde = "^1.0""#;
1046 let position = Position {
1047 line: 0,
1048 character: 12,
1049 };
1050 let range = Range {
1051 start: Position {
1052 line: 0,
1053 character: 9,
1054 },
1055 end: Position {
1056 line: 0,
1057 character: 14,
1058 },
1059 };
1060
1061 let prefix = extract_prefix(content, position, range);
1062 assert_eq!(prefix, "^1.");
1063 }
1064
1065 #[test]
1068 fn test_build_package_completion_full() {
1069 let metadata = MockMetadata {
1070 name: "serde".to_string(),
1071 description: Some("Serialization framework".to_string()),
1072 repository: Some("https://github.com/serde-rs/serde".to_string()),
1073 documentation: Some("https://docs.rs/serde".to_string()),
1074 latest_version: "1.0.214".to_string(),
1075 };
1076
1077 let range = Range::default();
1078 let item = build_package_completion(&metadata, range);
1079
1080 assert_eq!(item.label, "serde");
1081 assert_eq!(item.kind, Some(CompletionItemKind::MODULE));
1082 assert_eq!(item.detail, Some("v1.0.214".to_string()));
1083 assert!(matches!(
1084 item.documentation,
1085 Some(Documentation::MarkupContent(_))
1086 ));
1087
1088 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1089 assert!(content.value.contains("**serde** v1.0.214"));
1090 assert!(content.value.contains("Serialization framework"));
1091 assert!(content.value.contains("Repository"));
1092 assert!(content.value.contains("Documentation"));
1093 }
1094 }
1095
1096 #[test]
1097 fn test_build_package_completion_minimal() {
1098 let metadata = MockMetadata {
1099 name: "test-pkg".to_string(),
1100 description: None,
1101 repository: None,
1102 documentation: None,
1103 latest_version: "0.1.0".to_string(),
1104 };
1105
1106 let range = Range::default();
1107 let item = build_package_completion(&metadata, range);
1108
1109 assert_eq!(item.label, "test-pkg");
1110 assert_eq!(item.detail, Some("v0.1.0".to_string()));
1111
1112 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1113 assert!(content.value.contains("**test-pkg** v0.1.0"));
1114 assert!(!content.value.contains("Repository"));
1115 }
1116 }
1117
1118 #[test]
1119 fn test_build_version_completion_stable() {
1120 let version = MockVersion {
1121 version: "1.0.0".to_string(),
1122 yanked: false,
1123 prerelease: false,
1124 };
1125
1126 let display_item = VersionDisplayItem::new(&version, "serde", 0, false);
1127 let item = build_version_completion(&display_item, None);
1128
1129 assert_eq!(item.label, "1.0.0");
1130 assert_eq!(item.kind, Some(CompletionItemKind::VALUE));
1131 assert_eq!(item.detail, Some("Update serde to 1.0.0".to_string()));
1132 assert_eq!(item.documentation, None);
1133 assert_eq!(item.preselect, Some(false));
1134 assert_eq!(item.sort_text, Some("00000".to_string()));
1135 assert_eq!(item.text_edit, None); }
1137
1138 #[test]
1139 fn test_build_version_completion_latest() {
1140 let version = MockVersion {
1141 version: "1.0.0".to_string(),
1142 yanked: false,
1143 prerelease: false,
1144 };
1145
1146 let display_item = VersionDisplayItem::new(&version, "serde", 0, true);
1147 let item = build_version_completion(&display_item, None);
1148
1149 assert_eq!(item.label, "1.0.0 (latest)");
1150 assert_eq!(item.kind, Some(CompletionItemKind::VALUE));
1151 assert_eq!(item.detail, Some("Update serde to 1.0.0".to_string()));
1152 assert_eq!(item.documentation, None);
1153 assert_eq!(item.preselect, Some(true));
1154 assert_eq!(item.sort_text, Some("00000".to_string()));
1155 assert_eq!(item.text_edit, None); }
1157
1158 #[test]
1159 fn test_build_version_completion_not_latest() {
1160 let version = MockVersion {
1161 version: "0.9.0".to_string(),
1162 yanked: false,
1163 prerelease: false,
1164 };
1165
1166 let display_item = VersionDisplayItem::new(&version, "tokio", 1, false);
1167 let item = build_version_completion(&display_item, None);
1168
1169 assert_eq!(item.label, "0.9.0");
1170 assert_eq!(item.detail, Some("Update tokio to 0.9.0".to_string()));
1171 assert_eq!(item.documentation, None);
1172 assert_eq!(item.preselect, Some(false));
1173 assert_eq!(item.sort_text, Some("00001".to_string()));
1174 assert_eq!(item.text_edit, None); }
1176
1177 #[test]
1178 fn test_build_version_completion_sort_order() {
1179 let v1 = MockVersion {
1180 version: "1.0.0".to_string(),
1181 yanked: false,
1182 prerelease: false,
1183 };
1184 let v2 = MockVersion {
1185 version: "0.9.0".to_string(),
1186 yanked: false,
1187 prerelease: false,
1188 };
1189 let v3 = MockVersion {
1190 version: "0.8.0".to_string(),
1191 yanked: false,
1192 prerelease: false,
1193 };
1194
1195 let display_item1 = VersionDisplayItem::new(&v1, "test", 0, true);
1196 let display_item2 = VersionDisplayItem::new(&v2, "test", 1, false);
1197 let display_item3 = VersionDisplayItem::new(&v3, "test", 2, false);
1198 let item1 = build_version_completion(&display_item1, None);
1199 let item2 = build_version_completion(&display_item2, None);
1200 let item3 = build_version_completion(&display_item3, None);
1201
1202 assert_eq!(item1.sort_text.as_ref().unwrap(), "00000");
1204 assert_eq!(item2.sort_text.as_ref().unwrap(), "00001");
1205 assert_eq!(item3.sort_text.as_ref().unwrap(), "00002");
1206
1207 assert_eq!(item1.preselect, Some(true));
1209 assert_eq!(item2.preselect, Some(false));
1210 assert_eq!(item3.preselect, Some(false));
1211 }
1212
1213 #[test]
1214 fn test_version_completion_semantic_ordering() {
1215 let versions = [
1216 MockVersion {
1217 version: "0.14.0".to_string(),
1218 yanked: false,
1219 prerelease: false,
1220 },
1221 MockVersion {
1222 version: "0.8.0".to_string(),
1223 yanked: false,
1224 prerelease: false,
1225 },
1226 MockVersion {
1227 version: "0.2.0".to_string(),
1228 yanked: false,
1229 prerelease: false,
1230 },
1231 ];
1232
1233 let items: Vec<_> = versions
1234 .iter()
1235 .enumerate()
1236 .map(|(idx, v)| {
1237 let display_item = VersionDisplayItem::new(v, "test", idx, idx == 0);
1238 build_version_completion(&display_item, None)
1239 })
1240 .collect();
1241
1242 assert_eq!(items[0].sort_text.as_ref().unwrap(), "00000");
1243 assert_eq!(items[1].sort_text.as_ref().unwrap(), "00001");
1244 assert_eq!(items[2].sort_text.as_ref().unwrap(), "00002");
1245
1246 let mut sorted_items = items;
1247 sorted_items.sort_by(|a, b| {
1248 a.sort_text
1249 .as_ref()
1250 .unwrap()
1251 .cmp(b.sort_text.as_ref().unwrap())
1252 });
1253
1254 assert_eq!(sorted_items[0].label, "0.14.0 (latest)");
1255 assert_eq!(sorted_items[1].label, "0.8.0");
1256 assert_eq!(sorted_items[2].label, "0.2.0");
1257 }
1258
1259 #[test]
1260 fn test_version_completion_index_ordering() {
1261 let versions = ["1.20.0", "1.9.0", "1.2.0", "0.99.0", "0.50.0"];
1262
1263 let items: Vec<_> = versions
1264 .iter()
1265 .enumerate()
1266 .map(|(idx, ver)| {
1267 let v = MockVersion {
1268 version: ver.to_string(),
1269 yanked: false,
1270 prerelease: false,
1271 };
1272 let display_item = VersionDisplayItem::new(&v, "test", idx, idx == 0);
1273 build_version_completion(&display_item, None)
1274 })
1275 .collect();
1276
1277 assert_eq!(items[0].sort_text.as_ref().unwrap(), "00000");
1278 assert_eq!(items[1].sort_text.as_ref().unwrap(), "00001");
1279 assert_eq!(items[2].sort_text.as_ref().unwrap(), "00002");
1280 assert_eq!(items[3].sort_text.as_ref().unwrap(), "00003");
1281 assert_eq!(items[4].sort_text.as_ref().unwrap(), "00004");
1282
1283 let mut sorted_items = items;
1284 sorted_items.sort_by(|a, b| {
1285 a.sort_text
1286 .as_ref()
1287 .unwrap()
1288 .cmp(b.sort_text.as_ref().unwrap())
1289 });
1290
1291 assert_eq!(sorted_items[0].label, "1.20.0 (latest)");
1292 assert_eq!(sorted_items[1].label, "1.9.0");
1293 assert_eq!(sorted_items[2].label, "1.2.0");
1294 assert_eq!(sorted_items[3].label, "0.99.0");
1295 assert_eq!(sorted_items[4].label, "0.50.0");
1296 }
1297
1298 #[test]
1299 fn test_version_display_item_latest() {
1300 let version = MockVersion {
1301 version: "1.0.0".to_string(),
1302 yanked: false,
1303 prerelease: false,
1304 };
1305
1306 let item = VersionDisplayItem::new(&version, "serde", 0, true);
1307
1308 assert_eq!(item.version, "1.0.0");
1309 assert_eq!(item.label, "1.0.0 (latest)");
1310 assert_eq!(item.description, "Update serde to 1.0.0");
1311 assert_eq!(item.index, 0);
1312 assert!(item.is_latest);
1313 }
1314
1315 #[test]
1316 fn test_version_display_item_not_latest() {
1317 let version = MockVersion {
1318 version: "0.9.0".to_string(),
1319 yanked: false,
1320 prerelease: false,
1321 };
1322
1323 let item = VersionDisplayItem::new(&version, "tokio", 1, false);
1324
1325 assert_eq!(item.version, "0.9.0");
1326 assert_eq!(item.label, "0.9.0");
1327 assert_eq!(item.description, "Update tokio to 0.9.0");
1328 assert_eq!(item.index, 1);
1329 assert!(!item.is_latest);
1330 }
1331
1332 #[test]
1333 fn test_prepare_version_display_items_filters_yanked() {
1334 let versions: Vec<std::sync::Arc<dyn crate::Version>> = vec![
1335 std::sync::Arc::new(MockVersion {
1336 version: "1.0.0".to_string(),
1337 yanked: false,
1338 prerelease: false,
1339 }),
1340 std::sync::Arc::new(MockVersion {
1341 version: "0.9.0".to_string(),
1342 yanked: true,
1343 prerelease: false,
1344 }),
1345 std::sync::Arc::new(MockVersion {
1346 version: "0.8.0".to_string(),
1347 yanked: false,
1348 prerelease: false,
1349 }),
1350 ];
1351
1352 let items = prepare_version_display_items(&versions, "test");
1353
1354 assert_eq!(items.len(), 2);
1355 assert_eq!(items[0].version, "1.0.0");
1356 assert_eq!(items[0].label, "1.0.0 (latest)");
1357 assert!(items[0].is_latest);
1358 assert_eq!(items[1].version, "0.8.0");
1359 assert_eq!(items[1].label, "0.8.0");
1360 assert!(!items[1].is_latest);
1361 }
1362
1363 #[test]
1364 fn test_prepare_version_display_items_limits_to_5() {
1365 let versions: Vec<std::sync::Arc<dyn crate::Version>> = (0..10)
1366 .map(|i| {
1367 std::sync::Arc::new(MockVersion {
1368 version: format!("1.0.{}", i),
1369 yanked: false,
1370 prerelease: false,
1371 }) as std::sync::Arc<dyn crate::Version>
1372 })
1373 .collect();
1374
1375 let items = prepare_version_display_items(&versions, "test");
1376
1377 assert_eq!(items.len(), 5);
1378 assert_eq!(items[0].version, "1.0.0");
1379 assert_eq!(items[0].label, "1.0.0 (latest)");
1380 assert_eq!(items[4].version, "1.0.4");
1381 assert_eq!(items[4].label, "1.0.4");
1382 }
1383
1384 #[test]
1385 fn test_prepare_version_display_items_empty() {
1386 let versions: Vec<std::sync::Arc<dyn crate::Version>> = vec![];
1387
1388 let items = prepare_version_display_items(&versions, "test");
1389
1390 assert_eq!(items.len(), 0);
1391 }
1392
1393 #[test]
1394 fn test_prepare_version_display_items_all_yanked() {
1395 let versions: Vec<std::sync::Arc<dyn crate::Version>> = vec![
1396 std::sync::Arc::new(MockVersion {
1397 version: "1.0.0".to_string(),
1398 yanked: true,
1399 prerelease: false,
1400 }),
1401 std::sync::Arc::new(MockVersion {
1402 version: "0.9.0".to_string(),
1403 yanked: true,
1404 prerelease: false,
1405 }),
1406 ];
1407
1408 let items = prepare_version_display_items(&versions, "test");
1409
1410 assert_eq!(items.len(), 0);
1411 }
1412
1413 #[test]
1414 fn test_build_feature_completion() {
1415 let range = Range::default();
1416 let item = build_feature_completion("derive", "serde", range);
1417
1418 assert_eq!(item.label, "derive");
1419 assert_eq!(item.kind, Some(CompletionItemKind::PROPERTY));
1420 assert_eq!(item.detail, Some("Feature of serde".to_string()));
1421 assert!(item.documentation.is_none());
1422 assert_eq!(item.sort_text, Some("derive".to_string()));
1423 }
1424
1425 #[test]
1426 fn test_position_in_range_within() {
1427 let range = Range {
1428 start: Position {
1429 line: 0,
1430 character: 5,
1431 },
1432 end: Position {
1433 line: 0,
1434 character: 10,
1435 },
1436 };
1437
1438 let position = Position {
1439 line: 0,
1440 character: 7,
1441 };
1442
1443 assert!(position_in_range(position, range));
1444 }
1445
1446 #[test]
1447 fn test_position_in_range_at_start() {
1448 let range = Range {
1449 start: Position {
1450 line: 0,
1451 character: 5,
1452 },
1453 end: Position {
1454 line: 0,
1455 character: 10,
1456 },
1457 };
1458
1459 let position = Position {
1460 line: 0,
1461 character: 5,
1462 };
1463
1464 assert!(position_in_range(position, range));
1465 }
1466
1467 #[test]
1468 fn test_position_in_range_at_end() {
1469 let range = Range {
1470 start: Position {
1471 line: 0,
1472 character: 5,
1473 },
1474 end: Position {
1475 line: 0,
1476 character: 10,
1477 },
1478 };
1479
1480 let position = Position {
1481 line: 0,
1482 character: 10,
1483 };
1484
1485 assert!(position_in_range(position, range));
1486 }
1487
1488 #[test]
1489 fn test_position_in_range_one_past_end() {
1490 let range = Range {
1491 start: Position {
1492 line: 0,
1493 character: 5,
1494 },
1495 end: Position {
1496 line: 0,
1497 character: 10,
1498 },
1499 };
1500
1501 let position = Position {
1503 line: 0,
1504 character: 11,
1505 };
1506
1507 assert!(position_in_range(position, range));
1508 }
1509
1510 #[test]
1511 fn test_position_in_range_before() {
1512 let range = Range {
1513 start: Position {
1514 line: 0,
1515 character: 5,
1516 },
1517 end: Position {
1518 line: 0,
1519 character: 10,
1520 },
1521 };
1522
1523 let position = Position {
1524 line: 0,
1525 character: 4,
1526 };
1527
1528 assert!(!position_in_range(position, range));
1529 }
1530
1531 #[test]
1532 fn test_position_in_range_after() {
1533 let range = Range {
1534 start: Position {
1535 line: 0,
1536 character: 5,
1537 },
1538 end: Position {
1539 line: 0,
1540 character: 10,
1541 },
1542 };
1543
1544 let position = Position {
1545 line: 0,
1546 character: 12,
1547 };
1548
1549 assert!(!position_in_range(position, range));
1550 }
1551
1552 #[test]
1555 fn test_utf16_to_byte_offset_ascii() {
1556 let s = "hello";
1557 assert_eq!(utf16_to_byte_offset(s, 0), Some(0));
1558 assert_eq!(utf16_to_byte_offset(s, 2), Some(2));
1559 assert_eq!(utf16_to_byte_offset(s, 5), Some(5));
1560 }
1561
1562 #[test]
1563 fn test_utf16_to_byte_offset_multibyte() {
1564 let s = "日本語";
1566 assert_eq!(utf16_to_byte_offset(s, 0), Some(0));
1567 assert_eq!(utf16_to_byte_offset(s, 1), Some(3));
1568 assert_eq!(utf16_to_byte_offset(s, 2), Some(6));
1569 assert_eq!(utf16_to_byte_offset(s, 3), Some(9));
1570 }
1571
1572 #[test]
1573 fn test_utf16_to_byte_offset_emoji() {
1574 let s = "😀test";
1576 assert_eq!(utf16_to_byte_offset(s, 0), Some(0));
1577 assert_eq!(utf16_to_byte_offset(s, 2), Some(4)); assert_eq!(utf16_to_byte_offset(s, 3), Some(5)); }
1580
1581 #[test]
1582 fn test_utf16_to_byte_offset_mixed() {
1583 let s = "hello 世界 😀!";
1585 assert_eq!(utf16_to_byte_offset(s, 0), Some(0)); assert_eq!(utf16_to_byte_offset(s, 6), Some(6)); assert_eq!(utf16_to_byte_offset(s, 7), Some(9)); assert_eq!(utf16_to_byte_offset(s, 9), Some(13)); assert_eq!(utf16_to_byte_offset(s, 11), Some(17)); }
1591
1592 #[test]
1593 fn test_utf16_to_byte_offset_out_of_bounds() {
1594 let s = "hello";
1595 assert_eq!(utf16_to_byte_offset(s, 100), None);
1596 }
1597
1598 #[test]
1599 fn test_utf16_to_byte_offset_empty() {
1600 let s = "";
1601 assert_eq!(utf16_to_byte_offset(s, 0), Some(0));
1602 assert_eq!(utf16_to_byte_offset(s, 1), None);
1603 }
1604
1605 #[test]
1608 fn test_build_package_completion_long_description_ascii() {
1609 let long_desc = "a".repeat(250);
1610 let metadata = MockMetadata {
1611 name: "test-pkg".to_string(),
1612 description: Some(long_desc),
1613 repository: None,
1614 documentation: None,
1615 latest_version: "1.0.0".to_string(),
1616 };
1617
1618 let range = Range::default();
1619 let item = build_package_completion(&metadata, range);
1620
1621 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1622 let lines: Vec<_> = content.value.lines().collect();
1624 assert!(lines[2].ends_with("..."));
1625 assert!(lines[2].len() <= 203); } else {
1627 panic!("Expected MarkupContent documentation");
1628 }
1629 }
1630
1631 #[test]
1632 fn test_build_package_completion_long_description_unicode() {
1633 let mut long_desc = String::new();
1636 for _ in 0..67 {
1637 long_desc.push('日');
1638 }
1639
1640 let metadata = MockMetadata {
1641 name: "test-pkg".to_string(),
1642 description: Some(long_desc),
1643 repository: None,
1644 documentation: None,
1645 latest_version: "1.0.0".to_string(),
1646 };
1647
1648 let range = Range::default();
1649 let item = build_package_completion(&metadata, range);
1650
1651 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1653 let lines: Vec<_> = content.value.lines().collect();
1654 assert!(lines[2].ends_with("..."));
1655 assert!(lines[2].is_char_boundary(lines[2].len()));
1657 } else {
1658 panic!("Expected MarkupContent documentation");
1659 }
1660 }
1661
1662 #[test]
1663 fn test_build_package_completion_long_description_emoji() {
1664 let long_desc = "😀".repeat(51);
1667
1668 let metadata = MockMetadata {
1669 name: "test-pkg".to_string(),
1670 description: Some(long_desc),
1671 repository: None,
1672 documentation: None,
1673 latest_version: "1.0.0".to_string(),
1674 };
1675
1676 let range = Range::default();
1677 let item = build_package_completion(&metadata, range);
1678
1679 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1681 let lines: Vec<_> = content.value.lines().collect();
1682 assert!(lines[2].ends_with("..."));
1683 assert!(lines[2].is_char_boundary(lines[2].len()));
1685 } else {
1686 panic!("Expected MarkupContent documentation");
1687 }
1688 }
1689
1690 #[test]
1691 fn test_extract_prefix_unicode_package_name() {
1692 let content = "日本語-crate = \"1.0\"";
1694 let position = Position {
1695 line: 0,
1696 character: 3, };
1698 let range = Range {
1699 start: Position {
1700 line: 0,
1701 character: 0,
1702 },
1703 end: Position {
1704 line: 0,
1705 character: 10,
1706 },
1707 };
1708
1709 let prefix = extract_prefix(content, position, range);
1710 assert_eq!(prefix, "日本語");
1711 }
1712
1713 #[test]
1714 fn test_extract_prefix_emoji_in_content() {
1715 let content = "emoji-😀-crate = \"1.0\"";
1717 let position = Position {
1718 line: 0,
1719 character: 8, };
1721 let range = Range {
1722 start: Position {
1723 line: 0,
1724 character: 0,
1725 },
1726 end: Position {
1727 line: 0,
1728 character: 14,
1729 },
1730 };
1731
1732 let prefix = extract_prefix(content, position, range);
1733 assert_eq!(prefix, "emoji-😀");
1734 }
1735
1736 #[tokio::test]
1739 async fn test_complete_versions_generic_operator_stripping() {
1740 let registry = MockRegistry {
1741 versions: vec![
1742 MockVersion {
1743 version: "1.0.0".to_string(),
1744 yanked: false,
1745 prerelease: false,
1746 },
1747 MockVersion {
1748 version: "1.0.1".to_string(),
1749 yanked: false,
1750 prerelease: false,
1751 },
1752 MockVersion {
1753 version: "1.1.0".to_string(),
1754 yanked: false,
1755 prerelease: false,
1756 },
1757 MockVersion {
1758 version: "2.0.0".to_string(),
1759 yanked: false,
1760 prerelease: false,
1761 },
1762 ],
1763 };
1764
1765 let items =
1767 complete_versions_generic(®istry, "test-pkg", "^1.0", &['^', '~', '=', '<', '>'])
1768 .await;
1769
1770 assert_eq!(items.len(), 2);
1772 assert_eq!(items[0].label, "1.0.0 (latest)");
1773 assert_eq!(items[1].label, "1.0.1");
1774
1775 let items =
1777 complete_versions_generic(®istry, "test-pkg", "~1.1", &['^', '~', '=', '<', '>'])
1778 .await;
1779
1780 assert_eq!(items.len(), 1);
1781 assert_eq!(items[0].label, "1.1.0 (latest)");
1782
1783 let items =
1785 complete_versions_generic(®istry, "test-pkg", "=2.0", &['^', '~', '=', '<', '>'])
1786 .await;
1787
1788 assert_eq!(items.len(), 1);
1789 assert_eq!(items[0].label, "2.0.0 (latest)");
1790
1791 let items =
1793 complete_versions_generic(®istry, "test-pkg", "1.0", &['^', '~', '=', '<', '>'])
1794 .await;
1795
1796 assert_eq!(items.len(), 2);
1797 assert_eq!(items[0].label, "1.0.0 (latest)");
1798 assert_eq!(items[1].label, "1.0.1");
1799 }
1800
1801 #[tokio::test]
1802 async fn test_complete_versions_generic_fallback_when_no_prefix_match() {
1803 let registry = MockRegistry {
1804 versions: vec![
1805 MockVersion {
1806 version: "1.0.0".to_string(),
1807 yanked: false,
1808 prerelease: false,
1809 },
1810 MockVersion {
1811 version: "1.1.0".to_string(),
1812 yanked: false,
1813 prerelease: false,
1814 },
1815 MockVersion {
1816 version: "2.0.0".to_string(),
1817 yanked: false,
1818 prerelease: false,
1819 },
1820 MockVersion {
1821 version: "2.1.0".to_string(),
1822 yanked: true, prerelease: false,
1824 },
1825 ],
1826 };
1827
1828 let items =
1830 complete_versions_generic(®istry, "test-pkg", "3.0", &['^', '~', '=', '<', '>'])
1831 .await;
1832
1833 assert_eq!(items.len(), 3);
1835 assert_eq!(items[0].label, "1.0.0 (latest)");
1836 assert_eq!(items[1].label, "1.1.0");
1837 assert_eq!(items[2].label, "2.0.0");
1838
1839 assert!(!items.iter().any(|item| item.label == "2.1.0"));
1841
1842 let items = complete_versions_generic(®istry, "test-pkg", "", &[]).await;
1844
1845 assert_eq!(items.len(), 3);
1846 assert_eq!(items[0].label, "1.0.0 (latest)");
1847 assert_eq!(items[1].label, "1.1.0");
1848 assert_eq!(items[2].label, "2.0.0");
1849 }
1850
1851 #[tokio::test]
1852 async fn test_complete_versions_generic_filters_yanked_in_prefix_match() {
1853 let registry = MockRegistry {
1854 versions: vec![
1855 MockVersion {
1856 version: "1.0.0".to_string(),
1857 yanked: false,
1858 prerelease: false,
1859 },
1860 MockVersion {
1861 version: "1.0.1".to_string(),
1862 yanked: true, prerelease: false,
1864 },
1865 MockVersion {
1866 version: "1.0.2".to_string(),
1867 yanked: false,
1868 prerelease: false,
1869 },
1870 ],
1871 };
1872
1873 let items = complete_versions_generic(®istry, "test-pkg", "1.0", &[]).await;
1875
1876 assert_eq!(items.len(), 2);
1878 assert_eq!(items[0].label, "1.0.0 (latest)");
1879 assert_eq!(items[1].label, "1.0.2");
1880
1881 assert!(!items.iter().any(|item| item.label == "1.0.1"));
1883 }
1884
1885 #[tokio::test]
1886 async fn test_complete_versions_generic_limit_5() {
1887 let versions: Vec<_> = (0..10)
1889 .map(|i| MockVersion {
1890 version: format!("1.0.{}", i),
1891 yanked: false,
1892 prerelease: false,
1893 })
1894 .collect();
1895
1896 let registry = MockRegistry { versions };
1897
1898 let items = complete_versions_generic(®istry, "test-pkg", "1.0", &[]).await;
1900
1901 assert_eq!(items.len(), 5);
1902 assert_eq!(items[0].label, "1.0.0 (latest)");
1903 assert_eq!(items[4].label, "1.0.4");
1904 }
1905
1906 #[tokio::test]
1907 async fn test_complete_versions_generic_go_no_operators() {
1908 let registry = MockRegistry {
1909 versions: vec![
1910 MockVersion {
1911 version: "v1.9.0".to_string(),
1912 yanked: false,
1913 prerelease: false,
1914 },
1915 MockVersion {
1916 version: "v1.9.1".to_string(),
1917 yanked: false,
1918 prerelease: false,
1919 },
1920 MockVersion {
1921 version: "v1.10.0".to_string(),
1922 yanked: false,
1923 prerelease: false,
1924 },
1925 ],
1926 };
1927
1928 let items =
1930 complete_versions_generic(®istry, "github.com/gin-gonic/gin", "v1.9", &[]).await;
1931
1932 assert_eq!(items.len(), 2);
1933 assert_eq!(items[0].label, "v1.9.0 (latest)");
1934 assert_eq!(items[1].label, "v1.9.1");
1935 }
1936}