diff --git a/README.md b/README.md index 2925687..4c32907 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,12 @@ Features that are currently supported: - Diagnostics for: - A newer dependency version is available - Invalid dependency name / version +- Autocomplete for dependencies - versions - Quick action to update to a new dependency version Features that will be supported: -- Autocomplete for dependencies - versions, features +- Autocomplete for dependencies - features diff --git a/src/tools/cargo/completion.rs b/src/tools/cargo/completion.rs new file mode 100644 index 0000000..82a096e --- /dev/null +++ b/src/tools/cargo/completion.rs @@ -0,0 +1,67 @@ +use std::ops::Range as StdRange; + +use semver::Version; + +use tower_lsp::jsonrpc::Result; +use tower_lsp::lsp_types::*; + +use crate::clients::*; +use crate::server::*; + +async fn complete_package_version( + clients: &Clients, + document: &Document, + range: StdRange, + name: &str, + version: &str, +) -> Result> { + let metadatas = match clients.crates.get_index_metadatas(name).await { + Err(_) => return Ok(Vec::new()), + Ok(m) => m, + }; + + let mut valid_metadatas = metadatas + .into_iter() + .filter_map(|metadata| { + let ver = metadata.version.to_ascii_lowercase(); + let smallest_len = version.len().min(ver.len()); + if version.is_empty() + || version[..smallest_len].eq_ignore_ascii_case(&ver[..smallest_len]) + { + if let Ok(version) = Version::parse(&ver) { + Some((version, metadata)) + } else { + None + } + } else { + None + } + }) + .collect::>(); + valid_metadatas.sort_by(|left, right| right.0.cmp(&left.0)); + + Ok(valid_metadatas + .into_iter() + .enumerate() + .map(|(index, (version, _))| CompletionItem { + label: version.to_string(), + kind: Some(CompletionItemKind::VALUE), + sort_text: Some(format!("{:0>5}", index)), + text_edit: Some(CompletionTextEdit::Edit( + document.create_edit(range.clone(), version.to_string()), + )), + ..Default::default() + }) + .collect()) +} + +pub async fn get_package_completions( + clients: &Clients, + document: &Document, + range: StdRange, + name: &str, + version: &str, +) -> Result { + let items = complete_package_version(clients, document, range, name, version).await?; + Ok(CompletionResponse::Array(items)) +} diff --git a/src/tools/cargo/manifest.rs b/src/tools/cargo/manifest.rs index b51a0fd..5ab4976 100644 --- a/src/tools/cargo/manifest.rs +++ b/src/tools/cargo/manifest.rs @@ -60,6 +60,10 @@ impl ManifestDependency { self.spec.key_span() } + pub fn name_text(&self) -> &str { + self.spec.key_text() + } + pub fn version_span(&self) -> Range { self.spec.value_span() } diff --git a/src/tools/cargo/mod.rs b/src/tools/cargo/mod.rs index f616792..ce43813 100644 --- a/src/tools/cargo/mod.rs +++ b/src/tools/cargo/mod.rs @@ -12,10 +12,12 @@ use crate::util::*; use super::*; +mod completion; mod diagnostics; mod lockfile; mod manifest; +use completion::*; use diagnostics::*; use lockfile::*; use manifest::*; @@ -159,6 +161,51 @@ impl Tool for Cargo { })) } + async fn completion(&self, params: CompletionParams) -> Result { + let uri = params.text_document_position.text_document.uri; + let pos = params.text_document_position.position; + + let (document, _, manifest, _) = match self.get_documents(&uri) { + None => return Ok(CompletionResponse::Array(Vec::new())), + Some(d) => d, + }; + + let offset = document.lsp_position_to_offset(pos); + let try_find = |deps: &HashMap| { + deps.iter().find_map(|(_, dep)| { + let span = dep.version_span(); + if offset >= span.start && offset <= span.end { + Some((span.clone(), dep.clone())) + } else { + None + } + }) + }; + + let found = try_find(&manifest.dependencies) + .or_else(|| try_find(&manifest.dev_dependencies)) + .or_else(|| try_find(&manifest.build_dependencies)); + let (found_range, found_dep) = match found { + Some((range, dep)) => (range, dep), + _ => return Ok(CompletionResponse::Array(Vec::new())), + }; + + let range_before = document.lsp_range_to_span(Range { + start: document.lsp_position_from_offset(found_range.start + 1), + end: pos, + }); + + let slice_before = &document.as_str()[range_before.clone()]; + get_package_completions( + &self.clients, + &document, + range_before, + found_dep.name_text(), + slice_before, + ) + .await + } + async fn diagnostics(&self, params: DocumentDiagnosticParams) -> Result> { let uri = params.text_document.uri;