1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
//! `completions` crate provides utilities for generating completions of user input.
mod completions;
mod config;
mod context;
mod item;
mod render;
mod snippet;
#[cfg(test)]
mod tests;
use ide_db::text_edit::TextEdit;
use ide_db::{
helpers::mod_path_to_ast,
imports::{
import_assets::NameToImport,
insert_use::{self, ImportScope},
},
items_locator,
syntax_helpers::tree_diff::diff,
FilePosition, RootDatabase,
};
use crate::{
completions::Completions,
context::{
CompletionAnalysis, CompletionContext, NameRefContext, NameRefKind, PathCompletionCtx,
PathKind,
},
};
pub use crate::{
config::{CallableSnippets, CompletionConfig},
item::{
CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
},
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 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,
}
}
}
//FIXME: split the following feature into fine-grained features.
// Feature: Magic Completions
//
// In addition to usual reference completion, rust-analyzer provides some ✨magic✨
// completions as well:
//
// Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
// is placed at the appropriate position. Even though `if` is easy to type, you
// still want to complete it, to get ` { }` for free! `return` is inserted with a
// space or `;` depending on the return type of the function.
//
// When completing a function call, `()` are automatically inserted. If a function
// takes arguments, the cursor is positioned inside the parenthesis.
//
// There are postfix completions, which can be triggered by typing something like
// `foo().if`. The word after `.` determines postfix completion. Possible variants are:
//
// - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
// - `expr.match` -> `match expr {}`
// - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
// - `expr.ref` -> `&expr`
// - `expr.refm` -> `&mut expr`
// - `expr.let` -> `let $0 = expr;`
// - `expr.lete` -> `let $1 = expr else { $0 };`
// - `expr.letm` -> `let mut $0 = expr;`
// - `expr.not` -> `!expr`
// - `expr.dbg` -> `dbg!(expr)`
// - `expr.dbgr` -> `dbg!(&expr)`
// - `expr.call` -> `(expr)`
//
// There also snippet completions:
//
// .Expressions
// - `pd` -> `eprintln!(" = {:?}", );`
// - `ppd` -> `eprintln!(" = {:#?}", );`
//
// .Items
// - `tfn` -> `#[test] fn feature(){}`
// - `tmod` ->
// ```rust
// #[cfg(test)]
// mod tests {
// use super::*;
//
// #[test]
// fn test_name() {}
// }
// ```
//
// And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities.
// Those are the additional completion options with automatic `use` import and options from all project importable items,
// fuzzy matched against the completion input.
//
// image::https://user-images.githubusercontent.com/48062697/113020667-b72ab880-917a-11eb-8778-716cf26a0eb3.gif[]
/// Main entry point for completion. We run completion as a two-phase process.
///
/// First, we look at the position and collect a so-called `CompletionContext`.
/// This is a somewhat messy process, because, during completion, syntax tree is
/// incomplete and can look really weird.
///
/// Once the context is collected, we run a series of completion routines which
/// look at the context and produce completion items. One subtlety about this
/// phase is that completion engine should not filter by the substring which is
/// already present, it should give all possible variants for the identifier at
/// the caret. In other words, for
///
/// ```no_run
/// fn f() {
/// let foo = 92;
/// let _ = bar$0
/// }
/// ```
///
/// `foo` *should* be present among the completion variants. Filtering by
/// identifier prefix/fuzzy match should be done higher in the stack, together
/// with ordering of completions (currently this is done by the client).
///
/// # Speculative Completion Problem
///
/// There's a curious unsolved problem in the current implementation. Often, you
/// want to compute completions on a *slightly different* text document.
///
/// In the simplest case, when the code looks like `let x = `, you want to
/// insert a fake identifier to get a better syntax tree: `let x = complete_me`.
///
/// We do this in `CompletionContext`, and it works OK-enough for *syntax*
/// analysis. However, we might want to, eg, ask for the type of `complete_me`
/// variable, and that's where our current infrastructure breaks down. salsa
/// doesn't allow such "phantom" inputs.
///
/// Another case where this would be instrumental is macro expansion. We want to
/// insert a fake ident and re-expand code. There's `expand_speculative` as a
/// workaround for this.
///
/// A different use-case is completion of injection (examples and links in doc
/// comments). When computing completion for a path in a doc-comment, you want
/// to inject a fake path expression into the item being documented and complete
/// that.
///
/// IntelliJ has CodeFragment/Context infrastructure for that. You can create a
/// temporary PSI node, and say that the context ("parent") of this node is some
/// existing node. Asking for, eg, type of this `CodeFragment` node works
/// correctly, as the underlying infrastructure makes use of contexts to do
/// analysis.
pub fn completions(
db: &RootDatabase,
config: &CompletionConfig,
position: FilePosition,
trigger_character: Option<char>,
) -> Option<Vec<CompletionItem>> {
let (ctx, analysis) = &CompletionContext::new(db, position, config)?;
let mut completions = Completions::default();
// prevent `(` from triggering unwanted completion noise
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());
}
// when the user types a bare `_` (that is it does not belong to an identifier)
// the user might just wanted to type a `_` for type inference or pattern discarding
// so try to suppress completions in those cases
if trigger_character == Some('_') && ctx.original_token.kind() == syntax::SyntaxKind::UNDERSCORE
{
if let CompletionAnalysis::NameRef(NameRefContext {
kind:
NameRefKind::Path(
path_ctx @ PathCompletionCtx {
kind: PathKind::Type { .. } | PathKind::Pat { .. },
..
},
),
..
}) = analysis
{
if 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);
}
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::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (),
}
}
Some(completions.into())
}
/// Resolves additional completion data at the position given.
/// This is used for import insertion done via completions like flyimport and custom user snippets.
pub fn resolve_completion_edits(
db: &RootDatabase,
config: &CompletionConfig,
FilePosition { file_id, offset }: FilePosition,
imports: impl IntoIterator<Item = (String, String)>,
) -> Option<Vec<TextEdit>> {
let _p = tracing::info_span!("resolve_completion_edits").entered();
let sema = hir::Semantics::new(db);
let original_file = sema.parse(sema.attach_first_edition(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();
let current_edition = current_crate.edition(db);
let new_ast = scope.clone_for_update();
let mut import_insert = TextEdit::builder();
let cfg = config.import_path_config();
imports.into_iter().for_each(|(full_import_path, imported_name)| {
let items_with_name = items_locator::items_with_name(
&sema,
current_crate,
NameToImport::exact_case_sensitive(imported_name),
items_locator::AssocSearchMode::Include,
);
let import = items_with_name
.filter_map(|candidate| {
current_module.find_use_path(db, candidate, config.insert_use.prefix_kind, cfg)
})
.find(|mod_path| mod_path.display(db, current_edition).to_string() == full_import_path);
if let Some(import_path) = import {
insert_use::insert_use(
&new_ast,
mod_path_to_ast(&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()])
}