#![allow(rustdoc::private_intra_doc_links)]
#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
#[cfg(feature = "in-rust-tree")]
extern crate rustc_driver as _;
mod completions;
mod config;
mod context;
mod item;
mod render;
mod snippet;
#[cfg(test)]
mod tests;
use ide_db::{
FilePosition, FxHashSet, RootDatabase,
imports::insert_use::{self, ImportScope},
syntax_helpers::tree_diff::diff,
text_edit::TextEdit,
};
use syntax::ast::make;
use crate::{
completions::Completions,
context::{
CompletionAnalysis, CompletionContext, NameRefContext, NameRefKind, PathCompletionCtx,
PathKind,
},
};
pub use crate::{
config::{AutoImportExclusionType, CallableSnippets, CompletionConfig},
item::{
CompletionItem, CompletionItemKind, CompletionItemRefMode, CompletionRelevance,
CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
CompletionRelevanceTypeMatch,
},
snippet::{Snippet, SnippetScope},
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CompletionFieldsToResolve {
pub resolve_label_details: bool,
pub resolve_tags: bool,
pub resolve_detail: bool,
pub resolve_documentation: bool,
pub resolve_filter_text: bool,
pub resolve_text_edit: bool,
pub resolve_command: bool,
}
impl CompletionFieldsToResolve {
pub fn from_client_capabilities(client_capability_fields: &FxHashSet<&str>) -> Self {
Self {
resolve_label_details: client_capability_fields.contains("labelDetails"),
resolve_tags: client_capability_fields.contains("tags"),
resolve_detail: client_capability_fields.contains("detail"),
resolve_documentation: client_capability_fields.contains("documentation"),
resolve_filter_text: client_capability_fields.contains("filterText"),
resolve_text_edit: client_capability_fields.contains("textEdit"),
resolve_command: client_capability_fields.contains("command"),
}
}
pub const fn empty() -> Self {
Self {
resolve_label_details: false,
resolve_tags: false,
resolve_detail: false,
resolve_documentation: false,
resolve_filter_text: false,
resolve_text_edit: false,
resolve_command: false,
}
}
}
pub fn completions(
db: &RootDatabase,
config: &CompletionConfig<'_>,
position: FilePosition,
trigger_character: Option<char>,
) -> Option<Vec<CompletionItem>> {
let (ctx, analysis) = &CompletionContext::new(db, position, config, trigger_character)?;
let mut completions = Completions::default();
if trigger_character == Some('(') {
if let CompletionAnalysis::NameRef(NameRefContext {
kind:
NameRefKind::Path(
path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. },
),
..
}) = analysis
{
completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token);
}
return Some(completions.into());
}
if trigger_character == Some('_')
&& ctx.original_token.kind() == syntax::SyntaxKind::UNDERSCORE
&& let CompletionAnalysis::NameRef(NameRefContext {
kind:
NameRefKind::Path(
path_ctx @ PathCompletionCtx {
kind: PathKind::Type { .. } | PathKind::Pat { .. },
..
},
),
..
}) = analysis
&& path_ctx.is_trivial_path()
{
return None;
}
{
let acc = &mut completions;
match analysis {
CompletionAnalysis::Name(name_ctx) => completions::complete_name(acc, ctx, name_ctx),
CompletionAnalysis::NameRef(name_ref_ctx) => {
completions::complete_name_ref(acc, ctx, name_ref_ctx)
}
CompletionAnalysis::Lifetime(lifetime_ctx) => {
completions::lifetime::complete_label(acc, ctx, lifetime_ctx);
completions::lifetime::complete_lifetime(acc, ctx, lifetime_ctx);
}
CompletionAnalysis::String { original, expanded: Some(expanded) } => {
completions::extern_abi::complete_extern_abi(acc, ctx, expanded);
completions::format_string::format_string(acc, ctx, original, expanded);
completions::env_vars::complete_cargo_env_vars(acc, ctx, original, expanded);
completions::ra_fixture::complete_ra_fixture(acc, ctx, original, expanded);
}
CompletionAnalysis::UnexpandedAttrTT {
colon_prefix,
fake_attribute_under_caret: Some(attr),
extern_crate,
} => {
completions::attribute::complete_known_attribute_input(
acc,
ctx,
colon_prefix,
attr,
extern_crate.as_ref(),
);
}
CompletionAnalysis::MacroSegment => {
completions::macro_def::complete_macro_segment(acc, ctx);
}
CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (),
}
}
Some(completions.into())
}
pub fn resolve_completion_edits(
db: &RootDatabase,
config: &CompletionConfig<'_>,
FilePosition { file_id, offset }: FilePosition,
imports: impl IntoIterator<Item = String>,
) -> Option<Vec<TextEdit>> {
let _p = tracing::info_span!("resolve_completion_edits").entered();
let sema = hir::Semantics::new(db);
let editioned_file_id = sema.attach_first_edition(file_id);
let original_file = sema.parse(editioned_file_id);
let original_token =
syntax::AstNode::syntax(&original_file).token_at_offset(offset).left_biased()?;
let position_for_import = &original_token.parent()?;
let scope = ImportScope::find_insert_use_container(position_for_import, &sema)?;
let current_module = sema.scope(position_for_import)?.module();
let current_crate = current_module.krate(db);
let current_edition = current_crate.edition(db);
let new_ast = scope.clone_for_update();
let mut import_insert = TextEdit::builder();
imports.into_iter().for_each(|full_import_path| {
insert_use::insert_use(
&new_ast,
make::path_from_text_with_edition(&full_import_path, current_edition),
&config.insert_use,
);
});
diff(scope.as_syntax_node(), new_ast.as_syntax_node()).into_text_edit(&mut import_insert);
Some(vec![import_insert.finish()])
}