Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide_completion/src/context.rs')
| -rw-r--r-- | crates/ide_completion/src/context.rs | 90 |
1 files changed, 56 insertions, 34 deletions
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 9eec4fd0c9..59c16c08d6 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -27,6 +27,8 @@ use crate::{ CompletionConfig, }; +const COMPLETION_MARKER: &str = "intellijRulezz"; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum PatternRefutability { Refutable, @@ -68,7 +70,7 @@ pub(crate) struct PathCompletionContext { #[derive(Debug)] pub(super) struct PatternContext { pub(super) refutability: PatternRefutability, - pub(super) is_param: Option<ParamKind>, + pub(super) param_ctx: Option<(ast::ParamList, ast::Param, ParamKind)>, pub(super) has_type_ascription: bool, } @@ -80,10 +82,10 @@ pub(super) enum LifetimeContext { LabelDef, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ParamKind { - Function, - Closure, + Function(ast::Fn), + Closure(ast::ClosureExpr), } /// `CompletionContext` is created early during completion to figure out, where @@ -382,7 +384,7 @@ impl<'a> CompletionContext<'a> { // actual completion. let file_with_fake_ident = { let parse = db.parse(file_id); - let edit = Indel::insert(offset, "intellijRulezz".to_string()); + let edit = Indel::insert(offset, COMPLETION_MARKER.to_string()); parse.reparse(&edit).tree() }; let fake_ident_token = @@ -390,7 +392,7 @@ impl<'a> CompletionContext<'a> { let original_token = original_file.syntax().token_at_offset(offset).left_biased()?; let token = sema.descend_into_macros_single(original_token.clone()); - let scope = sema.scope_at_offset(&token, offset); + let scope = sema.scope_at_offset(&token.parent()?, offset); let krate = scope.krate(); let mut locals = vec![]; scope.process_all_names(&mut |name, scope| { @@ -723,7 +725,7 @@ impl<'a> CompletionContext<'a> { } } ast::NameLike::Name(name) => { - self.pattern_ctx = Self::classify_name(&self.sema, name); + self.pattern_ctx = Self::classify_name(&self.sema, original_file, name); } } } @@ -750,7 +752,11 @@ impl<'a> CompletionContext<'a> { }) } - fn classify_name(_sema: &Semantics<RootDatabase>, name: ast::Name) -> Option<PatternContext> { + fn classify_name( + _sema: &Semantics<RootDatabase>, + original_file: &SyntaxNode, + name: ast::Name, + ) -> Option<PatternContext> { let bind_pat = name.syntax().parent().and_then(ast::IdentPat::cast)?; let is_name_in_field_pat = bind_pat .syntax() @@ -763,7 +769,7 @@ impl<'a> CompletionContext<'a> { if !bind_pat.is_simple_ident() { return None; } - Some(pattern_context_for(bind_pat.into())) + Some(pattern_context_for(original_file, bind_pat.into())) } fn classify_name_ref( @@ -799,15 +805,15 @@ impl<'a> CompletionContext<'a> { }, ast::TupleStructPat(it) => { path_ctx.has_call_parens = true; - pat_ctx = Some(pattern_context_for(it.into())); + pat_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, ast::RecordPat(it) => { - pat_ctx = Some(pattern_context_for(it.into())); + pat_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, ast::PathPat(it) => { - pat_ctx = Some(pattern_context_for(it.into())); + pat_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, ast::MacroCall(it) => it.excl_token().and(Some(PathKind::Mac)), @@ -824,12 +830,7 @@ impl<'a> CompletionContext<'a> { path_ctx.use_tree_parent = use_tree_parent; path_ctx.qualifier = path .segment() - .and_then(|it| { - find_node_with_range::<ast::PathSegment>( - original_file, - it.syntax().text_range(), - ) - }) + .and_then(|it| find_node_in_file(original_file, &it)) .map(|it| it.parent_path()); return Some((path_ctx, pat_ctx)); } @@ -864,7 +865,7 @@ impl<'a> CompletionContext<'a> { } } -fn pattern_context_for(pat: ast::Pat) -> PatternContext { +fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternContext { let mut is_param = None; let (refutability, has_type_ascription) = pat @@ -877,18 +878,21 @@ fn pattern_context_for(pat: ast::Pat) -> PatternContext { match node { ast::LetStmt(let_) => return (PatternRefutability::Irrefutable, let_.ty().is_some()), ast::Param(param) => { - let is_closure_param = param - .syntax() - .ancestors() - .nth(2) - .and_then(ast::ClosureExpr::cast) - .is_some(); - is_param = Some(if is_closure_param { - ParamKind::Closure - } else { - ParamKind::Function - }); - return (PatternRefutability::Irrefutable, param.ty().is_some()) + let has_type_ascription = param.ty().is_some(); + is_param = (|| { + let fake_param_list = param.syntax().parent().and_then(ast::ParamList::cast)?; + let param_list = find_node_in_file_compensated(original_file, &fake_param_list)?; + let param_list_owner = param_list.syntax().parent()?; + let kind = match_ast! { + match param_list_owner { + ast::ClosureExpr(closure) => ParamKind::Closure(closure), + ast::Fn(fn_) => ParamKind::Function(fn_), + _ => return None, + } + }; + Some((param_list, param, kind)) + })(); + return (PatternRefutability::Irrefutable, has_type_ascription) }, ast::MatchArm(_) => PatternRefutability::Refutable, ast::Condition(_) => PatternRefutability::Refutable, @@ -898,11 +902,29 @@ fn pattern_context_for(pat: ast::Pat) -> PatternContext { }; (refutability, false) }); - PatternContext { refutability, is_param, has_type_ascription } + PatternContext { refutability, param_ctx: is_param, has_type_ascription } +} + +fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> { + let syntax_range = syntax.text_range(); + let range = node.syntax().text_range(); + let intersection = range.intersect(syntax_range)?; + syntax.covering_element(intersection).ancestors().find_map(N::cast) } -fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> { - syntax.covering_element(range).ancestors().find_map(N::cast) +/// Compensates for the offset introduced by the fake ident +/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead. +fn find_node_in_file_compensated<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> { + let syntax_range = syntax.text_range(); + let range = node.syntax().text_range(); + let end = range.end().checked_sub(TextSize::try_from(COMPLETION_MARKER.len()).ok()?)?; + if end < range.start() { + return None; + } + let range = TextRange::new(range.start(), end); + // our inserted ident could cause `range` to be go outside of the original syntax, so cap it + let intersection = range.intersect(syntax_range)?; + syntax.covering_element(intersection).ancestors().find_map(N::cast) } fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> { |