diff --git a/src/lsp.rs b/src/lsp.rs index 7146278..cd59e1d 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -196,8 +196,18 @@ impl ProtoLanguageServer { if let Some(tree) = self.state.get_tree(&uri) { let content = self.state.get_content(&uri); if let Some(package_name) = tree.get_package_name(content.as_bytes()) { - completions.extend(self.state.completion_items(package_name)); + completions.extend(self.state.completion_items_for_package(package_name)); } + + if let Some(ipath) = self.configs.get_include_paths(&uri) { + for import in tree.get_import_paths(content.as_bytes()).iter() { + if let Some(p) = ipath.iter().map(|p| p.join(import)).find(|p| p.exists()) + && let Ok(uri) = Url::from_file_path(p.clone()) + { + completions.extend(self.state.completion_items_for_tree(&uri)); + } + } + }; } Box::pin(async move { Ok(Some(CompletionResponse::Array(completions))) }) } diff --git a/src/state.rs b/src/state.rs index 2d269d9..b7b0e8b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -368,7 +368,40 @@ impl ProtoLanguageState { } } - pub fn completion_items(&self, package: &str) -> Vec { + pub fn completion_items_for_tree(&self, url: &Url) -> Vec { + let collector = |f: fn(&Node) -> bool, k: CompletionItemKind| { + self.get_tree(url) + .map(|tree| { + let content = self.get_content(&tree.uri); + + tree.find_all_nodes(f) + .into_iter() + .map(|n| { + let name = n.utf8_text(content.as_bytes()).unwrap().to_string(); + + CompletionItem { + label: format!(".{}.{name}", tree.package), + kind: Some(k), + ..Default::default() + } + }) + .collect::>() + }) + .unwrap_or_default() + }; + + let mut result = collector(NodeKind::is_enum_name, CompletionItemKind::ENUM); + result.extend(collector( + NodeKind::is_message_name, + CompletionItemKind::STRUCT, + )); + // Better ways to dedup, but who cares?... + result.sort_by_key(|k| k.label.clone()); + result.dedup_by_key(|k| k.label.clone()); + result + } + + pub fn completion_items_for_package(&self, package: &str) -> Vec { let collector = |f: fn(&Node) -> bool, k: CompletionItemKind| { self.get_trees_for_package(package) .into_iter() @@ -469,25 +502,35 @@ mod test { } #[test] - fn test_completion_items() { + fn test_tree_completion_items() { + let state = setup_state(); + let items = state.completion_items_for_tree(&uri("file:///test.proto")); + let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect(); + assert!(labels.contains(&".com.test.Book")); + assert!(labels.contains(&".com.test.Color")); + assert!(!labels.contains(&".com.test.Author")); + } + + #[test] + fn test_package_completion_items() { let state = setup_state(); - let items = state.completion_items("com.test"); + let items = state.completion_items_for_package("com.test"); let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect(); assert!(labels.contains(&"Book")); assert!(labels.contains(&"Color")); assert!(labels.contains(&"Author")); assert!(!labels.contains(&"Foo")); - let other_items = state.completion_items("com.other"); + let other_items = state.completion_items_for_package("com.other"); let other_labels: Vec<&str> = other_items.iter().map(|i| i.label.as_str()).collect(); assert!(other_labels.contains(&"Foo")); assert!(!other_labels.contains(&"Book")); } #[test] - fn test_completion_items_empty_package() { + fn test_package_completion_items_empty_package() { let state = setup_state(); - let items = state.completion_items("com.nonexistent"); + let items = state.completion_items_for_package("com.nonexistent"); assert!(items.is_empty()); }