mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 12:33:33 +00:00
Draft completion hashing
This commit is contained in:
parent
99a6ecd41e
commit
62d97d9ba7
9 changed files with 142 additions and 24 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -1659,6 +1659,7 @@ dependencies = [
|
|||
"hir-def",
|
||||
"hir-ty",
|
||||
"ide",
|
||||
"ide-completion",
|
||||
"ide-db",
|
||||
"ide-ssr",
|
||||
"intern",
|
||||
|
@ -1687,6 +1688,7 @@ dependencies = [
|
|||
"stdx",
|
||||
"syntax",
|
||||
"syntax-bridge",
|
||||
"tenthash",
|
||||
"test-fixture",
|
||||
"test-utils",
|
||||
"tikv-jemallocator",
|
||||
|
@ -1990,6 +1992,12 @@ dependencies = [
|
|||
"tt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tenthash"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d67f9f3cf70e0852941d7bc3cb884b49b24b8ee956baf91ad0abae31f5ef11fb"
|
||||
|
||||
[[package]]
|
||||
name = "test-fixture"
|
||||
version = "0.0.0"
|
||||
|
|
|
@ -346,8 +346,7 @@ pub enum CompletionItemKind {
|
|||
impl_from!(SymbolKind for CompletionItemKind);
|
||||
|
||||
impl CompletionItemKind {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn tag(self) -> &'static str {
|
||||
pub fn tag(self) -> &'static str {
|
||||
match self {
|
||||
CompletionItemKind::SymbolKind(kind) => match kind {
|
||||
SymbolKind::Attribute => "at",
|
||||
|
|
|
@ -34,6 +34,7 @@ pub use crate::{
|
|||
config::{CallableSnippets, CompletionConfig},
|
||||
item::{
|
||||
CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
|
||||
CompletionRelevanceReturnType, CompletionRelevanceTypeMatch,
|
||||
},
|
||||
snippet::{Snippet, SnippetScope},
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ anyhow.workspace = true
|
|||
crossbeam-channel.workspace = true
|
||||
dirs = "5.0.1"
|
||||
dissimilar.workspace = true
|
||||
ide-completion.workspace = true
|
||||
itertools.workspace = true
|
||||
scip = "0.5.1"
|
||||
lsp-types = { version = "=0.95.0", features = ["proposed"] }
|
||||
|
@ -34,6 +35,7 @@ rayon.workspace = true
|
|||
rustc-hash.workspace = true
|
||||
serde_json = { workspace = true, features = ["preserve_order"] }
|
||||
serde.workspace = true
|
||||
tenthash = "0.4.0"
|
||||
num_cpus = "1.15.0"
|
||||
mimalloc = { version = "0.1.30", default-features = false, optional = true }
|
||||
lsp-server.workspace = true
|
||||
|
@ -90,13 +92,13 @@ jemalloc = ["jemallocator", "profile/jemalloc"]
|
|||
force-always-assert = ["always-assert/force"]
|
||||
sysroot-abi = []
|
||||
in-rust-tree = [
|
||||
"sysroot-abi",
|
||||
"syntax/in-rust-tree",
|
||||
"parser/in-rust-tree",
|
||||
"hir/in-rust-tree",
|
||||
"hir-def/in-rust-tree",
|
||||
"hir-ty/in-rust-tree",
|
||||
"load-cargo/in-rust-tree",
|
||||
"sysroot-abi",
|
||||
"syntax/in-rust-tree",
|
||||
"parser/in-rust-tree",
|
||||
"hir/in-rust-tree",
|
||||
"hir-def/in-rust-tree",
|
||||
"hir-ty/in-rust-tree",
|
||||
"load-cargo/in-rust-tree",
|
||||
]
|
||||
|
||||
[lints]
|
||||
|
|
|
@ -36,6 +36,7 @@ use triomphe::Arc;
|
|||
use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
|
||||
|
||||
use crate::{
|
||||
completion_item_hash,
|
||||
config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
|
||||
diagnostics::convert_diagnostic,
|
||||
global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
|
||||
|
@ -1122,12 +1123,15 @@ pub(crate) fn handle_completion_resolve(
|
|||
return Ok(original_completion);
|
||||
};
|
||||
let source_root = snap.analysis.source_root_id(file_id)?;
|
||||
let Some(completion_hash_for_resolve) = &resolve_data.completion_item_hash else {
|
||||
return Ok(original_completion);
|
||||
};
|
||||
|
||||
let mut forced_resolve_completions_config = snap.config.completion(Some(source_root));
|
||||
forced_resolve_completions_config.fields_to_resolve = CompletionFieldsToResolve::empty();
|
||||
|
||||
let position = FilePosition { file_id, offset };
|
||||
let Some(resolved_completions) = snap.analysis.completions(
|
||||
let Some(completions) = snap.analysis.completions(
|
||||
&forced_resolve_completions_config,
|
||||
position,
|
||||
resolve_data.trigger_character,
|
||||
|
@ -1135,6 +1139,14 @@ pub(crate) fn handle_completion_resolve(
|
|||
else {
|
||||
return Ok(original_completion);
|
||||
};
|
||||
|
||||
let Some(corresponding_completion) = completions.into_iter().find(|completion_item| {
|
||||
let hash = completion_item_hash(&completion_item, resolve_data.for_ref);
|
||||
&hash == completion_hash_for_resolve
|
||||
}) else {
|
||||
return Ok(original_completion);
|
||||
};
|
||||
|
||||
let mut resolved_completions = to_proto::completion_items(
|
||||
&snap.config,
|
||||
&forced_resolve_completions_config.fields_to_resolve,
|
||||
|
@ -1142,15 +1154,11 @@ pub(crate) fn handle_completion_resolve(
|
|||
snap.file_version(position.file_id),
|
||||
resolve_data.position,
|
||||
resolve_data.trigger_character,
|
||||
resolved_completions,
|
||||
vec![corresponding_completion],
|
||||
);
|
||||
|
||||
let mut resolved_completion =
|
||||
if resolved_completions.get(resolve_data.completion_item_index).is_some() {
|
||||
resolved_completions.swap_remove(resolve_data.completion_item_index)
|
||||
} else {
|
||||
return Ok(original_completion);
|
||||
};
|
||||
let Some(mut resolved_completion) = resolved_completions.pop() else {
|
||||
return Ok(original_completion);
|
||||
};
|
||||
|
||||
if !resolve_data.imports.is_empty() {
|
||||
let additional_edits = snap
|
||||
|
|
|
@ -47,7 +47,9 @@ use self::lsp::ext as lsp_ext;
|
|||
#[cfg(test)]
|
||||
mod integrated_benchmarks;
|
||||
|
||||
use ide::{CompletionItem, CompletionRelevance, TextEdit, TextRange};
|
||||
use serde::de::DeserializeOwned;
|
||||
use tenthash::TentHasher;
|
||||
|
||||
pub use crate::{
|
||||
lsp::capabilities::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph,
|
||||
|
@ -61,3 +63,90 @@ pub fn from_json<T: DeserializeOwned>(
|
|||
serde_json::from_value(json.clone())
|
||||
.map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}"))
|
||||
}
|
||||
|
||||
fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8; 20] {
|
||||
fn hash_text_range(hasher: &mut TentHasher, text_range: &TextRange) {
|
||||
hasher.update(u32::from(text_range.start()).to_le_bytes());
|
||||
hasher.update(u32::from(text_range.end()).to_le_bytes());
|
||||
}
|
||||
|
||||
fn hash_text_edit(hasher: &mut TentHasher, edit: &TextEdit) {
|
||||
for indel in edit.iter() {
|
||||
hasher.update(&indel.insert);
|
||||
hash_text_range(hasher, &indel.delete);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_completion_relevance(hasher: &mut TentHasher, relevance: &CompletionRelevance) {
|
||||
use ide_completion::{
|
||||
CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
|
||||
CompletionRelevanceTypeMatch,
|
||||
};
|
||||
|
||||
if let Some(type_match) = &relevance.type_match {
|
||||
let label = match type_match {
|
||||
CompletionRelevanceTypeMatch::CouldUnify => "could_unify",
|
||||
CompletionRelevanceTypeMatch::Exact => "exact",
|
||||
};
|
||||
hasher.update(label);
|
||||
}
|
||||
hasher.update(&[u8::from(relevance.exact_name_match), u8::from(relevance.is_local)]);
|
||||
if let Some(trait_) = &relevance.trait_ {
|
||||
hasher.update(&[u8::from(trait_.is_op_method), u8::from(trait_.notable_trait)]);
|
||||
}
|
||||
hasher.update(&[
|
||||
u8::from(relevance.is_name_already_imported),
|
||||
u8::from(relevance.requires_import),
|
||||
u8::from(relevance.is_private_editable),
|
||||
]);
|
||||
if let Some(postfix_match) = &relevance.postfix_match {
|
||||
let label = match postfix_match {
|
||||
CompletionRelevancePostfixMatch::NonExact => "non_exact",
|
||||
CompletionRelevancePostfixMatch::Exact => "exact",
|
||||
};
|
||||
hasher.update(label);
|
||||
}
|
||||
if let Some(function) = &relevance.function {
|
||||
hasher.update(&[u8::from(function.has_params), u8::from(function.has_self_param)]);
|
||||
let label = match function.return_type {
|
||||
CompletionRelevanceReturnType::Other => "other",
|
||||
CompletionRelevanceReturnType::DirectConstructor => "direct_constructor",
|
||||
CompletionRelevanceReturnType::Constructor => "constructor",
|
||||
CompletionRelevanceReturnType::Builder => "builder",
|
||||
};
|
||||
hasher.update(label);
|
||||
}
|
||||
}
|
||||
|
||||
let mut hasher = TentHasher::new();
|
||||
hasher.update(&[
|
||||
u8::from(is_ref_completion),
|
||||
u8::from(item.is_snippet),
|
||||
u8::from(item.deprecated),
|
||||
u8::from(item.trigger_call_info),
|
||||
]);
|
||||
hasher.update(&item.label);
|
||||
if let Some(label_detail) = &item.label_detail {
|
||||
hasher.update(label_detail);
|
||||
}
|
||||
hash_text_range(&mut hasher, &item.source_range);
|
||||
hash_text_edit(&mut hasher, &item.text_edit);
|
||||
hasher.update(item.kind.tag());
|
||||
hasher.update(&item.lookup);
|
||||
if let Some(detail) = &item.detail {
|
||||
hasher.update(detail);
|
||||
}
|
||||
if let Some(documentation) = &item.documentation {
|
||||
hasher.update(documentation.as_str());
|
||||
}
|
||||
has_completion_relevance(&mut hasher, &item.relevance);
|
||||
if let Some((mutability, text_size)) = &item.ref_match {
|
||||
hasher.update(mutability.as_keyword_for_ref());
|
||||
hasher.update(u32::from(*text_size).to_le_bytes());
|
||||
}
|
||||
for (import_path, import_name) in &item.import_to_add {
|
||||
hasher.update(import_path);
|
||||
hasher.update(import_name);
|
||||
}
|
||||
hasher.finalize()
|
||||
}
|
||||
|
|
|
@ -826,7 +826,8 @@ pub struct CompletionResolveData {
|
|||
pub imports: Vec<CompletionImport>,
|
||||
pub version: Option<i32>,
|
||||
pub trigger_character: Option<char>,
|
||||
pub completion_item_index: usize,
|
||||
pub for_ref: bool,
|
||||
pub completion_item_hash: Option<[u8; 20]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
|
|
@ -21,6 +21,7 @@ use serde_json::to_value;
|
|||
use vfs::AbsPath;
|
||||
|
||||
use crate::{
|
||||
completion_item_hash,
|
||||
config::{CallInfoConfig, Config},
|
||||
global_state::GlobalStateSnapshot,
|
||||
line_index::{LineEndings, LineIndex, PositionEncoding},
|
||||
|
@ -274,6 +275,11 @@ fn completion_item(
|
|||
completion_trigger_character: Option<char>,
|
||||
item: CompletionItem,
|
||||
) {
|
||||
let original_completion_item = if fields_to_resolve == &CompletionFieldsToResolve::empty() {
|
||||
None
|
||||
} else {
|
||||
Some(item.clone())
|
||||
};
|
||||
let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
|
||||
let ref_match = item.ref_match();
|
||||
|
||||
|
@ -393,16 +399,17 @@ fn completion_item(
|
|||
Vec::new()
|
||||
};
|
||||
let (ref_resolve_data, resolve_data) = if something_to_resolve || !imports.is_empty() {
|
||||
let mut item_index = acc.len();
|
||||
let ref_resolve_data = if ref_match.is_some() {
|
||||
let ref_resolve_data = lsp_ext::CompletionResolveData {
|
||||
position: tdpp.clone(),
|
||||
imports: Vec::new(),
|
||||
version,
|
||||
trigger_character: completion_trigger_character,
|
||||
completion_item_index: item_index,
|
||||
for_ref: true,
|
||||
completion_item_hash: original_completion_item
|
||||
.as_ref()
|
||||
.map(|item| completion_item_hash(item, true)),
|
||||
};
|
||||
item_index += 1;
|
||||
Some(to_value(ref_resolve_data).unwrap())
|
||||
} else {
|
||||
None
|
||||
|
@ -412,7 +419,10 @@ fn completion_item(
|
|||
imports,
|
||||
version,
|
||||
trigger_character: completion_trigger_character,
|
||||
completion_item_index: item_index,
|
||||
for_ref: false,
|
||||
completion_item_hash: original_completion_item
|
||||
.as_ref()
|
||||
.map(|item| completion_item_hash(item, false)),
|
||||
};
|
||||
(ref_resolve_data, Some(to_value(resolve_data).unwrap()))
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!---
|
||||
lsp/ext.rs hash: 96f88b7a5d0080c6
|
||||
lsp/ext.rs hash: 7d77940d7e63a8b
|
||||
|
||||
If you need to change the above hash to make the test pass, please check if you
|
||||
need to adjust this doc as well and ping this issue:
|
||||
|
|
Loading…
Reference in a new issue