Use stateless completion resolve

This commit is contained in:
Kirill Bulatov 2020-12-04 16:03:22 +02:00
parent 93bc009a59
commit deda74edd8
6 changed files with 123 additions and 70 deletions

View file

@ -9,7 +9,7 @@ use test_utils::mark;
use crate::{ use crate::{
render::{render_resolution_with_import, RenderContext}, render::{render_resolution_with_import, RenderContext},
CompletionContext, Completions, CompletionContext, Completions, ImportEdit,
}; };
pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 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)| { .filter_map(|(import_path, definition)| {
render_resolution_with_import( render_resolution_with_import(
RenderContext::new(ctx), RenderContext::new(ctx),
import_path.clone(), ImportEdit {
import_scope.clone(), import_path: import_path.clone(),
ctx.config.merge, import_scope: import_scope.clone(),
merge_behaviour: ctx.config.merge,
},
&definition, &definition,
) )
}); });

View file

@ -276,7 +276,6 @@ pub struct ImportEdit {
} }
impl 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. /// 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. /// May return no edit in edge cases, such as scope already containing the import.
pub fn to_text_edit(&self) -> Option<TextEdit> { pub fn to_text_edit(&self) -> Option<TextEdit> {

View file

@ -11,8 +11,11 @@ mod render;
mod completions; mod completions;
use ide_db::base_db::FilePosition; use ide_db::{
use ide_db::RootDatabase; 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}; use crate::{completions::Completions, context::CompletionContext, item::CompletionKind};
@ -131,6 +134,31 @@ pub fn completions(
Some(acc) 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)] #[cfg(test)]
mod tests { mod tests {
use crate::config::CompletionConfig; use crate::config::CompletionConfig;

View file

@ -9,8 +9,7 @@ pub(crate) mod type_alias;
mod builder_ext; mod builder_ext;
use hir::{Documentation, HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type}; use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type};
use ide_db::helpers::insert_use::{ImportScope, MergeBehaviour};
use ide_db::RootDatabase; use ide_db::RootDatabase;
use syntax::TextRange; use syntax::TextRange;
use test_utils::mark; use test_utils::mark;
@ -48,15 +47,12 @@ pub(crate) fn render_resolution<'a>(
pub(crate) fn render_resolution_with_import<'a>( pub(crate) fn render_resolution_with_import<'a>(
ctx: RenderContext<'a>, ctx: RenderContext<'a>,
import_path: ModPath, import_edit: ImportEdit,
import_scope: ImportScope,
merge_behaviour: Option<MergeBehaviour>,
resolution: &ScopeDef, resolution: &ScopeDef,
) -> Option<CompletionItem> { ) -> Option<CompletionItem> {
let local_name = import_path.segments.last()?.to_string();
Render::new(ctx).render_resolution( Render::new(ctx).render_resolution(
local_name, import_edit.import_path.segments.last()?.to_string(),
Some(ImportEdit { import_path, import_scope, merge_behaviour }), Some(import_edit),
resolution, resolution,
) )
} }

View file

@ -469,6 +469,28 @@ impl Analysis {
self.with_db(|db| completion::completions(db, config, position).map(Into::into)) 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. /// Computes resolved assists with source changes for the given position.
pub fn resolved_assists( pub fn resolved_assists(
&self, &self,

View file

@ -8,8 +8,8 @@ use std::{
}; };
use ide::{ use ide::{
CompletionResolveCapability, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, CompletionConfig, CompletionResolveCapability, FileId, FilePosition, FileRange, HoverAction,
ImportEdit, LineIndex, NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, HoverGotoTypeData, NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
TextEdit, TextEdit,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -22,7 +22,7 @@ use lsp_types::{
HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, Range, RenameParams, HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, Range, RenameParams,
SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams, SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams,
SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit, SymbolTag, TextDocumentIdentifier, TextDocumentPositionParams, Url, WorkspaceEdit,
}; };
use project_model::TargetKind; use project_model::TargetKind;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -35,7 +35,6 @@ use crate::{
config::RustfmtConfig, config::RustfmtConfig,
from_json, from_proto, from_json, from_proto,
global_state::{GlobalState, GlobalStateSnapshot}, global_state::{GlobalState, GlobalStateSnapshot},
line_endings::LineEndings,
lsp_ext::{self, InlayHint, InlayHintsParams}, lsp_ext::{self, InlayHint, InlayHintsParams},
to_proto, LspError, Result, to_proto, LspError, Result,
}; };
@ -541,7 +540,7 @@ pub(crate) fn handle_completion(
params: lsp_types::CompletionParams, params: lsp_types::CompletionParams,
) -> Result<Option<lsp_types::CompletionResponse>> { ) -> Result<Option<lsp_types::CompletionResponse>> {
let _p = profile::span("handle_completion"); 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 position = from_proto::file_position(&snap, params.text_document_position)?;
let completion_triggered_after_single_colon = { let completion_triggered_after_single_colon = {
let mut res = false; let mut res = false;
@ -574,23 +573,18 @@ pub(crate) fn handle_completion(
let items: Vec<CompletionItem> = items let items: Vec<CompletionItem> = items
.into_iter() .into_iter()
.enumerate() .flat_map(|item| {
.flat_map(|(item_index, item)| {
let mut new_completion_items = let mut new_completion_items =
to_proto::completion_item(&line_index, line_endings, item.clone()); to_proto::completion_item(&line_index, line_endings, item.clone());
if snap.config.completion.resolve_additional_edits_lazily() { for new_item in &mut new_completion_items {
// TODO kb add resolve data somehow here let _ = fill_resolve_data(
if let Some(import_edit) = item.import_to_add() { &mut new_item.data,
// let data = serde_json::to_value(&CompletionData { &item,
// document_url: text_document_url.clone(), &snap.config.completion,
// import_id: item_index, &text_document_position,
// }) )
// .expect(&format!("Should be able to serialize usize value {}", item_index)); .take();
for new_item in &mut new_completion_items {
// new_item.data = Some(data.clone());
}
}
} }
new_completion_items new_completion_items
@ -603,8 +597,8 @@ pub(crate) fn handle_completion(
pub(crate) fn handle_completion_resolve( pub(crate) fn handle_completion_resolve(
snap: GlobalStateSnapshot, snap: GlobalStateSnapshot,
mut original_completion: lsp_types::CompletionItem, mut original_completion: CompletionItem,
) -> Result<lsp_types::CompletionItem> { ) -> Result<CompletionItem> {
let _p = profile::span("handle_resolve_completion"); let _p = profile::span("handle_resolve_completion");
// FIXME resolve the other capabilities also? // FIXME resolve the other capabilities also?
@ -627,21 +621,30 @@ pub(crate) fn handle_completion_resolve(
None => return Ok(original_completion), 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, &resolve_data.position.text_document.uri)?;
// let file_id = from_proto::file_id(&snap, &document_url)?; let line_index = snap.analysis.file_line_index(file_id)?;
// let root = snap.analysis.parse(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) = let mut additional_edits = snap
// import_edit_ptr.and_then(|import_edit| import_edit.into_import_edit(root.syntax())) .analysis
// { .resolve_completion_edits(
// // FIXME actually add all additional edits here? see `to_proto::completion_item` for more &snap.config.completion,
// append_import_edits( FilePosition { file_id, offset },
// &mut original_completion, &resolve_data.full_import_path,
// &import_to_add, &resolve_data.imported_name,
// snap.analysis.file_line_index(file_id)?.as_ref(), )?
// snap.file_line_endings(file_id), .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) Ok(original_completion)
} }
@ -1606,27 +1609,30 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>)
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct CompletionResolveData { struct CompletionResolveData {
document_url: Url, position: lsp_types::TextDocumentPositionParams,
import_id: usize, full_import_path: String,
imported_name: String,
} }
fn append_import_edits( fn fill_resolve_data(
completion: &mut lsp_types::CompletionItem, resolve_data: &mut Option<serde_json::Value>,
import_to_add: &ImportEdit, item: &ide::CompletionItem,
line_index: &LineIndex, completion_config: &CompletionConfig,
line_endings: LineEndings, position: &TextDocumentPositionParams,
) { ) -> Option<()> {
let import_edits = import_to_add.to_text_edit().map(|import_edit| { if completion_config.resolve_additional_edits_lazily() {
import_edit let import_edit = item.import_to_add()?;
.into_iter() let full_import_path = import_edit.import_path.to_string();
.map(|indel| to_proto::text_edit(line_index, line_endings, indel)) let imported_name = import_edit.import_path.segments.clone().pop()?.to_string();
.collect_vec()
}); *resolve_data = Some(
if let Some(original_additional_edits) = completion.additional_text_edits.as_mut() { serde_json::to_value(CompletionResolveData {
if let Some(mut new_edits) = import_edits { position: position.to_owned(),
original_additional_edits.extend(new_edits.drain(..)) full_import_path,
} imported_name,
} else { })
completion.additional_text_edits = import_edits; .expect("Failed to serialize a regular struct with derives"),
)
} }
Some(())
} }