From 6bca91af532d79abbced5b151cb4188ff8625c04 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 8 Jan 2019 22:30:56 +0300 Subject: [PATCH] rename ra_analysis -> ra_ide_api --- crates/ra_analysis/Cargo.toml | 23 - crates/ra_analysis/src/call_info.rs | 451 ---------------- crates/ra_analysis/src/completion.rs | 77 --- .../src/completion/complete_dot.rs | 121 ----- .../src/completion/complete_fn_param.rs | 102 ---- .../src/completion/complete_keyword.rs | 339 ------------ .../src/completion/complete_path.rs | 128 ----- .../src/completion/complete_scope.rs | 192 ------- .../src/completion/complete_snippet.rs | 73 --- .../src/completion/completion_context.rs | 205 ------- .../src/completion/completion_item.rs | 244 --------- crates/ra_analysis/src/db.rs | 128 ----- crates/ra_analysis/src/extend_selection.rs | 56 -- crates/ra_analysis/src/goto_defenition.rs | 139 ----- crates/ra_analysis/src/hover.rs | 257 --------- crates/ra_analysis/src/imp.rs | 309 ----------- crates/ra_analysis/src/lib.rs | 509 ------------------ crates/ra_analysis/src/mock_analysis.rs | 135 ----- crates/ra_analysis/src/runnables.rs | 89 --- crates/ra_analysis/src/symbol_index.rs | 222 -------- crates/ra_analysis/src/syntax_highlighting.rs | 92 ---- crates/ra_analysis/tests/test/main.rs | 249 --------- crates/ra_analysis/tests/test/runnables.rs | 109 ---- 23 files changed, 4249 deletions(-) delete mode 100644 crates/ra_analysis/Cargo.toml delete mode 100644 crates/ra_analysis/src/call_info.rs delete mode 100644 crates/ra_analysis/src/completion.rs delete mode 100644 crates/ra_analysis/src/completion/complete_dot.rs delete mode 100644 crates/ra_analysis/src/completion/complete_fn_param.rs delete mode 100644 crates/ra_analysis/src/completion/complete_keyword.rs delete mode 100644 crates/ra_analysis/src/completion/complete_path.rs delete mode 100644 crates/ra_analysis/src/completion/complete_scope.rs delete mode 100644 crates/ra_analysis/src/completion/complete_snippet.rs delete mode 100644 crates/ra_analysis/src/completion/completion_context.rs delete mode 100644 crates/ra_analysis/src/completion/completion_item.rs delete mode 100644 crates/ra_analysis/src/db.rs delete mode 100644 crates/ra_analysis/src/extend_selection.rs delete mode 100644 crates/ra_analysis/src/goto_defenition.rs delete mode 100644 crates/ra_analysis/src/hover.rs delete mode 100644 crates/ra_analysis/src/imp.rs delete mode 100644 crates/ra_analysis/src/lib.rs delete mode 100644 crates/ra_analysis/src/mock_analysis.rs delete mode 100644 crates/ra_analysis/src/runnables.rs delete mode 100644 crates/ra_analysis/src/symbol_index.rs delete mode 100644 crates/ra_analysis/src/syntax_highlighting.rs delete mode 100644 crates/ra_analysis/tests/test/main.rs delete mode 100644 crates/ra_analysis/tests/test/runnables.rs diff --git a/crates/ra_analysis/Cargo.toml b/crates/ra_analysis/Cargo.toml deleted file mode 100644 index 3c08142795..0000000000 --- a/crates/ra_analysis/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -edition = "2018" -name = "ra_analysis" -version = "0.1.0" -authors = ["Aleksey Kladov "] - -[dependencies] -itertools = "0.8.0" -log = "0.4.5" -relative-path = "0.4.0" -rayon = "1.0.2" -fst = "0.3.1" -salsa = "0.9.1" -rustc-hash = "1.0" -parking_lot = "0.7.0" -unicase = "2.2.0" - -ra_syntax = { path = "../ra_syntax" } -ra_ide_api_light = { path = "../ra_ide_api_light" } -ra_text_edit = { path = "../ra_text_edit" } -ra_db = { path = "../ra_db" } -hir = { path = "../ra_hir", package = "ra_hir" } -test_utils = { path = "../test_utils" } diff --git a/crates/ra_analysis/src/call_info.rs b/crates/ra_analysis/src/call_info.rs deleted file mode 100644 index 27b760780c..0000000000 --- a/crates/ra_analysis/src/call_info.rs +++ /dev/null @@ -1,451 +0,0 @@ -use std::cmp::{max, min}; - -use ra_db::{SyntaxDatabase, Cancelable}; -use ra_syntax::{ - AstNode, SyntaxNode, TextUnit, TextRange, - SyntaxKind::FN_DEF, - ast::{self, ArgListOwner, DocCommentsOwner}, - algo::find_node_at_offset, -}; - -use crate::{FilePosition, CallInfo, db::RootDatabase}; - -/// Computes parameter information for the given call expression. -pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Cancelable> { - let file = db.source_file(position.file_id); - let syntax = file.syntax(); - - // Find the calling expression and it's NameRef - let calling_node = ctry!(FnCallNode::with_node(syntax, position.offset)); - let name_ref = ctry!(calling_node.name_ref()); - - // Resolve the function's NameRef (NOTE: this isn't entirely accurate). - let file_symbols = db.index_resolve(name_ref)?; - let symbol = ctry!(file_symbols.into_iter().find(|it| it.ptr.kind() == FN_DEF)); - let fn_file = db.source_file(symbol.file_id); - let fn_def = symbol.ptr.resolve(&fn_file); - let fn_def = ast::FnDef::cast(&fn_def).unwrap(); - let mut call_info = ctry!(CallInfo::new(fn_def)); - // If we have a calling expression let's find which argument we are on - let num_params = call_info.parameters.len(); - let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some(); - - if num_params == 1 { - if !has_self { - call_info.active_parameter = Some(0); - } - } else if num_params > 1 { - // Count how many parameters into the call we are. - // TODO: This is best effort for now and should be fixed at some point. - // It may be better to see where we are in the arg_list and then check - // where offset is in that list (or beyond). - // Revisit this after we get documentation comments in. - if let Some(ref arg_list) = calling_node.arg_list() { - let start = arg_list.syntax().range().start(); - - let range_search = TextRange::from_to(start, position.offset); - let mut commas: usize = arg_list - .syntax() - .text() - .slice(range_search) - .to_string() - .matches(',') - .count(); - - // If we have a method call eat the first param since it's just self. - if has_self { - commas += 1; - } - - call_info.active_parameter = Some(commas); - } - } - - Ok(Some(call_info)) -} - -enum FnCallNode<'a> { - CallExpr(&'a ast::CallExpr), - MethodCallExpr(&'a ast::MethodCallExpr), -} - -impl<'a> FnCallNode<'a> { - pub fn with_node(syntax: &'a SyntaxNode, offset: TextUnit) -> Option> { - if let Some(expr) = find_node_at_offset::(syntax, offset) { - return Some(FnCallNode::CallExpr(expr)); - } - if let Some(expr) = find_node_at_offset::(syntax, offset) { - return Some(FnCallNode::MethodCallExpr(expr)); - } - None - } - - pub fn name_ref(&self) -> Option<&'a ast::NameRef> { - match *self { - FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()?.kind() { - ast::ExprKind::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, - _ => return None, - }), - - FnCallNode::MethodCallExpr(call_expr) => call_expr - .syntax() - .children() - .filter_map(ast::NameRef::cast) - .nth(0), - } - } - - pub fn arg_list(&self) -> Option<&'a ast::ArgList> { - match *self { - FnCallNode::CallExpr(expr) => expr.arg_list(), - FnCallNode::MethodCallExpr(expr) => expr.arg_list(), - } - } -} - -impl CallInfo { - fn new(node: &ast::FnDef) -> Option { - let mut doc = None; - - // Strip the body out for the label. - let mut label: String = if let Some(body) = node.body() { - let body_range = body.syntax().range(); - let label: String = node - .syntax() - .children() - .filter(|child| !child.range().is_subrange(&body_range)) - .map(|node| node.text().to_string()) - .collect(); - label - } else { - node.syntax().text().to_string() - }; - - if let Some((comment_range, docs)) = extract_doc_comments(node) { - let comment_range = comment_range - .checked_sub(node.syntax().range().start()) - .unwrap(); - let start = comment_range.start().to_usize(); - let end = comment_range.end().to_usize(); - - // Remove the comment from the label - label.replace_range(start..end, ""); - - // Massage markdown - let mut processed_lines = Vec::new(); - let mut in_code_block = false; - for line in docs.lines() { - if line.starts_with("```") { - in_code_block = !in_code_block; - } - - let line = if in_code_block && line.starts_with("```") && !line.contains("rust") { - "```rust".into() - } else { - line.to_string() - }; - - processed_lines.push(line); - } - - if !processed_lines.is_empty() { - doc = Some(processed_lines.join("\n")); - } - } - - Some(CallInfo { - parameters: param_list(node), - label: label.trim().to_owned(), - doc, - active_parameter: None, - }) - } -} - -fn extract_doc_comments(node: &ast::FnDef) -> Option<(TextRange, String)> { - if node.doc_comments().count() == 0 { - return None; - } - - let comment_text = node.doc_comment_text(); - - let (begin, end) = node - .doc_comments() - .map(|comment| comment.syntax().range()) - .map(|range| (range.start().to_usize(), range.end().to_usize())) - .fold((std::usize::MAX, std::usize::MIN), |acc, range| { - (min(acc.0, range.0), max(acc.1, range.1)) - }); - - let range = TextRange::from_to(TextUnit::from_usize(begin), TextUnit::from_usize(end)); - - Some((range, comment_text)) -} - -fn param_list(node: &ast::FnDef) -> Vec { - let mut res = vec![]; - if let Some(param_list) = node.param_list() { - if let Some(self_param) = param_list.self_param() { - res.push(self_param.syntax().text().to_string()) - } - - // Maybe use param.pat here? See if we can just extract the name? - //res.extend(param_list.params().map(|p| p.syntax().text().to_string())); - res.extend( - param_list - .params() - .filter_map(|p| p.pat()) - .map(|pat| pat.syntax().text().to_string()), - ); - } - res -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::mock_analysis::single_file_with_position; - - fn call_info(text: &str) -> CallInfo { - let (analysis, position) = single_file_with_position(text); - analysis.call_info(position).unwrap().unwrap() - } - - #[test] - fn test_fn_signature_two_args_first() { - let info = call_info( - r#"fn foo(x: u32, y: u32) -> u32 {x + y} -fn bar() { foo(<|>3, ); }"#, - ); - - assert_eq!(info.parameters, vec!("x".to_string(), "y".to_string())); - assert_eq!(info.active_parameter, Some(0)); - } - - #[test] - fn test_fn_signature_two_args_second() { - let info = call_info( - r#"fn foo(x: u32, y: u32) -> u32 {x + y} -fn bar() { foo(3, <|>); }"#, - ); - - assert_eq!(info.parameters, vec!("x".to_string(), "y".to_string())); - assert_eq!(info.active_parameter, Some(1)); - } - - #[test] - fn test_fn_signature_for_impl() { - let info = call_info( - r#"struct F; impl F { pub fn new() { F{}} } -fn bar() {let _ : F = F::new(<|>);}"#, - ); - - assert_eq!(info.parameters, Vec::::new()); - assert_eq!(info.active_parameter, None); - } - - #[test] - fn test_fn_signature_for_method_self() { - let info = call_info( - r#"struct F; -impl F { - pub fn new() -> F{ - F{} - } - - pub fn do_it(&self) {} -} - -fn bar() { - let f : F = F::new(); - f.do_it(<|>); -}"#, - ); - - assert_eq!(info.parameters, vec!["&self".to_string()]); - assert_eq!(info.active_parameter, None); - } - - #[test] - fn test_fn_signature_for_method_with_arg() { - let info = call_info( - r#"struct F; -impl F { - pub fn new() -> F{ - F{} - } - - pub fn do_it(&self, x: i32) {} -} - -fn bar() { - let f : F = F::new(); - f.do_it(<|>); -}"#, - ); - - assert_eq!(info.parameters, vec!["&self".to_string(), "x".to_string()]); - assert_eq!(info.active_parameter, Some(1)); - } - - #[test] - fn test_fn_signature_with_docs_simple() { - let info = call_info( - r#" -/// test -// non-doc-comment -fn foo(j: u32) -> u32 { - j -} - -fn bar() { - let _ = foo(<|>); -} -"#, - ); - - assert_eq!(info.parameters, vec!["j".to_string()]); - assert_eq!(info.active_parameter, Some(0)); - assert_eq!(info.label, "fn foo(j: u32) -> u32".to_string()); - assert_eq!(info.doc, Some("test".into())); - } - - #[test] - fn test_fn_signature_with_docs() { - let info = call_info( - r#" -/// Adds one to the number given. -/// -/// # Examples -/// -/// ``` -/// let five = 5; -/// -/// assert_eq!(6, my_crate::add_one(5)); -/// ``` -pub fn add_one(x: i32) -> i32 { - x + 1 -} - -pub fn do() { - add_one(<|> -}"#, - ); - - assert_eq!(info.parameters, vec!["x".to_string()]); - assert_eq!(info.active_parameter, Some(0)); - assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string()); - assert_eq!( - info.doc, - Some( - r#"Adds one to the number given. - -# Examples - -```rust -let five = 5; - -assert_eq!(6, my_crate::add_one(5)); -```"# - .into() - ) - ); - } - - #[test] - fn test_fn_signature_with_docs_impl() { - let info = call_info( - r#" -struct addr; -impl addr { - /// Adds one to the number given. - /// - /// # Examples - /// - /// ``` - /// let five = 5; - /// - /// assert_eq!(6, my_crate::add_one(5)); - /// ``` - pub fn add_one(x: i32) -> i32 { - x + 1 - } -} - -pub fn do_it() { - addr {}; - addr::add_one(<|>); -}"#, - ); - - assert_eq!(info.parameters, vec!["x".to_string()]); - assert_eq!(info.active_parameter, Some(0)); - assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string()); - assert_eq!( - info.doc, - Some( - r#"Adds one to the number given. - -# Examples - -```rust -let five = 5; - -assert_eq!(6, my_crate::add_one(5)); -```"# - .into() - ) - ); - } - - #[test] - fn test_fn_signature_with_docs_from_actix() { - let info = call_info( - r#" -pub trait WriteHandler -where - Self: Actor, - Self::Context: ActorContext, -{ - /// Method is called when writer emits error. - /// - /// If this method returns `ErrorAction::Continue` writer processing - /// continues otherwise stream processing stops. - fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running { - Running::Stop - } - - /// Method is called when writer finishes. - /// - /// By default this method stops actor's `Context`. - fn finished(&mut self, ctx: &mut Self::Context) { - ctx.stop() - } -} - -pub fn foo() { - WriteHandler r; - r.finished(<|>); -} - -"#, - ); - - assert_eq!( - info.parameters, - vec!["&mut self".to_string(), "ctx".to_string()] - ); - assert_eq!(info.active_parameter, Some(1)); - assert_eq!( - info.doc, - Some( - r#"Method is called when writer finishes. - -By default this method stops actor's `Context`."# - .into() - ) - ); - } - -} diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs deleted file mode 100644 index ce777a771e..0000000000 --- a/crates/ra_analysis/src/completion.rs +++ /dev/null @@ -1,77 +0,0 @@ -mod completion_item; -mod completion_context; - -mod complete_dot; -mod complete_fn_param; -mod complete_keyword; -mod complete_snippet; -mod complete_path; -mod complete_scope; - -use ra_db::SyntaxDatabase; - -use crate::{ - db, - Cancelable, FilePosition, - completion::{ - completion_item::{Completions, CompletionKind}, - completion_context::CompletionContext, - }, -}; - -pub use crate::completion::completion_item::{CompletionItem, InsertText, CompletionItemKind}; - -/// 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 subtelty 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<|> -/// } -/// ``` -/// -/// `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). -pub(crate) fn completions( - db: &db::RootDatabase, - position: FilePosition, -) -> Cancelable> { - let original_file = db.source_file(position.file_id); - let ctx = ctry!(CompletionContext::new(db, &original_file, position)?); - - let mut acc = Completions::default(); - - complete_fn_param::complete_fn_param(&mut acc, &ctx); - complete_keyword::complete_expr_keyword(&mut acc, &ctx); - complete_keyword::complete_use_tree_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)?; - complete_dot::complete_dot(&mut acc, &ctx)?; - - Ok(Some(acc)) -} - -#[cfg(test)] -fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) { - use crate::mock_analysis::{single_file_with_position, analysis_and_position}; - let (analysis, position) = if code.contains("//-") { - analysis_and_position(code) - } else { - single_file_with_position(code) - }; - let completions = completions(&analysis.db, position).unwrap().unwrap(); - completions.assert_match(expected_completions, kind); -} diff --git a/crates/ra_analysis/src/completion/complete_dot.rs b/crates/ra_analysis/src/completion/complete_dot.rs deleted file mode 100644 index 5d4e60dc52..0000000000 --- a/crates/ra_analysis/src/completion/complete_dot.rs +++ /dev/null @@ -1,121 +0,0 @@ -use hir::{Ty, Def}; - -use crate::Cancelable; -use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem, CompletionItemKind}; - -/// Complete dot accesses, i.e. fields or methods (currently only fields). -pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> { - let (function, receiver) = match (&ctx.function, ctx.dot_receiver) { - (Some(function), Some(receiver)) => (function, receiver), - _ => return Ok(()), - }; - let infer_result = function.infer(ctx.db)?; - let syntax_mapping = function.body_syntax_mapping(ctx.db)?; - let expr = match syntax_mapping.node_expr(receiver) { - Some(expr) => expr, - None => return Ok(()), - }; - let receiver_ty = infer_result[expr].clone(); - if !ctx.is_method_call { - complete_fields(acc, ctx, receiver_ty)?; - } - Ok(()) -} - -fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) -> Cancelable<()> { - for receiver in receiver.autoderef(ctx.db) { - match receiver { - Ty::Adt { def_id, .. } => { - match def_id.resolve(ctx.db)? { - Def::Struct(s) => { - let variant_data = s.variant_data(ctx.db)?; - for field in variant_data.fields() { - CompletionItem::new( - CompletionKind::Reference, - field.name().to_string(), - ) - .kind(CompletionItemKind::Field) - .add_to(acc); - } - } - // TODO unions - _ => {} - } - } - Ty::Tuple(fields) => { - for (i, _ty) in fields.iter().enumerate() { - CompletionItem::new(CompletionKind::Reference, i.to_string()) - .kind(CompletionItemKind::Field) - .add_to(acc); - } - } - _ => {} - }; - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use crate::completion::*; - - fn check_ref_completion(code: &str, expected_completions: &str) { - check_completion(code, expected_completions, CompletionKind::Reference); - } - - #[test] - fn test_struct_field_completion() { - check_ref_completion( - r" - struct A { the_field: u32 } - fn foo(a: A) { - a.<|> - } - ", - r#"the_field"#, - ); - } - - #[test] - fn test_struct_field_completion_self() { - check_ref_completion( - r" - struct A { the_field: u32 } - impl A { - fn foo(self) { - self.<|> - } - } - ", - r#"the_field"#, - ); - } - - #[test] - fn test_struct_field_completion_autoderef() { - check_ref_completion( - r" - struct A { the_field: u32 } - impl A { - fn foo(&self) { - self.<|> - } - } - ", - r#"the_field"#, - ); - } - - #[test] - fn test_no_struct_field_completion_for_method_call() { - check_ref_completion( - r" - struct A { the_field: u32 } - fn foo(a: A) { - a.<|>() - } - ", - r#""#, - ); - } -} diff --git a/crates/ra_analysis/src/completion/complete_fn_param.rs b/crates/ra_analysis/src/completion/complete_fn_param.rs deleted file mode 100644 index c1739e47ea..0000000000 --- a/crates/ra_analysis/src/completion/complete_fn_param.rs +++ /dev/null @@ -1,102 +0,0 @@ -use ra_syntax::{ - algo::visit::{visitor_ctx, VisitorCtx}, - ast, - AstNode, -}; -use rustc_hash::FxHashMap; - -use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem}; - -/// Complete repeated parametes, both name and type. For example, if all -/// functions in a file have a `spam: &mut Spam` parameter, a completion with -/// `spam: &mut Spam` insert text/label and `spam` lookup string will be -/// suggested. -pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_param { - return; - } - - let mut params = FxHashMap::default(); - for node in ctx.leaf.ancestors() { - let _ = visitor_ctx(&mut params) - .visit::(process) - .visit::(process) - .accept(node); - } - params - .into_iter() - .filter_map(|(label, (count, param))| { - let lookup = param.pat()?.syntax().text().to_string(); - if count < 2 { - None - } else { - Some((label, lookup)) - } - }) - .for_each(|(label, lookup)| { - CompletionItem::new(CompletionKind::Magic, label) - .lookup_by(lookup) - .add_to(acc) - }); - - fn process<'a, N: ast::FnDefOwner>( - node: &'a N, - params: &mut FxHashMap, - ) { - node.functions() - .filter_map(|it| it.param_list()) - .flat_map(|it| it.params()) - .for_each(|param| { - let text = param.syntax().text().to_string(); - params.entry(text).or_insert((0, param)).0 += 1; - }) - } -} - -#[cfg(test)] -mod tests { - use crate::completion::*; - - fn check_magic_completion(code: &str, expected_completions: &str) { - check_completion(code, expected_completions, CompletionKind::Magic); - } - - #[test] - fn test_param_completion_last_param() { - check_magic_completion( - r" - fn foo(file_id: FileId) {} - fn bar(file_id: FileId) {} - fn baz(file<|>) {} - ", - r#"file_id "file_id: FileId""#, - ); - } - - #[test] - fn test_param_completion_nth_param() { - check_magic_completion( - r" - fn foo(file_id: FileId) {} - fn bar(file_id: FileId) {} - fn baz(file<|>, x: i32) {} - ", - r#"file_id "file_id: FileId""#, - ); - } - - #[test] - fn test_param_completion_trait_param() { - check_magic_completion( - r" - pub(crate) trait SourceRoot { - pub fn contains(&self, file_id: FileId) -> bool; - pub fn module_map(&self) -> &ModuleMap; - pub fn lines(&self, file_id: FileId) -> &LineIndex; - pub fn syntax(&self, file<|>) - } - ", - r#"file_id "file_id: FileId""#, - ); - } -} diff --git a/crates/ra_analysis/src/completion/complete_keyword.rs b/crates/ra_analysis/src/completion/complete_keyword.rs deleted file mode 100644 index d350f06ceb..0000000000 --- a/crates/ra_analysis/src/completion/complete_keyword.rs +++ /dev/null @@ -1,339 +0,0 @@ -use ra_syntax::{ - algo::visit::{visitor, Visitor}, - AstNode, - ast::{self, LoopBodyOwner}, - SyntaxKind::*, SyntaxNode, -}; - -use crate::completion::{CompletionContext, CompletionItem, Completions, CompletionKind, CompletionItemKind}; - -pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { - // complete keyword "crate" in use stmt - match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) { - (Some(_), None) => { - CompletionItem::new(CompletionKind::Keyword, "crate") - .kind(CompletionItemKind::Keyword) - .lookup_by("crate") - .snippet("crate::") - .add_to(acc); - CompletionItem::new(CompletionKind::Keyword, "self") - .kind(CompletionItemKind::Keyword) - .lookup_by("self") - .add_to(acc); - CompletionItem::new(CompletionKind::Keyword, "super") - .kind(CompletionItemKind::Keyword) - .lookup_by("super") - .add_to(acc); - } - (Some(_), Some(_)) => { - CompletionItem::new(CompletionKind::Keyword, "self") - .kind(CompletionItemKind::Keyword) - .lookup_by("self") - .add_to(acc); - CompletionItem::new(CompletionKind::Keyword, "super") - .kind(CompletionItemKind::Keyword) - .lookup_by("super") - .add_to(acc); - } - _ => {} - } -} - -fn keyword(kw: &str, snippet: &str) -> CompletionItem { - CompletionItem::new(CompletionKind::Keyword, kw) - .kind(CompletionItemKind::Keyword) - .snippet(snippet) - .build() -} - -pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_trivial_path { - return; - } - - let fn_def = match ctx.function_syntax { - Some(it) => it, - None => return, - }; - acc.add(keyword("if", "if $0 {}")); - acc.add(keyword("match", "match $0 {}")); - acc.add(keyword("while", "while $0 {}")); - acc.add(keyword("loop", "loop {$0}")); - - if ctx.after_if { - acc.add(keyword("else", "else {$0}")); - acc.add(keyword("else if", "else if $0 {}")); - } - if is_in_loop_body(ctx.leaf) { - if ctx.can_be_stmt { - acc.add(keyword("continue", "continue;")); - acc.add(keyword("break", "break;")); - } else { - acc.add(keyword("continue", "continue")); - acc.add(keyword("break", "break")); - } - } - acc.add_all(complete_return(fn_def, ctx.can_be_stmt)); -} - -fn is_in_loop_body(leaf: &SyntaxNode) -> bool { - for node in leaf.ancestors() { - if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { - break; - } - let loop_body = visitor() - .visit::(LoopBodyOwner::loop_body) - .visit::(LoopBodyOwner::loop_body) - .visit::(LoopBodyOwner::loop_body) - .accept(node); - if let Some(Some(body)) = loop_body { - if leaf.range().is_subrange(&body.syntax().range()) { - return true; - } - } - } - false -} - -fn complete_return(fn_def: &ast::FnDef, can_be_stmt: bool) -> Option { - let snip = match (can_be_stmt, fn_def.ret_type().is_some()) { - (true, true) => "return $0;", - (true, false) => "return;", - (false, true) => "return $0", - (false, false) => "return", - }; - Some(keyword("return", snip)) -} - -#[cfg(test)] -mod tests { - use crate::completion::{CompletionKind, check_completion}; - fn check_keyword_completion(code: &str, expected_completions: &str) { - check_completion(code, expected_completions, CompletionKind::Keyword); - } - - #[test] - fn completes_keywords_in_use_stmt() { - check_keyword_completion( - r" - use <|> - ", - r#" - crate "crate" "crate::" - self "self" - super "super" - "#, - ); - - check_keyword_completion( - r" - use a::<|> - ", - r#" - self "self" - super "super" - "#, - ); - - check_keyword_completion( - r" - use a::{b, <|>} - ", - r#" - self "self" - super "super" - "#, - ); - } - - #[test] - fn completes_various_keywords_in_function() { - check_keyword_completion( - r" - fn quux() { - <|> - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return;" - "#, - ); - } - - #[test] - fn completes_else_after_if() { - check_keyword_completion( - r" - fn quux() { - if true { - () - } <|> - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - else "else {$0}" - else if "else if $0 {}" - return "return;" - "#, - ); - } - - #[test] - fn test_completion_return_value() { - check_keyword_completion( - r" - fn quux() -> i32 { - <|> - 92 - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return $0;" - "#, - ); - check_keyword_completion( - r" - fn quux() { - <|> - 92 - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return;" - "#, - ); - } - - #[test] - fn dont_add_semi_after_return_if_not_a_statement() { - check_keyword_completion( - r" - fn quux() -> i32 { - match () { - () => <|> - } - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return $0" - "#, - ); - } - - #[test] - fn last_return_in_block_has_semi() { - check_keyword_completion( - r" - fn quux() -> i32 { - if condition { - <|> - } - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return $0;" - "#, - ); - check_keyword_completion( - r" - fn quux() -> i32 { - if condition { - <|> - } - let x = 92; - x - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return $0;" - "#, - ); - } - - #[test] - fn completes_break_and_continue_in_loops() { - check_keyword_completion( - r" - fn quux() -> i32 { - loop { <|> } - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - continue "continue;" - break "break;" - return "return $0;" - "#, - ); - // No completion: lambda isolates control flow - check_keyword_completion( - r" - fn quux() -> i32 { - loop { || { <|> } } - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return $0;" - "#, - ); - } - - #[test] - fn no_semi_after_break_continue_in_expr() { - check_keyword_completion( - r" - fn f() { - loop { - match () { - () => br<|> - } - } - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - continue "continue" - break "break" - return "return" - "#, - ) - } -} diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs deleted file mode 100644 index 4723a65a6b..0000000000 --- a/crates/ra_analysis/src/completion/complete_path.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::{ - Cancelable, - completion::{CompletionItem, CompletionItemKind, Completions, CompletionKind, CompletionContext}, -}; - -pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> { - let (path, module) = match (&ctx.path_prefix, &ctx.module) { - (Some(path), Some(module)) => (path.clone(), module), - _ => return Ok(()), - }; - let def_id = match module.resolve_path(ctx.db, &path)?.take_types() { - Some(it) => it, - None => return Ok(()), - }; - match def_id.resolve(ctx.db)? { - hir::Def::Module(module) => { - let module_scope = module.scope(ctx.db)?; - module_scope.entries().for_each(|(name, res)| { - CompletionItem::new(CompletionKind::Reference, name.to_string()) - .from_resolution(ctx, res) - .add_to(acc) - }); - } - hir::Def::Enum(e) => e - .variants(ctx.db)? - .into_iter() - .for_each(|(name, _variant)| { - CompletionItem::new(CompletionKind::Reference, name.to_string()) - .kind(CompletionItemKind::EnumVariant) - .add_to(acc) - }), - _ => return Ok(()), - }; - Ok(()) -} - -#[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 completes_use_item_starting_with_self() { - check_reference_completion( - r" - use self::m::<|>; - - mod m { - struct Bar; - } - ", - "Bar", - ); - } - - #[test] - fn completes_use_item_starting_with_crate() { - check_reference_completion( - " - //- /lib.rs - mod foo; - struct Spam; - //- /foo.rs - use crate::Sp<|> - ", - "Spam;foo", - ); - } - - #[test] - fn completes_nested_use_tree() { - check_reference_completion( - " - //- /lib.rs - mod foo; - struct Spam; - //- /foo.rs - use crate::{Sp<|>}; - ", - "Spam;foo", - ); - } - - #[test] - fn completes_deeply_nested_use_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", - ); - } - - #[test] - fn completes_enum_variant() { - check_reference_completion( - " - //- /lib.rs - enum E { Foo, Bar(i32) } - fn foo() { let _ = E::<|> } - ", - "Foo;Bar", - ); - } - - #[test] - fn dont_render_function_parens_in_use_item() { - check_reference_completion( - " - //- /lib.rs - mod m { pub fn foo() {} } - use crate::m::f<|>; - ", - "foo", - ) - } -} diff --git a/crates/ra_analysis/src/completion/complete_scope.rs b/crates/ra_analysis/src/completion/complete_scope.rs deleted file mode 100644 index ee9052d3d5..0000000000 --- a/crates/ra_analysis/src/completion/complete_scope.rs +++ /dev/null @@ -1,192 +0,0 @@ -use rustc_hash::FxHashSet; -use ra_syntax::TextUnit; - -use crate::{ - Cancelable, - completion::{CompletionItem, CompletionItemKind, Completions, CompletionKind, CompletionContext}, -}; - -pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> { - if !ctx.is_trivial_path { - return Ok(()); - } - let module = match &ctx.module { - Some(it) => it, - None => return Ok(()), - }; - if let Some(function) = &ctx.function { - let scopes = function.scopes(ctx.db)?; - complete_fn(acc, &scopes, ctx.offset); - } - - let module_scope = module.scope(ctx.db)?; - let (file_id, _) = module.defenition_source(ctx.db)?; - module_scope - .entries() - .filter(|(_name, res)| { - // Don't expose this item - // FIXME: this penetrates through all kinds of abstractions, - // we need to figura out the way to do it less ugly. - match res.import { - None => true, - Some(import) => { - let range = import.range(ctx.db, file_id); - !range.is_subrange(&ctx.leaf.range()) - } - } - }) - .for_each(|(name, res)| { - CompletionItem::new(CompletionKind::Reference, name.to_string()) - .from_resolution(ctx, res) - .add_to(acc) - }); - Ok(()) -} - -fn complete_fn(acc: &mut Completions, scopes: &hir::ScopesWithSyntaxMapping, offset: TextUnit) { - let mut shadowed = FxHashSet::default(); - scopes - .scope_chain_for_offset(offset) - .flat_map(|scope| scopes.scopes.entries(scope).iter()) - .filter(|entry| shadowed.insert(entry.name())) - .for_each(|entry| { - CompletionItem::new(CompletionKind::Reference, entry.name().to_string()) - .kind(CompletionItemKind::Binding) - .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 completes_bindings_from_let() { - check_reference_completion( - r" - fn quux(x: i32) { - let y = 92; - 1 + <|>; - let z = (); - } - ", - r#"y;x;quux "quux($0)""#, - ); - } - - #[test] - fn completes_bindings_from_if_let() { - check_reference_completion( - r" - fn quux() { - if let Some(x) = foo() { - let y = 92; - }; - if let Some(a) = bar() { - let b = 62; - 1 + <|> - } - } - ", - r#"b;a;quux "quux()$0""#, - ); - } - - #[test] - fn completes_bindings_from_for() { - check_reference_completion( - r" - fn quux() { - for x in &[1, 2, 3] { - <|> - } - } - ", - r#"x;quux "quux()$0""#, - ); - } - - #[test] - fn completes_module_items() { - check_reference_completion( - r" - struct Foo; - enum Baz {} - fn quux() { - <|> - } - ", - r#"quux "quux()$0";Foo;Baz"#, - ); - } - - #[test] - fn completes_module_items_in_nested_modules() { - check_reference_completion( - r" - struct Foo; - mod m { - struct Bar; - fn quux() { <|> } - } - ", - r#"quux "quux()$0";Bar"#, - ); - } - - #[test] - fn completes_return_type() { - check_reference_completion( - r" - struct Foo; - fn x() -> <|> - ", - r#"Foo;x "x()$0""#, - ) - } - - #[test] - fn dont_show_both_completions_for_shadowing() { - check_reference_completion( - r" - fn foo() -> { - let bar = 92; - { - let bar = 62; - <|> - } - } - ", - r#"bar;foo "foo()$0""#, - ) - } - - #[test] - fn completes_self_in_methods() { - check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self") - } - - #[test] - fn inserts_parens_for_function_calls() { - check_reference_completion( - r" - fn no_args() {} - fn main() { no_<|> } - ", - r#"no_args "no_args()$0" - main "main()$0""#, - ); - check_reference_completion( - r" - fn with_args(x: i32, y: String) {} - fn main() { with_<|> } - ", - r#"main "main()$0" - with_args "with_args($0)""#, - ); - } -} diff --git a/crates/ra_analysis/src/completion/complete_snippet.rs b/crates/ra_analysis/src/completion/complete_snippet.rs deleted file mode 100644 index a495751dda..0000000000 --- a/crates/ra_analysis/src/completion/complete_snippet.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::completion::{CompletionItem, Completions, CompletionKind, CompletionItemKind, CompletionContext, completion_item::Builder}; - -fn snippet(label: &str, snippet: &str) -> Builder { - CompletionItem::new(CompletionKind::Snippet, label) - .snippet(snippet) - .kind(CompletionItemKind::Snippet) -} - -pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { - if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) { - return; - } - snippet("pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); - snippet("ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); -} - -pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_new_item { - return; - } - snippet( - "Test function", - "\ -#[test] -fn ${1:feature}() { - $0 -}", - ) - .lookup_by("tfn") - .add_to(acc); - - snippet("pub(crate)", "pub(crate) $0").add_to(acc); -} - -#[cfg(test)] -mod tests { - use crate::completion::{CompletionKind, check_completion}; - fn check_snippet_completion(code: &str, expected_completions: &str) { - check_completion(code, expected_completions, CompletionKind::Snippet); - } - - #[test] - fn completes_snippets_in_expressions() { - check_snippet_completion( - r"fn foo(x: i32) { <|> }", - r##" - pd "eprintln!(\"$0 = {:?}\", $0);" - ppd "eprintln!(\"$0 = {:#?}\", $0);" - "##, - ); - } - - #[test] - fn completes_snippets_in_items() { - // check_snippet_completion(r" - // <|> - // ", - // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##, - // ); - check_snippet_completion( - r" - #[cfg(test)] - mod tests { - <|> - } - ", - r##" - tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}" - pub(crate) "pub(crate) $0" - "##, - ); - } -} diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs deleted file mode 100644 index 01786bb69e..0000000000 --- a/crates/ra_analysis/src/completion/completion_context.rs +++ /dev/null @@ -1,205 +0,0 @@ -use ra_text_edit::AtomTextEdit; -use ra_syntax::{ - AstNode, SyntaxNode, SourceFile, TextUnit, TextRange, - ast, - algo::{find_leaf_at_offset, find_covering_node, find_node_at_offset}, - SyntaxKind::*, -}; -use hir::source_binder; - -use crate::{db, FilePosition, Cancelable}; - -/// `CompletionContext` is created early during completion to figure out, where -/// exactly is the cursor, syntax-wise. -#[derive(Debug)] -pub(super) struct CompletionContext<'a> { - pub(super) db: &'a db::RootDatabase, - pub(super) offset: TextUnit, - pub(super) leaf: &'a SyntaxNode, - pub(super) module: Option, - pub(super) function: Option, - pub(super) function_syntax: Option<&'a ast::FnDef>, - pub(super) use_item_syntax: Option<&'a ast::UseItem>, - pub(super) is_param: bool, - /// A single-indent path, like `foo`. - pub(super) is_trivial_path: bool, - /// If not a trivial, path, the prefix (qualifier). - pub(super) path_prefix: Option, - pub(super) after_if: bool, - /// `true` if we are a statement or a last expr in the block. - pub(super) can_be_stmt: bool, - /// Something is typed at the "top" level, in module or impl/trait. - pub(super) is_new_item: bool, - /// The receiver if this is a field or method access, i.e. writing something.<|> - pub(super) dot_receiver: Option<&'a ast::Expr>, - /// If this is a method call in particular, i.e. the () are already there. - pub(super) is_method_call: bool, -} - -impl<'a> CompletionContext<'a> { - pub(super) fn new( - db: &'a db::RootDatabase, - original_file: &'a SourceFile, - position: FilePosition, - ) -> Cancelable>> { - let module = source_binder::module_from_position(db, position)?; - let leaf = - ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased()); - let mut ctx = CompletionContext { - db, - leaf, - offset: position.offset, - module, - function: None, - function_syntax: None, - use_item_syntax: None, - is_param: false, - is_trivial_path: false, - path_prefix: None, - after_if: false, - can_be_stmt: false, - is_new_item: false, - dot_receiver: None, - is_method_call: false, - }; - ctx.fill(original_file, position.offset); - Ok(Some(ctx)) - } - - fn fill(&mut self, original_file: &'a SourceFile, offset: TextUnit) { - // Insert a fake ident to get a valid parse tree. We will use this file - // to determine context, though the original_file will be used for - // actual completion. - let file = { - let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string()); - original_file.reparse(&edit) - }; - - // First, let's try to complete a reference to some declaration. - if let Some(name_ref) = find_node_at_offset::(file.syntax(), offset) { - // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. - // See RFC#1685. - if is_node::(name_ref.syntax()) { - self.is_param = true; - return; - } - self.classify_name_ref(original_file, name_ref); - } - - // Otherwise, see if this is a declaration. We can use heuristics to - // suggest declaration names, see `CompletionKind::Magic`. - if let Some(name) = find_node_at_offset::(file.syntax(), offset) { - if is_node::(name.syntax()) { - self.is_param = true; - return; - } - } - } - fn classify_name_ref(&mut self, original_file: &'a SourceFile, name_ref: &ast::NameRef) { - 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) => { - self.is_new_item = true; - return; - } - _ => (), - } - - self.use_item_syntax = self.leaf.ancestors().find_map(ast::UseItem::cast); - - self.function_syntax = self - .leaf - .ancestors() - .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::FnDef::cast); - match (&self.module, self.function_syntax) { - (Some(module), Some(fn_def)) => { - let function = source_binder::function_from_module(self.db, module, fn_def); - self.function = Some(function); - } - _ => (), - } - - let parent = match name_ref.syntax().parent() { - Some(it) => it, - None => return, - }; - if let Some(segment) = ast::PathSegment::cast(parent) { - let path = segment.parent_path(); - if let Some(mut path) = hir::Path::from_ast(path) { - if !path.is_ident() { - path.segments.pop().unwrap(); - self.path_prefix = Some(path); - return; - } - } - if path.qualifier().is_none() { - self.is_trivial_path = true; - - // Find either enclosing expr statement (thing with `;`) or a - // block. If block, check that we are the last expr. - self.can_be_stmt = name_ref - .syntax() - .ancestors() - .find_map(|node| { - if let Some(stmt) = ast::ExprStmt::cast(node) { - return Some(stmt.syntax().range() == name_ref.syntax().range()); - } - if let Some(block) = ast::Block::cast(node) { - return Some( - block.expr().map(|e| e.syntax().range()) - == Some(name_ref.syntax().range()), - ); - } - None - }) - .unwrap_or(false); - - if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { - if let Some(if_expr) = - find_node_at_offset::(original_file.syntax(), off) - { - if if_expr.syntax().range().end() < name_ref.syntax().range().start() { - self.after_if = true; - } - } - } - } - } - if let Some(field_expr) = ast::FieldExpr::cast(parent) { - // The receiver comes before the point of insertion of the fake - // ident, so it should have the same range in the non-modified file - self.dot_receiver = field_expr - .expr() - .map(|e| e.syntax().range()) - .and_then(|r| find_node_with_range(original_file.syntax(), r)); - } - if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { - // As above - self.dot_receiver = method_call_expr - .expr() - .map(|e| e.syntax().range()) - .and_then(|r| find_node_with_range(original_file.syntax(), r)); - self.is_method_call = true; - } - } -} - -fn find_node_with_range(syntax: &SyntaxNode, range: TextRange) -> Option<&N> { - let node = find_covering_node(syntax, range); - node.ancestors().find_map(N::cast) -} - -fn is_node(node: &SyntaxNode) -> bool { - match node.ancestors().filter_map(N::cast).next() { - None => false, - Some(n) => n.syntax().range() == node.range(), - } -} diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs deleted file mode 100644 index a25b87beef..0000000000 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ /dev/null @@ -1,244 +0,0 @@ -use hir::PerNs; - -use crate::completion::CompletionContext; - -/// `CompletionItem` describes a single completion variant in the editor pop-up. -/// It is basically a POD with various properties. To construct a -/// `CompletionItem`, use `new` method and the `Builder` struct. -#[derive(Debug)] -pub struct CompletionItem { - /// Used only internally in tests, to check only specific kind of - /// completion. - completion_kind: CompletionKind, - label: String, - lookup: Option, - snippet: Option, - kind: Option, -} - -pub enum InsertText { - PlainText { text: String }, - Snippet { text: String }, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum CompletionItemKind { - Snippet, - Keyword, - Module, - Function, - Struct, - Enum, - EnumVariant, - Binding, - Field, -} - -#[derive(Debug, PartialEq, Eq)] -pub(crate) enum CompletionKind { - /// Parser-based keyword completion. - Keyword, - /// Your usual "complete all valid identifiers". - Reference, - /// "Secret sauce" completions. - Magic, - Snippet, -} - -impl CompletionItem { - pub(crate) fn new(completion_kind: CompletionKind, label: impl Into) -> Builder { - let label = label.into(); - Builder { - completion_kind, - label, - lookup: None, - snippet: None, - kind: None, - } - } - /// What user sees in pop-up in the UI. - pub fn label(&self) -> &str { - &self.label - } - /// What string is used for filtering. - pub fn lookup(&self) -> &str { - self.lookup - .as_ref() - .map(|it| it.as_str()) - .unwrap_or(self.label()) - } - /// What is inserted. - pub fn insert_text(&self) -> InsertText { - match &self.snippet { - None => InsertText::PlainText { - text: self.label.clone(), - }, - Some(it) => InsertText::Snippet { text: it.clone() }, - } - } - - pub fn kind(&self) -> Option { - self.kind - } -} - -/// A helper to make `CompletionItem`s. -#[must_use] -pub(crate) struct Builder { - completion_kind: CompletionKind, - label: String, - lookup: Option, - snippet: Option, - kind: Option, -} - -impl Builder { - pub(crate) fn add_to(self, acc: &mut Completions) { - acc.add(self.build()) - } - - pub(crate) fn build(self) -> CompletionItem { - CompletionItem { - label: self.label, - lookup: self.lookup, - snippet: self.snippet, - kind: self.kind, - completion_kind: self.completion_kind, - } - } - pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder { - self.lookup = Some(lookup.into()); - self - } - pub(crate) fn snippet(mut self, snippet: impl Into) -> Builder { - self.snippet = Some(snippet.into()); - self - } - pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { - self.kind = Some(kind); - self - } - pub(super) fn from_resolution( - mut self, - ctx: &CompletionContext, - resolution: &hir::Resolution, - ) -> Builder { - let resolved = resolution.def_id.and_then(|d| d.resolve(ctx.db).ok()); - let kind = match resolved { - PerNs { - types: Some(hir::Def::Module(..)), - .. - } => CompletionItemKind::Module, - PerNs { - types: Some(hir::Def::Struct(..)), - .. - } => CompletionItemKind::Struct, - PerNs { - types: Some(hir::Def::Enum(..)), - .. - } => CompletionItemKind::Enum, - PerNs { - values: Some(hir::Def::Function(function)), - .. - } => return self.from_function(ctx, function), - _ => return self, - }; - self.kind = Some(kind); - self - } - - fn from_function(mut self, ctx: &CompletionContext, function: hir::Function) -> Builder { - // If not an import, add parenthesis automatically. - if ctx.use_item_syntax.is_none() { - if function.signature(ctx.db).args().is_empty() { - self.snippet = Some(format!("{}()$0", self.label)); - } else { - self.snippet = Some(format!("{}($0)", self.label)); - } - } - self.kind = Some(CompletionItemKind::Function); - self - } -} - -impl Into for Builder { - fn into(self) -> CompletionItem { - self.build() - } -} - -/// Represents an in-progress set of completions being built. -#[derive(Debug, Default)] -pub(crate) struct Completions { - buf: Vec, -} - -impl Completions { - pub(crate) fn add(&mut self, item: impl Into) { - self.buf.push(item.into()) - } - pub(crate) fn add_all(&mut self, items: I) - where - I: IntoIterator, - I::Item: Into, - { - items.into_iter().for_each(|item| self.add(item.into())) - } - - #[cfg(test)] - pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) { - let expected = normalize(expected); - let actual = self.debug_render(kind); - test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),); - - /// Normalize the textual representation of `Completions`: - /// replace `;` with newlines, normalize whitespace - fn normalize(expected: &str) -> String { - use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI}; - let mut res = String::new(); - for line in expected.trim().lines() { - let line = line.trim(); - let mut start_offset: TextUnit = 0.into(); - // Yep, we use rust tokenize in completion tests :-) - for token in tokenize(line) { - let range = TextRange::offset_len(start_offset, token.len); - start_offset += token.len; - if token.kind == SEMI { - res.push('\n'); - } else { - res.push_str(&line[range]); - } - } - - res.push('\n'); - } - res - } - } - - #[cfg(test)] - fn debug_render(&self, kind: CompletionKind) -> String { - let mut res = String::new(); - for c in self.buf.iter() { - if c.completion_kind == kind { - if let Some(lookup) = &c.lookup { - res.push_str(lookup); - res.push_str(&format!(" {:?}", c.label)); - } else { - res.push_str(&c.label); - } - if let Some(snippet) = &c.snippet { - res.push_str(&format!(" {:?}", snippet)); - } - res.push('\n'); - } - } - res - } -} - -impl Into> for Completions { - fn into(self) -> Vec { - self.buf - } -} diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs deleted file mode 100644 index 9d46609ecf..0000000000 --- a/crates/ra_analysis/src/db.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::{fmt, sync::Arc}; - -use salsa::{self, Database}; -use ra_db::{LocationIntener, BaseDatabase, FileId}; - -use crate::{symbol_index, LineIndex}; - -#[derive(Debug)] -pub(crate) struct RootDatabase { - runtime: salsa::Runtime, - id_maps: Arc, -} - -#[derive(Default)] -struct IdMaps { - defs: LocationIntener, - macros: LocationIntener, -} - -impl fmt::Debug for IdMaps { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("IdMaps") - .field("n_defs", &self.defs.len()) - .finish() - } -} - -impl salsa::Database for RootDatabase { - fn salsa_runtime(&self) -> &salsa::Runtime { - &self.runtime - } -} - -impl Default for RootDatabase { - fn default() -> RootDatabase { - let mut db = RootDatabase { - runtime: salsa::Runtime::default(), - id_maps: Default::default(), - }; - db.query_mut(ra_db::CrateGraphQuery) - .set((), Default::default()); - db.query_mut(ra_db::LocalRootsQuery) - .set((), Default::default()); - db.query_mut(ra_db::LibraryRootsQuery) - .set((), Default::default()); - db - } -} - -impl salsa::ParallelDatabase for RootDatabase { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(RootDatabase { - runtime: self.runtime.snapshot(self), - id_maps: self.id_maps.clone(), - }) - } -} - -impl BaseDatabase for RootDatabase {} - -impl AsRef> for RootDatabase { - fn as_ref(&self) -> &LocationIntener { - &self.id_maps.defs - } -} - -impl AsRef> for RootDatabase { - fn as_ref(&self) -> &LocationIntener { - &self.id_maps.macros - } -} - -salsa::query_group! { - pub(crate) trait LineIndexDatabase: ra_db::FilesDatabase + BaseDatabase { - fn line_index(file_id: FileId) -> Arc { - type LineIndexQuery; - } - } -} - -fn line_index(db: &impl ra_db::FilesDatabase, file_id: FileId) -> Arc { - let text = db.file_text(file_id); - Arc::new(LineIndex::new(&*text)) -} - -salsa::database_storage! { - pub(crate) struct RootDatabaseStorage for RootDatabase { - impl ra_db::FilesDatabase { - fn file_text() for ra_db::FileTextQuery; - fn file_relative_path() for ra_db::FileRelativePathQuery; - fn file_source_root() for ra_db::FileSourceRootQuery; - fn source_root() for ra_db::SourceRootQuery; - fn local_roots() for ra_db::LocalRootsQuery; - fn library_roots() for ra_db::LibraryRootsQuery; - fn crate_graph() for ra_db::CrateGraphQuery; - } - impl ra_db::SyntaxDatabase { - fn source_file() for ra_db::SourceFileQuery; - } - impl LineIndexDatabase { - fn line_index() for LineIndexQuery; - } - impl symbol_index::SymbolsDatabase { - fn file_symbols() for symbol_index::FileSymbolsQuery; - fn library_symbols() for symbol_index::LibrarySymbolsQuery; - } - impl hir::db::HirDatabase { - fn hir_source_file() for hir::db::HirSourceFileQuery; - fn expand_macro_invocation() for hir::db::ExpandMacroCallQuery; - fn module_tree() for hir::db::ModuleTreeQuery; - fn fn_scopes() for hir::db::FnScopesQuery; - fn file_items() for hir::db::SourceFileItemsQuery; - fn file_item() for hir::db::FileItemQuery; - fn input_module_items() for hir::db::InputModuleItemsQuery; - fn item_map() for hir::db::ItemMapQuery; - fn submodules() for hir::db::SubmodulesQuery; - fn infer() for hir::db::InferQuery; - fn type_for_def() for hir::db::TypeForDefQuery; - fn type_for_field() for hir::db::TypeForFieldQuery; - fn struct_data() for hir::db::StructDataQuery; - fn enum_data() for hir::db::EnumDataQuery; - fn impls_in_module() for hir::db::ImplsInModuleQuery; - fn body_hir() for hir::db::BodyHirQuery; - fn body_syntax_mapping() for hir::db::BodySyntaxMappingQuery; - fn fn_signature() for hir::db::FnSignatureQuery; - } - } -} diff --git a/crates/ra_analysis/src/extend_selection.rs b/crates/ra_analysis/src/extend_selection.rs deleted file mode 100644 index c3c809c9fe..0000000000 --- a/crates/ra_analysis/src/extend_selection.rs +++ /dev/null @@ -1,56 +0,0 @@ -use ra_db::SyntaxDatabase; -use ra_syntax::{ - SyntaxNode, AstNode, SourceFile, - ast, algo::find_covering_node, -}; - -use crate::{ - TextRange, FileRange, - db::RootDatabase, -}; - -pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { - let source_file = db.source_file(frange.file_id); - if let Some(range) = extend_selection_in_macro(db, &source_file, frange) { - return range; - } - ra_ide_api_light::extend_selection(source_file.syntax(), frange.range).unwrap_or(frange.range) -} - -fn extend_selection_in_macro( - _db: &RootDatabase, - source_file: &SourceFile, - frange: FileRange, -) -> Option { - let macro_call = find_macro_call(source_file.syntax(), frange.range)?; - let (off, exp) = hir::MacroDef::ast_expand(macro_call)?; - let dst_range = exp.map_range_forward(frange.range - off)?; - let dst_range = ra_ide_api_light::extend_selection(&exp.syntax(), dst_range)?; - let src_range = exp.map_range_back(dst_range)? + off; - Some(src_range) -} - -fn find_macro_call(node: &SyntaxNode, range: TextRange) -> Option<&ast::MacroCall> { - find_covering_node(node, range) - .ancestors() - .find_map(ast::MacroCall::cast) -} - -#[cfg(test)] -mod tests { - use crate::mock_analysis::single_file_with_range; - use test_utils::assert_eq_dbg; - - #[test] - fn extend_selection_inside_macros() { - let (analysis, frange) = single_file_with_range( - " - fn main() { - ctry!(foo(|x| <|>x<|>)); - } - ", - ); - let r = analysis.extend_selection(frange); - assert_eq_dbg("[51; 56)", &r); - } -} diff --git a/crates/ra_analysis/src/goto_defenition.rs b/crates/ra_analysis/src/goto_defenition.rs deleted file mode 100644 index fcd8d315e2..0000000000 --- a/crates/ra_analysis/src/goto_defenition.rs +++ /dev/null @@ -1,139 +0,0 @@ -use ra_db::{FileId, Cancelable, SyntaxDatabase}; -use ra_syntax::{ - TextRange, AstNode, ast, SyntaxKind::{NAME, MODULE}, - algo::find_node_at_offset, -}; - -use crate::{FilePosition, NavigationTarget, db::RootDatabase}; - -pub(crate) fn goto_defenition( - db: &RootDatabase, - position: FilePosition, -) -> Cancelable>> { - let file = db.source_file(position.file_id); - let syntax = file.syntax(); - if let Some(name_ref) = find_node_at_offset::(syntax, position.offset) { - return Ok(Some(reference_defenition(db, position.file_id, name_ref)?)); - } - if let Some(name) = find_node_at_offset::(syntax, position.offset) { - return name_defenition(db, position.file_id, name); - } - Ok(None) -} - -pub(crate) fn reference_defenition( - db: &RootDatabase, - file_id: FileId, - name_ref: &ast::NameRef, -) -> Cancelable> { - if let Some(fn_descr) = - hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())? - { - let scope = fn_descr.scopes(db)?; - // First try to resolve the symbol locally - if let Some(entry) = scope.resolve_local_name(name_ref) { - let nav = NavigationTarget { - file_id, - name: entry.name().to_string().into(), - range: entry.ptr().range(), - kind: NAME, - ptr: None, - }; - return Ok(vec![nav]); - }; - } - // If that fails try the index based approach. - let navs = db - .index_resolve(name_ref)? - .into_iter() - .map(NavigationTarget::from_symbol) - .collect(); - Ok(navs) -} - -fn name_defenition( - db: &RootDatabase, - file_id: FileId, - name: &ast::Name, -) -> Cancelable>> { - if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) { - if module.has_semi() { - if let Some(child_module) = - hir::source_binder::module_from_declaration(db, file_id, module)? - { - let (file_id, _) = child_module.defenition_source(db)?; - let name = match child_module.name(db)? { - Some(name) => name.to_string().into(), - None => "".into(), - }; - let nav = NavigationTarget { - file_id, - name, - range: TextRange::offset_len(0.into(), 0.into()), - kind: MODULE, - ptr: None, - }; - return Ok(Some(vec![nav])); - } - } - } - Ok(None) -} - -#[cfg(test)] -mod tests { - use test_utils::assert_eq_dbg; - use crate::mock_analysis::analysis_and_position; - - #[test] - fn goto_defenition_works_in_items() { - let (analysis, pos) = analysis_and_position( - " - //- /lib.rs - struct Foo; - enum E { X(Foo<|>) } - ", - ); - - let symbols = analysis.goto_defenition(pos).unwrap().unwrap(); - assert_eq_dbg( - r#"[NavigationTarget { file_id: FileId(1), name: "Foo", - kind: STRUCT_DEF, range: [0; 11), - ptr: Some(LocalSyntaxPtr { range: [0; 11), kind: STRUCT_DEF }) }]"#, - &symbols, - ); - } - - #[test] - fn goto_defenition_works_for_module_declaration() { - let (analysis, pos) = analysis_and_position( - " - //- /lib.rs - mod <|>foo; - //- /foo.rs - // empty - ", - ); - - let symbols = analysis.goto_defenition(pos).unwrap().unwrap(); - assert_eq_dbg( - r#"[NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }]"#, - &symbols, - ); - - let (analysis, pos) = analysis_and_position( - " - //- /lib.rs - mod <|>foo; - //- /foo/mod.rs - // empty - ", - ); - - let symbols = analysis.goto_defenition(pos).unwrap().unwrap(); - assert_eq_dbg( - r#"[NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }]"#, - &symbols, - ); - } -} diff --git a/crates/ra_analysis/src/hover.rs b/crates/ra_analysis/src/hover.rs deleted file mode 100644 index 475524ee18..0000000000 --- a/crates/ra_analysis/src/hover.rs +++ /dev/null @@ -1,257 +0,0 @@ -use ra_db::{Cancelable, SyntaxDatabase}; -use ra_syntax::{ - AstNode, SyntaxNode, TreePtr, - ast::{self, NameOwner}, - algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}}, -}; - -use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget}; - -pub(crate) fn hover( - db: &RootDatabase, - position: FilePosition, -) -> Cancelable>> { - let file = db.source_file(position.file_id); - let mut res = Vec::new(); - - let mut range = None; - if let Some(name_ref) = find_node_at_offset::(file.syntax(), position.offset) { - let navs = crate::goto_defenition::reference_defenition(db, position.file_id, name_ref)?; - for nav in navs { - res.extend(doc_text_for(db, nav)?) - } - if !res.is_empty() { - range = Some(name_ref.syntax().range()) - } - } - if range.is_none() { - let node = find_leaf_at_offset(file.syntax(), position.offset).find_map(|leaf| { - leaf.ancestors() - .find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some()) - }); - let node = ctry!(node); - let frange = FileRange { - file_id: position.file_id, - range: node.range(), - }; - res.extend(type_of(db, frange)?); - range = Some(node.range()); - }; - - let range = ctry!(range); - if res.is_empty() { - return Ok(None); - } - let res = RangeInfo::new(range, res.join("\n\n---\n")); - Ok(Some(res)) -} - -pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Cancelable> { - let file = db.source_file(frange.file_id); - let syntax = file.syntax(); - let leaf_node = find_covering_node(syntax, frange.range); - // if we picked identifier, expand to pattern/expression - let node = leaf_node - .ancestors() - .take_while(|it| it.range() == leaf_node.range()) - .find(|&it| ast::Expr::cast(it).is_some() || ast::Pat::cast(it).is_some()) - .unwrap_or(leaf_node); - let parent_fn = ctry!(node.ancestors().find_map(ast::FnDef::cast)); - let function = ctry!(hir::source_binder::function_from_source( - db, - frange.file_id, - parent_fn - )?); - let infer = function.infer(db)?; - let syntax_mapping = function.body_syntax_mapping(db)?; - if let Some(expr) = ast::Expr::cast(node).and_then(|e| syntax_mapping.node_expr(e)) { - Ok(Some(infer[expr].to_string())) - } else if let Some(pat) = ast::Pat::cast(node).and_then(|p| syntax_mapping.node_pat(p)) { - Ok(Some(infer[pat].to_string())) - } else { - Ok(None) - } -} - -// FIXME: this should not really use navigation target. Rather, approximatelly -// resovled symbol should return a `DefId`. -fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Cancelable> { - let result = match (nav.description(db), nav.docs(db)) { - (Some(desc), Some(docs)) => Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs), - (Some(desc), None) => Some("```rust\n".to_string() + &*desc + "\n```"), - (None, Some(docs)) => Some(docs), - _ => None, - }; - - Ok(result) -} - -impl NavigationTarget { - fn node(&self, db: &RootDatabase) -> Option> { - let source_file = db.source_file(self.file_id); - let source_file = source_file.syntax(); - let node = source_file - .descendants() - .find(|node| node.kind() == self.kind && node.range() == self.range)? - .to_owned(); - Some(node) - } - - fn docs(&self, db: &RootDatabase) -> Option { - let node = self.node(db)?; - fn doc_comments(node: &N) -> Option { - let comments = node.doc_comment_text(); - if comments.is_empty() { - None - } else { - Some(comments) - } - } - - visitor() - .visit(doc_comments::) - .visit(doc_comments::) - .visit(doc_comments::) - .visit(doc_comments::) - .visit(doc_comments::) - .visit(doc_comments::) - .visit(doc_comments::) - .visit(doc_comments::) - .accept(&node)? - } - - /// Get a description of this node. - /// - /// e.g. `struct Name`, `enum Name`, `fn Name` - fn description(&self, db: &RootDatabase) -> Option { - // TODO: After type inference is done, add type information to improve the output - let node = self.node(db)?; - // TODO: Refactor to be have less repetition - visitor() - .visit(|node: &ast::FnDef| { - let mut string = "fn ".to_string(); - node.name()?.syntax().text().push_to(&mut string); - Some(string) - }) - .visit(|node: &ast::StructDef| { - let mut string = "struct ".to_string(); - node.name()?.syntax().text().push_to(&mut string); - Some(string) - }) - .visit(|node: &ast::EnumDef| { - let mut string = "enum ".to_string(); - node.name()?.syntax().text().push_to(&mut string); - Some(string) - }) - .visit(|node: &ast::TraitDef| { - let mut string = "trait ".to_string(); - node.name()?.syntax().text().push_to(&mut string); - Some(string) - }) - .visit(|node: &ast::Module| { - let mut string = "mod ".to_string(); - node.name()?.syntax().text().push_to(&mut string); - Some(string) - }) - .visit(|node: &ast::TypeDef| { - let mut string = "type ".to_string(); - node.name()?.syntax().text().push_to(&mut string); - Some(string) - }) - .visit(|node: &ast::ConstDef| { - let mut string = "const ".to_string(); - node.name()?.syntax().text().push_to(&mut string); - Some(string) - }) - .visit(|node: &ast::StaticDef| { - let mut string = "static ".to_string(); - node.name()?.syntax().text().push_to(&mut string); - Some(string) - }) - .accept(&node)? - } -} - -#[cfg(test)] -mod tests { - use ra_syntax::TextRange; - use crate::mock_analysis::{single_file_with_position, single_file_with_range}; - - #[test] - fn hover_shows_type_of_an_expression() { - let (analysis, position) = single_file_with_position( - " - pub fn foo() -> u32 { 1 } - - fn main() { - let foo_test = foo()<|>; - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); - assert_eq!(hover.info, "u32"); - } - - #[test] - fn hover_for_local_variable() { - let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(hover.info, "i32"); - } - - #[test] - fn hover_for_local_variable_pat() { - let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(hover.info, "i32"); - } - - #[test] - fn test_type_of_for_function() { - let (analysis, range) = single_file_with_range( - " - pub fn foo() -> u32 { 1 }; - - fn main() { - let foo_test = <|>foo()<|>; - } - ", - ); - - let type_name = analysis.type_of(range).unwrap().unwrap(); - assert_eq!("u32", &type_name); - } - - // FIXME: improve type_of to make this work - #[test] - fn test_type_of_for_expr_1() { - let (analysis, range) = single_file_with_range( - " - fn main() { - let foo = <|>1 + foo_test<|>; - } - ", - ); - - let type_name = analysis.type_of(range).unwrap().unwrap(); - assert_eq!("[unknown]", &type_name); - } - - // FIXME: improve type_of to make this work - #[test] - fn test_type_of_for_expr_2() { - let (analysis, range) = single_file_with_range( - " - fn main() { - let foo: usize = 1; - let bar = <|>1 + foo_test<|>; - } - ", - ); - - let type_name = analysis.type_of(range).unwrap().unwrap(); - assert_eq!("[unknown]", &type_name); - } - -} diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs deleted file mode 100644 index 7c60ab7d6f..0000000000 --- a/crates/ra_analysis/src/imp.rs +++ /dev/null @@ -1,309 +0,0 @@ -use std::sync::Arc; - -use salsa::Database; - -use hir::{ - self, Problem, source_binder, -}; -use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; -use ra_ide_api_light::{self, assists, LocalEdit, Severity}; -use ra_syntax::{ - TextRange, AstNode, SourceFile, - ast::{self, NameOwner}, - algo::find_node_at_offset, - SyntaxKind::*, -}; - -use crate::{ - AnalysisChange, - Cancelable, NavigationTarget, - CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit, - Query, RootChange, SourceChange, SourceFileEdit, - symbol_index::{LibrarySymbolsQuery, FileSymbol}, -}; - -impl db::RootDatabase { - pub(crate) fn apply_change(&mut self, change: AnalysisChange) { - log::info!("apply_change {:?}", change); - // self.gc_syntax_trees(); - if !change.new_roots.is_empty() { - let mut local_roots = Vec::clone(&self.local_roots()); - for (root_id, is_local) in change.new_roots { - self.query_mut(ra_db::SourceRootQuery) - .set(root_id, Default::default()); - if is_local { - local_roots.push(root_id); - } - } - self.query_mut(ra_db::LocalRootsQuery) - .set((), Arc::new(local_roots)); - } - - for (root_id, root_change) in change.roots_changed { - self.apply_root_change(root_id, root_change); - } - for (file_id, text) in change.files_changed { - self.query_mut(ra_db::FileTextQuery).set(file_id, text) - } - if !change.libraries_added.is_empty() { - let mut libraries = Vec::clone(&self.library_roots()); - for library in change.libraries_added { - libraries.push(library.root_id); - self.query_mut(ra_db::SourceRootQuery) - .set(library.root_id, Default::default()); - self.query_mut(LibrarySymbolsQuery) - .set_constant(library.root_id, Arc::new(library.symbol_index)); - self.apply_root_change(library.root_id, library.root_change); - } - self.query_mut(ra_db::LibraryRootsQuery) - .set((), Arc::new(libraries)); - } - if let Some(crate_graph) = change.crate_graph { - self.query_mut(ra_db::CrateGraphQuery) - .set((), Arc::new(crate_graph)) - } - } - - fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) { - let mut source_root = SourceRoot::clone(&self.source_root(root_id)); - for add_file in root_change.added { - self.query_mut(ra_db::FileTextQuery) - .set(add_file.file_id, add_file.text); - self.query_mut(ra_db::FileRelativePathQuery) - .set(add_file.file_id, add_file.path.clone()); - self.query_mut(ra_db::FileSourceRootQuery) - .set(add_file.file_id, root_id); - source_root.files.insert(add_file.path, add_file.file_id); - } - for remove_file in root_change.removed { - self.query_mut(ra_db::FileTextQuery) - .set(remove_file.file_id, Default::default()); - source_root.files.remove(&remove_file.path); - } - self.query_mut(ra_db::SourceRootQuery) - .set(root_id, Arc::new(source_root)); - } - - #[allow(unused)] - /// Ideally, we should call this function from time to time to collect heavy - /// syntax trees. However, if we actually do that, everything is recomputed - /// for some reason. Needs investigation. - fn gc_syntax_trees(&mut self) { - self.query(ra_db::SourceFileQuery) - .sweep(salsa::SweepStrategy::default().discard_values()); - self.query(hir::db::SourceFileItemsQuery) - .sweep(salsa::SweepStrategy::default().discard_values()); - self.query(hir::db::FileItemQuery) - .sweep(salsa::SweepStrategy::default().discard_values()); - } -} - -impl db::RootDatabase { - /// This returns `Vec` because a module may be included from several places. We - /// don't handle this case yet though, so the Vec has length at most one. - pub(crate) fn parent_module( - &self, - position: FilePosition, - ) -> Cancelable> { - let module = match source_binder::module_from_position(self, position)? { - None => return Ok(Vec::new()), - Some(it) => it, - }; - let (file_id, ast_module) = match module.declaration_source(self)? { - None => return Ok(Vec::new()), - Some(it) => it, - }; - let name = ast_module.name().unwrap(); - Ok(vec![NavigationTarget { - file_id, - name: name.text().clone(), - range: name.syntax().range(), - kind: MODULE, - ptr: None, - }]) - } - /// Returns `Vec` for the same reason as `parent_module` - pub(crate) fn crate_for(&self, file_id: FileId) -> Cancelable> { - let module = match source_binder::module_from_file_id(self, file_id)? { - Some(it) => it, - None => return Ok(Vec::new()), - }; - let krate = match module.krate(self)? { - Some(it) => it, - None => return Ok(Vec::new()), - }; - Ok(vec![krate.crate_id()]) - } - pub(crate) fn find_all_refs( - &self, - position: FilePosition, - ) -> Cancelable> { - let file = self.source_file(position.file_id); - // Find the binding associated with the offset - let (binding, descr) = match find_binding(self, &file, position)? { - None => return Ok(Vec::new()), - Some(it) => it, - }; - - let mut ret = binding - .name() - .into_iter() - .map(|name| (position.file_id, name.syntax().range())) - .collect::>(); - ret.extend( - descr - .scopes(self)? - .find_all_refs(binding) - .into_iter() - .map(|ref_desc| (position.file_id, ref_desc.range)), - ); - - return Ok(ret); - - fn find_binding<'a>( - db: &db::RootDatabase, - source_file: &'a SourceFile, - position: FilePosition, - ) -> Cancelable> { - let syntax = source_file.syntax(); - if let Some(binding) = find_node_at_offset::(syntax, position.offset) { - let descr = ctry!(source_binder::function_from_child_node( - db, - position.file_id, - binding.syntax(), - )?); - return Ok(Some((binding, descr))); - }; - let name_ref = ctry!(find_node_at_offset::(syntax, position.offset)); - let descr = ctry!(source_binder::function_from_child_node( - db, - position.file_id, - name_ref.syntax(), - )?); - let scope = descr.scopes(db)?; - let resolved = ctry!(scope.resolve_local_name(name_ref)); - let resolved = resolved.ptr().resolve(source_file); - let binding = ctry!(find_node_at_offset::( - syntax, - resolved.range().end() - )); - Ok(Some((binding, descr))) - } - } - - pub(crate) fn diagnostics(&self, file_id: FileId) -> Cancelable> { - let syntax = self.source_file(file_id); - - let mut res = ra_ide_api_light::diagnostics(&syntax) - .into_iter() - .map(|d| Diagnostic { - range: d.range, - message: d.msg, - severity: d.severity, - fix: d.fix.map(|fix| SourceChange::from_local_edit(file_id, fix)), - }) - .collect::>(); - if let Some(m) = source_binder::module_from_file_id(self, file_id)? { - for (name_node, problem) in m.problems(self)? { - let source_root = self.file_source_root(file_id); - let diag = match problem { - Problem::UnresolvedModule { candidate } => { - let create_file = FileSystemEdit::CreateFile { - source_root, - path: candidate.clone(), - }; - let fix = SourceChange { - label: "create module".to_string(), - source_file_edits: Vec::new(), - file_system_edits: vec![create_file], - cursor_position: None, - }; - Diagnostic { - range: name_node.range(), - message: "unresolved module".to_string(), - severity: Severity::Error, - fix: Some(fix), - } - } - Problem::NotDirOwner { move_to, candidate } => { - let move_file = FileSystemEdit::MoveFile { - src: file_id, - dst_source_root: source_root, - dst_path: move_to.clone(), - }; - let create_file = FileSystemEdit::CreateFile { - source_root, - path: move_to.join(candidate), - }; - let fix = SourceChange { - label: "move file and create module".to_string(), - source_file_edits: Vec::new(), - file_system_edits: vec![move_file, create_file], - cursor_position: None, - }; - Diagnostic { - range: name_node.range(), - message: "can't declare module at this location".to_string(), - severity: Severity::Error, - fix: Some(fix), - } - } - }; - res.push(diag) - } - }; - Ok(res) - } - - pub(crate) fn assists(&self, frange: FileRange) -> Vec { - let file = self.source_file(frange.file_id); - assists::assists(&file, frange.range) - .into_iter() - .map(|local_edit| SourceChange::from_local_edit(frange.file_id, local_edit)) - .collect() - } - - pub(crate) fn rename( - &self, - position: FilePosition, - new_name: &str, - ) -> Cancelable> { - let res = self - .find_all_refs(position)? - .iter() - .map(|(file_id, text_range)| SourceFileEdit { - file_id: *file_id, - edit: { - let mut builder = ra_text_edit::TextEditBuilder::default(); - builder.replace(*text_range, new_name.into()); - builder.finish() - }, - }) - .collect::>(); - Ok(res) - } - pub(crate) fn index_resolve(&self, name_ref: &ast::NameRef) -> Cancelable> { - let name = name_ref.text(); - let mut query = Query::new(name.to_string()); - query.exact(); - query.limit(4); - crate::symbol_index::world_symbols(self, query) - } -} - -impl SourceChange { - pub(crate) fn from_local_edit(file_id: FileId, edit: LocalEdit) -> SourceChange { - let file_edit = SourceFileEdit { - file_id, - edit: edit.edit, - }; - SourceChange { - label: edit.label, - source_file_edits: vec![file_edit], - file_system_edits: vec![], - cursor_position: edit - .cursor_position - .map(|offset| FilePosition { offset, file_id }), - } - } -} diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs deleted file mode 100644 index 183e367069..0000000000 --- a/crates/ra_analysis/src/lib.rs +++ /dev/null @@ -1,509 +0,0 @@ -//! ra_analyzer crate provides "ide-centric" APIs for the rust-analyzer. What -//! powers this API are the `RootDatabase` struct, which defines a `salsa` -//! database, and the `ra_hir` crate, where majority of the analysis happens. -//! However, IDE specific bits of the analysis (most notably completion) happen -//! in this crate. -macro_rules! ctry { - ($expr:expr) => { - match $expr { - None => return Ok(None), - Some(it) => it, - } - }; -} - -mod completion; -mod db; -mod goto_defenition; -mod imp; -pub mod mock_analysis; -mod runnables; -mod symbol_index; - -mod extend_selection; -mod hover; -mod call_info; -mod syntax_highlighting; - -use std::{fmt, sync::Arc}; - -use ra_syntax::{SmolStr, SourceFile, TreePtr, SyntaxKind, TextRange, TextUnit}; -use ra_text_edit::TextEdit; -use ra_db::{SyntaxDatabase, FilesDatabase, LocalSyntaxPtr}; -use rayon::prelude::*; -use relative_path::RelativePathBuf; -use rustc_hash::FxHashMap; -use salsa::ParallelDatabase; - -use crate::{ - symbol_index::{FileSymbol, SymbolIndex}, - db::LineIndexDatabase, -}; - -pub use crate::{ - completion::{CompletionItem, CompletionItemKind, InsertText}, - runnables::{Runnable, RunnableKind}, -}; -pub use ra_ide_api_light::{ - Fold, FoldKind, HighlightedRange, Severity, StructureNode, - LineIndex, LineCol, translate_offset_with_edit, -}; -pub use ra_db::{ - Cancelable, Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId -}; - -#[derive(Default)] -pub struct AnalysisChange { - new_roots: Vec<(SourceRootId, bool)>, - roots_changed: FxHashMap, - files_changed: Vec<(FileId, Arc)>, - libraries_added: Vec, - crate_graph: Option, -} - -#[derive(Default)] -struct RootChange { - added: Vec, - removed: Vec, -} - -#[derive(Debug)] -struct AddFile { - file_id: FileId, - path: RelativePathBuf, - text: Arc, -} - -#[derive(Debug)] -struct RemoveFile { - file_id: FileId, - path: RelativePathBuf, -} - -impl fmt::Debug for AnalysisChange { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let mut d = fmt.debug_struct("AnalysisChange"); - if !self.new_roots.is_empty() { - d.field("new_roots", &self.new_roots); - } - if !self.roots_changed.is_empty() { - d.field("roots_changed", &self.roots_changed); - } - if !self.files_changed.is_empty() { - d.field("files_changed", &self.files_changed.len()); - } - if !self.libraries_added.is_empty() { - d.field("libraries_added", &self.libraries_added.len()); - } - if !self.crate_graph.is_some() { - d.field("crate_graph", &self.crate_graph); - } - d.finish() - } -} - -impl fmt::Debug for RootChange { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("AnalysisChange") - .field("added", &self.added.len()) - .field("removed", &self.removed.len()) - .finish() - } -} - -impl AnalysisChange { - pub fn new() -> AnalysisChange { - AnalysisChange::default() - } - pub fn add_root(&mut self, root_id: SourceRootId, is_local: bool) { - self.new_roots.push((root_id, is_local)); - } - pub fn add_file( - &mut self, - root_id: SourceRootId, - file_id: FileId, - path: RelativePathBuf, - text: Arc, - ) { - let file = AddFile { - file_id, - path, - text, - }; - self.roots_changed - .entry(root_id) - .or_default() - .added - .push(file); - } - pub fn change_file(&mut self, file_id: FileId, new_text: Arc) { - self.files_changed.push((file_id, new_text)) - } - pub fn remove_file(&mut self, root_id: SourceRootId, file_id: FileId, path: RelativePathBuf) { - let file = RemoveFile { file_id, path }; - self.roots_changed - .entry(root_id) - .or_default() - .removed - .push(file); - } - pub fn add_library(&mut self, data: LibraryData) { - self.libraries_added.push(data) - } - pub fn set_crate_graph(&mut self, graph: CrateGraph) { - self.crate_graph = Some(graph); - } -} - -#[derive(Debug)] -pub struct SourceChange { - pub label: String, - pub source_file_edits: Vec, - pub file_system_edits: Vec, - pub cursor_position: Option, -} - -#[derive(Debug)] -pub struct SourceFileEdit { - pub file_id: FileId, - pub edit: TextEdit, -} - -#[derive(Debug)] -pub enum FileSystemEdit { - CreateFile { - source_root: SourceRootId, - path: RelativePathBuf, - }, - MoveFile { - src: FileId, - dst_source_root: SourceRootId, - dst_path: RelativePathBuf, - }, -} - -#[derive(Debug)] -pub struct Diagnostic { - pub message: String, - pub range: TextRange, - pub fix: Option, - pub severity: Severity, -} - -#[derive(Debug)] -pub struct Query { - query: String, - lowercased: String, - only_types: bool, - libs: bool, - exact: bool, - limit: usize, -} - -impl Query { - pub fn new(query: String) -> Query { - let lowercased = query.to_lowercase(); - Query { - query, - lowercased, - only_types: false, - libs: false, - exact: false, - limit: usize::max_value(), - } - } - pub fn only_types(&mut self) { - self.only_types = true; - } - pub fn libs(&mut self) { - self.libs = true; - } - pub fn exact(&mut self) { - self.exact = true; - } - pub fn limit(&mut self, limit: usize) { - self.limit = limit - } -} - -/// `NavigationTarget` represents and element in the editor's UI whihc you can -/// click on to navigate to a particular piece of code. -/// -/// Typically, a `NavigationTarget` corresponds to some element in the source -/// code, like a function or a struct, but this is not strictly required. -#[derive(Debug, Clone)] -pub struct NavigationTarget { - file_id: FileId, - name: SmolStr, - kind: SyntaxKind, - range: TextRange, - // Should be DefId ideally - ptr: Option, -} - -impl NavigationTarget { - fn from_symbol(symbol: FileSymbol) -> NavigationTarget { - NavigationTarget { - file_id: symbol.file_id, - name: symbol.name.clone(), - kind: symbol.ptr.kind(), - range: symbol.ptr.range(), - ptr: Some(symbol.ptr.clone()), - } - } - pub fn name(&self) -> &SmolStr { - &self.name - } - pub fn kind(&self) -> SyntaxKind { - self.kind - } - pub fn file_id(&self) -> FileId { - self.file_id - } - pub fn range(&self) -> TextRange { - self.range - } -} - -#[derive(Debug)] -pub struct RangeInfo { - pub range: TextRange, - pub info: T, -} - -impl RangeInfo { - fn new(range: TextRange, info: T) -> RangeInfo { - RangeInfo { range, info } - } -} - -#[derive(Debug)] -pub struct CallInfo { - pub label: String, - pub doc: Option, - pub parameters: Vec, - pub active_parameter: Option, -} - -/// `AnalysisHost` stores the current state of the world. -#[derive(Debug, Default)] -pub struct AnalysisHost { - db: db::RootDatabase, -} - -impl AnalysisHost { - /// Returns a snapshot of the current state, which you can query for - /// semantic information. - pub fn analysis(&self) -> Analysis { - Analysis { - db: self.db.snapshot(), - } - } - /// Applies changes to the current state of the world. If there are - /// outstanding snapshots, they will be canceled. - pub fn apply_change(&mut self, change: AnalysisChange) { - self.db.apply_change(change) - } -} - -/// Analysis is a snapshot of a world state at a moment in time. It is the main -/// entry point for asking semantic information about the world. When the world -/// state is advanced using `AnalysisHost::apply_change` method, all existing -/// `Analysis` are canceled (most method return `Err(Canceled)`). -#[derive(Debug)] -pub struct Analysis { - db: salsa::Snapshot, -} - -impl Analysis { - /// Gets the text of the source file. - pub fn file_text(&self, file_id: FileId) -> Arc { - self.db.file_text(file_id) - } - /// Gets the syntax tree of the file. - pub fn file_syntax(&self, file_id: FileId) -> TreePtr { - self.db.source_file(file_id).clone() - } - /// Gets the file's `LineIndex`: data structure to convert between absolute - /// offsets and line/column representation. - pub fn file_line_index(&self, file_id: FileId) -> Arc { - self.db.line_index(file_id) - } - /// Selects the next syntactic nodes encopasing the range. - pub fn extend_selection(&self, frange: FileRange) -> TextRange { - extend_selection::extend_selection(&self.db, frange) - } - /// Returns position of the mathcing brace (all types of braces are - /// supported). - pub fn matching_brace(&self, file: &SourceFile, offset: TextUnit) -> Option { - ra_ide_api_light::matching_brace(file, offset) - } - /// Returns a syntax tree represented as `String`, for debug purposes. - // FIXME: use a better name here. - pub fn syntax_tree(&self, file_id: FileId) -> String { - let file = self.db.source_file(file_id); - ra_ide_api_light::syntax_tree(&file) - } - /// Returns an edit to remove all newlines in the range, cleaning up minor - /// stuff like trailing commas. - pub fn join_lines(&self, frange: FileRange) -> SourceChange { - let file = self.db.source_file(frange.file_id); - SourceChange::from_local_edit( - frange.file_id, - ra_ide_api_light::join_lines(&file, frange.range), - ) - } - /// Returns an edit which should be applied when opening a new line, fixing - /// up minor stuff like continuing the comment. - pub fn on_enter(&self, position: FilePosition) -> Option { - let file = self.db.source_file(position.file_id); - let edit = ra_ide_api_light::on_enter(&file, position.offset)?; - Some(SourceChange::from_local_edit(position.file_id, edit)) - } - /// Returns an edit which should be applied after `=` was typed. Primarily, - /// this works when adding `let =`. - // FIXME: use a snippet completion instead of this hack here. - pub fn on_eq_typed(&self, position: FilePosition) -> Option { - let file = self.db.source_file(position.file_id); - let edit = ra_ide_api_light::on_eq_typed(&file, position.offset)?; - Some(SourceChange::from_local_edit(position.file_id, edit)) - } - /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. - pub fn on_dot_typed(&self, position: FilePosition) -> Option { - let file = self.db.source_file(position.file_id); - let edit = ra_ide_api_light::on_dot_typed(&file, position.offset)?; - Some(SourceChange::from_local_edit(position.file_id, edit)) - } - /// Returns a tree representation of symbols in the file. Useful to draw a - /// file outline. - pub fn file_structure(&self, file_id: FileId) -> Vec { - let file = self.db.source_file(file_id); - ra_ide_api_light::file_structure(&file) - } - /// Returns the set of folding ranges. - pub fn folding_ranges(&self, file_id: FileId) -> Vec { - let file = self.db.source_file(file_id); - ra_ide_api_light::folding_ranges(&file) - } - /// Fuzzy searches for a symbol. - pub fn symbol_search(&self, query: Query) -> Cancelable> { - let res = symbol_index::world_symbols(&*self.db, query)? - .into_iter() - .map(NavigationTarget::from_symbol) - .collect(); - Ok(res) - } - pub fn goto_defenition( - &self, - position: FilePosition, - ) -> Cancelable>> { - goto_defenition::goto_defenition(&*self.db, position) - } - /// Finds all usages of the reference at point. - pub fn find_all_refs(&self, position: FilePosition) -> Cancelable> { - self.db.find_all_refs(position) - } - /// Returns a short text descrbing element at position. - pub fn hover(&self, position: FilePosition) -> Cancelable>> { - hover::hover(&*self.db, position) - } - /// Computes parameter information for the given call expression. - pub fn call_info(&self, position: FilePosition) -> Cancelable> { - call_info::call_info(&*self.db, position) - } - /// Returns a `mod name;` declaration which created the current module. - pub fn parent_module(&self, position: FilePosition) -> Cancelable> { - self.db.parent_module(position) - } - /// Returns crates this file belongs too. - pub fn crate_for(&self, file_id: FileId) -> Cancelable> { - self.db.crate_for(file_id) - } - /// Returns the root file of the given crate. - pub fn crate_root(&self, crate_id: CrateId) -> Cancelable { - Ok(self.db.crate_graph().crate_root(crate_id)) - } - /// Returns the set of possible targets to run for the current file. - pub fn runnables(&self, file_id: FileId) -> Cancelable> { - runnables::runnables(&*self.db, file_id) - } - /// Computes syntax highlighting for the given file. - pub fn highlight(&self, file_id: FileId) -> Cancelable> { - syntax_highlighting::highlight(&*self.db, file_id) - } - /// Computes completions at the given position. - pub fn completions(&self, position: FilePosition) -> Cancelable>> { - let completions = completion::completions(&self.db, position)?; - Ok(completions.map(|it| it.into())) - } - /// Computes assists (aks code actons aka intentions) for the given - /// position. - pub fn assists(&self, frange: FileRange) -> Cancelable> { - Ok(self.db.assists(frange)) - } - /// Computes the set of diagnostics for the given file. - pub fn diagnostics(&self, file_id: FileId) -> Cancelable> { - self.db.diagnostics(file_id) - } - /// Computes the type of the expression at the given position. - pub fn type_of(&self, frange: FileRange) -> Cancelable> { - hover::type_of(&*self.db, frange) - } - /// Returns the edit required to rename reference at the position to the new - /// name. - pub fn rename( - &self, - position: FilePosition, - new_name: &str, - ) -> Cancelable> { - self.db.rename(position, new_name) - } -} - -pub struct LibraryData { - root_id: SourceRootId, - root_change: RootChange, - symbol_index: SymbolIndex, -} - -impl fmt::Debug for LibraryData { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("LibraryData") - .field("root_id", &self.root_id) - .field("root_change", &self.root_change) - .field("n_symbols", &self.symbol_index.len()) - .finish() - } -} - -impl LibraryData { - pub fn prepare( - root_id: SourceRootId, - files: Vec<(FileId, RelativePathBuf, Arc)>, - ) -> LibraryData { - let symbol_index = SymbolIndex::for_files(files.par_iter().map(|(file_id, _, text)| { - let file = SourceFile::parse(text); - (*file_id, file) - })); - let mut root_change = RootChange::default(); - root_change.added = files - .into_iter() - .map(|(file_id, path, text)| AddFile { - file_id, - path, - text, - }) - .collect(); - LibraryData { - root_id, - root_change, - symbol_index, - } - } -} - -#[test] -fn analysis_is_send() { - fn is_send() {} - is_send::(); -} diff --git a/crates/ra_analysis/src/mock_analysis.rs b/crates/ra_analysis/src/mock_analysis.rs deleted file mode 100644 index 846c76cfe7..0000000000 --- a/crates/ra_analysis/src/mock_analysis.rs +++ /dev/null @@ -1,135 +0,0 @@ -use std::sync::Arc; - -use relative_path::RelativePathBuf; -use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; -use ra_db::mock::FileMap; - -use crate::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, FilePosition, FileRange, SourceRootId}; - -/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis -/// from a set of in-memory files. -#[derive(Debug, Default)] -pub struct MockAnalysis { - files: Vec<(String, String)>, -} - -impl MockAnalysis { - pub fn new() -> MockAnalysis { - MockAnalysis::default() - } - /// Creates `MockAnalysis` using a fixture data in the following format: - /// - /// ```notrust - /// //- /main.rs - /// mod foo; - /// fn main() {} - /// - /// //- /foo.rs - /// struct Baz; - /// ``` - pub fn with_files(fixture: &str) -> MockAnalysis { - let mut res = MockAnalysis::new(); - for entry in parse_fixture(fixture) { - res.add_file(&entry.meta, &entry.text); - } - res - } - - /// Same as `with_files`, but requires that a single file contains a `<|>` marker, - /// whose position is also returned. - pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) { - let mut position = None; - let mut res = MockAnalysis::new(); - for entry in parse_fixture(fixture) { - if entry.text.contains(CURSOR_MARKER) { - assert!( - position.is_none(), - "only one marker (<|>) per fixture is allowed" - ); - position = Some(res.add_file_with_position(&entry.meta, &entry.text)); - } else { - res.add_file(&entry.meta, &entry.text); - } - } - let position = position.expect("expected a marker (<|>)"); - (res, position) - } - - pub fn add_file(&mut self, path: &str, text: &str) -> FileId { - let file_id = FileId((self.files.len() + 1) as u32); - self.files.push((path.to_string(), text.to_string())); - file_id - } - pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { - let (offset, text) = extract_offset(text); - let file_id = FileId((self.files.len() + 1) as u32); - self.files.push((path.to_string(), text.to_string())); - FilePosition { file_id, offset } - } - pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { - let (range, text) = extract_range(text); - let file_id = FileId((self.files.len() + 1) as u32); - self.files.push((path.to_string(), text.to_string())); - FileRange { file_id, range } - } - pub fn id_of(&self, path: &str) -> FileId { - let (idx, _) = self - .files - .iter() - .enumerate() - .find(|(_, (p, _text))| path == p) - .expect("no file in this mock"); - FileId(idx as u32 + 1) - } - pub fn analysis_host(self) -> AnalysisHost { - let mut host = AnalysisHost::default(); - let mut file_map = FileMap::default(); - let source_root = SourceRootId(0); - let mut change = AnalysisChange::new(); - change.add_root(source_root, true); - let mut crate_graph = CrateGraph::default(); - for (path, contents) in self.files.into_iter() { - assert!(path.starts_with('/')); - let path = RelativePathBuf::from_path(&path[1..]).unwrap(); - let file_id = file_map.add(path.clone()); - if path == "/lib.rs" || path == "/main.rs" { - crate_graph.add_crate_root(file_id); - } - change.add_file(source_root, file_id, path, Arc::new(contents)); - } - change.set_crate_graph(crate_graph); - // change.set_file_resolver(Arc::new(file_map)); - host.apply_change(change); - host - } - pub fn analysis(self) -> Analysis { - self.analysis_host().analysis() - } -} - -/// Creates analysis from a multi-file fixture, returns positions marked with <|>. -pub fn analysis_and_position(fixture: &str) -> (Analysis, FilePosition) { - let (mock, position) = MockAnalysis::with_files_and_position(fixture); - (mock.analysis(), position) -} - -/// Creates analysis for a single file. -pub fn single_file(code: &str) -> (Analysis, FileId) { - let mut mock = MockAnalysis::new(); - let file_id = mock.add_file("/main.rs", code); - (mock.analysis(), file_id) -} - -/// Creates analysis for a single file, returns position marked with <|>. -pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) { - let mut mock = MockAnalysis::new(); - let pos = mock.add_file_with_position("/main.rs", code); - (mock.analysis(), pos) -} - -/// Creates analysis for a single file, returns range marked with a pair of <|>. -pub fn single_file_with_range(code: &str) -> (Analysis, FileRange) { - let mut mock = MockAnalysis::new(); - let pos = mock.add_file_with_range("/main.rs", code); - (mock.analysis(), pos) -} diff --git a/crates/ra_analysis/src/runnables.rs b/crates/ra_analysis/src/runnables.rs deleted file mode 100644 index 98b1d2d552..0000000000 --- a/crates/ra_analysis/src/runnables.rs +++ /dev/null @@ -1,89 +0,0 @@ -use itertools::Itertools; -use ra_syntax::{ - TextRange, SyntaxNode, - ast::{self, AstNode, NameOwner, ModuleItemOwner}, -}; -use ra_db::{Cancelable, SyntaxDatabase}; - -use crate::{db::RootDatabase, FileId}; - -#[derive(Debug)] -pub struct Runnable { - pub range: TextRange, - pub kind: RunnableKind, -} - -#[derive(Debug)] -pub enum RunnableKind { - Test { name: String }, - TestMod { path: String }, - Bin, -} - -pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Cancelable> { - let source_file = db.source_file(file_id); - let res = source_file - .syntax() - .descendants() - .filter_map(|i| runnable(db, file_id, i)) - .collect(); - Ok(res) -} - -fn runnable(db: &RootDatabase, file_id: FileId, item: &SyntaxNode) -> Option { - if let Some(fn_def) = ast::FnDef::cast(item) { - runnable_fn(fn_def) - } else if let Some(m) = ast::Module::cast(item) { - runnable_mod(db, file_id, m) - } else { - None - } -} - -fn runnable_fn(fn_def: &ast::FnDef) -> Option { - let name = fn_def.name()?.text(); - let kind = if name == "main" { - RunnableKind::Bin - } else if fn_def.has_atom_attr("test") { - RunnableKind::Test { - name: name.to_string(), - } - } else { - return None; - }; - Some(Runnable { - range: fn_def.syntax().range(), - kind, - }) -} - -fn runnable_mod(db: &RootDatabase, file_id: FileId, module: &ast::Module) -> Option { - let has_test_function = module - .item_list()? - .items() - .filter_map(|it| match it.kind() { - ast::ModuleItemKind::FnDef(it) => Some(it), - _ => None, - }) - .any(|f| f.has_atom_attr("test")); - if !has_test_function { - return None; - } - let range = module.syntax().range(); - let module = - hir::source_binder::module_from_child_node(db, file_id, module.syntax()).ok()??; - - // FIXME: thread cancellation instead of `.ok`ing - let path = module - .path_to_root(db) - .ok()? - .into_iter() - .rev() - .filter_map(|it| it.name(db).ok()) - .filter_map(|it| it) - .join("::"); - Some(Runnable { - range, - kind: RunnableKind::TestMod { path }, - }) -} diff --git a/crates/ra_analysis/src/symbol_index.rs b/crates/ra_analysis/src/symbol_index.rs deleted file mode 100644 index 8dd15b40e5..0000000000 --- a/crates/ra_analysis/src/symbol_index.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! This module handles fuzzy-searching of functions, structs and other symbols -//! by name across the whole workspace and dependencies. -//! -//! It works by building an incrementally-updated text-search index of all -//! symbols. The backbone of the index is the **awesome** `fst` crate by -//! @BurntSushi. -//! -//! In a nutshell, you give a set of strings to the `fst`, and it builds a -//! finite state machine describing this set of strtings. The strings which -//! could fuzzy-match a pattern can also be described by a finite state machine. -//! What is freakingly cool is that you can now traverse both state machines in -//! lock-step to enumerate the strings which are both in the input set and -//! fuzz-match the query. Or, more formally, given two langauges described by -//! fsts, one can build an product fst which describes the intersection of the -//! languages. -//! -//! `fst` does not support cheap updating of the index, but it supports unioning -//! of state machines. So, to account for changing source code, we build an fst -//! for each library (which is assumed to never change) and an fst for each rust -//! file in the current workspace, and run a query aginst the union of all -//! thouse fsts. -use std::{ - cmp::Ordering, - hash::{Hash, Hasher}, - sync::Arc, -}; - -use fst::{self, Streamer}; -use ra_syntax::{ - SyntaxNode, SourceFile, SmolStr, TreePtr, AstNode, - algo::{visit::{visitor, Visitor}, find_covering_node}, - SyntaxKind::{self, *}, - ast::{self, NameOwner}, -}; -use ra_db::{SourceRootId, FilesDatabase, LocalSyntaxPtr}; -use salsa::ParallelDatabase; -use rayon::prelude::*; - -use crate::{ - Cancelable, FileId, Query, - db::RootDatabase, -}; - -salsa::query_group! { - pub(crate) trait SymbolsDatabase: hir::db::HirDatabase { - fn file_symbols(file_id: FileId) -> Cancelable> { - type FileSymbolsQuery; - } - fn library_symbols(id: SourceRootId) -> Arc { - type LibrarySymbolsQuery; - storage input; - } - } -} - -fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Cancelable> { - db.check_canceled()?; - let source_file = db.source_file(file_id); - let mut symbols = source_file - .syntax() - .descendants() - .filter_map(to_symbol) - .map(move |(name, ptr)| FileSymbol { name, ptr, file_id }) - .collect::>(); - - for (name, text_range) in hir::source_binder::macro_symbols(db, file_id)? { - let node = find_covering_node(source_file.syntax(), text_range); - let ptr = LocalSyntaxPtr::new(node); - symbols.push(FileSymbol { file_id, name, ptr }) - } - - Ok(Arc::new(SymbolIndex::new(symbols))) -} - -pub(crate) fn world_symbols(db: &RootDatabase, query: Query) -> Cancelable> { - /// Need to wrap Snapshot to provide `Clone` impl for `map_with` - struct Snap(salsa::Snapshot); - impl Clone for Snap { - fn clone(&self) -> Snap { - Snap(self.0.snapshot()) - } - } - - let buf: Vec> = if query.libs { - let snap = Snap(db.snapshot()); - db.library_roots() - .par_iter() - .map_with(snap, |db, &lib_id| db.0.library_symbols(lib_id)) - .collect() - } else { - let mut files = Vec::new(); - for &root in db.local_roots().iter() { - let sr = db.source_root(root); - files.extend(sr.files.values().map(|&it| it)) - } - - let snap = Snap(db.snapshot()); - files - .par_iter() - .map_with(snap, |db, &file_id| db.0.file_symbols(file_id)) - .filter_map(|it| it.ok()) - .collect() - }; - Ok(query.search(&buf)) -} - -#[derive(Default, Debug)] -pub(crate) struct SymbolIndex { - symbols: Vec, - map: fst::Map, -} - -impl PartialEq for SymbolIndex { - fn eq(&self, other: &SymbolIndex) -> bool { - self.symbols == other.symbols - } -} - -impl Eq for SymbolIndex {} - -impl Hash for SymbolIndex { - fn hash(&self, hasher: &mut H) { - self.symbols.hash(hasher) - } -} - -impl SymbolIndex { - fn new(mut symbols: Vec) -> SymbolIndex { - fn cmp(s1: &FileSymbol, s2: &FileSymbol) -> Ordering { - unicase::Ascii::new(s1.name.as_str()).cmp(&unicase::Ascii::new(s2.name.as_str())) - } - symbols.par_sort_by(cmp); - symbols.dedup_by(|s1, s2| cmp(s1, s2) == Ordering::Equal); - let names = symbols.iter().map(|it| it.name.as_str().to_lowercase()); - let map = fst::Map::from_iter(names.into_iter().zip(0u64..)).unwrap(); - SymbolIndex { symbols, map } - } - - pub(crate) fn len(&self) -> usize { - self.symbols.len() - } - - pub(crate) fn for_files( - files: impl ParallelIterator)>, - ) -> SymbolIndex { - let symbols = files - .flat_map(|(file_id, file)| { - file.syntax() - .descendants() - .filter_map(to_symbol) - .map(move |(name, ptr)| FileSymbol { name, ptr, file_id }) - .collect::>() - }) - .collect::>(); - SymbolIndex::new(symbols) - } -} - -impl Query { - pub(crate) fn search(self, indices: &[Arc]) -> Vec { - let mut op = fst::map::OpBuilder::new(); - for file_symbols in indices.iter() { - let automaton = fst::automaton::Subsequence::new(&self.lowercased); - op = op.add(file_symbols.map.search(automaton)) - } - let mut stream = op.union(); - let mut res = Vec::new(); - while let Some((_, indexed_values)) = stream.next() { - if res.len() >= self.limit { - break; - } - for indexed_value in indexed_values { - let file_symbols = &indices[indexed_value.index]; - let idx = indexed_value.value as usize; - - let symbol = &file_symbols.symbols[idx]; - if self.only_types && !is_type(symbol.ptr.kind()) { - continue; - } - if self.exact && symbol.name != self.query { - continue; - } - res.push(symbol.clone()); - } - } - res - } -} - -fn is_type(kind: SyntaxKind) -> bool { - match kind { - STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_DEF => true, - _ => false, - } -} - -/// The actual data that is stored in the index. It should be as compact as -/// possible. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct FileSymbol { - pub(crate) file_id: FileId, - pub(crate) name: SmolStr, - pub(crate) ptr: LocalSyntaxPtr, -} - -fn to_symbol(node: &SyntaxNode) -> Option<(SmolStr, LocalSyntaxPtr)> { - fn decl(node: &N) -> Option<(SmolStr, LocalSyntaxPtr)> { - let name = node.name()?.text().clone(); - let ptr = LocalSyntaxPtr::new(node.syntax()); - Some((name, ptr)) - } - visitor() - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .accept(node)? -} diff --git a/crates/ra_analysis/src/syntax_highlighting.rs b/crates/ra_analysis/src/syntax_highlighting.rs deleted file mode 100644 index cb19e9515a..0000000000 --- a/crates/ra_analysis/src/syntax_highlighting.rs +++ /dev/null @@ -1,92 +0,0 @@ -use ra_syntax::{ast, AstNode,}; -use ra_db::SyntaxDatabase; - -use crate::{ - FileId, Cancelable, HighlightedRange, - db::RootDatabase, -}; - -pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Cancelable> { - let source_file = db.source_file(file_id); - let mut res = ra_ide_api_light::highlight(source_file.syntax()); - for macro_call in source_file - .syntax() - .descendants() - .filter_map(ast::MacroCall::cast) - { - if let Some((off, exp)) = hir::MacroDef::ast_expand(macro_call) { - let mapped_ranges = ra_ide_api_light::highlight(&exp.syntax()) - .into_iter() - .filter_map(|r| { - let mapped_range = exp.map_range_back(r.range)?; - let res = HighlightedRange { - range: mapped_range + off, - tag: r.tag, - }; - Some(res) - }); - res.extend(mapped_ranges); - } - } - Ok(res) -} - -#[cfg(test)] -mod tests { - use crate::mock_analysis::single_file; - use test_utils::assert_eq_dbg; - - #[test] - fn highlights_code_inside_macros() { - let (analysis, file_id) = single_file( - " - fn main() { - ctry!({ let x = 92; x}); - vec![{ let x = 92; x}]; - } - ", - ); - let highlights = analysis.highlight(file_id).unwrap(); - assert_eq_dbg( - r#"[HighlightedRange { range: [13; 15), tag: "keyword" }, - HighlightedRange { range: [16; 20), tag: "function" }, - HighlightedRange { range: [41; 46), tag: "macro" }, - HighlightedRange { range: [49; 52), tag: "keyword" }, - HighlightedRange { range: [57; 59), tag: "literal" }, - HighlightedRange { range: [82; 86), tag: "macro" }, - HighlightedRange { range: [89; 92), tag: "keyword" }, - HighlightedRange { range: [97; 99), tag: "literal" }, - HighlightedRange { range: [49; 52), tag: "keyword" }, - HighlightedRange { range: [53; 54), tag: "function" }, - HighlightedRange { range: [57; 59), tag: "literal" }, - HighlightedRange { range: [61; 62), tag: "text" }, - HighlightedRange { range: [89; 92), tag: "keyword" }, - HighlightedRange { range: [93; 94), tag: "function" }, - HighlightedRange { range: [97; 99), tag: "literal" }, - HighlightedRange { range: [101; 102), tag: "text" }]"#, - &highlights, - ) - } - - // FIXME: this test is not really necessary: artifact of the inital hacky - // macros implementation. - #[test] - fn highlight_query_group_macro() { - let (analysis, file_id) = single_file( - " - salsa::query_group! { - pub trait HirDatabase: SyntaxDatabase {} - } - ", - ); - let highlights = analysis.highlight(file_id).unwrap(); - assert_eq_dbg( - r#"[HighlightedRange { range: [20; 32), tag: "macro" }, - HighlightedRange { range: [13; 18), tag: "text" }, - HighlightedRange { range: [51; 54), tag: "keyword" }, - HighlightedRange { range: [55; 60), tag: "keyword" }, - HighlightedRange { range: [61; 72), tag: "function" }]"#, - &highlights, - ) - } -} diff --git a/crates/ra_analysis/tests/test/main.rs b/crates/ra_analysis/tests/test/main.rs deleted file mode 100644 index 2c0735cb50..0000000000 --- a/crates/ra_analysis/tests/test/main.rs +++ /dev/null @@ -1,249 +0,0 @@ -mod runnables; - -use ra_syntax::TextRange; -use test_utils::{assert_eq_dbg, assert_eq_text}; - -use ra_analysis::{ - mock_analysis::{analysis_and_position, single_file, single_file_with_position, MockAnalysis}, - AnalysisChange, CrateGraph, FileId, Query -}; - -#[test] -fn test_unresolved_module_diagnostic() { - let (analysis, file_id) = single_file("mod foo;"); - let diagnostics = analysis.diagnostics(file_id).unwrap(); - assert_eq_dbg( - r#"[Diagnostic { - message: "unresolved module", - range: [4; 7), - fix: Some(SourceChange { - label: "create module", - source_file_edits: [], - file_system_edits: [CreateFile { source_root: SourceRootId(0), path: "foo.rs" }], - cursor_position: None }), - severity: Error }]"#, - &diagnostics, - ); -} - -// FIXME: move this test to hir -#[test] -fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() { - let (analysis, file_id) = single_file("mod foo {}"); - let diagnostics = analysis.diagnostics(file_id).unwrap(); - assert_eq_dbg(r#"[]"#, &diagnostics); -} - -#[test] -fn test_resolve_parent_module() { - let (analysis, pos) = analysis_and_position( - " - //- /lib.rs - mod foo; - //- /foo.rs - <|>// empty - ", - ); - let symbols = analysis.parent_module(pos).unwrap(); - assert_eq_dbg( - r#"[NavigationTarget { file_id: FileId(1), name: "foo", kind: MODULE, range: [4; 7), ptr: None }]"#, - &symbols, - ); -} - -#[test] -fn test_resolve_parent_module_for_inline() { - let (analysis, pos) = analysis_and_position( - " - //- /lib.rs - mod foo { - mod bar { - mod baz { <|> } - } - } - ", - ); - let symbols = analysis.parent_module(pos).unwrap(); - assert_eq_dbg( - r#"[NavigationTarget { file_id: FileId(1), name: "baz", kind: MODULE, range: [36; 39), ptr: None }]"#, - &symbols, - ); -} - -#[test] -fn test_resolve_crate_root() { - let mock = MockAnalysis::with_files( - " - //- /bar.rs - mod foo; - //- /bar/foo.rs - // emtpy <|> - ", - ); - let root_file = mock.id_of("/bar.rs"); - let mod_file = mock.id_of("/bar/foo.rs"); - let mut host = mock.analysis_host(); - assert!(host.analysis().crate_for(mod_file).unwrap().is_empty()); - - let mut crate_graph = CrateGraph::default(); - let crate_id = crate_graph.add_crate_root(root_file); - let mut change = AnalysisChange::new(); - change.set_crate_graph(crate_graph); - host.apply_change(change); - - assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]); -} - -fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { - let (analysis, position) = single_file_with_position(text); - analysis.find_all_refs(position).unwrap() -} - -#[test] -fn test_find_all_refs_for_local() { - let code = r#" - fn main() { - let mut i = 1; - let j = 1; - i = i<|> + j; - - { - i = 0; - } - - i = 5; - }"#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 5); -} - -#[test] -fn test_find_all_refs_for_param_inside() { - let code = r#" - fn foo(i : u32) -> u32 { - i<|> - }"#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 2); -} - -#[test] -fn test_find_all_refs_for_fn_param() { - let code = r#" - fn foo(i<|> : u32) -> u32 { - i - }"#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 2); -} -#[test] -fn test_rename_for_local() { - test_rename( - r#" - fn main() { - let mut i = 1; - let j = 1; - i = i<|> + j; - - { - i = 0; - } - - i = 5; - }"#, - "k", - r#" - fn main() { - let mut k = 1; - let j = 1; - k = k + j; - - { - k = 0; - } - - k = 5; - }"#, - ); -} - -#[test] -fn test_rename_for_param_inside() { - test_rename( - r#" - fn foo(i : u32) -> u32 { - i<|> - }"#, - "j", - r#" - fn foo(j : u32) -> u32 { - j - }"#, - ); -} - -#[test] -fn test_rename_refs_for_fn_param() { - test_rename( - r#" - fn foo(i<|> : u32) -> u32 { - i - }"#, - "new_name", - r#" - fn foo(new_name : u32) -> u32 { - new_name - }"#, - ); -} - -#[test] -fn test_rename_for_mut_param() { - test_rename( - r#" - fn foo(mut i<|> : u32) -> u32 { - i - }"#, - "new_name", - r#" - fn foo(mut new_name : u32) -> u32 { - new_name - }"#, - ); -} - -fn test_rename(text: &str, new_name: &str, expected: &str) { - let (analysis, position) = single_file_with_position(text); - let edits = analysis.rename(position, new_name).unwrap(); - let mut text_edit_bulder = ra_text_edit::TextEditBuilder::default(); - let mut file_id: Option = None; - for edit in edits { - file_id = Some(edit.file_id); - for atom in edit.edit.as_atoms() { - text_edit_bulder.replace(atom.delete, atom.insert.clone()); - } - } - let result = text_edit_bulder - .finish() - .apply(&*analysis.file_text(file_id.unwrap())); - assert_eq_text!(expected, &*result); -} - -#[test] -fn world_symbols_include_stuff_from_macros() { - let (analysis, _) = single_file( - " -salsa::query_group! { -pub trait HirDatabase: SyntaxDatabase {} -} - ", - ); - - let mut symbols = analysis.symbol_search(Query::new("Hir".into())).unwrap(); - let s = symbols.pop().unwrap(); - assert_eq!(s.name(), "HirDatabase"); - assert_eq!(s.range(), TextRange::from_to(33.into(), 44.into())); -} diff --git a/crates/ra_analysis/tests/test/runnables.rs b/crates/ra_analysis/tests/test/runnables.rs deleted file mode 100644 index e6e0afbc31..0000000000 --- a/crates/ra_analysis/tests/test/runnables.rs +++ /dev/null @@ -1,109 +0,0 @@ -use test_utils::assert_eq_dbg; - -use ra_analysis::mock_analysis::analysis_and_position; - -#[test] -fn test_runnables() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - fn main() {} - - #[test] - fn test_foo() {} - - #[test] - #[ignore] - fn test_foo() {} - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert_eq_dbg( - r#"[Runnable { range: [1; 21), kind: Bin }, - Runnable { range: [22; 46), kind: Test { name: "test_foo" } }, - Runnable { range: [47; 81), kind: Test { name: "test_foo" } }]"#, - &runnables, - ) -} - -#[test] -fn test_runnables_module() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - mod test_mod { - #[test] - fn test_foo1() {} - } - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert_eq_dbg( - r#"[Runnable { range: [1; 59), kind: TestMod { path: "test_mod" } }, - Runnable { range: [28; 57), kind: Test { name: "test_foo1" } }]"#, - &runnables, - ) -} - -#[test] -fn test_runnables_one_depth_layer_module() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - mod foo { - mod test_mod { - #[test] - fn test_foo1() {} - } - } - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert_eq_dbg( - r#"[Runnable { range: [23; 85), kind: TestMod { path: "foo::test_mod" } }, - Runnable { range: [46; 79), kind: Test { name: "test_foo1" } }]"#, - &runnables, - ) -} - -#[test] -fn test_runnables_multiple_depth_module() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - mod foo { - mod bar { - mod test_mod { - #[test] - fn test_foo1() {} - } - } - } - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert_eq_dbg( - r#"[Runnable { range: [41; 115), kind: TestMod { path: "foo::bar::test_mod" } }, - Runnable { range: [68; 105), kind: Test { name: "test_foo1" } }]"#, - &runnables, - ) -} - -#[test] -fn test_runnables_no_test_function_in_module() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - mod test_mod { - fn foo1() {} - } - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert_eq_dbg(r#"[]"#, &runnables) -}