Optionally disable term search for autocompletion

This commit is contained in:
Tavo Annus 2024-01-22 20:34:17 +02:00
parent 0b838e3e23
commit 88964c0b6a
13 changed files with 163 additions and 41 deletions

View file

@ -11,7 +11,12 @@ use crate::{
}; };
/// Helper function to get path to `ModuleDef` /// Helper function to get path to `ModuleDef`
fn mod_item_path(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> Option<ModPath> { fn mod_item_path(
sema_scope: &SemanticsScope<'_>,
def: &ModuleDef,
prefer_no_std: bool,
prefer_prelude: bool,
) -> Option<ModPath> {
let db = sema_scope.db; let db = sema_scope.db;
// Account for locals shadowing items from module // Account for locals shadowing items from module
let name_hit_count = def.name(db).map(|def_name| { let name_hit_count = def.name(db).map(|def_name| {
@ -26,25 +31,43 @@ fn mod_item_path(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> Option<Mod
let m = sema_scope.module(); let m = sema_scope.module();
match name_hit_count { match name_hit_count {
Some(0..=1) | None => m.find_use_path(db.upcast(), *def, false, true), Some(0..=1) | None => m.find_use_path(db.upcast(), *def, prefer_no_std, prefer_prelude),
Some(_) => m.find_use_path_prefixed(db.upcast(), *def, PrefixKind::ByCrate, false, true), Some(_) => m.find_use_path_prefixed(
db.upcast(),
*def,
PrefixKind::ByCrate,
prefer_no_std,
prefer_prelude,
),
} }
} }
/// Helper function to get path to `ModuleDef` as string /// Helper function to get path to `ModuleDef` as string
fn mod_item_path_str(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> String { fn mod_item_path_str(
let path = mod_item_path(sema_scope, def); sema_scope: &SemanticsScope<'_>,
def: &ModuleDef,
prefer_no_std: bool,
prefer_prelude: bool,
) -> String {
let path = mod_item_path(sema_scope, def, prefer_no_std, prefer_prelude);
path.map(|it| it.display(sema_scope.db.upcast()).to_string()).unwrap() path.map(|it| it.display(sema_scope.db.upcast()).to_string()).unwrap()
} }
/// Helper function to get path to `Type` /// Helper function to get path to `Type`
fn type_path(sema_scope: &SemanticsScope<'_>, ty: &Type) -> String { fn type_path(
sema_scope: &SemanticsScope<'_>,
ty: &Type,
prefer_no_std: bool,
prefer_prelude: bool,
) -> String {
let db = sema_scope.db; let db = sema_scope.db;
match ty.as_adt() { match ty.as_adt() {
Some(adt) => { Some(adt) => {
let ty_name = ty.display(db).to_string(); let ty_name = ty.display(db).to_string();
let mut path = mod_item_path(sema_scope, &ModuleDef::Adt(adt)).unwrap(); let mut path =
mod_item_path(sema_scope, &ModuleDef::Adt(adt), prefer_no_std, prefer_prelude)
.unwrap();
path.pop_segment(); path.pop_segment();
let path = path.display(db.upcast()).to_string(); let path = path.display(db.upcast()).to_string();
match path.is_empty() { match path.is_empty() {
@ -125,8 +148,11 @@ impl Expr {
&self, &self,
sema_scope: &SemanticsScope<'_>, sema_scope: &SemanticsScope<'_>,
many_formatter: &mut dyn FnMut(&Type) -> String, many_formatter: &mut dyn FnMut(&Type) -> String,
prefer_no_std: bool,
prefer_prelude: bool,
) -> String { ) -> String {
let db = sema_scope.db; let db = sema_scope.db;
let mod_item_path_str = |s, def| mod_item_path_str(s, def, prefer_no_std, prefer_prelude);
match self { match self {
Expr::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), Expr::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)),
Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)), Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)),
@ -134,8 +160,12 @@ impl Expr {
Expr::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(), Expr::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(),
Expr::FamousType { value, .. } => return value.to_string(), Expr::FamousType { value, .. } => return value.to_string(),
Expr::Function { func, params, .. } => { Expr::Function { func, params, .. } => {
let args = let args = params
params.iter().map(|f| f.gen_source_code(sema_scope, many_formatter)).join(", "); .iter()
.map(|f| {
f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude)
})
.join(", ");
match func.as_assoc_item(db).map(|it| it.container(db)) { match func.as_assoc_item(db).map(|it| it.container(db)) {
Some(container) => { Some(container) => {
@ -146,10 +176,14 @@ impl Expr {
crate::AssocItemContainer::Impl(imp) => { crate::AssocItemContainer::Impl(imp) => {
let self_ty = imp.self_ty(db); let self_ty = imp.self_ty(db);
// Should it be guaranteed that `mod_item_path` always exists? // Should it be guaranteed that `mod_item_path` always exists?
match self_ty match self_ty.as_adt().and_then(|adt| {
.as_adt() mod_item_path(
.and_then(|adt| mod_item_path(sema_scope, &adt.into())) sema_scope,
{ &adt.into(),
prefer_no_std,
prefer_prelude,
)
}) {
Some(path) => path.display(sema_scope.db.upcast()).to_string(), Some(path) => path.display(sema_scope.db.upcast()).to_string(),
None => self_ty.display(db).to_string(), None => self_ty.display(db).to_string(),
} }
@ -171,9 +205,18 @@ impl Expr {
let func_name = func.name(db).display(db.upcast()).to_string(); let func_name = func.name(db).display(db.upcast()).to_string();
let self_param = func.self_param(db).unwrap(); let self_param = func.self_param(db).unwrap();
let target = target.gen_source_code(sema_scope, many_formatter); let target = target.gen_source_code(
let args = sema_scope,
params.iter().map(|f| f.gen_source_code(sema_scope, many_formatter)).join(", "); many_formatter,
prefer_no_std,
prefer_prelude,
);
let args = params
.iter()
.map(|f| {
f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude)
})
.join(", ");
match func.as_assoc_item(db).and_then(|it| it.containing_trait_or_trait_impl(db)) { match func.as_assoc_item(db).and_then(|it| it.containing_trait_or_trait_impl(db)) {
Some(trait_) => { Some(trait_) => {
@ -196,8 +239,10 @@ impl Expr {
let generics_str = match generics.is_empty() { let generics_str = match generics.is_empty() {
true => String::new(), true => String::new(),
false => { false => {
let generics = let generics = generics
generics.iter().map(|it| type_path(sema_scope, it)).join(", "); .iter()
.map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude))
.join(", ");
format!("::<{generics}>") format!("::<{generics}>")
} }
}; };
@ -205,7 +250,14 @@ impl Expr {
StructKind::Tuple => { StructKind::Tuple => {
let args = params let args = params
.iter() .iter()
.map(|f| f.gen_source_code(sema_scope, many_formatter)) .map(|f| {
f.gen_source_code(
sema_scope,
many_formatter,
prefer_no_std,
prefer_prelude,
)
})
.join(", "); .join(", ");
format!("{generics_str}({args})") format!("{generics_str}({args})")
} }
@ -218,7 +270,12 @@ impl Expr {
format!( format!(
"{}: {}", "{}: {}",
f.name(db).display(db.upcast()).to_string(), f.name(db).display(db.upcast()).to_string(),
a.gen_source_code(sema_scope, many_formatter) a.gen_source_code(
sema_scope,
many_formatter,
prefer_no_std,
prefer_prelude
)
) )
}) })
.join(", "); .join(", ");
@ -236,7 +293,14 @@ impl Expr {
StructKind::Tuple => { StructKind::Tuple => {
let args = params let args = params
.iter() .iter()
.map(|a| a.gen_source_code(sema_scope, many_formatter)) .map(|a| {
a.gen_source_code(
sema_scope,
many_formatter,
prefer_no_std,
prefer_prelude,
)
})
.join(", "); .join(", ");
format!("({args})") format!("({args})")
} }
@ -249,7 +313,12 @@ impl Expr {
format!( format!(
"{}: {}", "{}: {}",
f.name(db).display(db.upcast()).to_string(), f.name(db).display(db.upcast()).to_string(),
a.gen_source_code(sema_scope, many_formatter) a.gen_source_code(
sema_scope,
many_formatter,
prefer_no_std,
prefer_prelude
)
) )
}) })
.join(", "); .join(", ");
@ -258,8 +327,10 @@ impl Expr {
StructKind::Unit => match generics.is_empty() { StructKind::Unit => match generics.is_empty() {
true => String::new(), true => String::new(),
false => { false => {
let generics = let generics = generics
generics.iter().map(|it| type_path(sema_scope, it)).join(", "); .iter()
.map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude))
.join(", ");
format!("::<{generics}>") format!("::<{generics}>")
} }
}, },
@ -273,7 +344,8 @@ impl Expr {
return many_formatter(&expr.ty(db)); return many_formatter(&expr.ty(db));
} }
let strukt = expr.gen_source_code(sema_scope, many_formatter); let strukt =
expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude);
let field = field.name(db).display(db.upcast()).to_string(); let field = field.name(db).display(db.upcast()).to_string();
format!("{strukt}.{field}") format!("{strukt}.{field}")
} }
@ -282,7 +354,8 @@ impl Expr {
return many_formatter(&expr.ty(db)); return many_formatter(&expr.ty(db));
} }
let inner = expr.gen_source_code(sema_scope, many_formatter); let inner =
expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude);
format!("&{inner}") format!("&{inner}")
} }
Expr::Many(ty) => many_formatter(ty), Expr::Many(ty) => many_formatter(ty),

View file

@ -760,7 +760,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
.count(); .count();
// Ignore bigger number of generics for now as they kill the performance // Ignore bigger number of generics for now as they kill the performance
if non_default_type_params_len > 0 { if non_default_type_params_len > 1 {
return None; return None;
} }

View file

@ -38,7 +38,12 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
let mut formatter = |_: &hir::Type| String::from("todo!()"); let mut formatter = |_: &hir::Type| String::from("todo!()");
for path in paths.iter().unique() { for path in paths.iter().unique() {
let code = path.gen_source_code(&scope, &mut formatter); let code = path.gen_source_code(
&scope,
&mut formatter,
ctx.config.prefer_no_std,
ctx.config.prefer_prelude,
);
acc.add_group( acc.add_group(
&GroupLabel(String::from("Term search")), &GroupLabel(String::from("Term search")),
AssistId("term_search", AssistKind::Generate), AssistId("term_search", AssistKind::Generate),

View file

@ -331,6 +331,11 @@ pub(crate) fn complete_expr_path(
pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) { pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) {
let _p = tracing::span!(tracing::Level::INFO, "complete_expr").entered(); let _p = tracing::span!(tracing::Level::INFO, "complete_expr").entered();
if !ctx.config.enable_term_search {
return;
}
if !ctx.qualifier_ctx.none() { if !ctx.qualifier_ctx.none() {
return; return;
} }

View file

@ -14,6 +14,7 @@ pub struct CompletionConfig {
pub enable_imports_on_the_fly: bool, pub enable_imports_on_the_fly: bool,
pub enable_self_on_the_fly: bool, pub enable_self_on_the_fly: bool,
pub enable_private_editable: bool, pub enable_private_editable: bool,
pub enable_term_search: bool,
pub full_function_signatures: bool, pub full_function_signatures: bool,
pub callable: Option<CallableSnippets>, pub callable: Option<CallableSnippets>,
pub snippet_cap: Option<SnippetCap>, pub snippet_cap: Option<SnippetCap>,

View file

@ -295,7 +295,12 @@ pub(crate) fn render_expr(
.unwrap_or_else(|| String::from("...")) .unwrap_or_else(|| String::from("..."))
}; };
let label = expr.gen_source_code(&ctx.scope, &mut label_formatter); let label = expr.gen_source_code(
&ctx.scope,
&mut label_formatter,
ctx.config.prefer_no_std,
ctx.config.prefer_prelude,
);
let source_range = match ctx.original_token.parent() { let source_range = match ctx.original_token.parent() {
Some(node) => match node.ancestors().find_map(|n| ast::Path::cast(n)) { Some(node) => match node.ancestors().find_map(|n| ast::Path::cast(n)) {
@ -307,7 +312,15 @@ pub(crate) fn render_expr(
let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label.clone()); let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label.clone());
let snippet = format!("{}$0", expr.gen_source_code(&ctx.scope, &mut snippet_formatter)); let snippet = format!(
"{}$0",
expr.gen_source_code(
&ctx.scope,
&mut snippet_formatter,
ctx.config.prefer_no_std,
ctx.config.prefer_prelude
)
);
let edit = TextEdit::replace(source_range, snippet); let edit = TextEdit::replace(source_range, snippet);
item.snippet_edit(ctx.config.snippet_cap?, edit); item.snippet_edit(ctx.config.snippet_cap?, edit);
item.documentation(Documentation::new(String::from("Autogenerated expression by term search"))); item.documentation(Documentation::new(String::from("Autogenerated expression by term search")));

View file

@ -65,6 +65,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
enable_imports_on_the_fly: true, enable_imports_on_the_fly: true,
enable_self_on_the_fly: true, enable_self_on_the_fly: true,
enable_private_editable: false, enable_private_editable: false,
enable_term_search: true,
full_function_signatures: false, full_function_signatures: false,
callable: Some(CallableSnippets::FillArguments), callable: Some(CallableSnippets::FillArguments),
snippet_cap: SnippetCap::new(true), snippet_cap: SnippetCap::new(true),

View file

@ -1,13 +1,12 @@
use hir::{ use hir::{
db::ExpandDatabase, db::ExpandDatabase,
term_search::{term_search, TermSearchCtx}, term_search::{term_search, TermSearchCtx},
ClosureStyle, HirDisplay, Semantics, ClosureStyle, HirDisplay,
}; };
use ide_db::{ use ide_db::{
assists::{Assist, AssistId, AssistKind, GroupLabel}, assists::{Assist, AssistId, AssistKind, GroupLabel},
label::Label, label::Label,
source_change::SourceChange, source_change::SourceChange,
RootDatabase,
}; };
use itertools::Itertools; use itertools::Itertools;
use text_edit::TextEdit; use text_edit::TextEdit;
@ -29,7 +28,7 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di
"invalid `_` expression, expected type `{}`", "invalid `_` expression, expected type `{}`",
d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
), ),
fixes(&ctx.sema, d), fixes(ctx, d),
) )
}; };
@ -37,21 +36,30 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di
.with_fixes(fixes) .with_fixes(fixes)
} }
fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::TypedHole) -> Option<Vec<Assist>> { fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>> {
let db = sema.db; let db = ctx.sema.db;
let root = db.parse_or_expand(d.expr.file_id); let root = db.parse_or_expand(d.expr.file_id);
let (original_range, _) = let (original_range, _) =
d.expr.as_ref().map(|it| it.to_node(&root)).syntax().original_file_range_opt(db)?; d.expr.as_ref().map(|it| it.to_node(&root)).syntax().original_file_range_opt(db)?;
let scope = sema.scope(d.expr.value.to_node(&root).syntax())?; let scope = ctx.sema.scope(d.expr.value.to_node(&root).syntax())?;
let ctx = let term_search_ctx = TermSearchCtx {
TermSearchCtx { sema, scope: &scope, goal: d.expected.clone(), config: Default::default() }; sema: &ctx.sema,
let paths = term_search(&ctx); scope: &scope,
goal: d.expected.clone(),
config: Default::default(),
};
let paths = term_search(&term_search_ctx);
let mut assists = vec![]; let mut assists = vec![];
let mut formatter = |_: &hir::Type| String::from("_"); let mut formatter = |_: &hir::Type| String::from("_");
for path in paths.into_iter().unique() { for path in paths.into_iter().unique() {
let code = path.gen_source_code(&scope, &mut formatter); let code = path.gen_source_code(
&scope,
&mut formatter,
ctx.config.prefer_no_std,
ctx.config.prefer_prelude,
);
assists.push(Assist { assists.push(Assist {
id: AssistId("typed-hole", AssistKind::QuickFix), id: AssistId("typed-hole", AssistKind::QuickFix),

View file

@ -432,7 +432,7 @@ impl flags::AnalysisStats {
let mut formatter = |_: &hir::Type| todo.clone(); let mut formatter = |_: &hir::Type| todo.clone();
let mut syntax_hit_found = false; let mut syntax_hit_found = false;
for term in found_terms { for term in found_terms {
let generated = term.gen_source_code(&scope, &mut formatter); let generated = term.gen_source_code(&scope, &mut formatter, false, true);
syntax_hit_found |= trim(&original_text) == trim(&generated); syntax_hit_found |= trim(&original_text) == trim(&generated);
// Validate if type-checks // Validate if type-checks

View file

@ -286,6 +286,8 @@ config_data! {
"scope": "expr" "scope": "expr"
} }
}"#, }"#,
/// Whether to enable term search based snippets like `Some(foo.bar().baz())`.
completion_term_search_enable: bool = "true",
/// List of rust-analyzer diagnostics to disable. /// List of rust-analyzer diagnostics to disable.
diagnostics_disabled: FxHashSet<String> = "[]", diagnostics_disabled: FxHashSet<String> = "[]",
@ -1535,6 +1537,7 @@ impl Config {
&& completion_item_edit_resolve(&self.caps), && completion_item_edit_resolve(&self.caps),
enable_self_on_the_fly: self.data.completion_autoself_enable, enable_self_on_the_fly: self.data.completion_autoself_enable,
enable_private_editable: self.data.completion_privateEditable_enable, enable_private_editable: self.data.completion_privateEditable_enable,
enable_term_search: self.data.completion_term_search_enable,
full_function_signatures: self.data.completion_fullFunctionSignatures_enable, full_function_signatures: self.data.completion_fullFunctionSignatures_enable,
callable: match self.data.completion_callable_snippets { callable: match self.data.completion_callable_snippets {
CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),

View file

@ -132,6 +132,7 @@ fn integrated_completion_benchmark() {
enable_imports_on_the_fly: true, enable_imports_on_the_fly: true,
enable_self_on_the_fly: true, enable_self_on_the_fly: true,
enable_private_editable: true, enable_private_editable: true,
enable_term_search: true,
full_function_signatures: false, full_function_signatures: false,
callable: Some(CallableSnippets::FillArguments), callable: Some(CallableSnippets::FillArguments),
snippet_cap: SnippetCap::new(true), snippet_cap: SnippetCap::new(true),
@ -175,6 +176,7 @@ fn integrated_completion_benchmark() {
enable_imports_on_the_fly: true, enable_imports_on_the_fly: true,
enable_self_on_the_fly: true, enable_self_on_the_fly: true,
enable_private_editable: true, enable_private_editable: true,
enable_term_search: true,
full_function_signatures: false, full_function_signatures: false,
callable: Some(CallableSnippets::FillArguments), callable: Some(CallableSnippets::FillArguments),
snippet_cap: SnippetCap::new(true), snippet_cap: SnippetCap::new(true),
@ -216,6 +218,7 @@ fn integrated_completion_benchmark() {
enable_imports_on_the_fly: true, enable_imports_on_the_fly: true,
enable_self_on_the_fly: true, enable_self_on_the_fly: true,
enable_private_editable: true, enable_private_editable: true,
enable_term_search: true,
full_function_signatures: false, full_function_signatures: false,
callable: Some(CallableSnippets::FillArguments), callable: Some(CallableSnippets::FillArguments),
snippet_cap: SnippetCap::new(true), snippet_cap: SnippetCap::new(true),

View file

@ -343,6 +343,11 @@ Default:
---- ----
Custom completion snippets. Custom completion snippets.
--
[[rust-analyzer.completion.term.search.enable]]rust-analyzer.completion.term.search.enable (default: `true`)::
+
--
Whether to enable term search based snippets like `Some(foo.bar().baz())`.
-- --
[[rust-analyzer.diagnostics.disabled]]rust-analyzer.diagnostics.disabled (default: `[]`):: [[rust-analyzer.diagnostics.disabled]]rust-analyzer.diagnostics.disabled (default: `[]`)::
+ +

View file

@ -902,6 +902,11 @@
}, },
"type": "object" "type": "object"
}, },
"rust-analyzer.completion.term.search.enable": {
"markdownDescription": "Whether to enable term search based snippets like `Some(foo.bar().baz())`.",
"default": true,
"type": "boolean"
},
"rust-analyzer.diagnostics.disabled": { "rust-analyzer.diagnostics.disabled": {
"markdownDescription": "List of rust-analyzer diagnostics to disable.", "markdownDescription": "List of rust-analyzer diagnostics to disable.",
"default": [], "default": [],