self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
}
- /// Whether the completions' additional edits are calculated later, during a resolve request or not.
- /// See `CompletionResolveCapability` for the details.
- pub fn resolve_edits_immediately(&self) -> bool {
- !self
- .active_resolve_capabilities
- .contains(&CompletionResolveCapability::AdditionalTextEdits)
+ /// Whether the completions' additional edits are calculated when sending an initional completions list
+ /// or later, in a separate resolve request.
+ pub fn resolve_additional_edits_lazily(&self) -> bool {
+ self.active_resolve_capabilities.contains(&CompletionResolveCapability::AdditionalTextEdits)
}
}
ref_match: Option<(Mutability, CompletionScore)>,
/// The import data to add to completion's edits.
- import_to_add: Option<ImportToAdd>,
+ import_to_add: Option<ImportEdit>,
}
// We use custom debug for CompletionItem to make snapshot tests more readable.
score: None,
ref_match: None,
import_to_add: None,
- resolve_import_immediately: true,
+ resolve_import_lazily: false,
}
}
self.ref_match
}
- pub fn import_to_add(&self) -> Option<&ImportToAdd> {
+ pub fn import_to_add(&self) -> Option<&ImportEdit> {
self.import_to_add.as_ref()
}
}
/// An extra import to add after the completion is applied.
#[derive(Debug, Clone)]
-pub struct ImportToAdd {
+pub struct ImportEdit {
pub import_path: ModPath,
pub import_scope: ImportScope,
pub merge_behaviour: Option<MergeBehaviour>,
}
+impl ImportEdit {
+ /// 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> {
+ let _p = profile::span("ImportEdit::to_edit");
+
+ let rewriter = insert_use::insert_use(
+ &self.import_scope,
+ mod_path_to_ast(&self.import_path),
+ self.merge_behaviour,
+ );
+ let old_ast = rewriter.rewrite_root()?;
+ let mut import_insert = TextEdit::builder();
+ algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
+
+ Some(import_insert.finish())
+ }
+}
+
/// A helper to make `CompletionItem`s.
#[must_use]
#[derive(Clone)]
pub(crate) struct Builder {
source_range: TextRange,
completion_kind: CompletionKind,
- import_to_add: Option<ImportToAdd>,
- resolve_import_immediately: bool,
+ import_to_add: Option<ImportEdit>,
+ resolve_import_lazily: bool,
label: String,
insert_text: Option<String>,
insert_text_format: InsertTextFormat,
let mut label = self.label;
let mut lookup = self.lookup;
let mut insert_text = self.insert_text;
- let mut text_edits = TextEdit::builder();
if let Some(import_to_add) = self.import_to_add.as_ref() {
let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
}
label = format!("{}::{}", import_path_without_last_segment, label);
}
-
- if self.resolve_import_immediately {
- let rewriter = insert_use::insert_use(
- &import_to_add.import_scope,
- mod_path_to_ast(&import_to_add.import_path),
- import_to_add.merge_behaviour,
- );
- if let Some(old_ast) = rewriter.rewrite_root() {
- algo::diff(&old_ast, &rewriter.rewrite(&old_ast))
- .into_text_edit(&mut text_edits);
- }
- }
}
- let original_edit = match self.text_edit {
+ let mut text_edit = match self.text_edit {
Some(it) => it,
None => {
TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone()))
}
};
- let mut resulting_edit = text_edits.finish();
- resulting_edit.union(original_edit).expect("Failed to unite text edits");
+ if !self.resolve_import_lazily {
+ if let Some(import_edit) =
+ self.import_to_add.as_ref().and_then(|import_edit| import_edit.to_text_edit())
+ {
+ text_edit.union(import_edit).expect("Failed to unite import and completion edits");
+ }
+ }
CompletionItem {
source_range: self.source_range,
label,
insert_text_format: self.insert_text_format,
- text_edit: resulting_edit,
+ text_edit,
detail: self.detail,
documentation: self.documentation,
lookup,
}
pub(crate) fn add_import(
mut self,
- import_to_add: Option<ImportToAdd>,
- resolve_import_immediately: bool,
+ import_to_add: Option<ImportEdit>,
+ resolve_import_lazily: bool,
) -> Builder {
self.import_to_add = import_to_add;
- self.resolve_import_immediately = resolve_import_immediately;
+ self.resolve_import_lazily = resolve_import_lazily;
self
}
pub(crate) fn set_ref_match(
pub use crate::{
config::{CompletionConfig, CompletionResolveCapability},
- item::{CompletionItem, CompletionItemKind, CompletionScore, ImportToAdd, InsertTextFormat},
+ item::{CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, InsertTextFormat},
};
//FIXME: split the following feature into fine-grained features.
use test_utils::mark;
use crate::{
- config::SnippetCap, item::ImportToAdd, CompletionContext, CompletionItem, CompletionItemKind,
+ config::SnippetCap, item::ImportEdit, CompletionContext, CompletionItem, CompletionItemKind,
CompletionKind, CompletionScore,
};
let local_name = import_path.segments.last()?.to_string();
Render::new(ctx).render_resolution(
local_name,
- Some(ImportToAdd { import_path, import_scope, merge_behaviour }),
+ Some(ImportEdit { import_path, import_scope, merge_behaviour }),
resolution,
)
}
fn render_resolution(
self,
local_name: String,
- import_to_add: Option<ImportToAdd>,
+ import_to_add: Option<ImportEdit>,
resolution: &ScopeDef,
) -> Option<CompletionItem> {
let _p = profile::span("render_resolution");
local_name,
)
.kind(CompletionItemKind::UnresolvedReference)
- .add_import(import_to_add, self.ctx.completion.config.resolve_edits_immediately())
+ .add_import(
+ import_to_add,
+ self.ctx.completion.config.resolve_additional_edits_lazily(),
+ )
.build();
return Some(item);
}
let item = item
.kind(kind)
- .add_import(import_to_add, self.ctx.completion.config.resolve_edits_immediately())
+ .add_import(import_to_add, self.ctx.completion.config.resolve_additional_edits_lazily())
.set_documentation(docs)
.set_ref_match(ref_match)
.build();
use test_utils::mark;
use crate::{
- item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd},
+ item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
render::{builder_ext::Params, RenderContext},
};
pub(crate) fn render_enum_variant<'a>(
ctx: RenderContext<'a>,
- import_to_add: Option<ImportToAdd>,
+ import_to_add: Option<ImportEdit>,
local_name: Option<String>,
variant: hir::EnumVariant,
path: Option<ModPath>,
}
}
- fn render(self, import_to_add: Option<ImportToAdd>) -> CompletionItem {
+ fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
let mut builder = CompletionItem::new(
CompletionKind::Reference,
self.ctx.source_range(),
.kind(CompletionItemKind::EnumVariant)
.set_documentation(self.variant.docs(self.ctx.db()))
.set_deprecated(self.ctx.is_deprecated(self.variant))
- .add_import(import_to_add, self.ctx.completion.config.resolve_edits_immediately())
+ .add_import(import_to_add, self.ctx.completion.config.resolve_additional_edits_lazily())
.detail(self.detail());
if self.variant_kind == StructKind::Tuple {
use test_utils::mark;
use crate::{
- item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd},
+ item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
render::{builder_ext::Params, RenderContext},
};
pub(crate) fn render_fn<'a>(
ctx: RenderContext<'a>,
- import_to_add: Option<ImportToAdd>,
+ import_to_add: Option<ImportEdit>,
local_name: Option<String>,
fn_: hir::Function,
) -> CompletionItem {
FunctionRender { ctx, name, func: fn_, ast_node }
}
- fn render(self, import_to_add: Option<ImportToAdd>) -> CompletionItem {
+ fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
let params = self.params();
CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone())
.kind(self.kind())
.set_deprecated(self.ctx.is_deprecated(self.func))
.detail(self.detail())
.add_call_parens(self.ctx.completion, self.name, params)
- .add_import(import_to_add, self.ctx.completion.config.resolve_edits_immediately())
+ .add_import(import_to_add, self.ctx.completion.config.resolve_additional_edits_lazily())
.build()
}
use test_utils::mark;
use crate::{
- item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd},
+ item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
render::RenderContext,
};
pub(crate) fn render_macro<'a>(
ctx: RenderContext<'a>,
- import_to_add: Option<ImportToAdd>,
+ import_to_add: Option<ImportEdit>,
name: String,
macro_: hir::MacroDef,
) -> Option<CompletionItem> {
MacroRender { ctx, name, macro_, docs, bra, ket }
}
- fn render(&self, import_to_add: Option<ImportToAdd>) -> Option<CompletionItem> {
+ fn render(&self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> {
// FIXME: Currently proc-macro do not have ast-node,
// such that it does not have source
if self.macro_.is_proc_macro() {
.kind(CompletionItemKind::Macro)
.set_documentation(self.docs.clone())
.set_deprecated(self.ctx.is_deprecated(self.macro_))
- .add_import(import_to_add, self.ctx.completion.config.resolve_edits_immediately())
+ .add_import(
+ import_to_add,
+ self.ctx.completion.config.resolve_additional_edits_lazily(),
+ )
.detail(self.detail());
let needs_bang = self.needs_bang();
};
pub use completion::{
CompletionConfig, CompletionItem, CompletionItemKind, CompletionResolveCapability,
- CompletionScore, ImportToAdd, InsertTextFormat,
+ CompletionScore, ImportEdit, InsertTextFormat,
};
pub use ide_db::{
call_info::CallInfo,
}
/// Parses client capabilities and returns all completion resolve capabilities rust-analyzer supports.
-pub fn enabled_completions_resolve_capabilities(
+pub(crate) fn enabled_completions_resolve_capabilities(
caps: &ClientCapabilities,
) -> Option<FxHashSet<CompletionResolveCapability>> {
Some(
.as_ref()?
.properties
.iter()
- .filter_map(|cap_string| {
- Some(match cap_string.as_str() {
- "additionalTextEdits" => CompletionResolveCapability::AdditionalTextEdits,
- "detail" => CompletionResolveCapability::Detail,
- "documentation" => CompletionResolveCapability::Documentation,
- _unsupported => return None,
- })
+ .filter_map(|cap_string| match cap_string.as_str() {
+ "additionalTextEdits" => Some(CompletionResolveCapability::AdditionalTextEdits),
+ "detail" => Some(CompletionResolveCapability::Detail),
+ "documentation" => Some(CompletionResolveCapability::Documentation),
+ _unsupported => None,
})
.collect(),
)
use crossbeam_channel::{unbounded, Receiver, Sender};
use flycheck::FlycheckHandle;
-use ide::{Analysis, AnalysisHost, Change, CompletionItem, FileId};
+use ide::{Analysis, AnalysisHost, Change, FileId, ImportEdit};
use ide_db::base_db::{CrateId, VfsPath};
use lsp_types::{SemanticTokens, Url};
use parking_lot::{Mutex, RwLock};
pub(crate) struct CompletionResolveData {
pub(crate) file_id: FileId,
- pub(crate) item: CompletionItem,
+ pub(crate) import_edit: ImportEdit,
}
/// `GlobalState` is the primary mutable state of the language server
};
use ide::{
- FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, ImportToAdd, LineIndex,
+ FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, ImportEdit, LineIndex,
NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit,
};
-use ide_db::helpers::{insert_use, mod_path_to_ast};
use itertools::Itertools;
use lsp_server::ErrorCode;
use lsp_types::{
let mut new_completion_items =
to_proto::completion_item(&line_index, line_endings, item.clone());
- if !snap.config.completion.active_resolve_capabilities.is_empty() {
- let item_id = serde_json::to_value(&item_index)
- .expect(&format!("Should be able to serialize usize value {}", item_index));
- completion_resolve_data
- .insert(item_index, CompletionResolveData { file_id: position.file_id, item });
- for new_item in &mut new_completion_items {
- new_item.data = Some(item_id.clone());
+ if snap.config.completion.resolve_additional_edits_lazily() {
+ if let Some(import_edit) = item.import_to_add() {
+ completion_resolve_data.insert(
+ item_index,
+ CompletionResolveData {
+ file_id: position.file_id,
+ import_edit: import_edit.clone(),
+ },
+ );
+
+ let item_id = serde_json::to_value(&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(item_id.clone());
+ }
}
}
Ok(Some(completion_list.into()))
}
-pub(crate) fn handle_resolve_completion(
+pub(crate) fn handle_completion_resolve(
global_state: &mut GlobalState,
mut original_completion: lsp_types::CompletionItem,
) -> Result<lsp_types::CompletionItem> {
let _p = profile::span("handle_resolve_completion");
+ let active_resolve_caps = &global_state.config.completion.active_resolve_capabilities;
+ if active_resolve_caps.is_empty() {
+ return Ok(original_completion);
+ }
+
let server_completion_data = match original_completion
.data
.as_ref()
};
let snap = &global_state.snapshot();
- for supported_completion_resolve_cap in &snap.config.completion.active_resolve_capabilities {
+ for supported_completion_resolve_cap in active_resolve_caps {
match supported_completion_resolve_cap {
+ // FIXME actually add all additional edits here? see `to_proto::completion_item` for more
ide::CompletionResolveCapability::AdditionalTextEdits => {
- // FIXME actually add all additional edits here?
- if let Some(import_to_add) = server_completion_data.item.import_to_add() {
- append_import_edits(
- &mut original_completion,
- import_to_add,
- snap.analysis.file_line_index(server_completion_data.file_id)?.as_ref(),
- snap.file_line_endings(server_completion_data.file_id),
- );
- }
+ append_import_edits(
+ &mut original_completion,
+ &server_completion_data.import_edit,
+ snap.analysis.file_line_index(server_completion_data.file_id)?.as_ref(),
+ snap.file_line_endings(server_completion_data.file_id),
+ );
}
// FIXME resolve the other capabilities also?
_ => {}
fn append_import_edits(
completion: &mut lsp_types::CompletionItem,
- import_to_add: &ImportToAdd,
+ import_to_add: &ImportEdit,
line_index: &LineIndex,
line_endings: LineEndings,
) {
- let new_edits = import_into_edits(import_to_add, line_index, line_endings);
+ 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) = new_edits {
+ if let Some(mut new_edits) = import_edits {
original_additional_edits.extend(new_edits.drain(..))
}
} else {
- completion.additional_text_edits = new_edits;
+ completion.additional_text_edits = import_edits;
}
}
-
-fn import_into_edits(
- import_to_add: &ImportToAdd,
- line_index: &LineIndex,
- line_endings: LineEndings,
-) -> Option<Vec<lsp_types::TextEdit>> {
- let _p = profile::span("add_import_edits");
-
- let rewriter = insert_use::insert_use(
- &import_to_add.import_scope,
- mod_path_to_ast(&import_to_add.import_path),
- import_to_add.merge_behaviour,
- );
- let old_ast = rewriter.rewrite_root()?;
- let mut import_insert = TextEdit::builder();
- algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
- let import_edit = import_insert.finish();
-
- Some(
- import_edit
- .into_iter()
- .map(|indel| to_proto::text_edit(line_index, line_endings, indel))
- .collect_vec(),
- )
-}
.on_sync::<lsp_ext::MemoryUsage>(|s, p| handlers::handle_memory_usage(s, p))?
.on_sync::<lsp_types::request::Completion>(handlers::handle_completion)?
.on_sync::<lsp_types::request::ResolveCompletionItem>(
- handlers::handle_resolve_completion,
+ handlers::handle_completion_resolve,
)?
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)