From deda74edd89affb3f77d274776d2a672bc11db90 Mon Sep 17 00:00:00 2001
From: Kirill Bulatov <mail4score@gmail.com>
Date: Fri, 4 Dec 2020 16:03:22 +0200
Subject: [PATCH] Use stateless completion resolve

---
 .../src/completions/unqualified_path.rs       |  10 +-
 crates/completion/src/item.rs                 |   1 -
 crates/completion/src/lib.rs                  |  32 ++++-
 crates/completion/src/render.rs               |  12 +-
 crates/ide/src/lib.rs                         |  22 ++++
 crates/rust-analyzer/src/handlers.rs          | 116 +++++++++---------
 6 files changed, 123 insertions(+), 70 deletions(-)

diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs
index 81691cd7f23..26a2b7a1bd0 100644
--- a/crates/completion/src/completions/unqualified_path.rs
+++ b/crates/completion/src/completions/unqualified_path.rs
@@ -9,7 +9,7 @@ use test_utils::mark;
 
 use crate::{
     render::{render_resolution_with_import, RenderContext},
-    CompletionContext, Completions,
+    CompletionContext, Completions, ImportEdit,
 };
 
 pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
@@ -103,9 +103,11 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()
     .filter_map(|(import_path, definition)| {
         render_resolution_with_import(
             RenderContext::new(ctx),
-            import_path.clone(),
-            import_scope.clone(),
-            ctx.config.merge,
+            ImportEdit {
+                import_path: import_path.clone(),
+                import_scope: import_scope.clone(),
+                merge_behaviour: ctx.config.merge,
+            },
             &definition,
         )
     });
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs
index 2dadf7e5b92..4e56f28f3cc 100644
--- a/crates/completion/src/item.rs
+++ b/crates/completion/src/item.rs
@@ -276,7 +276,6 @@ pub struct ImportEdit {
 }
 
 impl ImportEdit {
-    // TODO kb remove this at all now, since it's used only once?
     /// Attempts to insert the import to the given scope, producing a text edit.
     /// May return no edit in edge cases, such as scope already containing the import.
     pub fn to_text_edit(&self) -> Option<TextEdit> {
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index c57203c808b..938c92dbb07 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -11,8 +11,11 @@ mod render;
 
 mod completions;
 
-use ide_db::base_db::FilePosition;
-use ide_db::RootDatabase;
+use ide_db::{
+    base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase,
+};
+use syntax::AstNode;
+use text_edit::TextEdit;
 
 use crate::{completions::Completions, context::CompletionContext, item::CompletionKind};
 
@@ -131,6 +134,31 @@ pub fn completions(
     Some(acc)
 }
 
+/// Resolves additional completion data at the position given.
+pub fn resolve_completion_edits(
+    db: &RootDatabase,
+    config: &CompletionConfig,
+    position: FilePosition,
+    full_import_path: &str,
+    imported_name: &str,
+) -> Option<TextEdit> {
+    let ctx = CompletionContext::new(db, position, config)?;
+    let anchor = ctx.name_ref_syntax.as_ref()?;
+    let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
+
+    let current_module = ctx.sema.scope(anchor.syntax()).module()?;
+    let current_crate = current_module.krate();
+
+    let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name)
+        .filter_map(|candidate| {
+            let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
+            current_module.find_use_path(db, item)
+        })
+        .find(|mod_path| mod_path.to_string() == full_import_path)?;
+
+    ImportEdit { import_path, import_scope, merge_behaviour: config.merge }.to_text_edit()
+}
+
 #[cfg(test)]
 mod tests {
     use crate::config::CompletionConfig;
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
index a6faedb182b..9a43480e1a2 100644
--- a/crates/completion/src/render.rs
+++ b/crates/completion/src/render.rs
@@ -9,8 +9,7 @@ pub(crate) mod type_alias;
 
 mod builder_ext;
 
-use hir::{Documentation, HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type};
-use ide_db::helpers::insert_use::{ImportScope, MergeBehaviour};
+use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type};
 use ide_db::RootDatabase;
 use syntax::TextRange;
 use test_utils::mark;
@@ -48,15 +47,12 @@ pub(crate) fn render_resolution<'a>(
 
 pub(crate) fn render_resolution_with_import<'a>(
     ctx: RenderContext<'a>,
-    import_path: ModPath,
-    import_scope: ImportScope,
-    merge_behaviour: Option<MergeBehaviour>,
+    import_edit: ImportEdit,
     resolution: &ScopeDef,
 ) -> Option<CompletionItem> {
-    let local_name = import_path.segments.last()?.to_string();
     Render::new(ctx).render_resolution(
-        local_name,
-        Some(ImportEdit { import_path, import_scope, merge_behaviour }),
+        import_edit.import_path.segments.last()?.to_string(),
+        Some(import_edit),
         resolution,
     )
 }
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 9e38d650634..4a274f5bac2 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -469,6 +469,28 @@ impl Analysis {
         self.with_db(|db| completion::completions(db, config, position).map(Into::into))
     }
 
