Skip to content

Commit 2a7be4a

Browse files
Better support client completion resolve caps
1 parent 47464e5 commit 2a7be4a

File tree

5 files changed

+120
-105
lines changed

5 files changed

+120
-105
lines changed

crates/completion/src/item.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::config::SnippetCap;
1212
/// `CompletionItem` describes a single completion variant in the editor pop-up.
1313
/// It is basically a POD with various properties. To construct a
1414
/// `CompletionItem`, use `new` method and the `Builder` struct.
15+
#[derive(Clone)]
1516
pub struct CompletionItem {
1617
/// Used only internally in tests, to check only specific kind of
1718
/// completion (postfix, keyword, reference, etc).

crates/completion/src/test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub(crate) fn check_edit_with_config(
9797
.unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
9898
let mut actual = db.file_text(position.file_id).to_string();
9999
completion.text_edit().apply(&mut actual);
100+
// TODO kb how to apply imports now?
100101
assert_eq_text!(&ra_fixture_after, &actual)
101102
}
102103

crates/rust-analyzer/src/global_state.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{sync::Arc, time::Instant};
77

88
use crossbeam_channel::{unbounded, Receiver, Sender};
99
use flycheck::FlycheckHandle;
10-
use ide::{Analysis, AnalysisHost, Change, FileId, ImportToAdd};
10+
use ide::{Analysis, AnalysisHost, Change, CompletionItem, FileId};
1111
use ide_db::base_db::{CrateId, VfsPath};
1212
use lsp_types::{SemanticTokens, Url};
1313
use parking_lot::{Mutex, RwLock};
@@ -51,6 +51,11 @@ pub(crate) struct Handle<H, C> {
5151
pub(crate) type ReqHandler = fn(&mut GlobalState, lsp_server::Response);
5252
pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
5353

54+
pub(crate) struct CompletionResolveData {
55+
pub(crate) file_id: FileId,
56+
pub(crate) item: CompletionItem,
57+
}
58+
5459
/// `GlobalState` is the primary mutable state of the language server
5560
///
5661
/// The most interesting components are `vfs`, which stores a consistent
@@ -69,7 +74,7 @@ pub(crate) struct GlobalState {
6974
pub(crate) config: Config,
7075
pub(crate) analysis_host: AnalysisHost,
7176
pub(crate) diagnostics: DiagnosticCollection,
72-
pub(crate) additional_imports: FxHashMap<usize, ImportToAdd>,
77+
pub(crate) completion_resolve_data: FxHashMap<usize, CompletionResolveData>,
7378
pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
7479
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
7580
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
@@ -122,7 +127,7 @@ impl GlobalState {
122127
config,
123128
analysis_host,
124129
diagnostics: Default::default(),
125-
additional_imports: FxHashMap::default(),
130+
completion_resolve_data: FxHashMap::default(),
126131
mem_docs: FxHashMap::default(),
127132
semantic_tokens_cache: Arc::new(Default::default()),
128133
vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),

crates/rust-analyzer/src/handlers.rs

Lines changed: 47 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@
55
use std::{
66
io::Write as _,
77
process::{self, Stdio},
8-
sync::Arc,
98
};
109

1110
use ide::{
12-
FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, ImportToAdd, LineIndex,
13-
NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit,
11+
FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query,
12+
RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit,
1413
};
15-
use ide_db::helpers::{insert_use, mod_path_to_ast};
1614
use itertools::Itertools;
1715
use lsp_server::ErrorCode;
1816
use lsp_types::{
@@ -36,10 +34,10 @@ use crate::{
3634
cargo_target_spec::CargoTargetSpec,
3735
config::RustfmtConfig,
3836
from_json, from_proto,
39-
global_state::{GlobalState, GlobalStateSnapshot},
40-
line_endings::LineEndings,
37+
global_state::{CompletionResolveData, GlobalState, GlobalStateSnapshot},
4138
lsp_ext::{self, InlayHint, InlayHintsParams},
42-
to_proto, LspError, Result,
39+
to_proto::{self, append_import_edits},
40+
LspError, Result,
4341
};
4442

4543
pub(crate) fn handle_analyzer_status(
@@ -538,12 +536,6 @@ pub(crate) fn handle_runnables(
538536
Ok(res)
539537
}
540538

541-
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
542-
pub(crate) struct ResolveCompletionData {
543-
completion_id: usize,
544-
completion_file_id: u32,
545-
}
546-
547539
pub(crate) fn handle_completion(
548540
global_state: &mut GlobalState,
549541
params: lsp_types::CompletionParams,
@@ -579,38 +571,31 @@ pub(crate) fn handle_completion(
579571
};
580572
let line_index = snap.analysis.file_line_index(position.file_id)?;
581573
let line_endings = snap.file_line_endings(position.file_id);
582-
let mut additional_imports = FxHashMap::default();
574+
let mut completion_resolve_data = FxHashMap::default();
583575

584576
let items: Vec<CompletionItem> = items
585577
.into_iter()
586578
.enumerate()
587579
.flat_map(|(item_index, item)| {
588-
let resolve_completion_data = ResolveCompletionData {
589-
completion_id: item_index,
590-
completion_file_id: position.file_id.0,
591-
};
592-
let import_to_add = item.import_to_add().cloned();
593-
let mut new_completion_items =
594-
to_proto::completion_item(&line_index, line_endings, item);
595-
596-
if let Some(import_to_add) = import_to_add {
597-
for new_item in &mut new_completion_items {
598-
match serde_json::to_value(&resolve_completion_data) {
599-
Ok(resolve_value) => {
600-
new_item.data = Some(resolve_value);
601-
additional_imports.insert(item_index, import_to_add.clone());
602-
}
603-
Err(e) => {
604-
log::error!("Failed to serialize completion resolve metadata: {}", e)
605-
}
606-
}
607-
}
580+
let mut new_completion_items = to_proto::completion_item(
581+
&line_index,
582+
line_endings,
583+
item.clone(),
584+
&snap.config.completion.resolve_capabilities,
585+
);
586+
587+
let item_id = serde_json::to_value(&item_index)
588+
.expect(&format!("Should be able to serialize usize value {}", item_index));
589+
completion_resolve_data
590+
.insert(item_index, CompletionResolveData { file_id: position.file_id, item });
591+
for new_item in &mut new_completion_items {
592+
new_item.data = Some(item_id.clone());
608593
}
609594
new_completion_items
610595
})
611596
.collect();
612597

613-
global_state.additional_imports = additional_imports;
598+
global_state.completion_resolve_data = completion_resolve_data;
614599

615600
let completion_list = lsp_types::CompletionList { is_incomplete: true, items };
616601
Ok(Some(completion_list.into()))
@@ -622,71 +607,38 @@ pub(crate) fn handle_resolve_completion(
622607
) -> Result<lsp_types::CompletionItem> {
623608
let _p = profile::span("handle_resolve_completion");
624609

625-
match original_completion.data.as_ref() {
626-
Some(completion_data) => {
627-
match serde_json::from_value::<ResolveCompletionData>(completion_data.clone()) {
628-
Ok(resolve_completion_data) => {
629-
if let Some(import_to_add) =
630-
global_state.additional_imports.get(&resolve_completion_data.completion_id)
631-
{
632-
let snap = global_state.snapshot();
633-
let file_id = FileId(resolve_completion_data.completion_file_id);
634-
let line_index = snap.analysis.file_line_index(file_id)?;
635-
let line_endings = snap.file_line_endings(file_id);
636-
637-
let resolved_edits =
638-
resolve_additional_edits(import_to_add, line_index, line_endings);
639-
640-
original_completion.additional_text_edits =
641-
match original_completion.additional_text_edits {
642-
Some(mut original_additional_edits) => {
643-
if let Some(mut new_edits) = resolved_edits {
644-
original_additional_edits.extend(new_edits.drain(..))
645-
}
646-
Some(original_additional_edits)
647-
}
648-
None => resolved_edits,
649-
};
650-
} else {
651-
log::error!(
652-
"Got no import data for completion with label {}, id {}",
653-
original_completion.label,
654-
resolve_completion_data.completion_id
655-
)
656-
}
610+
let server_completion_data = match original_completion
611+
.data
612+
.as_ref()
613+
.map(|data| serde_json::from_value::<usize>(data.clone()))
614+
.transpose()?
615+
.and_then(|server_completion_id| {
616+
global_state.completion_resolve_data.get(&server_completion_id)
617+
}) {
618+
Some(data) => data,
619+
None => return Ok(original_completion),
620+
};
621+
622+
let snap = &global_state.snapshot();
623+
for supported_completion_resolve_cap in &snap.config.completion.resolve_capabilities {
624+
match supported_completion_resolve_cap {
625+
ide::CompletionResolveCapability::AdditionalTextEdits => {
626+
// TODO kb actually add all additional edits here?
627+
if let Some(import_to_add) = server_completion_data.item.import_to_add() {
628+
append_import_edits(
629+
&mut original_completion,
630+
import_to_add,
631+
snap.analysis.file_line_index(server_completion_data.file_id)?.as_ref(),
632+
snap.file_line_endings(server_completion_data.file_id),
633+
);
657634
}
658-
Err(e) => log::error!("Failed to deserialize completion resolve metadata: {}", e),
659635
}
636+
// TODO kb calculate the rest also?
637+
_ => {}
660638
}
661-
None => (),
662639
}
663-
Ok(original_completion)
664-
}
665640

666-
// TODO kb what to do when no resolve is available on the client?
667-
fn resolve_additional_edits(
668-
import_to_add: &ImportToAdd,
669-
line_index: Arc<LineIndex>,
670-
line_endings: LineEndings,
671-
) -> Option<Vec<lsp_types::TextEdit>> {
672-
let _p = profile::span("resolve_additional_edits");
673-
674-
let rewriter = insert_use::insert_use(
675-
&import_to_add.import_scope,
676-
mod_path_to_ast(&import_to_add.import_path),
677-
import_to_add.merge_behaviour,
678-
);
679-
let old_ast = rewriter.rewrite_root()?;
680-
let mut import_insert = TextEdit::builder();
681-
algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
682-
let text_edit = import_insert.finish();
683-
684-
Some(
685-
text_edit
686-
.into_iter()
687-
.map(|indel| to_proto::text_edit(&line_index, line_endings, indel))
688-
.collect_vec(),
689-
)
641+
Ok(original_completion)
690642
}
691643

692644
pub(crate) fn handle_folding_range(

crates/rust-analyzer/src/to_proto.rs

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ use std::{
55
};
66

77
use ide::{
8-
Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation,
9-
FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier, HighlightTag, HighlightedRange,
10-
Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget,
11-
ReferenceAccess, ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit,
8+
Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, CompletionResolveCapability,
9+
Documentation, FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier, HighlightTag,
10+
HighlightedRange, ImportToAdd, Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex,
11+
Markup, NavigationTarget, ReferenceAccess, ResolvedAssist, Runnable, Severity, SourceChange,
12+
SourceFileEdit, TextEdit,
13+
};
14+
use ide_db::{
15+
base_db::{FileId, FileRange},
16+
helpers::{insert_use, mod_path_to_ast},
1217
};
13-
use ide_db::base_db::{FileId, FileRange};
1418
use itertools::Itertools;
15-
use syntax::{SyntaxKind, TextRange, TextSize};
19+
use rustc_hash::FxHashSet;
20+
use syntax::{algo, SyntaxKind, TextRange, TextSize};
1621

1722
use crate::{
1823
cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot,
@@ -158,6 +163,7 @@ pub(crate) fn completion_item(
158163
line_index: &LineIndex,
159164
line_endings: LineEndings,
160165
completion_item: CompletionItem,
166+
resolve_capabilities: &FxHashSet<CompletionResolveCapability>,
161167
) -> Vec<lsp_types::CompletionItem> {
162168
fn set_score(res: &mut lsp_types::CompletionItem, label: &str) {
163169
res.preselect = Some(true);
@@ -231,9 +237,17 @@ pub(crate) fn completion_item(
231237
None => vec![res],
232238
};
233239

240+
let unapplied_import_data = completion_item.import_to_add().filter(|_| {
241+
!resolve_capabilities.contains(&CompletionResolveCapability::AdditionalTextEdits)
242+
});
243+
234244
for mut r in all_results.iter_mut() {
235245
r.insert_text_format = Some(insert_text_format(completion_item.insert_text_format()));
246+
if let Some(unapplied_import_data) = unapplied_import_data {
247+
append_import_edits(r, unapplied_import_data, line_index, line_endings);
248+
}
236249
}
250+
237251
all_results
238252
}
239253

@@ -817,6 +831,47 @@ pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent {
817831
lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value }
818832
}
819833

834+
pub(crate) fn import_into_edits(
835+
import_to_add: &ImportToAdd,
836+
line_index: &LineIndex,
837+
line_endings: LineEndings,
838+
) -> Option<Vec<lsp_types::TextEdit>> {
839+
let _p = profile::span("add_import_edits");
840+
841+
let rewriter = insert_use::insert_use(
842+
&import_to_add.import_scope,
843+
mod_path_to_ast(&import_to_add.import_path),
844+
import_to_add.merge_behaviour,
845+
);
846+
let old_ast = rewriter.rewrite_root()?;
847+
let mut import_insert = TextEdit::builder();
848+
algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
849+
let import_edit = import_insert.finish();
850+
851+
Some(
852+
import_edit
853+
.into_iter()
854+
.map(|indel| text_edit(line_index, line_endings, indel))
855+
.collect_vec(),
856+
)
857+
}
858+
859+
pub(crate) fn append_import_edits(
860+
completion: &mut lsp_types::CompletionItem,
861+
import_to_add: &ImportToAdd,
862+
line_index: &LineIndex,
863+
line_endings: LineEndings,
864+
) {
865+
let new_edits = import_into_edits(import_to_add, line_index, line_endings);
866+
if let Some(original_additional_edits) = completion.additional_text_edits.as_mut() {
867+
if let Some(mut new_edits) = new_edits {
868+
original_additional_edits.extend(new_edits.drain(..))
869+
}
870+
} else {
871+
completion.additional_text_edits = new_edits;
872+
}
873+
}
874+
820875
#[cfg(test)]
821876
mod tests {
822877
use ide::Analysis;
@@ -836,6 +891,7 @@ mod tests {
836891
let (offset, text) = test_utils::extract_offset(fixture);
837892
let line_index = LineIndex::new(&text);
838893
let (analysis, file_id) = Analysis::from_single_file(text);
894+
let resolve_caps = FxHashSet::default();
839895
let completions: Vec<(String, Option<String>)> = analysis
840896
.completions(
841897
&ide::CompletionConfig::default(),
@@ -845,7 +901,7 @@ mod tests {
845901
.unwrap()
846902
.into_iter()
847903
.filter(|c| c.label().ends_with("arg"))
848-
.map(|c| completion_item(&line_index, LineEndings::Unix, c))
904+
.map(|c| completion_item(&line_index, LineEndings::Unix, c, &resolve_caps))
849905
.flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
850906
.collect();
851907
expect_test::expect![[r#"

0 commit comments

Comments
 (0)