aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorKirill Bulatov <[email protected]>2020-12-01 23:02:15 +0000
committerKirill Bulatov <[email protected]>2020-12-07 21:41:08 +0000
commit2a7be4afb000d97948bb7f11bcd074fc1e11716e (patch)
tree8d8e7dadaca7a24e4a40ab3976baf19876d6a7e0 /crates
parent47464e556c160ce705c2e3c84f501ad4e8dbb123 (diff)
Better support client completion resolve caps
Diffstat (limited to 'crates')
-rw-r--r--crates/completion/src/item.rs1
-rw-r--r--crates/completion/src/test_utils.rs1
-rw-r--r--crates/rust-analyzer/src/global_state.rs11
-rw-r--r--crates/rust-analyzer/src/handlers.rs142
-rw-r--r--crates/rust-analyzer/src/to_proto.rs70
5 files changed, 120 insertions, 105 deletions
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs
index 0e59f73cb..dc67df075 100644
--- a/crates/completion/src/item.rs
+++ b/crates/completion/src/item.rs
@@ -12,6 +12,7 @@ use crate::config::SnippetCap;
12/// `CompletionItem` describes a single completion variant in the editor pop-up. 12/// `CompletionItem` describes a single completion variant in the editor pop-up.
13/// It is basically a POD with various properties. To construct a 13/// It is basically a POD with various properties. To construct a
14/// `CompletionItem`, use `new` method and the `Builder` struct. 14/// `CompletionItem`, use `new` method and the `Builder` struct.
15#[derive(Clone)]
15pub struct CompletionItem { 16pub struct CompletionItem {
16 /// Used only internally in tests, to check only specific kind of 17 /// Used only internally in tests, to check only specific kind of
17 /// completion (postfix, keyword, reference, etc). 18 /// completion (postfix, keyword, reference, etc).
diff --git a/crates/completion/src/test_utils.rs b/crates/completion/src/test_utils.rs
index 4c1b1a839..516a63b4d 100644
--- a/crates/completion/src/test_utils.rs
+++ b/crates/completion/src/test_utils.rs
@@ -97,6 +97,7 @@ pub(crate) fn check_edit_with_config(
97 .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions)); 97 .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
98 let mut actual = db.file_text(position.file_id).to_string(); 98 let mut actual = db.file_text(position.file_id).to_string();
99 completion.text_edit().apply(&mut actual); 99 completion.text_edit().apply(&mut actual);
100 // TODO kb how to apply imports now?
100 assert_eq_text!(&ra_fixture_after, &actual) 101 assert_eq_text!(&ra_fixture_after, &actual)
101} 102}
102 103
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index ba4bf2eeb..e12651937 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -7,7 +7,7 @@ use std::{sync::Arc, time::Instant};
7 7
8use crossbeam_channel::{unbounded, Receiver, Sender}; 8use crossbeam_channel::{unbounded, Receiver, Sender};
9use flycheck::FlycheckHandle; 9use flycheck::FlycheckHandle;
10use ide::{Analysis, AnalysisHost, Change, FileId, ImportToAdd}; 10use ide::{Analysis, AnalysisHost, Change, CompletionItem, FileId};
11use ide_db::base_db::{CrateId, VfsPath}; 11use ide_db::base_db::{CrateId, VfsPath};
12use lsp_types::{SemanticTokens, Url}; 12use lsp_types::{SemanticTokens, Url};
13use parking_lot::{Mutex, RwLock}; 13use parking_lot::{Mutex, RwLock};
@@ -51,6 +51,11 @@ pub(crate) struct Handle<H, C> {
51pub(crate) type ReqHandler = fn(&mut GlobalState, lsp_server::Response); 51pub(crate) type ReqHandler = fn(&mut GlobalState, lsp_server::Response);
52pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>; 52pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
53 53
54pub(crate) struct CompletionResolveData {
55 pub(crate) file_id: FileId,
56 pub(crate) item: CompletionItem,
57}
58
54/// `GlobalState` is the primary mutable state of the language server 59/// `GlobalState` is the primary mutable state of the language server
55/// 60///
56/// The most interesting components are `vfs`, which stores a consistent 61/// The most interesting components are `vfs`, which stores a consistent
@@ -69,7 +74,7 @@ pub(crate) struct GlobalState {
69 pub(crate) config: Config, 74 pub(crate) config: Config,
70 pub(crate) analysis_host: AnalysisHost, 75 pub(crate) analysis_host: AnalysisHost,
71 pub(crate) diagnostics: DiagnosticCollection, 76 pub(crate) diagnostics: DiagnosticCollection,
72 pub(crate) additional_imports: FxHashMap<usize, ImportToAdd>, 77 pub(crate) completion_resolve_data: FxHashMap<usize, CompletionResolveData>,
73 pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>, 78 pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
74 pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, 79 pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
75 pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, 80 pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
@@ -122,7 +127,7 @@ impl GlobalState {
122 config, 127 config,
123 analysis_host, 128 analysis_host,
124 diagnostics: Default::default(), 129 diagnostics: Default::default(),
125 additional_imports: FxHashMap::default(), 130 completion_resolve_data: FxHashMap::default(),
126 mem_docs: FxHashMap::default(), 131 mem_docs: FxHashMap::default(),
127 semantic_tokens_cache: Arc::new(Default::default()), 132 semantic_tokens_cache: Arc::new(Default::default()),
128 vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), 133 vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index d9fdf0434..a186d2e5d 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -5,14 +5,12 @@
5use std::{ 5use std::{
6 io::Write as _, 6 io::Write as _,
7 process::{self, Stdio}, 7 process::{self, Stdio},
8 sync::Arc,
9}; 8};
10 9
11use ide::{ 10use ide::{
12 FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, ImportToAdd, LineIndex, 11 FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query,
13 NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, 12 RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit,
14}; 13};
15use ide_db::helpers::{insert_use, mod_path_to_ast};
16use itertools::Itertools; 14use itertools::Itertools;
17use lsp_server::ErrorCode; 15use lsp_server::ErrorCode;
18use lsp_types::{ 16use lsp_types::{
@@ -36,10 +34,10 @@ use crate::{
36 cargo_target_spec::CargoTargetSpec, 34 cargo_target_spec::CargoTargetSpec,
37 config::RustfmtConfig, 35 config::RustfmtConfig,
38 from_json, from_proto, 36 from_json, from_proto,
39 global_state::{GlobalState, GlobalStateSnapshot}, 37 global_state::{CompletionResolveData, GlobalState, GlobalStateSnapshot},
40 line_endings::LineEndings,
41 lsp_ext::{self, InlayHint, InlayHintsParams}, 38 lsp_ext::{self, InlayHint, InlayHintsParams},
42 to_proto, LspError, Result, 39 to_proto::{self, append_import_edits},
40 LspError, Result,
43}; 41};
44 42
45pub(crate) fn handle_analyzer_status( 43pub(crate) fn handle_analyzer_status(
@@ -538,12 +536,6 @@ pub(crate) fn handle_runnables(
538 Ok(res) 536 Ok(res)
539} 537}
540 538
541#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
542pub(crate) struct ResolveCompletionData {
543 completion_id: usize,
544 completion_file_id: u32,
545}
546
547pub(crate) fn handle_completion( 539pub(crate) fn handle_completion(
548 global_state: &mut GlobalState, 540 global_state: &mut GlobalState,
549 params: lsp_types::CompletionParams, 541 params: lsp_types::CompletionParams,
@@ -579,38 +571,31 @@ pub(crate) fn handle_completion(
579 }; 571 };
580 let line_index = snap.analysis.file_line_index(position.file_id)?; 572 let line_index = snap.analysis.file_line_index(position.file_id)?;
581 let line_endings = snap.file_line_endings(position.file_id); 573 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();
583 575
584 let items: Vec<CompletionItem> = items 576 let items: Vec<CompletionItem> = items
585 .into_iter() 577 .into_iter()
586 .enumerate() 578 .enumerate()
587 .flat_map(|(item_index, item)| { 579 .flat_map(|(item_index, item)| {
588 let resolve_completion_data = ResolveCompletionData { 580 let mut new_completion_items = to_proto::completion_item(
589 completion_id: item_index, 581 &line_index,
590 completion_file_id: position.file_id.0, 582 line_endings,
591 }; 583 item.clone(),
592 let import_to_add = item.import_to_add().cloned(); 584 &snap.config.completion.resolve_capabilities,
593 let mut new_completion_items = 585 );
594 to_proto::completion_item(&line_index, line_endings, item); 586
595 587 let item_id = serde_json::to_value(&item_index)
596 if let Some(import_to_add) = import_to_add { 588 .expect(&format!("Should be able to serialize usize value {}", item_index));
597 for new_item in &mut new_completion_items { 589 completion_resolve_data
598 match serde_json::to_value(&resolve_completion_data) { 590 .insert(item_index, CompletionResolveData { file_id: position.file_id, item });
599 Ok(resolve_value) => { 591 for new_item in &mut new_completion_items {
600 new_item.data = Some(resolve_value); 592 new_item.data = Some(item_id.clone());
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 }
608 } 593 }
609 new_completion_items 594 new_completion_items
610 }) 595 })
611 .collect(); 596 .collect();
612 597
613 global_state.additional_imports = additional_imports; 598 global_state.completion_resolve_data = completion_resolve_data;
614 599
615 let completion_list = lsp_types::CompletionList { is_incomplete: true, items }; 600 let completion_list = lsp_types::CompletionList { is_incomplete: true, items };
616 Ok(Some(completion_list.into())) 601 Ok(Some(completion_list.into()))
@@ -622,71 +607,38 @@ pub(crate) fn handle_resolve_completion(
622) -> Result<lsp_types::CompletionItem> { 607) -> Result<lsp_types::CompletionItem> {
623 let _p = profile::span("handle_resolve_completion"); 608 let _p = profile::span("handle_resolve_completion");
624 609
625 match original_completion.data.as_ref() { 610 let server_completion_data = match original_completion
626 Some(completion_data) => { 611 .data
627 match serde_json::from_value::<ResolveCompletionData>(completion_data.clone()) { 612 .as_ref()
628 Ok(resolve_completion_data) => { 613 .map(|data| serde_json::from_value::<usize>(data.clone()))
629 if let Some(import_to_add) = 614 .transpose()?
630 global_state.additional_imports.get(&resolve_completion_data.completion_id) 615 .and_then(|server_completion_id| {
631 { 616 global_state.completion_resolve_data.get(&server_completion_id)
632 let snap = global_state.snapshot(); 617 }) {
633 let file_id = FileId(resolve_completion_data.completion_file_id); 618 Some(data) => data,
634 let line_index = snap.analysis.file_line_index(file_id)?; 619 None => return Ok(original_completion),
635 let line_endings = snap.file_line_endings(file_id); 620 };
636 621
637 let resolved_edits = 622 let snap = &global_state.snapshot();
638 resolve_additional_edits(import_to_add, line_index, line_endings); 623 for supported_completion_resolve_cap in &snap.config.completion.resolve_capabilities {
639 624 match supported_completion_resolve_cap {
640 original_completion.additional_text_edits = 625 ide::CompletionResolveCapability::AdditionalTextEdits => {
641 match original_completion.additional_text_edits { 626 // TODO kb actually add all additional edits here?
642 Some(mut original_additional_edits) => { 627 if let Some(import_to_add) = server_completion_data.item.import_to_add() {
643 if let Some(mut new_edits) = resolved_edits { 628 append_import_edits(
644 original_additional_edits.extend(new_edits.drain(..)) 629 &mut original_completion,
645 } 630 import_to_add,
646 Some(original_additional_edits) 631 snap.analysis.file_line_index(server_completion_data.file_id)?.as_ref(),
647 } 632 snap.file_line_endings(server_completion_data.file_id),
648 None => resolved_edits, 633 );
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 }
657 } 634 }
658 Err(e) => log::error!("Failed to deserialize completion resolve metadata: {}", e),
659 } 635 }
636 // TODO kb calculate the rest also?
637 _ => {}
660 } 638 }
661 None => (),
662 } 639 }
663 Ok(original_completion)
664}
665 640
666// TODO kb what to do when no resolve is available on the client? 641 Ok(original_completion)
667fn 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 )
690} 642}
691 643
692pub(crate) fn handle_folding_range( 644pub(crate) fn handle_folding_range(
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 01eabe852..aa542f5d1 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -5,14 +5,19 @@ use std::{
5}; 5};
6 6
7use ide::{ 7use ide::{
8 Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation, 8 Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, CompletionResolveCapability,
9 FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier, HighlightTag, HighlightedRange, 9 Documentation, FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier, HighlightTag,
10 Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget, 10 HighlightedRange, ImportToAdd, Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex,
11 ReferenceAccess, ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit, 11 Markup, NavigationTarget, ReferenceAccess, ResolvedAssist, Runnable, Severity, SourceChange,
12 SourceFileEdit, TextEdit,
13};
14use ide_db::{
15 base_db::{FileId, FileRange},
16 helpers::{insert_use, mod_path_to_ast},
12}; 17};
13use ide_db::base_db::{FileId, FileRange};
14use itertools::Itertools; 18use itertools::Itertools;
15use syntax::{SyntaxKind, TextRange, TextSize}; 19use rustc_hash::FxHashSet;
20use syntax::{algo, SyntaxKind, TextRange, TextSize};
16 21
17use crate::{ 22use crate::{
18 cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, 23 cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot,
@@ -158,6 +163,7 @@ pub(crate) fn completion_item(
158 line_index: &LineIndex, 163 line_index: &LineIndex,
159 line_endings: LineEndings, 164 line_endings: LineEndings,
160 completion_item: CompletionItem, 165 completion_item: CompletionItem,
166 resolve_capabilities: &FxHashSet<CompletionResolveCapability>,
161) -> Vec<lsp_types::CompletionItem> { 167) -> Vec<lsp_types::CompletionItem> {
162 fn set_score(res: &mut lsp_types::CompletionItem, label: &str) { 168 fn set_score(res: &mut lsp_types::CompletionItem, label: &str) {
163 res.preselect = Some(true); 169 res.preselect = Some(true);
@@ -231,9 +237,17 @@ pub(crate) fn completion_item(
231 None => vec![res], 237 None => vec![res],
232 }; 238 };
233 239
240 let unapplied_import_data = completion_item.import_to_add().filter(|_| {
241 !resolve_capabilities.contains(&CompletionResolveCapability::AdditionalTextEdits)
242 });
243
234 for mut r in all_results.iter_mut() { 244 for mut r in all_results.iter_mut() {
235 r.insert_text_format = Some(insert_text_format(completion_item.insert_text_format())); 245 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 }
236 } 249 }
250
237 all_results 251 all_results
238} 252}
239 253
@@ -817,6 +831,47 @@ pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent {
817 lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value } 831 lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value }
818} 832}
819 833
834pub(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
859pub(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
820#[cfg(test)] 875#[cfg(test)]
821mod tests { 876mod tests {
822 use ide::Analysis; 877 use ide::Analysis;
@@ -836,6 +891,7 @@ mod tests {
836 let (offset, text) = test_utils::extract_offset(fixture); 891 let (offset, text) = test_utils::extract_offset(fixture);
837 let line_index = LineIndex::new(&text); 892 let line_index = LineIndex::new(&text);
838 let (analysis, file_id) = Analysis::from_single_file(text); 893 let (analysis, file_id) = Analysis::from_single_file(text);
894 let resolve_caps = FxHashSet::default();
839 let completions: Vec<(String, Option<String>)> = analysis 895 let completions: Vec<(String, Option<String>)> = analysis
840 .completions( 896 .completions(
841 &ide::CompletionConfig::default(), 897 &ide::CompletionConfig::default(),
@@ -845,7 +901,7 @@ mod tests {
845 .unwrap() 901 .unwrap()
846 .into_iter() 902 .into_iter()
847 .filter(|c| c.label().ends_with("arg")) 903 .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))
849 .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text))) 905 .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
850 .collect(); 906 .collect();
851 expect_test::expect![[r#" 907 expect_test::expect![[r#"