Skip to content

Commit

Permalink
Implement completion for cargo crate versions
Browse files Browse the repository at this point in the history
  • Loading branch information
filiptibell committed Sep 14, 2023
1 parent c479bcd commit a2b8bfd
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 1 deletion.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

</details>

Expand Down
67 changes: 67 additions & 0 deletions src/tools/cargo/completion.rs
Original file line number Diff line number Diff line change
@@ -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<usize>,
name: &str,
version: &str,
) -> Result<Vec<CompletionItem>> {
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::<Vec<_>>();
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<usize>,
name: &str,
version: &str,
) -> Result<CompletionResponse> {
let items = complete_package_version(clients, document, range, name, version).await?;
Ok(CompletionResponse::Array(items))
}
4 changes: 4 additions & 0 deletions src/tools/cargo/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize> {
self.spec.value_span()
}
Expand Down
47 changes: 47 additions & 0 deletions src/tools/cargo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -159,6 +161,51 @@ impl Tool for Cargo {
}))
}

async fn completion(&self, params: CompletionParams) -> Result<CompletionResponse> {
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<String, ManifestDependency>| {
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<Vec<Diagnostic>> {
let uri = params.text_document.uri;

Expand Down

0 comments on commit a2b8bfd

Please sign in to comment.