From f34762abb7a33991f3835030bcbe1ac6f3033cf1 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 4 Jul 2021 14:08:33 +0300 Subject: [PATCH] internal: better factoring for to_proto::completion One source completion can produce up to two lsp completions. Additionally, `preselct` and `sort_text` are global properties of the whole set of completions, so the right granularity here is to convert many completions. As a side-benefit, we no loger allocate intermediate vec. --- crates/rust-analyzer/src/handlers.rs | 67 ++++------------- crates/rust-analyzer/src/lsp_ext.rs | 7 ++ crates/rust-analyzer/src/to_proto.rs | 107 +++++++++++++++++++-------- docs/dev/lsp-extensions.md | 2 +- 4 files changed, 98 insertions(+), 85 deletions(-) diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index b06bfe49d6..4a7525c94b 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -22,12 +22,10 @@ use lsp_types::{ FoldingRangeParams, HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, - SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, - TextDocumentPositionParams, Url, WorkspaceEdit, + SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit, }; use project_model::TargetKind; -use serde::{Deserialize, Serialize}; -use serde_json::{json, to_value}; +use serde_json::json; use stdx::format_to; use syntax::{algo, ast, AstNode, TextRange, TextSize}; @@ -764,23 +762,13 @@ pub(crate) fn handle_completion( }; let line_index = snap.file_line_index(position.file_id)?; - let insert_replace_support = - snap.config.insert_replace_support().then(|| text_document_position.position); - let items: Vec = items - .into_iter() - .flat_map(|item| { - let mut new_completion_items = - to_proto::completion_item(insert_replace_support, &line_index, item.clone()); - - if completion_config.enable_imports_on_the_fly { - for new_item in &mut new_completion_items { - fill_resolve_data(&mut new_item.data, &item, &text_document_position); - } - } - - new_completion_items - }) - .collect(); + let items = to_proto::completion_items( + snap.config.insert_replace_support(), + completion_config.enable_imports_on_the_fly, + &line_index, + text_document_position.clone(), + items.clone(), + ); let completion_list = lsp_types::CompletionList { is_incomplete: true, items }; Ok(Some(completion_list.into())) @@ -800,16 +788,13 @@ pub(crate) fn handle_completion_resolve( .into()); } - let resolve_data = match original_completion - .data - .take() - .map(serde_json::from_value::) - .transpose()? - { - Some(data) => data, + let data = match original_completion.data.take() { + Some(it) => it, None => return Ok(original_completion), }; + let resolve_data: lsp_ext::CompletionResolveData = serde_json::from_value(data)?; + let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?; let line_index = snap.file_line_index(file_id)?; let offset = from_proto::offset(&line_index, resolve_data.position.position); @@ -1760,29 +1745,3 @@ fn run_rustfmt( Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text)))) } } - -#[derive(Debug, Serialize, Deserialize)] -struct CompletionResolveData { - position: lsp_types::TextDocumentPositionParams, - full_import_path: String, - imported_name: String, -} - -fn fill_resolve_data( - resolve_data: &mut Option, - item: &ide::CompletionItem, - position: &TextDocumentPositionParams, -) -> Option<()> { - let import_edit = item.import_to_add()?; - let import_path = &import_edit.import.import_path; - - *resolve_data = Some( - to_value(CompletionResolveData { - position: position.to_owned(), - full_import_path: import_path.to_string(), - imported_name: import_path.segments().last()?.to_string(), - }) - .unwrap(), - ); - Some(()) -} diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 07497e4c44..f11ad396e7 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -499,3 +499,10 @@ pub enum WorkspaceSymbolSearchKind { OnlyTypes, AllSymbols, } + +#[derive(Debug, Serialize, Deserialize)] +pub struct CompletionResolveData { + pub position: lsp_types::TextDocumentPositionParams, + pub full_import_path: String, + pub imported_name: String, +} diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index c6146a9ebb..f48312abb7 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -197,11 +197,35 @@ pub(crate) fn snippet_text_edit_vec( .collect() } -pub(crate) fn completion_item( - insert_replace_support: Option, +pub(crate) fn completion_items( + insert_replace_support: bool, + enable_imports_on_the_fly: bool, line_index: &LineIndex, - item: CompletionItem, + tdpp: lsp_types::TextDocumentPositionParams, + items: Vec, ) -> Vec { + let mut res = Vec::with_capacity(items.len()); + for item in items { + completion_item( + &mut res, + insert_replace_support, + enable_imports_on_the_fly, + line_index, + &tdpp, + item, + ) + } + res +} + +fn completion_item( + acc: &mut Vec, + insert_replace_support: bool, + enable_imports_on_the_fly: bool, + line_index: &LineIndex, + tdpp: &lsp_types::TextDocumentPositionParams, + item: CompletionItem, +) { let mut additional_text_edits = Vec::new(); // LSP does not allow arbitrary edits in completion, so we have to do a @@ -211,6 +235,7 @@ pub(crate) fn completion_item( let source_range = item.source_range(); for indel in item.text_edit().iter() { if indel.delete.contains_range(source_range) { + let insert_replace_support = insert_replace_support.then(|| tdpp.position); text_edit = Some(if indel.delete == source_range { self::completion_text_edit(line_index, insert_replace_support, indel.clone()) } else { @@ -253,29 +278,38 @@ pub(crate) fn completion_item( lsp_item.command = Some(command::trigger_parameter_hints()); } - let mut res = match item.ref_match() { - Some((mutability, relevance)) => { - let mut lsp_item_with_ref = lsp_item.clone(); - set_score(&mut lsp_item_with_ref, relevance); - lsp_item_with_ref.label = - format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label); - if let Some(it) = &mut lsp_item_with_ref.text_edit { - let new_text = match it { - lsp_types::CompletionTextEdit::Edit(it) => &mut it.new_text, - lsp_types::CompletionTextEdit::InsertAndReplace(it) => &mut it.new_text, + lsp_item.insert_text_format = Some(insert_text_format(item.insert_text_format())); + if enable_imports_on_the_fly { + if let Some(import_edit) = item.import_to_add() { + let import_path = &import_edit.import.import_path; + if let Some(import_name) = import_path.segments().last() { + let data = lsp_ext::CompletionResolveData { + position: tdpp.clone(), + full_import_path: import_path.to_string(), + imported_name: import_name.to_string(), }; - *new_text = format!("&{}{}", mutability.as_keyword_for_ref(), new_text); + lsp_item.data = Some(to_value(data).unwrap()); } - vec![lsp_item_with_ref, lsp_item] } - None => vec![lsp_item], - }; - - for lsp_item in res.iter_mut() { - lsp_item.insert_text_format = Some(insert_text_format(item.insert_text_format())); } - return res; + if let Some((mutability, relevance)) = item.ref_match() { + let mut lsp_item_with_ref = lsp_item.clone(); + set_score(&mut lsp_item_with_ref, relevance); + lsp_item_with_ref.label = + format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label); + if let Some(it) = &mut lsp_item_with_ref.text_edit { + let new_text = match it { + lsp_types::CompletionTextEdit::Edit(it) => &mut it.new_text, + lsp_types::CompletionTextEdit::InsertAndReplace(it) => &mut it.new_text, + }; + *new_text = format!("&{}{}", mutability.as_keyword_for_ref(), new_text); + } + + acc.push(lsp_item_with_ref); + }; + + acc.push(lsp_item); fn set_score(res: &mut lsp_types::CompletionItem, relevance: CompletionRelevance) { if relevance.is_relevant() { @@ -1179,7 +1213,9 @@ mod tests { encoding: OffsetEncoding::Utf16, }; let (analysis, file_id) = Analysis::from_single_file(text); - let completions: Vec<(String, Option)> = analysis + + let file_position = ide_db::base_db::FilePosition { file_id, offset }; + let mut items = analysis .completions( &ide::CompletionConfig { enable_postfix_completions: true, @@ -1196,15 +1232,26 @@ mod tests { skip_glob_imports: true, }, }, - ide_db::base_db::FilePosition { file_id, offset }, + file_position, ) .unwrap() - .unwrap() - .into_iter() - .filter(|c| c.label().ends_with("arg")) - .map(|c| completion_item(None, &line_index, c)) - .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text))) - .collect(); + .unwrap(); + items.retain(|c| c.label().ends_with("arg")); + let items = completion_items( + false, + false, + &line_index, + lsp_types::TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { + uri: "file://main.rs".parse().unwrap(), + }, + position: position(&line_index, file_position.offset), + }, + items, + ); + let items: Vec<(String, Option)> = + items.into_iter().map(|c| (c.label, c.sort_text)).collect(); + expect_test::expect![[r#" [ ( @@ -1221,7 +1268,7 @@ mod tests { ), ] "#]] - .assert_debug_eq(&completions); + .assert_debug_eq(&items); } #[test] diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 8fb1da351f..78e14b01e4 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@