+    /// Resolves additional completion data at the position given.
+    pub fn resolve_completion_edits(
+        &self,
+        config: &CompletionConfig,
+        position: FilePosition,
+        full_import_path: &str,
+        imported_name: &str,
+    ) -> Cancelable<Vec<TextEdit>> {
+        Ok(self
+            .with_db(|db| {
+                completion::resolve_completion_edits(
+                    db,
+                    config,
+                    position,
+                    full_import_path,
+                    imported_name,
+                )
+            })?
+            .map(|edit| vec![edit])
+            .unwrap_or_default())
+    }
+
     /// Computes resolved assists with source changes for the given position.
     pub fn resolved_assists(
         &self,
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index dacd4ec5081..f92280524dc 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -8,8 +8,8 @@ use std::{
 };
 
 use ide::{
-    CompletionResolveCapability, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData,
-    ImportEdit, LineIndex, NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
+    CompletionConfig, CompletionResolveCapability, FileId, FilePosition, FileRange, HoverAction,
+    HoverGotoTypeData, NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
     TextEdit,
 };
 use itertools::Itertools;
@@ -22,7 +22,7 @@ use lsp_types::{
     HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, Range, RenameParams,
     SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams,
     SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
-    SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit,
+    SymbolTag, TextDocumentIdentifier, TextDocumentPositionParams, Url, WorkspaceEdit,
 };
 use project_model::TargetKind;
 use serde::{Deserialize, Serialize};
@@ -35,7 +35,6 @@ use crate::{
     config::RustfmtConfig,
     from_json, from_proto,
     global_state::{GlobalState, GlobalStateSnapshot},
-    line_endings::LineEndings,
     lsp_ext::{self, InlayHint, InlayHintsParams},
     to_proto, LspError, Result,
 };
@@ -541,7 +540,7 @@ pub(crate) fn handle_completion(
     params: lsp_types::CompletionParams,
 ) -> Result<Option<lsp_types::CompletionResponse>> {
     let _p = profile::span("handle_completion");
-    let text_document_url = params.text_document_position.text_document.uri.clone();
+    let text_document_position = params.text_document_position.clone();
     let position = from_proto::file_position(&snap, params.text_document_position)?;
     let completion_triggered_after_single_colon = {
         let mut res = false;
@@ -574,23 +573,18 @@ pub(crate) fn handle_completion(
 
     let items: Vec<CompletionItem> = items
         .into_iter()
-        .enumerate()
-        .flat_map(|(item_index, item)| {
+        .flat_map(|item| {
             let mut new_completion_items =
                 to_proto::completion_item(&line_index, line_endings, item.clone());
 
-            if snap.config.completion.resolve_additional_edits_lazily() {
-                // TODO kb add resolve data somehow here
-                if let Some(import_edit) = item.import_to_add() {
-                    //     let data = serde_json::to_value(&CompletionData {
-                    //         document_url: text_document_url.clone(),
-                    //         import_id: item_index,
-                    //     })
-                    //     .expect(&format!("Should be able to serialize usize value {}", item_index));
-                    for new_item in &mut new_completion_items {
-                        // new_item.data = Some(data.clone());
-                    }
-                }
+            for new_item in &mut new_completion_items {
+                let _ = fill_resolve_data(
+                    &mut new_item.data,
+                    &item,
+                    &snap.config.completion,
+                    &text_document_position,
+                )
+                .take();
             }
 
             new_completion_items
@@ -603,8 +597,8 @@ pub(crate) fn handle_completion(
 
 pub(crate) fn handle_completion_resolve(
     snap: GlobalStateSnapshot,
-    mut original_completion: lsp_types::CompletionItem,
-) -> Result<lsp_types::CompletionItem> {
+    mut original_completion: CompletionItem,
+) -> Result<CompletionItem> {
     let _p = profile::span("handle_resolve_completion");
 
     // FIXME resolve the other capabilities also?
@@ -627,21 +621,30 @@ pub(crate) fn handle_completion_resolve(
         None => return Ok(original_completion),
     };
 
-    // TODO kb get the resolve data and somehow reparse the whole ast again?
-    // let file_id = from_proto::file_id(&snap, &document_url)?;
-    // let root = snap.analysis.parse(file_id)?;
+    let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?;
+    let line_index = snap.analysis.file_line_index(file_id)?;
+    let line_endings = snap.file_line_endings(file_id);
+    let offset = from_proto::offset(&line_index, resolve_data.position.position);
 
-    // if let Some(import_to_add) =
-    //     import_edit_ptr.and_then(|import_edit| import_edit.into_import_edit(root.syntax()))
-    // {
-    //     // FIXME actually add all additional edits here? see `to_proto::completion_item` for more
-    //     append_import_edits(
-    //         &mut original_completion,
-    //         &import_to_add,
-    //         snap.analysis.file_line_index(file_id)?.as_ref(),
-    //         snap.file_line_endings(file_id),
-    //     );
-    // }
+    let mut additional_edits = snap
+        .analysis
+        .resolve_completion_edits(
+            &snap.config.completion,
+            FilePosition { file_id, offset },
+            &resolve_data.full_import_path,
+            &resolve_data.imported_name,
+        )?
+        .into_iter()
+        .flat_map(|edit| {
+            edit.into_iter().map(|indel| to_proto::text_edit(&line_index, line_endings, indel))
+        })
+        .collect_vec();
+
+    if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() {
+        original_additional_edits.extend(additional_edits.drain(..))
+    } else {
+        original_completion.additional_text_edits = Some(additional_edits);
+    }
 
     Ok(original_completion)
 }
@@ -1606,27 +1609,30 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>)
 
 #[derive(Debug, Serialize, Deserialize)]
 struct CompletionResolveData {
-    document_url: Url,
-    import_id: usize,
+    position: lsp_types::TextDocumentPositionParams,
+    full_import_path: String,
+    imported_name: String,
 }
 
-fn append_import_edits(
-    completion: &mut lsp_types::CompletionItem,
-    import_to_add: &ImportEdit,
-    line_index: &LineIndex,
-    line_endings: LineEndings,
-) {
-    let import_edits = import_to_add.to_text_edit().map(|import_edit| {
-        import_edit
-            .into_iter()
-            .map(|indel| to_proto::text_edit(line_index, line_endings, indel))
-            .collect_vec()
-    });
-    if let Some(original_additional_edits) = completion.additional_text_edits.as_mut() {
-        if let Some(mut new_edits) = import_edits {
-            original_additional_edits.extend(new_edits.drain(..))
-        }
-    } else {
-        completion.additional_text_edits = import_edits;
+fn fill_resolve_data(
+    resolve_data: &mut Option<serde_json::Value>,
+    item: &ide::CompletionItem,
+    completion_config: &CompletionConfig,
+    position: &TextDocumentPositionParams,
+) -> Option<()> {
+    if completion_config.resolve_additional_edits_lazily() {
+        let import_edit = item.import_to_add()?;
+        let full_import_path = import_edit.import_path.to_string();
+        let imported_name = import_edit.import_path.segments.clone().pop()?.to_string();
+
+        *resolve_data = Some(
+            serde_json::to_value(CompletionResolveData {
+                position: position.to_owned(),
+                full_import_path,
+                imported_name,
+            })
+            .expect("Failed to serialize a regular struct with derives"),
+        )
     }
+    Some(())
 }