Make completion resolve async

This commit is contained in:
Kirill Bulatov 2020-12-03 15:58:18 +02:00
parent f6d2540df0
commit 74c3bbacc9
7 changed files with 115 additions and 52 deletions

View file

@ -4,10 +4,10 @@ use std::fmt;
use hir::{Documentation, ModPath, Mutability}; use hir::{Documentation, ModPath, Mutability};
use ide_db::helpers::{ use ide_db::helpers::{
insert_use::{self, ImportScope, MergeBehaviour}, insert_use::{self, ImportScope, ImportScopePtr, MergeBehaviour},
mod_path_to_ast, mod_path_to_ast,
}; };
use syntax::{algo, TextRange}; use syntax::{algo, SyntaxNode, TextRange};
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::config::SnippetCap; use crate::config::SnippetCap;
@ -275,7 +275,32 @@ pub struct ImportEdit {
pub merge_behaviour: Option<MergeBehaviour>, pub merge_behaviour: Option<MergeBehaviour>,
} }
#[derive(Debug, Clone)]
pub struct ImportEditPtr {
pub import_path: ModPath,
pub import_scope: ImportScopePtr,
pub merge_behaviour: Option<MergeBehaviour>,
}
impl ImportEditPtr {
pub fn into_import_edit(self, root: &SyntaxNode) -> Option<ImportEdit> {
Some(ImportEdit {
import_path: self.import_path,
import_scope: self.import_scope.into_scope(root)?,
merge_behaviour: self.merge_behaviour,
})
}
}
impl ImportEdit { impl ImportEdit {
pub fn get_edit_ptr(&self) -> ImportEditPtr {
ImportEditPtr {
import_path: self.import_path.clone(),
import_scope: self.import_scope.get_ptr(),
merge_behaviour: self.merge_behaviour,
}
}
/// 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

@ -18,7 +18,10 @@ use crate::{completions::Completions, context::CompletionContext, item::Completi
pub use crate::{ pub use crate::{
config::{CompletionConfig, CompletionResolveCapability}, config::{CompletionConfig, CompletionResolveCapability},
item::{CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, InsertTextFormat}, item::{
CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, ImportEditPtr,
InsertTextFormat,
},
}; };
//FIXME: split the following feature into fine-grained features. //FIXME: split the following feature into fine-grained features.

View file

@ -81,7 +81,7 @@ pub use crate::{
}; };
pub use completion::{ pub use completion::{
CompletionConfig, CompletionItem, CompletionItemKind, CompletionResolveCapability, CompletionConfig, CompletionItem, CompletionItemKind, CompletionResolveCapability,
CompletionScore, ImportEdit, InsertTextFormat, CompletionScore, ImportEdit, ImportEditPtr, InsertTextFormat,
}; };
pub use ide_db::{ pub use ide_db::{
call_info::CallInfo, call_info::CallInfo,

View file

@ -11,7 +11,7 @@ use syntax::{
edit::{AstNodeEdit, IndentLevel}, edit::{AstNodeEdit, IndentLevel},
make, AstNode, PathSegmentKind, VisibilityOwner, make, AstNode, PathSegmentKind, VisibilityOwner,
}, },
AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken, AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken,
}; };
use test_utils::mark; use test_utils::mark;
@ -21,6 +21,36 @@ pub enum ImportScope {
Module(ast::ItemList), Module(ast::ItemList),
} }
impl ImportScope {
pub fn get_ptr(&self) -> ImportScopePtr {
match self {
ImportScope::File(file) => ImportScopePtr::File(SyntaxNodePtr::new(file.syntax())),
ImportScope::Module(module) => {
ImportScopePtr::Module(SyntaxNodePtr::new(module.syntax()))
}
}
}
}
#[derive(Debug, Clone)]
pub enum ImportScopePtr {
File(SyntaxNodePtr),
Module(SyntaxNodePtr),
}
impl ImportScopePtr {
pub fn into_scope(self, root: &SyntaxNode) -> Option<ImportScope> {
Some(match self {
ImportScopePtr::File(file_ptr) => {
ImportScope::File(ast::SourceFile::cast(file_ptr.to_node(root))?)
}
ImportScopePtr::Module(module_ptr) => {
ImportScope::File(ast::SourceFile::cast(module_ptr.to_node(root))?)
}
})
}
}
impl ImportScope { impl ImportScope {
pub fn from(syntax: SyntaxNode) -> Option<Self> { pub fn from(syntax: SyntaxNode) -> Option<Self> {
if let Some(module) = ast::Module::cast(syntax.clone()) { if let Some(module) = ast::Module::cast(syntax.clone()) {

View file

@ -7,7 +7,7 @@ use std::{sync::Arc, time::Instant};
use crossbeam_channel::{unbounded, Receiver, Sender}; use crossbeam_channel::{unbounded, Receiver, Sender};
use flycheck::FlycheckHandle; use flycheck::FlycheckHandle;
use ide::{Analysis, AnalysisHost, Change, FileId, ImportEdit}; use ide::{Analysis, AnalysisHost, Change, FileId, ImportEditPtr};
use ide_db::base_db::{CrateId, VfsPath}; use ide_db::base_db::{CrateId, VfsPath};
use lsp_types::{SemanticTokens, Url}; use lsp_types::{SemanticTokens, Url};
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
@ -51,11 +51,6 @@ pub(crate) struct Handle<H, C> {
pub(crate) type ReqHandler = fn(&mut GlobalState, lsp_server::Response); pub(crate) type ReqHandler = fn(&mut GlobalState, lsp_server::Response);
pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>; pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
pub(crate) struct CompletionResolveData {
pub(crate) file_id: FileId,
pub(crate) import_edit: ImportEdit,
}
/// `GlobalState` is the primary mutable state of the language server /// `GlobalState` is the primary mutable state of the language server
/// ///
/// The most interesting components are `vfs`, which stores a consistent /// The most interesting components are `vfs`, which stores a consistent
@ -74,7 +69,7 @@ pub(crate) struct GlobalState {
pub(crate) config: Config, pub(crate) config: Config,
pub(crate) analysis_host: AnalysisHost, pub(crate) analysis_host: AnalysisHost,
pub(crate) diagnostics: DiagnosticCollection, pub(crate) diagnostics: DiagnosticCollection,
pub(crate) completion_resolve_data: FxHashMap<usize, CompletionResolveData>, pub(crate) completion_resolve_data: Arc<FxHashMap<usize, ImportEditPtr>>,
pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>, pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>,
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
@ -96,6 +91,7 @@ pub(crate) struct GlobalStateSnapshot {
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
pub(crate) completion_resolve_data: Arc<FxHashMap<usize, ImportEditPtr>>,
} }
impl GlobalState { impl GlobalState {
@ -127,7 +123,7 @@ impl GlobalState {
config, config,
analysis_host, analysis_host,
diagnostics: Default::default(), diagnostics: Default::default(),
completion_resolve_data: FxHashMap::default(), completion_resolve_data: Arc::new(FxHashMap::default()),
mem_docs: FxHashMap::default(), mem_docs: FxHashMap::default(),
semantic_tokens_cache: Arc::new(Default::default()), semantic_tokens_cache: Arc::new(Default::default()),
vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
@ -198,6 +194,7 @@ impl GlobalState {
check_fixes: Arc::clone(&self.diagnostics.check_fixes), check_fixes: Arc::clone(&self.diagnostics.check_fixes),
mem_docs: self.mem_docs.clone(), mem_docs: self.mem_docs.clone(),
semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache), semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache),
completion_resolve_data: Arc::clone(&self.completion_resolve_data),
} }
} }

View file

@ -5,11 +5,13 @@
use std::{ use std::{
io::Write as _, io::Write as _,
process::{self, Stdio}, process::{self, Stdio},
sync::Arc,
}; };
use ide::{ use ide::{
FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, ImportEdit, LineIndex, CompletionResolveCapability, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData,
NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, ImportEdit, LineIndex, NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
TextEdit,
}; };
use itertools::Itertools; use itertools::Itertools;
use lsp_server::ErrorCode; use lsp_server::ErrorCode;
@ -34,7 +36,7 @@ use crate::{
cargo_target_spec::CargoTargetSpec, cargo_target_spec::CargoTargetSpec,
config::RustfmtConfig, config::RustfmtConfig,
from_json, from_proto, from_json, from_proto,
global_state::{CompletionResolveData, GlobalState, GlobalStateSnapshot}, global_state::{GlobalState, GlobalStateSnapshot},
line_endings::LineEndings, line_endings::LineEndings,
lsp_ext::{self, InlayHint, InlayHintsParams}, lsp_ext::{self, InlayHint, InlayHintsParams},
to_proto, LspError, Result, to_proto, LspError, Result,
@ -542,6 +544,7 @@ pub(crate) fn handle_completion(
) -> Result<Option<lsp_types::CompletionResponse>> { ) -> Result<Option<lsp_types::CompletionResponse>> {
let _p = profile::span("handle_completion"); let _p = profile::span("handle_completion");
let snap = global_state.snapshot(); let snap = global_state.snapshot();
let text_document_url = params.text_document_position.text_document.uri.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;
@ -582,18 +585,15 @@ pub(crate) fn handle_completion(
if snap.config.completion.resolve_additional_edits_lazily() { if snap.config.completion.resolve_additional_edits_lazily() {
if let Some(import_edit) = item.import_to_add() { if let Some(import_edit) = item.import_to_add() {
completion_resolve_data.insert( completion_resolve_data.insert(item_index, import_edit.get_edit_ptr());
item_index,
CompletionResolveData {
file_id: position.file_id,
import_edit: import_edit.clone(),
},
);
let item_id = serde_json::to_value(&item_index) let data = serde_json::to_value(&CompletionData {
.expect(&format!("Should be able to serialize usize value {}", item_index)); 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 { for new_item in &mut new_completion_items {
new_item.data = Some(item_id.clone()); new_item.data = Some(data.clone());
} }
} }
} }
@ -602,50 +602,54 @@ pub(crate) fn handle_completion(
}) })
.collect(); .collect();
global_state.completion_resolve_data = completion_resolve_data; global_state.completion_resolve_data = Arc::new(completion_resolve_data);
let completion_list = lsp_types::CompletionList { is_incomplete: true, items }; let completion_list = lsp_types::CompletionList { is_incomplete: true, items };
Ok(Some(completion_list.into())) Ok(Some(completion_list.into()))
} }
pub(crate) fn handle_completion_resolve( pub(crate) fn handle_completion_resolve(
global_state: &mut GlobalState, snap: GlobalStateSnapshot,
mut original_completion: lsp_types::CompletionItem, mut original_completion: lsp_types::CompletionItem,
) -> Result<lsp_types::CompletionItem> { ) -> Result<lsp_types::CompletionItem> {
let _p = profile::span("handle_resolve_completion"); let _p = profile::span("handle_resolve_completion");
let active_resolve_caps = &global_state.config.completion.active_resolve_capabilities; // FIXME resolve the other capabilities also?
if active_resolve_caps.is_empty() { if !snap
.config
.completion
.active_resolve_capabilities
.contains(&CompletionResolveCapability::AdditionalTextEdits)
{
return Ok(original_completion); return Ok(original_completion);
} }
let server_completion_data = match original_completion let (import_edit_ptr, document_url) = match original_completion
.data .data
.as_ref() .as_ref()
.map(|data| serde_json::from_value::<usize>(data.clone())) .map(|data| serde_json::from_value::<CompletionData>(data.clone()))
.transpose()? .transpose()?
.and_then(|server_completion_id| { .and_then(|data| {
global_state.completion_resolve_data.get(&server_completion_id) let import_edit_ptr = snap.completion_resolve_data.get(&data.import_id).cloned();
Some((import_edit_ptr, data.document_url))
}) { }) {
Some(data) => data, Some(data) => data,
None => return Ok(original_completion), None => return Ok(original_completion),
}; };
let snap = &global_state.snapshot(); let file_id = from_proto::file_id(&snap, &document_url)?;
for supported_completion_resolve_cap in active_resolve_caps { let root = snap.analysis.parse(file_id)?;
match supported_completion_resolve_cap {
// FIXME actually add all additional edits here? see `to_proto::completion_item` for more if let Some(import_to_add) =
ide::CompletionResolveCapability::AdditionalTextEdits => { import_edit_ptr.and_then(|import_edit| import_edit.into_import_edit(root.syntax()))
append_import_edits( {
&mut original_completion, // FIXME actually add all additional edits here? see `to_proto::completion_item` for more
&server_completion_data.import_edit, append_import_edits(
snap.analysis.file_line_index(server_completion_data.file_id)?.as_ref(), &mut original_completion,
snap.file_line_endings(server_completion_data.file_id), &import_to_add,
); snap.analysis.file_line_index(file_id)?.as_ref(),
} snap.file_line_endings(file_id),
// FIXME resolve the other capabilities also? );
_ => {}
}
} }
Ok(original_completion) Ok(original_completion)
@ -1609,6 +1613,12 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>)
} }
} }
#[derive(Debug, Serialize, Deserialize)]
struct CompletionData {
document_url: Url,
import_id: usize,
}
fn append_import_edits( fn append_import_edits(
completion: &mut lsp_types::CompletionItem, completion: &mut lsp_types::CompletionItem,
import_to_add: &ImportEdit, import_to_add: &ImportEdit,

View file

@ -437,9 +437,7 @@ impl GlobalState {
})? })?
.on_sync::<lsp_ext::MemoryUsage>(|s, p| handlers::handle_memory_usage(s, p))? .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::Completion>(handlers::handle_completion)?
.on_sync::<lsp_types::request::ResolveCompletionItem>( .on::<lsp_types::request::ResolveCompletionItem>(handlers::handle_completion_resolve)
handlers::handle_completion_resolve,
)?
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status) .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree) .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
.on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro) .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)