diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index d91304bc28..93edcc4c26 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -1,10 +1,10 @@ mod completion_item; -mod reference_completion; mod complete_fn_param; mod complete_keyword; mod complete_snippet; mod complete_path; +mod complete_scope; use ra_editor::find_node_at_offset; use ra_text_edit::AtomTextEdit; @@ -33,26 +33,16 @@ pub(crate) fn completions( position: FilePosition, ) -> Cancelable> { let original_file = db.source_file(position.file_id); - // Insert a fake ident to get a valid parse tree - let file = { - let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); - original_file.reparse(&edit) - }; - let module = ctry!(source_binder::module_from_position(db, position)?); + let ctx = ctry!(SyntaxContext::new(db, &original_file, position)?); let mut acc = Completions::default(); - // First, let's try to complete a reference to some declaration. - if let Some(name_ref) = find_node_at_offset::(file.syntax(), position.offset) { - reference_completion::completions(&mut acc, db, &module, &file, name_ref)?; - } - - let ctx = ctry!(SyntaxContext::new(db, &original_file, position)?); complete_fn_param::complete_fn_param(&mut acc, &ctx); complete_keyword::complete_expr_keyword(&mut acc, &ctx); complete_snippet::complete_expr_snippet(&mut acc, &ctx); complete_snippet::complete_item_snippet(&mut acc, &ctx); complete_path::complete_path(&mut acc, &ctx)?; + complete_scope::complete_scope(&mut acc, &ctx)?; Ok(Some(acc)) } @@ -62,6 +52,7 @@ pub(crate) fn completions( #[derive(Debug)] pub(super) struct SyntaxContext<'a> { db: &'a db::RootDatabase, + offset: TextUnit, leaf: SyntaxNodeRef<'a>, module: Option, enclosing_fn: Option>, @@ -88,6 +79,7 @@ impl<'a> SyntaxContext<'a> { let mut ctx = SyntaxContext { db, leaf, + offset: position.offset, module, enclosing_fn: None, is_param: false, diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs index d04503e46a..8374ec3461 100644 --- a/crates/ra_analysis/src/completion/complete_path.rs +++ b/crates/ra_analysis/src/completion/complete_path.rs @@ -9,8 +9,8 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &SyntaxContext) -> Cance _ => return Ok(()), }; let def_id = match module.resolve_path(ctx.db, path)? { - None => return Ok(()), Some(it) => it, + None => return Ok(()), }; let target_module = match def_id.resolve(ctx.db)? { hir::Def::Module(it) => it, diff --git a/crates/ra_analysis/src/completion/complete_scope.rs b/crates/ra_analysis/src/completion/complete_scope.rs new file mode 100644 index 0000000000..4ffd630165 --- /dev/null +++ b/crates/ra_analysis/src/completion/complete_scope.rs @@ -0,0 +1,171 @@ +use rustc_hash::FxHashSet; +use ra_syntax::TextUnit; + +use crate::{ + completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext}, + Cancelable +}; + +pub(super) fn complete_scope(acc: &mut Completions, ctx: &SyntaxContext) -> Cancelable<()> { + if !ctx.is_trivial_path { + return Ok(()); + } + if let Some(fn_def) = ctx.enclosing_fn { + let scopes = hir::FnScopes::new(fn_def); + complete_fn(acc, &scopes, ctx.offset); + } + + if let Some(module) = &ctx.module { + let module_scope = module.scope(ctx.db)?; + module_scope + .entries() + .filter(|(_name, res)| { + // Don't expose this item + match res.import { + None => true, + Some(import) => { + let range = import.range(ctx.db, module.source().file_id()); + !range.is_subrange(&ctx.leaf.range()) + } + } + }) + .for_each(|(name, _res)| { + CompletionItem::new(name.to_string()) + .kind(Reference) + .add_to(acc) + }); + } + + Ok(()) +} + +fn complete_fn(acc: &mut Completions, scopes: &hir::FnScopes, offset: TextUnit) { + let mut shadowed = FxHashSet::default(); + scopes + .scope_chain_for_offset(offset) + .flat_map(|scope| scopes.entries(scope).iter()) + .filter(|entry| shadowed.insert(entry.name())) + .for_each(|entry| { + CompletionItem::new(entry.name().to_string()) + .kind(Reference) + .add_to(acc) + }); + if scopes.self_param.is_some() { + CompletionItem::new("self").kind(Reference).add_to(acc); + } +} + +#[cfg(test)] +mod tests { + use crate::completion::{CompletionKind, check_completion}; + + fn check_reference_completion(code: &str, expected_completions: &str) { + check_completion(code, expected_completions, CompletionKind::Reference); + } + + #[test] + fn test_completion_let_scope() { + check_reference_completion( + r" + fn quux(x: i32) { + let y = 92; + 1 + <|>; + let z = (); + } + ", + "y;x;quux", + ); + } + + #[test] + fn test_completion_if_let_scope() { + check_reference_completion( + r" + fn quux() { + if let Some(x) = foo() { + let y = 92; + }; + if let Some(a) = bar() { + let b = 62; + 1 + <|> + } + } + ", + "b;a;quux", + ); + } + + #[test] + fn test_completion_for_scope() { + check_reference_completion( + r" + fn quux() { + for x in &[1, 2, 3] { + <|> + } + } + ", + "x;quux", + ); + } + + #[test] + fn test_completion_mod_scope() { + check_reference_completion( + r" + struct Foo; + enum Baz {} + fn quux() { + <|> + } + ", + "quux;Foo;Baz", + ); + } + + #[test] + fn test_completion_mod_scope_nested() { + check_reference_completion( + r" + struct Foo; + mod m { + struct Bar; + fn quux() { <|> } + } + ", + "quux;Bar", + ); + } + + #[test] + fn test_complete_type() { + check_reference_completion( + r" + struct Foo; + fn x() -> <|> + ", + "Foo;x", + ) + } + + #[test] + fn test_complete_shadowing() { + check_reference_completion( + r" + fn foo() -> { + let bar = 92; + { + let bar = 62; + <|> + } + } + ", + "bar;foo", + ) + } + + #[test] + fn test_complete_self() { + check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self") + } +} diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs deleted file mode 100644 index 459ed8f6f7..0000000000 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ /dev/null @@ -1,307 +0,0 @@ -use rustc_hash::FxHashSet; -use ra_syntax::{ - SourceFileNode, AstNode, - ast, - SyntaxKind::*, -}; -use hir::{ - self, - FnScopes, Path -}; - -use crate::{ - db::RootDatabase, - completion::{CompletionItem, Completions, CompletionKind::*}, - Cancelable -}; - -pub(super) fn completions( - acc: &mut Completions, - db: &RootDatabase, - module: &hir::Module, - _file: &SourceFileNode, - name_ref: ast::NameRef, -) -> Cancelable<()> { - let kind = match classify_name_ref(name_ref) { - Some(it) => it, - None => return Ok(()), - }; - - match kind { - NameRefKind::LocalRef { enclosing_fn } => { - if let Some(fn_def) = enclosing_fn { - let scopes = FnScopes::new(fn_def); - complete_fn(name_ref, &scopes, acc); - } - - let module_scope = module.scope(db)?; - module_scope - .entries() - .filter(|(_name, res)| { - // Don't expose this item - match res.import { - None => true, - Some(import) => { - let range = import.range(db, module.source().file_id()); - !range.is_subrange(&name_ref.syntax().range()) - } - } - }) - .for_each(|(name, _res)| { - CompletionItem::new(name.to_string()) - .kind(Reference) - .add_to(acc) - }); - } - NameRefKind::Path(_) => (), - NameRefKind::BareIdentInMod => (), - } - Ok(()) -} - -enum NameRefKind<'a> { - /// NameRef is a part of single-segment path, for example, a refernece to a - /// local variable. - LocalRef { - enclosing_fn: Option>, - }, - /// NameRef is the last segment in some path - Path(Path), - /// NameRef is bare identifier at the module's root. - /// Used for keyword completion - BareIdentInMod, -} - -fn classify_name_ref(name_ref: ast::NameRef) -> Option { - let name_range = name_ref.syntax().range(); - let top_node = name_ref - .syntax() - .ancestors() - .take_while(|it| it.range() == name_range) - .last() - .unwrap(); - match top_node.parent().map(|it| it.kind()) { - Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod), - _ => (), - } - - let parent = name_ref.syntax().parent()?; - if let Some(segment) = ast::PathSegment::cast(parent) { - let path = segment.parent_path(); - if let Some(path) = Path::from_ast(path) { - if !path.is_ident() { - return Some(NameRefKind::Path(path)); - } - } - if path.qualifier().is_none() { - let enclosing_fn = name_ref - .syntax() - .ancestors() - .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::FnDef::cast); - return Some(NameRefKind::LocalRef { enclosing_fn }); - } - } - None -} - -fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) { - let mut shadowed = FxHashSet::default(); - scopes - .scope_chain(name_ref.syntax()) - .flat_map(|scope| scopes.entries(scope).iter()) - .filter(|entry| shadowed.insert(entry.name())) - .for_each(|entry| { - CompletionItem::new(entry.name().to_string()) - .kind(Reference) - .add_to(acc) - }); - if scopes.self_param.is_some() { - CompletionItem::new("self").kind(Reference).add_to(acc); - } -} - -#[cfg(test)] -mod tests { - use crate::completion::{CompletionKind, check_completion}; - - fn check_reference_completion(code: &str, expected_completions: &str) { - check_completion(code, expected_completions, CompletionKind::Reference); - } - - #[test] - fn test_completion_let_scope() { - check_reference_completion( - r" - fn quux(x: i32) { - let y = 92; - 1 + <|>; - let z = (); - } - ", - "y;x;quux", - ); - } - - #[test] - fn test_completion_if_let_scope() { - check_reference_completion( - r" - fn quux() { - if let Some(x) = foo() { - let y = 92; - }; - if let Some(a) = bar() { - let b = 62; - 1 + <|> - } - } - ", - "b;a;quux", - ); - } - - #[test] - fn test_completion_for_scope() { - check_reference_completion( - r" - fn quux() { - for x in &[1, 2, 3] { - <|> - } - } - ", - "x;quux", - ); - } - - #[test] - fn test_completion_mod_scope() { - check_reference_completion( - r" - struct Foo; - enum Baz {} - fn quux() { - <|> - } - ", - "quux;Foo;Baz", - ); - } - - #[test] - fn test_completion_mod_scope_no_self_use() { - check_reference_completion( - r" - use foo<|>; - ", - "", - ); - } - - #[test] - fn test_completion_self_path() { - check_reference_completion( - r" - use self::m::<|>; - - mod m { - struct Bar; - } - ", - "Bar", - ); - } - - #[test] - fn test_completion_mod_scope_nested() { - check_reference_completion( - r" - struct Foo; - mod m { - struct Bar; - fn quux() { <|> } - } - ", - "quux;Bar", - ); - } - - #[test] - fn test_complete_type() { - check_reference_completion( - r" - struct Foo; - fn x() -> <|> - ", - "Foo;x", - ) - } - - #[test] - fn test_complete_shadowing() { - check_reference_completion( - r" - fn foo() -> { - let bar = 92; - { - let bar = 62; - <|> - } - } - ", - "bar;foo", - ) - } - - #[test] - fn test_complete_self() { - check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self") - } - - #[test] - fn test_complete_crate_path() { - check_reference_completion( - " - //- /lib.rs - mod foo; - struct Spam; - //- /foo.rs - use crate::Sp<|> - ", - "Spam;foo", - ); - } - - #[test] - fn test_complete_crate_path_with_braces() { - check_reference_completion( - " - //- /lib.rs - mod foo; - struct Spam; - //- /foo.rs - use crate::{Sp<|>}; - ", - "Spam;foo", - ); - } - - #[test] - fn test_complete_crate_path_in_nested_tree() { - check_reference_completion( - " - //- /lib.rs - mod foo; - pub mod bar { - pub mod baz { - pub struct Spam; - } - } - //- /foo.rs - use crate::{bar::{baz::Sp<|>}}; - ", - "Spam", - ); - } -} diff --git a/crates/ra_hir/src/function/scope.rs b/crates/ra_hir/src/function/scope.rs index 8634532916..9f1aa1ef2d 100644 --- a/crates/ra_hir/src/function/scope.rs +++ b/crates/ra_hir/src/function/scope.rs @@ -1,7 +1,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use ra_syntax::{ - AstNode, SmolStr, SyntaxNodeRef, TextRange, + AstNode, SmolStr, SyntaxNodeRef, TextUnit, TextRange, algo::generate, ast::{self, ArgListOwner, LoopBodyOwner, NameOwner}, }; @@ -57,6 +57,48 @@ impl FnScopes { self.scopes[scope].parent }) } + pub fn scope_chain_for_offset<'a>( + &'a self, + offset: TextUnit, + ) -> impl Iterator + 'a { + let scope = self + .scope_for + .iter() + // find containin scope + .min_by_key(|(ptr, _scope)| { + ( + !(ptr.range().start() <= offset && offset <= ptr.range().end()), + ptr.range().len(), + ) + }) + .map(|(ptr, scope)| self.adjust(*ptr, *scope, offset)); + + generate(scope, move |&scope| self.scopes[scope].parent) + } + // XXX: during completion, cursor might be outside of any particular + // expression. Try to figure out the correct scope... + fn adjust(&self, ptr: LocalSyntaxPtr, original_scope: ScopeId, offset: TextUnit) -> ScopeId { + let r = ptr.range(); + let child_scopes = self + .scope_for + .iter() + .map(|(ptr, scope)| (ptr.range(), scope)) + .filter(|(range, _)| range.start() <= offset && range.is_subrange(&r) && *range != r); + + child_scopes + .max_by(|(r1, _), (r2, _)| { + if r2.is_subrange(&r1) { + std::cmp::Ordering::Greater + } else if r1.is_subrange(&r2) { + std::cmp::Ordering::Less + } else { + r1.start().cmp(&r2.start()) + } + }) + .map(|(ptr, scope)| *scope) + .unwrap_or(original_scope) + } + pub fn resolve_local_name<'a>(&'a self, name_ref: ast::NameRef) -> Option<&'a ScopeEntry> { let mut shadowed = FxHashSet::default(); let ret = self @@ -144,6 +186,8 @@ impl ScopeEntry { } fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) { + // A hack for completion :( + scopes.set_scope(block.syntax(), scope); for stmt in block.statements() { match stmt { ast::Stmt::LetStmt(stmt) => { @@ -165,6 +209,7 @@ fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: Sco } } if let Some(expr) = block.expr() { + eprintln!("{:?}", expr); scopes.set_scope(expr.syntax(), scope); compute_expr_scopes(expr, scopes, scope); }