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 if let Some(features_range) = dep.features_range()
143 && position_in_range(position, features_range)
144 {
145 let prefix = extract_feature_prefix(content, position);
146 return CompletionContext::Feature {
147 package_name: dep.name().to_string(),
148 prefix,
149 };
150 }
151 }
152
153 CompletionContext::None
154}
155
156const fn position_in_range(position: Position, range: Range) -> bool {
162 if position.line < range.start.line {
164 return false;
165 }
166
167 if position.line == range.start.line && position.character < range.start.character {
168 return false;
169 }
170
171 if position.line > range.end.line {
173 return false;
174 }
175
176 if position.line == range.end.line && position.character > range.end.character + 1 {
177 return false;
178 }
179
180 true
181}
182
183pub fn utf16_to_byte_offset(s: &str, utf16_offset: u32) -> Option<usize> {
214 let mut utf16_count = 0u32;
215 for (byte_idx, ch) in s.char_indices() {
216 if utf16_count >= utf16_offset {
217 return Some(byte_idx);
218 }
219 utf16_count += ch.len_utf16() as u32;
220 }
221 if utf16_count == utf16_offset {
222 return Some(s.len());
223 }
224 None
225}
226
227pub fn extract_prefix(content: &str, position: Position, range: Range) -> String {
259 let line = match content.lines().nth(position.line as usize) {
261 Some(l) => l,
262 None => return String::new(),
263 };
264
265 let start_byte = if position.line == range.start.line {
267 match utf16_to_byte_offset(line, range.start.character) {
268 Some(offset) => offset,
269 None => return String::new(),
270 }
271 } else {
272 0
273 };
274
275 let cursor_byte = match utf16_to_byte_offset(line, position.character) {
276 Some(offset) => offset,
277 None => return String::new(),
278 };
279
280 if start_byte > line.len() || cursor_byte > line.len() || start_byte > cursor_byte {
282 return String::new();
283 }
284
285 let prefix = &line[start_byte..cursor_byte];
287
288 prefix
290 .trim()
291 .trim_matches('"')
292 .trim_matches('\'')
293 .trim()
294 .to_string()
295}
296
297pub fn extract_feature_prefix(content: &str, position: Position) -> String {
319 let line = match content.lines().nth(position.line as usize) {
320 Some(l) => l,
321 None => return String::new(),
322 };
323
324 let cursor_byte = match utf16_to_byte_offset(line, position.character) {
325 Some(offset) => offset.min(line.len()),
326 None => return String::new(),
327 };
328
329 let before_cursor = &line[..cursor_byte];
330
331 let segment_start = before_cursor.rfind('[').map_or(0, |i| i + 1);
335 let segment = &before_cursor[segment_start..];
336
337 let quote_count = segment.chars().filter(|&c| c == '"').count();
340 if quote_count % 2 == 0 {
341 return String::new();
342 }
343
344 match segment.rfind('"') {
346 Some(pos) => segment[pos + 1..].to_string(),
347 None => String::new(),
348 }
349}
350
351pub fn build_package_completion(metadata: &dyn Metadata, insert_range: Range) -> CompletionItem {
378 let name = metadata.name();
379 let latest = metadata.latest_version();
380
381 let mut doc_parts = vec![format!("**{}** v{}", name, latest)];
383
384 if let Some(desc) = metadata.description() {
385 doc_parts.push(String::new()); let truncated = if desc.len() > 200 {
387 let mut end = 200;
388 while end > 0 && !desc.is_char_boundary(end) {
389 end -= 1;
390 }
391 format!("{}...", &desc[..end])
392 } else {
393 desc.to_string()
394 };
395 doc_parts.push(truncated);
396 }
397
398 let mut links = Vec::new();
400 if let Some(repo) = metadata.repository() {
401 links.push(format!("[Repository]({})", repo));
402 }
403 if let Some(docs) = metadata.documentation() {
404 links.push(format!("[Documentation]({})", docs));
405 }
406
407 if !links.is_empty() {
408 doc_parts.push(String::new()); doc_parts.push(links.join(" | "));
410 }
411
412 CompletionItem {
413 label: name.to_string(),
414 kind: Some(CompletionItemKind::MODULE),
415 detail: Some(format!("v{}", latest)),
416 documentation: Some(Documentation::MarkupContent(MarkupContent {
417 kind: MarkupKind::Markdown,
418 value: doc_parts.join("\n"),
419 })),
420 insert_text: Some(name.to_string()),
421 text_edit: Some(CompletionTextEdit::Edit(TextEdit {
422 range: insert_range,
423 new_text: name.to_string(),
424 })),
425 sort_text: Some(name.to_string()),
426 filter_text: Some(name.to_string()),
427 ..Default::default()
428 }
429}
430
431pub fn build_version_completion(
471 display_item: &VersionDisplayItem,
472 insert_range: Option<Range>,
473) -> CompletionItem {
474 let sort_text = format!("{:05}", display_item.index);
476
477 CompletionItem {
478 label: display_item.label.clone(),
479 kind: Some(CompletionItemKind::VALUE),
480 detail: Some(display_item.description.clone()),
481 documentation: None,
482 insert_text: Some(display_item.version.clone()),
483 text_edit: insert_range.map(|range| {
484 CompletionTextEdit::Edit(TextEdit {
485 range,
486 new_text: display_item.version.clone(),
487 })
488 }),
489 sort_text: Some(sort_text),
490 preselect: Some(display_item.is_latest),
491 ..Default::default()
492 }
493}
494
495#[derive(Debug, Clone)]
499pub struct VersionDisplayItem {
500 pub version: String,
502 pub label: String,
504 pub description: String,
506 pub index: usize,
508 pub is_latest: bool,
510}
511
512impl VersionDisplayItem {
513 pub fn new(version: &dyn Version, package_name: &str, index: usize, is_latest: bool) -> Self {
515 let version_str = version.version_string();
516 let label = if is_latest {
517 format!("{} (latest)", version_str)
518 } else {
519 version_str.to_string()
520 };
521 let description = format!("Update {} to {}", package_name, version_str);
522
523 Self {
524 version: version_str.to_string(),
525 label,
526 description,
527 index,
528 is_latest,
529 }
530 }
531}
532
533pub fn prepare_version_display_items<V: AsRef<dyn Version>>(
537 versions: &[V],
538 package_name: &str,
539) -> Vec<VersionDisplayItem> {
540 versions
541 .iter()
542 .map(|v| v.as_ref())
543 .filter(|v| !v.is_yanked())
544 .take(MAX_COMPLETION_VERSIONS)
545 .enumerate()
546 .map(|(index, version)| VersionDisplayItem::new(version, package_name, index, index == 0))
547 .collect()
548}
549
550pub fn build_feature_completion(
575 feature_name: &str,
576 package_name: &str,
577 insert_range: Option<Range>,
578) -> CompletionItem {
579 CompletionItem {
580 label: feature_name.to_string(),
581 kind: Some(CompletionItemKind::PROPERTY),
582 detail: Some(format!("Feature of {}", package_name)),
583 documentation: None,
584 insert_text: Some(feature_name.to_string()),
585 text_edit: insert_range.map(|range| {
586 CompletionTextEdit::Edit(TextEdit {
587 range,
588 new_text: feature_name.to_string(),
589 })
590 }),
591 sort_text: Some(feature_name.to_string()),
592 ..Default::default()
593 }
594}
595
596const MAX_COMPLETION_VERSIONS: usize = 5;
598
599pub async fn complete_package_names_generic(
646 registry: &dyn crate::Registry,
647 prefix: &str,
648 limit: usize,
649) -> Vec<CompletionItem> {
650 if prefix.len() < 2 || prefix.len() > 200 {
651 return vec![];
652 }
653
654 let results = match registry.search(prefix, limit).await {
655 Ok(r) => r,
656 Err(e) => {
657 tracing::warn!("Registry search failed for '{}': {}", prefix, e);
658 return vec![];
659 }
660 };
661
662 let insert_range = tower_lsp_server::ls_types::Range::default();
663
664 results
665 .into_iter()
666 .map(|metadata| build_package_completion(metadata.as_ref(), insert_range))
667 .collect()
668}
669
670pub async fn complete_versions_generic(
671 registry: &dyn crate::Registry,
672 package_name: &str,
673 prefix: &str,
674 operator_chars: &[char],
675) -> Vec<CompletionItem> {
676 let versions = match registry.get_versions(package_name).await {
677 Ok(v) => v,
678 Err(e) => {
679 tracing::warn!("Failed to fetch versions for '{}': {}", package_name, e);
680 return vec![];
681 }
682 };
683
684 let clean_prefix = prefix.trim_start_matches(operator_chars).trim();
685
686 let filtered_versions: Vec<_> = versions
688 .iter()
689 .filter(|v| v.version_string().starts_with(clean_prefix))
690 .collect();
691
692 let display_items = if filtered_versions.is_empty() {
694 prepare_version_display_items(&versions, package_name)
695 } else {
696 prepare_version_display_items(&filtered_versions, package_name)
697 };
698
699 display_items
701 .iter()
702 .map(|item| build_version_completion(item, None))
703 .collect()
704}
705
706#[cfg(test)]
707mod tests {
708 use super::*;
709 use std::any::Any;
710
711 struct MockDependency {
714 name: String,
715 name_range: Range,
716 version_range: Option<Range>,
717 features_range: Option<Range>,
718 }
719
720 impl crate::ecosystem::Dependency for MockDependency {
721 fn name(&self) -> &str {
722 &self.name
723 }
724
725 fn name_range(&self) -> Range {
726 self.name_range
727 }
728
729 fn version_requirement(&self) -> Option<&str> {
730 Some("1.0")
731 }
732
733 fn version_range(&self) -> Option<Range> {
734 self.version_range
735 }
736
737 fn features_range(&self) -> Option<Range> {
738 self.features_range
739 }
740
741 fn source(&self) -> crate::parser::DependencySource {
742 crate::parser::DependencySource::Registry
743 }
744
745 fn as_any(&self) -> &dyn Any {
746 self
747 }
748 }
749
750 struct MockParseResult {
751 dependencies: Vec<MockDependency>,
752 }
753
754 impl ParseResult for MockParseResult {
755 fn dependencies(&self) -> Vec<&dyn crate::ecosystem::Dependency> {
756 self.dependencies
757 .iter()
758 .map(|d| d as &dyn crate::ecosystem::Dependency)
759 .collect()
760 }
761
762 fn workspace_root(&self) -> Option<&std::path::Path> {
763 None
764 }
765
766 fn uri(&self) -> &tower_lsp_server::ls_types::Uri {
767 static URL: std::sync::LazyLock<tower_lsp_server::ls_types::Uri> =
768 std::sync::LazyLock::new(|| "file:///test/Cargo.toml".parse().unwrap());
769 &URL
770 }
771
772 fn as_any(&self) -> &dyn Any {
773 self
774 }
775 }
776
777 struct MockVersion {
778 version: String,
779 yanked: bool,
780 prerelease: bool,
781 }
782
783 impl crate::registry::Version for MockVersion {
784 fn version_string(&self) -> &str {
785 &self.version
786 }
787
788 fn is_yanked(&self) -> bool {
789 self.yanked
790 }
791
792 fn is_prerelease(&self) -> bool {
793 self.prerelease
794 }
795
796 fn as_any(&self) -> &dyn Any {
797 self
798 }
799 }
800
801 struct MockMetadata {
802 name: String,
803 description: Option<String>,
804 repository: Option<String>,
805 documentation: Option<String>,
806 latest_version: String,
807 }
808
809 impl crate::registry::Metadata for MockMetadata {
810 fn name(&self) -> &str {
811 &self.name
812 }
813
814 fn description(&self) -> Option<&str> {
815 self.description.as_deref()
816 }
817
818 fn repository(&self) -> Option<&str> {
819 self.repository.as_deref()
820 }
821
822 fn documentation(&self) -> Option<&str> {
823 self.documentation.as_deref()
824 }
825
826 fn latest_version(&self) -> &str {
827 &self.latest_version
828 }
829
830 fn as_any(&self) -> &dyn Any {
831 self
832 }
833 }
834
835 struct MockRegistry {
836 versions: Vec<MockVersion>,
837 }
838
839 impl crate::Registry for MockRegistry {
840 fn get_versions<'a>(
841 &'a self,
842 _package_name: &'a str,
843 ) -> crate::ecosystem::BoxFuture<'a, crate::error::Result<Vec<Box<dyn crate::Version>>>>
844 {
845 let versions: Vec<Box<dyn crate::Version>> = self
846 .versions
847 .iter()
848 .map(|v| {
849 Box::new(MockVersion {
850 version: v.version.clone(),
851 yanked: v.yanked,
852 prerelease: v.prerelease,
853 }) as Box<dyn crate::Version>
854 })
855 .collect();
856 Box::pin(async move { Ok(versions) })
857 }
858
859 fn get_latest_matching<'a>(
860 &'a self,
861 _name: &'a str,
862 _req: &'a str,
863 ) -> crate::ecosystem::BoxFuture<'a, crate::error::Result<Option<Box<dyn crate::Version>>>>
864 {
865 Box::pin(async move { Ok(None) })
866 }
867
868 fn search<'a>(
869 &'a self,
870 _query: &'a str,
871 _limit: usize,
872 ) -> crate::ecosystem::BoxFuture<'a, crate::error::Result<Vec<Box<dyn crate::Metadata>>>>
873 {
874 Box::pin(async move { Ok(vec![]) })
875 }
876
877 fn package_url(&self, _name: &str) -> String {
878 String::new()
879 }
880
881 fn as_any(&self) -> &dyn Any {
882 self
883 }
884 }
885
886 #[test]
889 fn test_detect_package_name_context_at_start() {
890 let parse_result = MockParseResult {
891 dependencies: vec![MockDependency {
892 name: "serde".to_string(),
893 name_range: Range {
894 start: Position {
895 line: 0,
896 character: 0,
897 },
898 end: Position {
899 line: 0,
900 character: 5,
901 },
902 },
903 version_range: None,
904 features_range: None,
905 }],
906 };
907
908 let content = "serde";
909 let position = Position {
910 line: 0,
911 character: 0,
912 };
913
914 let context = detect_completion_context(&parse_result, position, content);
915
916 match context {
917 CompletionContext::PackageName { prefix } => {
918 assert_eq!(prefix, "");
919 }
920 _ => panic!("Expected PackageName context, got {:?}", context),
921 }
922 }
923
924 #[test]
925 fn test_detect_package_name_context_partial() {
926 let parse_result = MockParseResult {
927 dependencies: vec![MockDependency {
928 name: "serde".to_string(),
929 name_range: Range {
930 start: Position {
931 line: 0,
932 character: 0,
933 },
934 end: Position {
935 line: 0,
936 character: 5,
937 },
938 },
939 version_range: None,
940 features_range: None,
941 }],
942 };
943
944 let content = "serde";
945 let position = Position {
946 line: 0,
947 character: 3,
948 };
949
950 let context = detect_completion_context(&parse_result, position, content);
951
952 match context {
953 CompletionContext::PackageName { prefix } => {
954 assert_eq!(prefix, "ser");
955 }
956 _ => panic!("Expected PackageName context, got {:?}", context),
957 }
958 }
959
960 #[test]
961 fn test_detect_version_context() {
962 let parse_result = MockParseResult {
963 dependencies: vec![MockDependency {
964 name: "serde".to_string(),
965 name_range: Range {
966 start: Position {
967 line: 0,
968 character: 0,
969 },
970 end: Position {
971 line: 0,
972 character: 5,
973 },
974 },
975 version_range: Some(Range {
976 start: Position {
977 line: 0,
978 character: 9,
979 },
980 end: Position {
981 line: 0,
982 character: 14,
983 },
984 }),
985 features_range: None,
986 }],
987 };
988
989 let content = r#"serde = "1.0.1""#;
990 let position = Position {
991 line: 0,
992 character: 11,
993 };
994
995 let context = detect_completion_context(&parse_result, position, content);
996
997 match context {
998 CompletionContext::Version {
999 package_name,
1000 prefix,
1001 } => {
1002 assert_eq!(package_name, "serde");
1003 assert_eq!(prefix, "1.");
1004 }
1005 _ => panic!("Expected Version context, got {:?}", context),
1006 }
1007 }
1008
1009 #[test]
1010 fn test_detect_no_context_before_dependencies() {
1011 let parse_result = MockParseResult {
1012 dependencies: vec![MockDependency {
1013 name: "serde".to_string(),
1014 name_range: Range {
1015 start: Position {
1016 line: 5,
1017 character: 0,
1018 },
1019 end: Position {
1020 line: 5,
1021 character: 5,
1022 },
1023 },
1024 version_range: None,
1025 features_range: None,
1026 }],
1027 };
1028
1029 let content = "[dependencies]\nserde";
1030 let position = Position {
1031 line: 0,
1032 character: 10,
1033 };
1034
1035 let context = detect_completion_context(&parse_result, position, content);
1036
1037 assert_eq!(context, CompletionContext::None);
1038 }
1039
1040 #[test]
1041 fn test_detect_no_context_invalid_position() {
1042 let parse_result = MockParseResult {
1043 dependencies: vec![],
1044 };
1045
1046 let content = "";
1047 let position = Position {
1048 line: 100,
1049 character: 100,
1050 };
1051
1052 let context = detect_completion_context(&parse_result, position, content);
1053
1054 assert_eq!(context, CompletionContext::None);
1055 }
1056
1057 #[test]
1060 fn test_extract_prefix_at_start() {
1061 let content = "serde";
1062 let position = Position {
1063 line: 0,
1064 character: 0,
1065 };
1066 let range = Range {
1067 start: Position {
1068 line: 0,
1069 character: 0,
1070 },
1071 end: Position {
1072 line: 0,
1073 character: 5,
1074 },
1075 };
1076
1077 let prefix = extract_prefix(content, position, range);
1078 assert_eq!(prefix, "");
1079 }
1080
1081 #[test]
1082 fn test_extract_prefix_partial() {
1083 let content = "serde";
1084 let position = Position {
1085 line: 0,
1086 character: 3,
1087 };
1088 let range = Range {
1089 start: Position {
1090 line: 0,
1091 character: 0,
1092 },
1093 end: Position {
1094 line: 0,
1095 character: 5,
1096 },
1097 };
1098
1099 let prefix = extract_prefix(content, position, range);
1100 assert_eq!(prefix, "ser");
1101 }
1102
1103 #[test]
1104 fn test_extract_prefix_with_quotes() {
1105 let content = r#"serde = "1.0""#;
1106 let position = Position {
1107 line: 0,
1108 character: 11,
1109 };
1110 let range = Range {
1111 start: Position {
1112 line: 0,
1113 character: 9,
1114 },
1115 end: Position {
1116 line: 0,
1117 character: 13,
1118 },
1119 };
1120
1121 let prefix = extract_prefix(content, position, range);
1122 assert_eq!(prefix, "1.");
1123 }
1124
1125 #[test]
1126 fn test_extract_prefix_empty() {
1127 let content = r#"serde = """#;
1128 let position = Position {
1129 line: 0,
1130 character: 9,
1131 };
1132 let range = Range {
1133 start: Position {
1134 line: 0,
1135 character: 9,
1136 },
1137 end: Position {
1138 line: 0,
1139 character: 11,
1140 },
1141 };
1142
1143 let prefix = extract_prefix(content, position, range);
1144 assert_eq!(prefix, "");
1145 }
1146
1147 #[test]
1148 fn test_extract_prefix_version_with_operator() {
1149 let content = r#"serde = "^1.0""#;
1150 let position = Position {
1151 line: 0,
1152 character: 12,
1153 };
1154 let range = Range {
1155 start: Position {
1156 line: 0,
1157 character: 9,
1158 },
1159 end: Position {
1160 line: 0,
1161 character: 14,
1162 },
1163 };
1164
1165 let prefix = extract_prefix(content, position, range);
1166 assert_eq!(prefix, "^1.");
1167 }
1168
1169 #[test]
1172 fn test_build_package_completion_full() {
1173 let metadata = MockMetadata {
1174 name: "serde".to_string(),
1175 description: Some("Serialization framework".to_string()),
1176 repository: Some("https://github.com/serde-rs/serde".to_string()),
1177 documentation: Some("https://docs.rs/serde".to_string()),
1178 latest_version: "1.0.214".to_string(),
1179 };
1180
1181 let range = Range::default();
1182 let item = build_package_completion(&metadata, range);
1183
1184 assert_eq!(item.label, "serde");
1185 assert_eq!(item.kind, Some(CompletionItemKind::MODULE));
1186 assert_eq!(item.detail, Some("v1.0.214".to_string()));
1187 assert!(matches!(
1188 item.documentation,
1189 Some(Documentation::MarkupContent(_))
1190 ));
1191
1192 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1193 assert!(content.value.contains("**serde** v1.0.214"));
1194 assert!(content.value.contains("Serialization framework"));
1195 assert!(content.value.contains("Repository"));
1196 assert!(content.value.contains("Documentation"));
1197 }
1198 }
1199
1200 #[test]
1201 fn test_build_package_completion_minimal() {
1202 let metadata = MockMetadata {
1203 name: "test-pkg".to_string(),
1204 description: None,
1205 repository: None,
1206 documentation: None,
1207 latest_version: "0.1.0".to_string(),
1208 };
1209
1210 let range = Range::default();
1211 let item = build_package_completion(&metadata, range);
1212
1213 assert_eq!(item.label, "test-pkg");
1214 assert_eq!(item.detail, Some("v0.1.0".to_string()));
1215
1216 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1217 assert!(content.value.contains("**test-pkg** v0.1.0"));
1218 assert!(!content.value.contains("Repository"));
1219 }
1220 }
1221
1222 #[test]
1223 fn test_build_version_completion_stable() {
1224 let version = MockVersion {
1225 version: "1.0.0".to_string(),
1226 yanked: false,
1227 prerelease: false,
1228 };
1229
1230 let display_item = VersionDisplayItem::new(&version, "serde", 0, false);
1231 let item = build_version_completion(&display_item, None);
1232
1233 assert_eq!(item.label, "1.0.0");
1234 assert_eq!(item.kind, Some(CompletionItemKind::VALUE));
1235 assert_eq!(item.detail, Some("Update serde to 1.0.0".to_string()));
1236 assert_eq!(item.documentation, None);
1237 assert_eq!(item.preselect, Some(false));
1238 assert_eq!(item.sort_text, Some("00000".to_string()));
1239 assert_eq!(item.text_edit, None); }
1241
1242 #[test]
1243 fn test_build_version_completion_latest() {
1244 let version = MockVersion {
1245 version: "1.0.0".to_string(),
1246 yanked: false,
1247 prerelease: false,
1248 };
1249
1250 let display_item = VersionDisplayItem::new(&version, "serde", 0, true);
1251 let item = build_version_completion(&display_item, None);
1252
1253 assert_eq!(item.label, "1.0.0 (latest)");
1254 assert_eq!(item.kind, Some(CompletionItemKind::VALUE));
1255 assert_eq!(item.detail, Some("Update serde to 1.0.0".to_string()));
1256 assert_eq!(item.documentation, None);
1257 assert_eq!(item.preselect, Some(true));
1258 assert_eq!(item.sort_text, Some("00000".to_string()));
1259 assert_eq!(item.text_edit, None); }
1261
1262 #[test]
1263 fn test_build_version_completion_not_latest() {
1264 let version = MockVersion {
1265 version: "0.9.0".to_string(),
1266 yanked: false,
1267 prerelease: false,
1268 };
1269
1270 let display_item = VersionDisplayItem::new(&version, "tokio", 1, false);
1271 let item = build_version_completion(&display_item, None);
1272
1273 assert_eq!(item.label, "0.9.0");
1274 assert_eq!(item.detail, Some("Update tokio to 0.9.0".to_string()));
1275 assert_eq!(item.documentation, None);
1276 assert_eq!(item.preselect, Some(false));
1277 assert_eq!(item.sort_text, Some("00001".to_string()));
1278 assert_eq!(item.text_edit, None); }
1280
1281 #[test]
1282 fn test_build_version_completion_sort_order() {
1283 let v1 = MockVersion {
1284 version: "1.0.0".to_string(),
1285 yanked: false,
1286 prerelease: false,
1287 };
1288 let v2 = MockVersion {
1289 version: "0.9.0".to_string(),
1290 yanked: false,
1291 prerelease: false,
1292 };
1293 let v3 = MockVersion {
1294 version: "0.8.0".to_string(),
1295 yanked: false,
1296 prerelease: false,
1297 };
1298
1299 let display_item1 = VersionDisplayItem::new(&v1, "test", 0, true);
1300 let display_item2 = VersionDisplayItem::new(&v2, "test", 1, false);
1301 let display_item3 = VersionDisplayItem::new(&v3, "test", 2, false);
1302 let item1 = build_version_completion(&display_item1, None);
1303 let item2 = build_version_completion(&display_item2, None);
1304 let item3 = build_version_completion(&display_item3, None);
1305
1306 assert_eq!(item1.sort_text.as_ref().unwrap(), "00000");
1308 assert_eq!(item2.sort_text.as_ref().unwrap(), "00001");
1309 assert_eq!(item3.sort_text.as_ref().unwrap(), "00002");
1310
1311 assert_eq!(item1.preselect, Some(true));
1313 assert_eq!(item2.preselect, Some(false));
1314 assert_eq!(item3.preselect, Some(false));
1315 }
1316
1317 #[test]
1318 fn test_version_completion_semantic_ordering() {
1319 let versions = [
1320 MockVersion {
1321 version: "0.14.0".to_string(),
1322 yanked: false,
1323 prerelease: false,
1324 },
1325 MockVersion {
1326 version: "0.8.0".to_string(),
1327 yanked: false,
1328 prerelease: false,
1329 },
1330 MockVersion {
1331 version: "0.2.0".to_string(),
1332 yanked: false,
1333 prerelease: false,
1334 },
1335 ];
1336
1337 let items: Vec<_> = versions
1338 .iter()
1339 .enumerate()
1340 .map(|(idx, v)| {
1341 let display_item = VersionDisplayItem::new(v, "test", idx, idx == 0);
1342 build_version_completion(&display_item, None)
1343 })
1344 .collect();
1345
1346 assert_eq!(items[0].sort_text.as_ref().unwrap(), "00000");
1347 assert_eq!(items[1].sort_text.as_ref().unwrap(), "00001");
1348 assert_eq!(items[2].sort_text.as_ref().unwrap(), "00002");
1349
1350 let mut sorted_items = items;
1351 sorted_items.sort_by(|a, b| {
1352 a.sort_text
1353 .as_ref()
1354 .unwrap()
1355 .cmp(b.sort_text.as_ref().unwrap())
1356 });
1357
1358 assert_eq!(sorted_items[0].label, "0.14.0 (latest)");
1359 assert_eq!(sorted_items[1].label, "0.8.0");
1360 assert_eq!(sorted_items[2].label, "0.2.0");
1361 }
1362
1363 #[test]
1364 fn test_version_completion_index_ordering() {
1365 let versions = ["1.20.0", "1.9.0", "1.2.0", "0.99.0", "0.50.0"];
1366
1367 let items: Vec<_> = versions
1368 .iter()
1369 .enumerate()
1370 .map(|(idx, ver)| {
1371 let v = MockVersion {
1372 version: ver.to_string(),
1373 yanked: false,
1374 prerelease: false,
1375 };
1376 let display_item = VersionDisplayItem::new(&v, "test", idx, idx == 0);
1377 build_version_completion(&display_item, None)
1378 })
1379 .collect();
1380
1381 assert_eq!(items[0].sort_text.as_ref().unwrap(), "00000");
1382 assert_eq!(items[1].sort_text.as_ref().unwrap(), "00001");
1383 assert_eq!(items[2].sort_text.as_ref().unwrap(), "00002");
1384 assert_eq!(items[3].sort_text.as_ref().unwrap(), "00003");
1385 assert_eq!(items[4].sort_text.as_ref().unwrap(), "00004");
1386
1387 let mut sorted_items = items;
1388 sorted_items.sort_by(|a, b| {
1389 a.sort_text
1390 .as_ref()
1391 .unwrap()
1392 .cmp(b.sort_text.as_ref().unwrap())
1393 });
1394
1395 assert_eq!(sorted_items[0].label, "1.20.0 (latest)");
1396 assert_eq!(sorted_items[1].label, "1.9.0");
1397 assert_eq!(sorted_items[2].label, "1.2.0");
1398 assert_eq!(sorted_items[3].label, "0.99.0");
1399 assert_eq!(sorted_items[4].label, "0.50.0");
1400 }
1401
1402 #[test]
1403 fn test_version_display_item_latest() {
1404 let version = MockVersion {
1405 version: "1.0.0".to_string(),
1406 yanked: false,
1407 prerelease: false,
1408 };
1409
1410 let item = VersionDisplayItem::new(&version, "serde", 0, true);
1411
1412 assert_eq!(item.version, "1.0.0");
1413 assert_eq!(item.label, "1.0.0 (latest)");
1414 assert_eq!(item.description, "Update serde to 1.0.0");
1415 assert_eq!(item.index, 0);
1416 assert!(item.is_latest);
1417 }
1418
1419 #[test]
1420 fn test_version_display_item_not_latest() {
1421 let version = MockVersion {
1422 version: "0.9.0".to_string(),
1423 yanked: false,
1424 prerelease: false,
1425 };
1426
1427 let item = VersionDisplayItem::new(&version, "tokio", 1, false);
1428
1429 assert_eq!(item.version, "0.9.0");
1430 assert_eq!(item.label, "0.9.0");
1431 assert_eq!(item.description, "Update tokio to 0.9.0");
1432 assert_eq!(item.index, 1);
1433 assert!(!item.is_latest);
1434 }
1435
1436 #[test]
1437 fn test_prepare_version_display_items_filters_yanked() {
1438 let versions: Vec<std::sync::Arc<dyn crate::Version>> = vec![
1439 std::sync::Arc::new(MockVersion {
1440 version: "1.0.0".to_string(),
1441 yanked: false,
1442 prerelease: false,
1443 }),
1444 std::sync::Arc::new(MockVersion {
1445 version: "0.9.0".to_string(),
1446 yanked: true,
1447 prerelease: false,
1448 }),
1449 std::sync::Arc::new(MockVersion {
1450 version: "0.8.0".to_string(),
1451 yanked: false,
1452 prerelease: false,
1453 }),
1454 ];
1455
1456 let items = prepare_version_display_items(&versions, "test");
1457
1458 assert_eq!(items.len(), 2);
1459 assert_eq!(items[0].version, "1.0.0");
1460 assert_eq!(items[0].label, "1.0.0 (latest)");
1461 assert!(items[0].is_latest);
1462 assert_eq!(items[1].version, "0.8.0");
1463 assert_eq!(items[1].label, "0.8.0");
1464 assert!(!items[1].is_latest);
1465 }
1466
1467 #[test]
1468 fn test_prepare_version_display_items_limits_to_5() {
1469 let versions: Vec<std::sync::Arc<dyn crate::Version>> = (0..10)
1470 .map(|i| {
1471 std::sync::Arc::new(MockVersion {
1472 version: format!("1.0.{}", i),
1473 yanked: false,
1474 prerelease: false,
1475 }) as std::sync::Arc<dyn crate::Version>
1476 })
1477 .collect();
1478
1479 let items = prepare_version_display_items(&versions, "test");
1480
1481 assert_eq!(items.len(), 5);
1482 assert_eq!(items[0].version, "1.0.0");
1483 assert_eq!(items[0].label, "1.0.0 (latest)");
1484 assert_eq!(items[4].version, "1.0.4");
1485 assert_eq!(items[4].label, "1.0.4");
1486 }
1487
1488 #[test]
1489 fn test_prepare_version_display_items_empty() {
1490 let versions: Vec<std::sync::Arc<dyn crate::Version>> = vec![];
1491
1492 let items = prepare_version_display_items(&versions, "test");
1493
1494 assert_eq!(items.len(), 0);
1495 }
1496
1497 #[test]
1498 fn test_prepare_version_display_items_all_yanked() {
1499 let versions: Vec<std::sync::Arc<dyn crate::Version>> = vec![
1500 std::sync::Arc::new(MockVersion {
1501 version: "1.0.0".to_string(),
1502 yanked: true,
1503 prerelease: false,
1504 }),
1505 std::sync::Arc::new(MockVersion {
1506 version: "0.9.0".to_string(),
1507 yanked: true,
1508 prerelease: false,
1509 }),
1510 ];
1511
1512 let items = prepare_version_display_items(&versions, "test");
1513
1514 assert_eq!(items.len(), 0);
1515 }
1516
1517 #[test]
1518 fn test_build_feature_completion() {
1519 let item = build_feature_completion("derive", "serde", None);
1520
1521 assert_eq!(item.label, "derive");
1522 assert_eq!(item.kind, Some(CompletionItemKind::PROPERTY));
1523 assert_eq!(item.detail, Some("Feature of serde".to_string()));
1524 assert!(item.documentation.is_none());
1525 assert!(item.text_edit.is_none());
1526 assert_eq!(item.sort_text, Some("derive".to_string()));
1527 }
1528
1529 #[test]
1530 fn test_build_feature_completion_with_range() {
1531 let range = Range::default();
1532 let item = build_feature_completion("derive", "serde", Some(range));
1533
1534 assert_eq!(item.label, "derive");
1535 assert!(item.text_edit.is_some());
1536 }
1537
1538 #[test]
1539 fn test_position_in_range_within() {
1540 let range = Range {
1541 start: Position {
1542 line: 0,
1543 character: 5,
1544 },
1545 end: Position {
1546 line: 0,
1547 character: 10,
1548 },
1549 };
1550
1551 let position = Position {
1552 line: 0,
1553 character: 7,
1554 };
1555
1556 assert!(position_in_range(position, range));
1557 }
1558
1559 #[test]
1560 fn test_position_in_range_at_start() {
1561 let range = Range {
1562 start: Position {
1563 line: 0,
1564 character: 5,
1565 },
1566 end: Position {
1567 line: 0,
1568 character: 10,
1569 },
1570 };
1571
1572 let position = Position {
1573 line: 0,
1574 character: 5,
1575 };
1576
1577 assert!(position_in_range(position, range));
1578 }
1579
1580 #[test]
1581 fn test_position_in_range_at_end() {
1582 let range = Range {
1583 start: Position {
1584 line: 0,
1585 character: 5,
1586 },
1587 end: Position {
1588 line: 0,
1589 character: 10,
1590 },
1591 };
1592
1593 let position = Position {
1594 line: 0,
1595 character: 10,
1596 };
1597
1598 assert!(position_in_range(position, range));
1599 }
1600
1601 #[test]
1602 fn test_position_in_range_one_past_end() {
1603 let range = Range {
1604 start: Position {
1605 line: 0,
1606 character: 5,
1607 },
1608 end: Position {
1609 line: 0,
1610 character: 10,
1611 },
1612 };
1613
1614 let position = Position {
1616 line: 0,
1617 character: 11,
1618 };
1619
1620 assert!(position_in_range(position, range));
1621 }
1622
1623 #[test]
1624 fn test_position_in_range_before() {
1625 let range = Range {
1626 start: Position {
1627 line: 0,
1628 character: 5,
1629 },
1630 end: Position {
1631 line: 0,
1632 character: 10,
1633 },
1634 };
1635
1636 let position = Position {
1637 line: 0,
1638 character: 4,
1639 };
1640
1641 assert!(!position_in_range(position, range));
1642 }
1643
1644 #[test]
1645 fn test_position_in_range_after() {
1646 let range = Range {
1647 start: Position {
1648 line: 0,
1649 character: 5,
1650 },
1651 end: Position {
1652 line: 0,
1653 character: 10,
1654 },
1655 };
1656
1657 let position = Position {
1658 line: 0,
1659 character: 12,
1660 };
1661
1662 assert!(!position_in_range(position, range));
1663 }
1664
1665 #[test]
1668 fn test_utf16_to_byte_offset_ascii() {
1669 let s = "hello";
1670 assert_eq!(utf16_to_byte_offset(s, 0), Some(0));
1671 assert_eq!(utf16_to_byte_offset(s, 2), Some(2));
1672 assert_eq!(utf16_to_byte_offset(s, 5), Some(5));
1673 }
1674
1675 #[test]
1676 fn test_utf16_to_byte_offset_multibyte() {
1677 let s = "日本語";
1679 assert_eq!(utf16_to_byte_offset(s, 0), Some(0));
1680 assert_eq!(utf16_to_byte_offset(s, 1), Some(3));
1681 assert_eq!(utf16_to_byte_offset(s, 2), Some(6));
1682 assert_eq!(utf16_to_byte_offset(s, 3), Some(9));
1683 }
1684
1685 #[test]
1686 fn test_utf16_to_byte_offset_emoji() {
1687 let s = "😀test";
1689 assert_eq!(utf16_to_byte_offset(s, 0), Some(0));
1690 assert_eq!(utf16_to_byte_offset(s, 2), Some(4)); assert_eq!(utf16_to_byte_offset(s, 3), Some(5)); }
1693
1694 #[test]
1695 fn test_utf16_to_byte_offset_mixed() {
1696 let s = "hello 世界 😀!";
1698 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)); }
1704
1705 #[test]
1706 fn test_utf16_to_byte_offset_out_of_bounds() {
1707 let s = "hello";
1708 assert_eq!(utf16_to_byte_offset(s, 100), None);
1709 }
1710
1711 #[test]
1712 fn test_utf16_to_byte_offset_empty() {
1713 let s = "";
1714 assert_eq!(utf16_to_byte_offset(s, 0), Some(0));
1715 assert_eq!(utf16_to_byte_offset(s, 1), None);
1716 }
1717
1718 #[test]
1721 fn test_build_package_completion_long_description_ascii() {
1722 let long_desc = "a".repeat(250);
1723 let metadata = MockMetadata {
1724 name: "test-pkg".to_string(),
1725 description: Some(long_desc),
1726 repository: None,
1727 documentation: None,
1728 latest_version: "1.0.0".to_string(),
1729 };
1730
1731 let range = Range::default();
1732 let item = build_package_completion(&metadata, range);
1733
1734 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1735 let lines: Vec<_> = content.value.lines().collect();
1737 assert!(lines[2].ends_with("..."));
1738 assert!(lines[2].len() <= 203); } else {
1740 panic!("Expected MarkupContent documentation");
1741 }
1742 }
1743
1744 #[test]
1745 fn test_build_package_completion_long_description_unicode() {
1746 let mut long_desc = String::new();
1749 for _ in 0..67 {
1750 long_desc.push('日');
1751 }
1752
1753 let metadata = MockMetadata {
1754 name: "test-pkg".to_string(),
1755 description: Some(long_desc),
1756 repository: None,
1757 documentation: None,
1758 latest_version: "1.0.0".to_string(),
1759 };
1760
1761 let range = Range::default();
1762 let item = build_package_completion(&metadata, range);
1763
1764 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1766 let lines: Vec<_> = content.value.lines().collect();
1767 assert!(lines[2].ends_with("..."));
1768 assert!(lines[2].is_char_boundary(lines[2].len()));
1770 } else {
1771 panic!("Expected MarkupContent documentation");
1772 }
1773 }
1774
1775 #[test]
1776 fn test_build_package_completion_long_description_emoji() {
1777 let long_desc = "😀".repeat(51);
1780
1781 let metadata = MockMetadata {
1782 name: "test-pkg".to_string(),
1783 description: Some(long_desc),
1784 repository: None,
1785 documentation: None,
1786 latest_version: "1.0.0".to_string(),
1787 };
1788
1789 let range = Range::default();
1790 let item = build_package_completion(&metadata, range);
1791
1792 if let Some(Documentation::MarkupContent(content)) = item.documentation {
1794 let lines: Vec<_> = content.value.lines().collect();
1795 assert!(lines[2].ends_with("..."));
1796 assert!(lines[2].is_char_boundary(lines[2].len()));
1798 } else {
1799 panic!("Expected MarkupContent documentation");
1800 }
1801 }
1802
1803 #[test]
1804 fn test_extract_prefix_unicode_package_name() {
1805 let content = "日本語-crate = \"1.0\"";
1807 let position = Position {
1808 line: 0,
1809 character: 3, };
1811 let range = Range {
1812 start: Position {
1813 line: 0,
1814 character: 0,
1815 },
1816 end: Position {
1817 line: 0,
1818 character: 10,
1819 },
1820 };
1821
1822 let prefix = extract_prefix(content, position, range);
1823 assert_eq!(prefix, "日本語");
1824 }
1825
1826 #[test]
1827 fn test_extract_prefix_emoji_in_content() {
1828 let content = "emoji-😀-crate = \"1.0\"";
1830 let position = Position {
1831 line: 0,
1832 character: 8, };
1834 let range = Range {
1835 start: Position {
1836 line: 0,
1837 character: 0,
1838 },
1839 end: Position {
1840 line: 0,
1841 character: 14,
1842 },
1843 };
1844
1845 let prefix = extract_prefix(content, position, range);
1846 assert_eq!(prefix, "emoji-😀");
1847 }
1848
1849 #[tokio::test]
1852 async fn test_complete_versions_generic_operator_stripping() {
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: false,
1863 prerelease: false,
1864 },
1865 MockVersion {
1866 version: "1.1.0".to_string(),
1867 yanked: false,
1868 prerelease: false,
1869 },
1870 MockVersion {
1871 version: "2.0.0".to_string(),
1872 yanked: false,
1873 prerelease: false,
1874 },
1875 ],
1876 };
1877
1878 let items =
1880 complete_versions_generic(®istry, "test-pkg", "^1.0", &['^', '~', '=', '<', '>'])
1881 .await;
1882
1883 assert_eq!(items.len(), 2);
1885 assert_eq!(items[0].label, "1.0.0 (latest)");
1886 assert_eq!(items[1].label, "1.0.1");
1887
1888 let items =
1890 complete_versions_generic(®istry, "test-pkg", "~1.1", &['^', '~', '=', '<', '>'])
1891 .await;
1892
1893 assert_eq!(items.len(), 1);
1894 assert_eq!(items[0].label, "1.1.0 (latest)");
1895
1896 let items =
1898 complete_versions_generic(®istry, "test-pkg", "=2.0", &['^', '~', '=', '<', '>'])
1899 .await;
1900
1901 assert_eq!(items.len(), 1);
1902 assert_eq!(items[0].label, "2.0.0 (latest)");
1903
1904 let items =
1906 complete_versions_generic(®istry, "test-pkg", "1.0", &['^', '~', '=', '<', '>'])
1907 .await;
1908
1909 assert_eq!(items.len(), 2);
1910 assert_eq!(items[0].label, "1.0.0 (latest)");
1911 assert_eq!(items[1].label, "1.0.1");
1912 }
1913
1914 #[tokio::test]
1915 async fn test_complete_versions_generic_fallback_when_no_prefix_match() {
1916 let registry = MockRegistry {
1917 versions: vec![
1918 MockVersion {
1919 version: "1.0.0".to_string(),
1920 yanked: false,
1921 prerelease: false,
1922 },
1923 MockVersion {
1924 version: "1.1.0".to_string(),
1925 yanked: false,
1926 prerelease: false,
1927 },
1928 MockVersion {
1929 version: "2.0.0".to_string(),
1930 yanked: false,
1931 prerelease: false,
1932 },
1933 MockVersion {
1934 version: "2.1.0".to_string(),
1935 yanked: true, prerelease: false,
1937 },
1938 ],
1939 };
1940
1941 let items =
1943 complete_versions_generic(®istry, "test-pkg", "3.0", &['^', '~', '=', '<', '>'])
1944 .await;
1945
1946 assert_eq!(items.len(), 3);
1948 assert_eq!(items[0].label, "1.0.0 (latest)");
1949 assert_eq!(items[1].label, "1.1.0");
1950 assert_eq!(items[2].label, "2.0.0");
1951
1952 assert!(!items.iter().any(|item| item.label == "2.1.0"));
1954
1955 let items = complete_versions_generic(®istry, "test-pkg", "", &[]).await;
1957
1958 assert_eq!(items.len(), 3);
1959 assert_eq!(items[0].label, "1.0.0 (latest)");
1960 assert_eq!(items[1].label, "1.1.0");
1961 assert_eq!(items[2].label, "2.0.0");
1962 }
1963
1964 #[tokio::test]
1965 async fn test_complete_versions_generic_filters_yanked_in_prefix_match() {
1966 let registry = MockRegistry {
1967 versions: vec![
1968 MockVersion {
1969 version: "1.0.0".to_string(),
1970 yanked: false,
1971 prerelease: false,
1972 },
1973 MockVersion {
1974 version: "1.0.1".to_string(),
1975 yanked: true, prerelease: false,
1977 },
1978 MockVersion {
1979 version: "1.0.2".to_string(),
1980 yanked: false,
1981 prerelease: false,
1982 },
1983 ],
1984 };
1985
1986 let items = complete_versions_generic(®istry, "test-pkg", "1.0", &[]).await;
1988
1989 assert_eq!(items.len(), 2);
1991 assert_eq!(items[0].label, "1.0.0 (latest)");
1992 assert_eq!(items[1].label, "1.0.2");
1993
1994 assert!(!items.iter().any(|item| item.label == "1.0.1"));
1996 }
1997
1998 #[tokio::test]
1999 async fn test_complete_versions_generic_limit_5() {
2000 let versions: Vec<_> = (0..10)
2002 .map(|i| MockVersion {
2003 version: format!("1.0.{}", i),
2004 yanked: false,
2005 prerelease: false,
2006 })
2007 .collect();
2008
2009 let registry = MockRegistry { versions };
2010
2011 let items = complete_versions_generic(®istry, "test-pkg", "1.0", &[]).await;
2013
2014 assert_eq!(items.len(), 5);
2015 assert_eq!(items[0].label, "1.0.0 (latest)");
2016 assert_eq!(items[4].label, "1.0.4");
2017 }
2018
2019 #[tokio::test]
2020 async fn test_complete_versions_generic_go_no_operators() {
2021 let registry = MockRegistry {
2022 versions: vec![
2023 MockVersion {
2024 version: "v1.9.0".to_string(),
2025 yanked: false,
2026 prerelease: false,
2027 },
2028 MockVersion {
2029 version: "v1.9.1".to_string(),
2030 yanked: false,
2031 prerelease: false,
2032 },
2033 MockVersion {
2034 version: "v1.10.0".to_string(),
2035 yanked: false,
2036 prerelease: false,
2037 },
2038 ],
2039 };
2040
2041 let items =
2043 complete_versions_generic(®istry, "github.com/gin-gonic/gin", "v1.9", &[]).await;
2044
2045 assert_eq!(items.len(), 2);
2046 assert_eq!(items[0].label, "v1.9.0 (latest)");
2047 assert_eq!(items[1].label, "v1.9.1");
2048 }
2049
2050 fn make_dep_with_features_range(
2053 name: &str,
2054 name_range: Range,
2055 features_range: Range,
2056 ) -> MockDependency {
2057 MockDependency {
2058 name: name.to_string(),
2059 name_range,
2060 version_range: None,
2061 features_range: Some(features_range),
2062 }
2063 }
2064
2065 #[test]
2066 fn test_detect_feature_context_inline() {
2067 let features_range = Range {
2070 start: Position {
2071 line: 0,
2072 character: 36,
2073 },
2074 end: Position {
2075 line: 0,
2076 character: 52,
2077 },
2078 };
2079 let dep = make_dep_with_features_range(
2080 "serde",
2081 Range {
2082 start: Position {
2083 line: 0,
2084 character: 0,
2085 },
2086 end: Position {
2087 line: 0,
2088 character: 5,
2089 },
2090 },
2091 features_range,
2092 );
2093 let parse_result = MockParseResult {
2094 dependencies: vec![dep],
2095 };
2096
2097 let content = r#"serde = { version = "1", features = ["derive", "std"] }"#;
2098
2099 let position = Position {
2102 line: 0,
2103 character: 41,
2104 };
2105 let context = detect_completion_context(&parse_result, position, content);
2106 assert!(
2107 matches!(context, CompletionContext::Feature { ref package_name, ref prefix }
2108 if package_name == "serde" && prefix == "der"),
2109 "Expected Feature context with prefix 'der', got {context:?}"
2110 );
2111 }
2112
2113 #[test]
2114 fn test_detect_feature_context_empty_prefix() {
2115 let features_range = Range {
2117 start: Position {
2118 line: 0,
2119 character: 11,
2120 },
2121 end: Position {
2122 line: 0,
2123 character: 15,
2124 },
2125 };
2126 let dep = make_dep_with_features_range(
2127 "tokio",
2128 Range {
2129 start: Position {
2130 line: 0,
2131 character: 0,
2132 },
2133 end: Position {
2134 line: 0,
2135 character: 5,
2136 },
2137 },
2138 features_range,
2139 );
2140 let parse_result = MockParseResult {
2141 dependencies: vec![dep],
2142 };
2143
2144 let content = r#"features = [""]"#;
2145 let position = Position {
2147 line: 0,
2148 character: 13,
2149 };
2150 let context = detect_completion_context(&parse_result, position, content);
2151 assert!(
2152 matches!(context, CompletionContext::Feature { ref package_name, ref prefix }
2153 if package_name == "tokio" && prefix.is_empty()),
2154 "Expected Feature context with empty prefix, got {context:?}"
2155 );
2156 }
2157
2158 #[test]
2159 fn test_detect_feature_context_second_item() {
2160 let features_range = Range {
2162 start: Position {
2163 line: 0,
2164 character: 11,
2165 },
2166 end: Position {
2167 line: 0,
2168 character: 28,
2169 },
2170 };
2171 let dep = make_dep_with_features_range(
2172 "tokio",
2173 Range {
2174 start: Position {
2175 line: 0,
2176 character: 0,
2177 },
2178 end: Position {
2179 line: 0,
2180 character: 5,
2181 },
2182 },
2183 features_range,
2184 );
2185 let parse_result = MockParseResult {
2186 dependencies: vec![dep],
2187 };
2188
2189 let content = r#"features = ["full", "rt-"]"#;
2190 let position = Position {
2192 line: 0,
2193 character: 24,
2194 };
2195 let context = detect_completion_context(&parse_result, position, content);
2196 assert!(
2197 matches!(context, CompletionContext::Feature { ref package_name, ref prefix }
2198 if package_name == "tokio" && prefix == "rt-"),
2199 "Expected Feature context with prefix 'rt-', got {context:?}"
2200 );
2201 }
2202
2203 #[test]
2204 fn test_detect_no_feature_context_outside_range() {
2205 let features_range = Range {
2206 start: Position {
2207 line: 2,
2208 character: 11,
2209 },
2210 end: Position {
2211 line: 2,
2212 character: 20,
2213 },
2214 };
2215 let dep = make_dep_with_features_range(
2216 "serde",
2217 Range {
2218 start: Position {
2219 line: 2,
2220 character: 0,
2221 },
2222 end: Position {
2223 line: 2,
2224 character: 5,
2225 },
2226 },
2227 features_range,
2228 );
2229 let parse_result = MockParseResult {
2230 dependencies: vec![dep],
2231 };
2232
2233 let content = "[package]\nname = \"test\"\nfeatures = [\"full\"]";
2235 let position = Position {
2236 line: 0,
2237 character: 5,
2238 };
2239 let context = detect_completion_context(&parse_result, position, content);
2240 assert_eq!(context, CompletionContext::None);
2241 }
2242
2243 #[test]
2244 fn test_extract_feature_prefix_basic() {
2245 let content = r#"serde = { features = ["derive"] }"#;
2246 let position = Position {
2249 line: 0,
2250 character: 27,
2251 };
2252 let prefix = extract_feature_prefix(content, position);
2253 assert_eq!(prefix, "deri");
2254 }
2255
2256 #[test]
2257 fn test_extract_feature_prefix_empty() {
2258 let content = r#"features = [""]"#;
2259 let position = Position {
2261 line: 0,
2262 character: 13,
2263 };
2264 let prefix = extract_feature_prefix(content, position);
2265 assert_eq!(prefix, "");
2266 }
2267
2268 #[test]
2269 fn test_extract_feature_prefix_multiline() {
2270 let content = "features = [\n \"rt-multi-thread\",\n \"mac\"\n]";
2271 let position = Position {
2274 line: 2,
2275 character: 8,
2276 };
2277 let prefix = extract_feature_prefix(content, position);
2278 assert_eq!(prefix, "mac");
2279 }
2280
2281 #[test]
2282 fn test_extract_feature_prefix_no_quote() {
2283 let content = "features = [\n \n]";
2284 let position = Position {
2286 line: 1,
2287 character: 4,
2288 };
2289 let prefix = extract_feature_prefix(content, position);
2290 assert_eq!(prefix, "");
2291 }
2292
2293 #[test]
2294 fn test_extract_feature_prefix_between_items_no_quote() {
2295 let content = r#"features = ["full", ]"#;
2298 let position = Position {
2300 line: 0,
2301 character: 19,
2302 };
2303 let prefix = extract_feature_prefix(content, position);
2304 assert_eq!(prefix, "");
2305 }
2306
2307 #[test]
2308 fn test_extract_feature_prefix_cursor_after_opening_bracket() {
2309 let content = "features = []";
2311 let position = Position {
2312 line: 0,
2313 character: 12,
2314 };
2315 let prefix = extract_feature_prefix(content, position);
2316 assert_eq!(prefix, "");
2317 }
2318}