From 1967884d6836219ee78a754ca5c66ac781351559 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 8 Jan 2019 22:17:36 +0300 Subject: rename ra_editor -> ra_ide_api_light --- crates/ra_editor/Cargo.toml | 19 - crates/ra_editor/src/assists.rs | 209 ------ crates/ra_editor/src/assists/add_derive.rs | 84 --- crates/ra_editor/src/assists/add_impl.rs | 66 -- crates/ra_editor/src/assists/change_visibility.rs | 116 --- crates/ra_editor/src/assists/flip_comma.rs | 31 - crates/ra_editor/src/assists/introduce_variable.rs | 144 ---- .../src/assists/replace_if_let_with_match.rs | 92 --- crates/ra_editor/src/assists/split_import.rs | 56 -- crates/ra_editor/src/diagnostics.rs | 266 ------- crates/ra_editor/src/extend_selection.rs | 281 ------- crates/ra_editor/src/folding_ranges.rs | 297 -------- crates/ra_editor/src/lib.rs | 168 ----- crates/ra_editor/src/line_index.rs | 399 ---------- crates/ra_editor/src/line_index_utils.rs | 363 --------- crates/ra_editor/src/structure.rs | 129 ---- crates/ra_editor/src/test_utils.rs | 41 - crates/ra_editor/src/typing.rs | 826 --------------------- 18 files changed, 3587 deletions(-) delete mode 100644 crates/ra_editor/Cargo.toml delete mode 100644 crates/ra_editor/src/assists.rs delete mode 100644 crates/ra_editor/src/assists/add_derive.rs delete mode 100644 crates/ra_editor/src/assists/add_impl.rs delete mode 100644 crates/ra_editor/src/assists/change_visibility.rs delete mode 100644 crates/ra_editor/src/assists/flip_comma.rs delete mode 100644 crates/ra_editor/src/assists/introduce_variable.rs delete mode 100644 crates/ra_editor/src/assists/replace_if_let_with_match.rs delete mode 100644 crates/ra_editor/src/assists/split_import.rs delete mode 100644 crates/ra_editor/src/diagnostics.rs delete mode 100644 crates/ra_editor/src/extend_selection.rs delete mode 100644 crates/ra_editor/src/folding_ranges.rs delete mode 100644 crates/ra_editor/src/lib.rs delete mode 100644 crates/ra_editor/src/line_index.rs delete mode 100644 crates/ra_editor/src/line_index_utils.rs delete mode 100644 crates/ra_editor/src/structure.rs delete mode 100644 crates/ra_editor/src/test_utils.rs delete mode 100644 crates/ra_editor/src/typing.rs (limited to 'crates/ra_editor') diff --git a/crates/ra_editor/Cargo.toml b/crates/ra_editor/Cargo.toml deleted file mode 100644 index a97d2308f..000000000 --- a/crates/ra_editor/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -edition = "2018" -name = "ra_editor" -version = "0.1.0" -authors = ["Aleksey Kladov "] -publish = false - -[dependencies] -itertools = "0.8.0" -superslice = "0.1.0" -join_to_string = "0.1.1" -rustc-hash = "1.0" - -ra_syntax = { path = "../ra_syntax" } -ra_text_edit = { path = "../ra_text_edit" } - -[dev-dependencies] -test_utils = { path = "../test_utils" } -proptest = "0.8.7" diff --git a/crates/ra_editor/src/assists.rs b/crates/ra_editor/src/assists.rs deleted file mode 100644 index 83eabfc85..000000000 --- a/crates/ra_editor/src/assists.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! This modules contains various "assits": suggestions for source code edits -//! which are likely to occur at a given cursor positon. For example, if the -//! cursor is on the `,`, a possible assist is swapping the elments around the -//! comma. - -mod flip_comma; -mod add_derive; -mod add_impl; -mod introduce_variable; -mod change_visibility; -mod split_import; -mod replace_if_let_with_match; - -use ra_text_edit::{TextEdit, TextEditBuilder}; -use ra_syntax::{ - Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode, - algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset}, - ast::{self, AstToken}, -}; -use itertools::Itertools; - -pub use self::{ - flip_comma::flip_comma, - add_derive::add_derive, - add_impl::add_impl, - introduce_variable::introduce_variable, - change_visibility::change_visibility, - split_import::split_import, - replace_if_let_with_match::replace_if_let_with_match, -}; - -/// Return all the assists applicable at the given position. -pub fn assists(file: &SourceFile, range: TextRange) -> Vec { - let ctx = AssistCtx::new(file, range); - [ - flip_comma, - add_derive, - add_impl, - introduce_variable, - change_visibility, - split_import, - replace_if_let_with_match, - ] - .iter() - .filter_map(|&assist| ctx.clone().apply(assist)) - .collect() -} - -#[derive(Debug)] -pub struct LocalEdit { - pub label: String, - pub edit: TextEdit, - pub cursor_position: Option, -} - -fn non_trivia_sibling(node: &SyntaxNode, direction: Direction) -> Option<&SyntaxNode> { - node.siblings(direction) - .skip(1) - .find(|node| !node.kind().is_trivia()) -} - -/// `AssistCtx` allows to apply an assist or check if it could be applied. -/// -/// Assists use a somewhat overengeneered approach, given the current needs. The -/// assists workflow consists of two phases. In the first phase, a user asks for -/// the list of available assists. In the second phase, the user picks a -/// particular assist and it gets applied. -/// -/// There are two peculiarities here: -/// -/// * first, we ideally avoid computing more things then neccessary to answer -/// "is assist applicable" in the first phase. -/// * second, when we are appling assist, we don't have a gurantee that there -/// weren't any changes between the point when user asked for assists and when -/// they applied a particular assist. So, when applying assist, we need to do -/// all the checks from scratch. -/// -/// To avoid repeating the same code twice for both "check" and "apply" -/// functions, we use an approach remeniscent of that of Django's function based -/// views dealing with forms. Each assist receives a runtime parameter, -/// `should_compute_edit`. It first check if an edit is applicable (potentially -/// computing info required to compute the actual edit). If it is applicable, -/// and `should_compute_edit` is `true`, it then computes the actual edit. -/// -/// So, to implement the original assists workflow, we can first apply each edit -/// with `should_compute_edit = false`, and then applying the selected edit -/// again, with `should_compute_edit = true` this time. -/// -/// Note, however, that we don't actually use such two-phase logic at the -/// moment, because the LSP API is pretty awkward in this place, and it's much -/// easier to just compute the edit eagarly :-) -#[derive(Debug, Clone)] -pub struct AssistCtx<'a> { - source_file: &'a SourceFile, - range: TextRange, - should_compute_edit: bool, -} - -#[derive(Debug)] -pub enum Assist { - Applicable, - Edit(LocalEdit), -} - -#[derive(Default)] -struct AssistBuilder { - edit: TextEditBuilder, - cursor_position: Option, -} - -impl<'a> AssistCtx<'a> { - pub fn new(source_file: &'a SourceFile, range: TextRange) -> AssistCtx { - AssistCtx { - source_file, - range, - should_compute_edit: false, - } - } - - pub fn apply(mut self, assist: fn(AssistCtx) -> Option) -> Option { - self.should_compute_edit = true; - match assist(self) { - None => None, - Some(Assist::Edit(e)) => Some(e), - Some(Assist::Applicable) => unreachable!(), - } - } - - pub fn check(mut self, assist: fn(AssistCtx) -> Option) -> bool { - self.should_compute_edit = false; - match assist(self) { - None => false, - Some(Assist::Edit(_)) => unreachable!(), - Some(Assist::Applicable) => true, - } - } - - fn build(self, label: impl Into, f: impl FnOnce(&mut AssistBuilder)) -> Option { - if !self.should_compute_edit { - return Some(Assist::Applicable); - } - let mut edit = AssistBuilder::default(); - f(&mut edit); - Some(Assist::Edit(LocalEdit { - label: label.into(), - edit: edit.edit.finish(), - cursor_position: edit.cursor_position, - })) - } - - pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> { - find_leaf_at_offset(self.source_file.syntax(), self.range.start()) - } - pub(crate) fn node_at_offset(&self) -> Option<&'a N> { - find_node_at_offset(self.source_file.syntax(), self.range.start()) - } - pub(crate) fn covering_node(&self) -> &'a SyntaxNode { - find_covering_node(self.source_file.syntax(), self.range) - } -} - -impl AssistBuilder { - fn replace(&mut self, range: TextRange, replace_with: impl Into) { - self.edit.replace(range, replace_with.into()) - } - fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into) { - let mut replace_with = replace_with.into(); - if let Some(indent) = calc_indent(node) { - replace_with = reindent(&replace_with, indent) - } - self.replace(node.range(), replace_with) - } - #[allow(unused)] - fn delete(&mut self, range: TextRange) { - self.edit.delete(range) - } - fn insert(&mut self, offset: TextUnit, text: impl Into) { - self.edit.insert(offset, text.into()) - } - fn set_cursor(&mut self, offset: TextUnit) { - self.cursor_position = Some(offset) - } -} - -fn calc_indent(node: &SyntaxNode) -> Option<&str> { - let prev = node.prev_sibling()?; - let ws_text = ast::Whitespace::cast(prev)?.text(); - ws_text.rfind('\n').map(|pos| &ws_text[pos + 1..]) -} - -fn reindent(text: &str, indent: &str) -> String { - let indent = format!("\n{}", indent); - text.lines().intersperse(&indent).collect() -} - -#[cfg(test)] -fn check_assist(assist: fn(AssistCtx) -> Option, before: &str, after: &str) { - crate::test_utils::check_action(before, after, |file, off| { - let range = TextRange::offset_len(off, 0.into()); - AssistCtx::new(file, range).apply(assist) - }) -} - -#[cfg(test)] -fn check_assist_range(assist: fn(AssistCtx) -> Option, before: &str, after: &str) { - crate::test_utils::check_action_range(before, after, |file, range| { - AssistCtx::new(file, range).apply(assist) - }) -} diff --git a/crates/ra_editor/src/assists/add_derive.rs b/crates/ra_editor/src/assists/add_derive.rs deleted file mode 100644 index 6e964d011..000000000 --- a/crates/ra_editor/src/assists/add_derive.rs +++ /dev/null @@ -1,84 +0,0 @@ -use ra_syntax::{ - ast::{self, AstNode, AttrsOwner}, - SyntaxKind::{WHITESPACE, COMMENT}, - TextUnit, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn add_derive(ctx: AssistCtx) -> Option { - let nominal = ctx.node_at_offset::()?; - let node_start = derive_insertion_offset(nominal)?; - ctx.build("add `#[derive]`", |edit| { - let derive_attr = nominal - .attrs() - .filter_map(|x| x.as_call()) - .filter(|(name, _arg)| name == "derive") - .map(|(_name, arg)| arg) - .next(); - let offset = match derive_attr { - None => { - edit.insert(node_start, "#[derive()]\n"); - node_start + TextUnit::of_str("#[derive(") - } - Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), - }; - edit.set_cursor(offset) - }) -} - -// Insert `derive` after doc comments. -fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option { - let non_ws_child = nominal - .syntax() - .children() - .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; - Some(non_ws_child.range().start()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn add_derive_new() { - check_assist( - add_derive, - "struct Foo { a: i32, <|>}", - "#[derive(<|>)]\nstruct Foo { a: i32, }", - ); - check_assist( - add_derive, - "struct Foo { <|> a: i32, }", - "#[derive(<|>)]\nstruct Foo { a: i32, }", - ); - } - - #[test] - fn add_derive_existing() { - check_assist( - add_derive, - "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", - "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", - ); - } - - #[test] - fn add_derive_new_with_doc_comment() { - check_assist( - add_derive, - " -/// `Foo` is a pretty important struct. -/// It does stuff. -struct Foo { a: i32<|>, } - ", - " -/// `Foo` is a pretty important struct. -/// It does stuff. -#[derive(<|>)] -struct Foo { a: i32, } - ", - ); - } -} diff --git a/crates/ra_editor/src/assists/add_impl.rs b/crates/ra_editor/src/assists/add_impl.rs deleted file mode 100644 index 2eda7cae2..000000000 --- a/crates/ra_editor/src/assists/add_impl.rs +++ /dev/null @@ -1,66 +0,0 @@ -use join_to_string::join; -use ra_syntax::{ - ast::{self, AstNode, AstToken, NameOwner, TypeParamsOwner}, - TextUnit, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn add_impl(ctx: AssistCtx) -> Option { - let nominal = ctx.node_at_offset::()?; - let name = nominal.name()?; - ctx.build("add impl", |edit| { - let type_params = nominal.type_param_list(); - let start_offset = nominal.syntax().range().end(); - let mut buf = String::new(); - buf.push_str("\n\nimpl"); - if let Some(type_params) = type_params { - type_params.syntax().text().push_to(&mut buf); - } - buf.push_str(" "); - buf.push_str(name.text().as_str()); - if let Some(type_params) = type_params { - let lifetime_params = type_params - .lifetime_params() - .filter_map(|it| it.lifetime()) - .map(|it| it.text()); - let type_params = type_params - .type_params() - .filter_map(|it| it.name()) - .map(|it| it.text()); - join(lifetime_params.chain(type_params)) - .surround_with("<", ">") - .to_buf(&mut buf); - } - buf.push_str(" {\n"); - edit.set_cursor(start_offset + TextUnit::of_str(&buf)); - buf.push_str("\n}"); - edit.insert(start_offset, buf); - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn test_add_impl() { - check_assist( - add_impl, - "struct Foo {<|>}\n", - "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", - ); - check_assist( - add_impl, - "struct Foo {<|>}", - "struct Foo {}\n\nimpl Foo {\n<|>\n}", - ); - check_assist( - add_impl, - "struct Foo<'a, T: Foo<'a>> {<|>}", - "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", - ); - } - -} diff --git a/crates/ra_editor/src/assists/change_visibility.rs b/crates/ra_editor/src/assists/change_visibility.rs deleted file mode 100644 index 89729e2c2..000000000 --- a/crates/ra_editor/src/assists/change_visibility.rs +++ /dev/null @@ -1,116 +0,0 @@ -use ra_syntax::{ - AstNode, - ast::{self, VisibilityOwner, NameOwner}, - SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT}, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn change_visibility(ctx: AssistCtx) -> Option { - if let Some(vis) = ctx.node_at_offset::() { - return change_vis(ctx, vis); - } - add_vis(ctx) -} - -fn add_vis(ctx: AssistCtx) -> Option { - let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() { - FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, - _ => false, - }); - - let offset = if let Some(keyword) = item_keyword { - let parent = keyword.parent()?; - let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; - // Parent is not a definition, can't add visibility - if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { - return None; - } - // Already have visibility, do nothing - if parent.children().any(|child| child.kind() == VISIBILITY) { - return None; - } - parent.range().start() - } else { - let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?; - let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?; - if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() { - return None; - } - field.syntax().range().start() - }; - - ctx.build("make pub(crate)", |edit| { - edit.insert(offset, "pub(crate) "); - edit.set_cursor(offset); - }) -} - -fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option { - if vis.syntax().text() != "pub" { - return None; - } - ctx.build("chage to pub(crate)", |edit| { - edit.replace(vis.syntax().range(), "pub(crate)"); - edit.set_cursor(vis.syntax().range().start()); - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn change_visibility_adds_pub_crate_to_items() { - check_assist( - change_visibility, - "<|>fn foo() {}", - "<|>pub(crate) fn foo() {}", - ); - check_assist( - change_visibility, - "f<|>n foo() {}", - "<|>pub(crate) fn foo() {}", - ); - check_assist( - change_visibility, - "<|>struct Foo {}", - "<|>pub(crate) struct Foo {}", - ); - check_assist( - change_visibility, - "<|>mod foo {}", - "<|>pub(crate) mod foo {}", - ); - check_assist( - change_visibility, - "<|>trait Foo {}", - "<|>pub(crate) trait Foo {}", - ); - check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); - check_assist( - change_visibility, - "unsafe f<|>n foo() {}", - "<|>pub(crate) unsafe fn foo() {}", - ); - } - - #[test] - fn change_visibility_works_with_struct_fields() { - check_assist( - change_visibility, - "struct S { <|>field: u32 }", - "struct S { <|>pub(crate) field: u32 }", - ) - } - - #[test] - fn change_visibility_pub_to_pub_crate() { - check_assist( - change_visibility, - "<|>pub fn foo() {}", - "<|>pub(crate) fn foo() {}", - ) - } -} diff --git a/crates/ra_editor/src/assists/flip_comma.rs b/crates/ra_editor/src/assists/flip_comma.rs deleted file mode 100644 index a343413cc..000000000 --- a/crates/ra_editor/src/assists/flip_comma.rs +++ /dev/null @@ -1,31 +0,0 @@ -use ra_syntax::{ - Direction, - SyntaxKind::COMMA, -}; - -use crate::assists::{non_trivia_sibling, AssistCtx, Assist}; - -pub fn flip_comma(ctx: AssistCtx) -> Option { - let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?; - let prev = non_trivia_sibling(comma, Direction::Prev)?; - let next = non_trivia_sibling(comma, Direction::Next)?; - ctx.build("flip comma", |edit| { - edit.replace(prev.range(), next.text()); - edit.replace(next.range(), prev.text()); - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn flip_comma_works_for_function_parameters() { - check_assist( - flip_comma, - "fn foo(x: i32,<|> y: Result<(), ()>) {}", - "fn foo(y: Result<(), ()>,<|> x: i32) {}", - ) - } -} diff --git a/crates/ra_editor/src/assists/introduce_variable.rs b/crates/ra_editor/src/assists/introduce_variable.rs deleted file mode 100644 index 523ec7034..000000000 --- a/crates/ra_editor/src/assists/introduce_variable.rs +++ /dev/null @@ -1,144 +0,0 @@ -use ra_syntax::{ - ast::{self, AstNode}, - SyntaxKind::WHITESPACE, - SyntaxNode, TextUnit, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option { - let node = ctx.covering_node(); - let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; - - let anchor_stmt = anchor_stmt(expr)?; - let indent = anchor_stmt.prev_sibling()?; - if indent.kind() != WHITESPACE { - return None; - } - ctx.build("introduce variable", move |edit| { - let mut buf = String::new(); - - buf.push_str("let var_name = "); - expr.syntax().text().push_to(&mut buf); - let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) { - Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) - } else { - false - }; - if is_full_stmt { - edit.replace(expr.syntax().range(), buf); - } else { - buf.push_str(";"); - indent.text().push_to(&mut buf); - edit.replace(expr.syntax().range(), "var_name".to_string()); - edit.insert(anchor_stmt.range().start(), buf); - } - edit.set_cursor(anchor_stmt.range().start() + TextUnit::of_str("let ")); - }) -} - -/// Statement or last in the block expression, which will follow -/// the freshly introduced var. -fn anchor_stmt(expr: &ast::Expr) -> Option<&SyntaxNode> { - expr.syntax().ancestors().find(|&node| { - if ast::Stmt::cast(node).is_some() { - return true; - } - if let Some(expr) = node - .parent() - .and_then(ast::Block::cast) - .and_then(|it| it.expr()) - { - if expr.syntax() == node { - return true; - } - } - false - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist_range; - - #[test] - fn test_introduce_var_simple() { - check_assist_range( - introduce_variable, - " -fn foo() { - foo(<|>1 + 1<|>); -}", - " -fn foo() { - let <|>var_name = 1 + 1; - foo(var_name); -}", - ); - } - - #[test] - fn test_introduce_var_expr_stmt() { - check_assist_range( - introduce_variable, - " -fn foo() { - <|>1 + 1<|>; -}", - " -fn foo() { - let <|>var_name = 1 + 1; -}", - ); - } - - #[test] - fn test_introduce_var_part_of_expr_stmt() { - check_assist_range( - introduce_variable, - " -fn foo() { - <|>1<|> + 1; -}", - " -fn foo() { - let <|>var_name = 1; - var_name + 1; -}", - ); - } - - #[test] - fn test_introduce_var_last_expr() { - check_assist_range( - introduce_variable, - " -fn foo() { - bar(<|>1 + 1<|>) -}", - " -fn foo() { - let <|>var_name = 1 + 1; - bar(var_name) -}", - ); - } - - #[test] - fn test_introduce_var_last_full_expr() { - check_assist_range( - introduce_variable, - " -fn foo() { - <|>bar(1 + 1)<|> -}", - " -fn foo() { - let <|>var_name = bar(1 + 1); - var_name -}", - ); - } - -} diff --git a/crates/ra_editor/src/assists/replace_if_let_with_match.rs b/crates/ra_editor/src/assists/replace_if_let_with_match.rs deleted file mode 100644 index 30c371480..000000000 --- a/crates/ra_editor/src/assists/replace_if_let_with_match.rs +++ /dev/null @@ -1,92 +0,0 @@ -use ra_syntax::{ - AstNode, SyntaxKind::{L_CURLY, R_CURLY, WHITESPACE}, - ast, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option { - let if_expr: &ast::IfExpr = ctx.node_at_offset()?; - let cond = if_expr.condition()?; - let pat = cond.pat()?; - let expr = cond.expr()?; - let then_block = if_expr.then_branch()?; - let else_block = if_expr.else_branch()?; - - ctx.build("replace with match", |edit| { - let match_expr = build_match_expr(expr, pat, then_block, else_block); - edit.replace_node_and_indent(if_expr.syntax(), match_expr); - edit.set_cursor(if_expr.syntax().range().start()) - }) -} - -fn build_match_expr( - expr: &ast::Expr, - pat1: &ast::Pat, - arm1: &ast::Block, - arm2: &ast::Block, -) -> String { - let mut buf = String::new(); - buf.push_str(&format!("match {} {{\n", expr.syntax().text())); - buf.push_str(&format!( - " {} => {}\n", - pat1.syntax().text(), - format_arm(arm1) - )); - buf.push_str(&format!(" _ => {}\n", format_arm(arm2))); - buf.push_str("}"); - buf -} - -fn format_arm(block: &ast::Block) -> String { - match extract_expression(block) { - None => block.syntax().text().to_string(), - Some(e) => format!("{},", e.syntax().text()), - } -} - -fn extract_expression(block: &ast::Block) -> Option<&ast::Expr> { - let expr = block.expr()?; - let non_trivial_children = block.syntax().children().filter(|it| { - !(it == &expr.syntax() - || it.kind() == L_CURLY - || it.kind() == R_CURLY - || it.kind() == WHITESPACE) - }); - if non_trivial_children.count() > 0 { - return None; - } - Some(expr) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn test_replace_if_let_with_match_unwraps_simple_expressions() { - check_assist( - replace_if_let_with_match, - " -impl VariantData { - pub fn is_struct(&self) -> bool { - if <|>let VariantData::Struct(..) = *self { - true - } else { - false - } - } -} ", - " -impl VariantData { - pub fn is_struct(&self) -> bool { - <|>match *self { - VariantData::Struct(..) => true, - _ => false, - } - } -} ", - ) - } -} diff --git a/crates/ra_editor/src/assists/split_import.rs b/crates/ra_editor/src/assists/split_import.rs deleted file mode 100644 index e4015f07d..000000000 --- a/crates/ra_editor/src/assists/split_import.rs +++ /dev/null @@ -1,56 +0,0 @@ -use ra_syntax::{ - TextUnit, AstNode, SyntaxKind::COLONCOLON, - ast, - algo::generate, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn split_import(ctx: AssistCtx) -> Option { - let colon_colon = ctx - .leaf_at_offset() - .find(|leaf| leaf.kind() == COLONCOLON)?; - let path = colon_colon.parent().and_then(ast::Path::cast)?; - let top_path = generate(Some(path), |it| it.parent_path()).last()?; - - let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast); - if use_tree.is_none() { - return None; - } - - let l_curly = colon_colon.range().end(); - let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) { - Some(tree) => tree.syntax().range().end(), - None => top_path.syntax().range().end(), - }; - - ctx.build("split import", |edit| { - edit.insert(l_curly, "{"); - edit.insert(r_curly, "}"); - edit.set_cursor(l_curly + TextUnit::of_str("{")); - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn test_split_import() { - check_assist( - split_import, - "use crate::<|>db::RootDatabase;", - "use crate::{<|>db::RootDatabase};", - ) - } - - #[test] - fn split_import_works_with_trees() { - check_assist( - split_import, - "use algo:<|>:visitor::{Visitor, visit}", - "use algo::{<|>visitor::{Visitor, visit}}", - ) - } -} diff --git a/crates/ra_editor/src/diagnostics.rs b/crates/ra_editor/src/diagnostics.rs deleted file mode 100644 index 2b695dfdf..000000000 --- a/crates/ra_editor/src/diagnostics.rs +++ /dev/null @@ -1,266 +0,0 @@ -use itertools::Itertools; - -use ra_syntax::{ - Location, SourceFile, SyntaxKind, TextRange, SyntaxNode, - ast::{self, AstNode}, - -}; -use ra_text_edit::{TextEdit, TextEditBuilder}; - -use crate::{Diagnostic, LocalEdit, Severity}; - -pub fn diagnostics(file: &SourceFile) -> Vec { - fn location_to_range(location: Location) -> TextRange { - match location { - Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), - Location::Range(range) => range, - } - } - - let mut errors: Vec = file - .errors() - .into_iter() - .map(|err| Diagnostic { - range: location_to_range(err.location()), - msg: format!("Syntax Error: {}", err), - severity: Severity::Error, - fix: None, - }) - .collect(); - - for node in file.syntax().descendants() { - check_unnecessary_braces_in_use_statement(&mut errors, node); - check_struct_shorthand_initialization(&mut errors, node); - } - - errors -} - -fn check_unnecessary_braces_in_use_statement( - acc: &mut Vec, - node: &SyntaxNode, -) -> Option<()> { - let use_tree_list = ast::UseTreeList::cast(node)?; - if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { - let range = use_tree_list.syntax().range(); - let edit = - text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree) - .unwrap_or_else(|| { - let to_replace = single_use_tree.syntax().text().to_string(); - let mut edit_builder = TextEditBuilder::default(); - edit_builder.delete(range); - edit_builder.insert(range.start(), to_replace); - edit_builder.finish() - }); - - acc.push(Diagnostic { - range, - msg: format!("Unnecessary braces in use statement"), - severity: Severity::WeakWarning, - fix: Some(LocalEdit { - label: "Remove unnecessary braces".to_string(), - edit, - cursor_position: None, - }), - }); - } - - Some(()) -} - -fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( - single_use_tree: &ast::UseTree, -) -> Option { - let use_tree_list_node = single_use_tree.syntax().parent()?; - if single_use_tree - .path()? - .segment()? - .syntax() - .first_child()? - .kind() - == SyntaxKind::SELF_KW - { - let start = use_tree_list_node.prev_sibling()?.range().start(); - let end = use_tree_list_node.range().end(); - let range = TextRange::from_to(start, end); - let mut edit_builder = TextEditBuilder::default(); - edit_builder.delete(range); - return Some(edit_builder.finish()); - } - None -} - -fn check_struct_shorthand_initialization( - acc: &mut Vec, - node: &SyntaxNode, -) -> Option<()> { - let struct_lit = ast::StructLit::cast(node)?; - let named_field_list = struct_lit.named_field_list()?; - for named_field in named_field_list.fields() { - if let (Some(name_ref), Some(expr)) = (named_field.name_ref(), named_field.expr()) { - let field_name = name_ref.syntax().text().to_string(); - let field_expr = expr.syntax().text().to_string(); - if field_name == field_expr { - let mut edit_builder = TextEditBuilder::default(); - edit_builder.delete(named_field.syntax().range()); - edit_builder.insert(named_field.syntax().range().start(), field_name); - let edit = edit_builder.finish(); - - acc.push(Diagnostic { - range: named_field.syntax().range(), - msg: format!("Shorthand struct initialization"), - severity: Severity::WeakWarning, - fix: Some(LocalEdit { - label: "use struct shorthand initialization".to_string(), - edit, - cursor_position: None, - }), - }); - } - } - } - Some(()) -} - -#[cfg(test)] -mod tests { - use crate::test_utils::assert_eq_text; - - use super::*; - - type DiagnosticChecker = fn(&mut Vec, &SyntaxNode) -> Option<()>; - - fn check_not_applicable(code: &str, func: DiagnosticChecker) { - let file = SourceFile::parse(code); - let mut diagnostics = Vec::new(); - for node in file.syntax().descendants() { - func(&mut diagnostics, node); - } - assert!(diagnostics.is_empty()); - } - - fn check_apply(before: &str, after: &str, func: DiagnosticChecker) { - let file = SourceFile::parse(before); - let mut diagnostics = Vec::new(); - for node in file.syntax().descendants() { - func(&mut diagnostics, node); - } - let diagnostic = diagnostics - .pop() - .unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); - let fix = diagnostic.fix.unwrap(); - let actual = fix.edit.apply(&before); - assert_eq_text!(after, &actual); - } - - #[test] - fn test_check_unnecessary_braces_in_use_statement() { - check_not_applicable( - " - use a; - use a::{c, d::e}; - ", - check_unnecessary_braces_in_use_statement, - ); - check_apply( - "use {b};", - "use b;", - check_unnecessary_braces_in_use_statement, - ); - check_apply( - "use a::{c};", - "use a::c;", - check_unnecessary_braces_in_use_statement, - ); - check_apply( - "use a::{self};", - "use a;", - check_unnecessary_braces_in_use_statement, - ); - check_apply( - "use a::{c, d::{e}};", - "use a::{c, d::e};", - check_unnecessary_braces_in_use_statement, - ); - } - - #[test] - fn test_check_struct_shorthand_initialization() { - check_not_applicable( - r#" - struct A { - a: &'static str - } - - fn main() { - A { - a: "hello" - } - } - "#, - check_struct_shorthand_initialization, - ); - - check_apply( - r#" -struct A { - a: &'static str -} - -fn main() { - let a = "haha"; - A { - a: a - } -} - "#, - r#" -struct A { - a: &'static str -} - -fn main() { - let a = "haha"; - A { - a - } -} - "#, - check_struct_shorthand_initialization, - ); - - check_apply( - r#" -struct A { - a: &'static str, - b: &'static str -} - -fn main() { - let a = "haha"; - let b = "bb"; - A { - a: a, - b - } -} - "#, - r#" -struct A { - a: &'static str, - b: &'static str -} - -fn main() { - let a = "haha"; - let b = "bb"; - A { - a, - b - } -} - "#, - check_struct_shorthand_initialization, - ); - } -} diff --git a/crates/ra_editor/src/extend_selection.rs b/crates/ra_editor/src/extend_selection.rs deleted file mode 100644 index 08cae5a51..000000000 --- a/crates/ra_editor/src/extend_selection.rs +++ /dev/null @@ -1,281 +0,0 @@ -use ra_syntax::{ - Direction, SyntaxNode, TextRange, TextUnit, - algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset}, - SyntaxKind::*, -}; - -pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option { - let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; - if range.is_empty() { - let offset = range.start(); - let mut leaves = find_leaf_at_offset(root, offset); - if leaves.clone().all(|it| it.kind() == WHITESPACE) { - return Some(extend_ws(root, leaves.next()?, offset)); - } - let leaf_range = match leaves { - LeafAtOffset::None => return None, - LeafAtOffset::Single(l) => { - if string_kinds.contains(&l.kind()) { - extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range()) - } else { - l.range() - } - } - LeafAtOffset::Between(l, r) => pick_best(l, r).range(), - }; - return Some(leaf_range); - }; - let node = find_covering_node(root, range); - if string_kinds.contains(&node.kind()) && range == node.range() { - if let Some(range) = extend_comments(node) { - return Some(range); - } - } - - match node.ancestors().skip_while(|n| n.range() == range).next() { - None => None, - Some(parent) => Some(parent.range()), - } -} - -fn extend_single_word_in_comment_or_string( - leaf: &SyntaxNode, - offset: TextUnit, -) -> Option { - let text: &str = leaf.leaf_text()?; - let cursor_position: u32 = (offset - leaf.range().start()).into(); - - let (before, after) = text.split_at(cursor_position as usize); - - fn non_word_char(c: char) -> bool { - !(c.is_alphanumeric() || c == '_') - } - - let start_idx = before.rfind(non_word_char)? as u32; - let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32; - - let from: TextUnit = (start_idx + 1).into(); - let to: TextUnit = (cursor_position + end_idx).into(); - - let range = TextRange::from_to(from, to); - if range.is_empty() { - None - } else { - Some(range + leaf.range().start()) - } -} - -fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange { - let ws_text = ws.leaf_text().unwrap(); - let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start(); - let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start(); - let ws_suffix = &ws_text.as_str()[suffix]; - let ws_prefix = &ws_text.as_str()[prefix]; - if ws_text.contains('\n') && !ws_suffix.contains('\n') { - if let Some(node) = ws.next_sibling() { - let start = match ws_prefix.rfind('\n') { - Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32), - None => node.range().start(), - }; - let end = if root.text().char_at(node.range().end()) == Some('\n') { - node.range().end() + TextUnit::of_char('\n') - } else { - node.range().end() - }; - return TextRange::from_to(start, end); - } - } - ws.range() -} - -fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode { - return if priority(r) > priority(l) { r } else { l }; - fn priority(n: &SyntaxNode) -> usize { - match n.kind() { - WHITESPACE => 0, - IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2, - _ => 1, - } - } -} - -fn extend_comments(node: &SyntaxNode) -> Option { - let prev = adj_comments(node, Direction::Prev); - let next = adj_comments(node, Direction::Next); - if prev != next { - Some(TextRange::from_to(prev.range().start(), next.range().end())) - } else { - None - } -} - -fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode { - let mut res = node; - for node in node.siblings(dir) { - match node.kind() { - COMMENT => res = node, - WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (), - _ => break, - } - } - res -} - -#[cfg(test)] -mod tests { - use ra_syntax::{SourceFile, AstNode}; - use test_utils::extract_offset; - - use super::*; - - fn do_check(before: &str, afters: &[&str]) { - let (cursor, before) = extract_offset(before); - let file = SourceFile::parse(&before); - let mut range = TextRange::offset_len(cursor, 0.into()); - for &after in afters { - range = extend_selection(file.syntax(), range).unwrap(); - let actual = &before[range]; - assert_eq!(after, actual); - } - } - - #[test] - fn test_extend_selection_arith() { - do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]); - } - - #[test] - fn test_extend_selection_start_of_the_lind() { - do_check( - r#" -impl S { -<|> fn foo() { - - } -}"#, - &[" fn foo() {\n\n }\n"], - ); - } - - #[test] - fn test_extend_selection_doc_comments() { - do_check( - r#" -struct A; - -/// bla -/// bla -struct B { - <|> -} - "#, - &[ - "\n \n", - "{\n \n}", - "/// bla\n/// bla\nstruct B {\n \n}", - ], - ) - } - - #[test] - fn test_extend_selection_comments() { - do_check( - r#" -fn bar(){} - -// fn foo() { -// 1 + <|>1 -// } - -// fn foo(){} - "#, - &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"], - ); - - do_check( - r#" -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// pub enum Direction { -// <|> Next, -// Prev -// } -"#, - &[ - "// Next,", - "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }", - ], - ); - - do_check( - r#" -/* -foo -_bar1<|>*/ - "#, - &["_bar1", "/*\nfoo\n_bar1*/"], - ); - - do_check( - r#" -//!<|>foo_2 bar - "#, - &["foo_2", "//!foo_2 bar"], - ); - - do_check( - r#" -/<|>/foo bar - "#, - &["//foo bar"], - ); - } - - #[test] - fn test_extend_selection_prefer_idents() { - do_check( - r#" -fn main() { foo<|>+bar;} - "#, - &["foo", "foo+bar"], - ); - do_check( - r#" -fn main() { foo+<|>bar;} - "#, - &["bar", "foo+bar"], - ); - } - - #[test] - fn test_extend_selection_prefer_lifetimes() { - do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]); - do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]); - } - - #[test] - fn test_extend_selection_select_first_word() { - do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]); - do_check( - r#" -impl S { - fn foo() { - // hel<|>lo world - } -} - "#, - &["hello", "// hello world"], - ); - } - - #[test] - fn test_extend_selection_string() { - do_check( - r#" -fn bar(){} - -" fn f<|>oo() {" - "#, - &["foo", "\" fn foo() {\""], - ); - } -} diff --git a/crates/ra_editor/src/folding_ranges.rs b/crates/ra_editor/src/folding_ranges.rs deleted file mode 100644 index 6f3106889..000000000 --- a/crates/ra_editor/src/folding_ranges.rs +++ /dev/null @@ -1,297 +0,0 @@ -use rustc_hash::FxHashSet; - -use ra_syntax::{ - ast, AstNode, Direction, SourceFile, SyntaxNode, TextRange, - SyntaxKind::{self, *}, -}; - -#[derive(Debug, PartialEq, Eq)] -pub enum FoldKind { - Comment, - Imports, - Block, -} - -#[derive(Debug)] -pub struct Fold { - pub range: TextRange, - pub kind: FoldKind, -} - -pub fn folding_ranges(file: &SourceFile) -> Vec { - let mut res = vec![]; - let mut visited_comments = FxHashSet::default(); - let mut visited_imports = FxHashSet::default(); - - for node in file.syntax().descendants() { - // Fold items that span multiple lines - if let Some(kind) = fold_kind(node.kind()) { - if has_newline(node) { - res.push(Fold { - range: node.range(), - kind, - }); - } - } - - // Fold groups of comments - if node.kind() == COMMENT && !visited_comments.contains(&node) { - if let Some(range) = contiguous_range_for_comment(node, &mut visited_comments) { - res.push(Fold { - range, - kind: FoldKind::Comment, - }) - } - } - - // Fold groups of imports - if node.kind() == USE_ITEM && !visited_imports.contains(&node) { - if let Some(range) = contiguous_range_for_group(node, &mut visited_imports) { - res.push(Fold { - range, - kind: FoldKind::Imports, - }) - } - } - } - - res -} - -fn fold_kind(kind: SyntaxKind) -> Option { - match kind { - COMMENT => Some(FoldKind::Comment), - USE_ITEM => Some(FoldKind::Imports), - NAMED_FIELD_DEF_LIST | FIELD_PAT_LIST | ITEM_LIST | EXTERN_ITEM_LIST | USE_TREE_LIST - | BLOCK | ENUM_VARIANT_LIST => Some(FoldKind::Block), - _ => None, - } -} - -fn has_newline(node: &SyntaxNode) -> bool { - for descendant in node.descendants() { - if let Some(ws) = ast::Whitespace::cast(descendant) { - if ws.has_newlines() { - return true; - } - } else if let Some(comment) = ast::Comment::cast(descendant) { - if comment.has_newlines() { - return true; - } - } - } - - false -} - -fn contiguous_range_for_group<'a>( - first: &'a SyntaxNode, - visited: &mut FxHashSet<&'a SyntaxNode>, -) -> Option { - visited.insert(first); - - let mut last = first; - for node in first.siblings(Direction::Next) { - if let Some(ws) = ast::Whitespace::cast(node) { - // There is a blank line, which means that the group ends here - if ws.count_newlines_lazy().take(2).count() == 2 { - break; - } - - // Ignore whitespace without blank lines - continue; - } - - // Stop if we find a node that doesn't belong to the group - if node.kind() != first.kind() { - break; - } - - visited.insert(node); - last = node; - } - - if first != last { - Some(TextRange::from_to( - first.range().start(), - last.range().end(), - )) - } else { - // The group consists of only one element, therefore it cannot be folded - None - } -} - -fn contiguous_range_for_comment<'a>( - first: &'a SyntaxNode, - visited: &mut FxHashSet<&'a SyntaxNode>, -) -> Option { - visited.insert(first); - - // Only fold comments of the same flavor - let group_flavor = ast::Comment::cast(first)?.flavor(); - - let mut last = first; - for node in first.siblings(Direction::Next) { - if let Some(ws) = ast::Whitespace::cast(node) { - // There is a blank line, which means the group ends here - if ws.count_newlines_lazy().take(2).count() == 2 { - break; - } - - // Ignore whitespace without blank lines - continue; - } - - match ast::Comment::cast(node) { - Some(next_comment) if next_comment.flavor() == group_flavor => { - visited.insert(node); - last = node; - } - // The comment group ends because either: - // * An element of a different kind was reached - // * A comment of a different flavor was reached - _ => break, - } - } - - if first != last { - Some(TextRange::from_to( - first.range().start(), - last.range().end(), - )) - } else { - // The group consists of only one element, therefore it cannot be folded - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::extract_ranges; - - fn do_check(text: &str, fold_kinds: &[FoldKind]) { - let (ranges, text) = extract_ranges(text, "fold"); - let file = SourceFile::parse(&text); - let folds = folding_ranges(&file); - - assert_eq!( - folds.len(), - ranges.len(), - "The amount of folds is different than the expected amount" - ); - assert_eq!( - folds.len(), - fold_kinds.len(), - "The amount of fold kinds is different than the expected amount" - ); - for ((fold, range), fold_kind) in folds - .into_iter() - .zip(ranges.into_iter()) - .zip(fold_kinds.into_iter()) - { - assert_eq!(fold.range.start(), range.start()); - assert_eq!(fold.range.end(), range.end()); - assert_eq!(&fold.kind, fold_kind); - } - } - - #[test] - fn test_fold_comments() { - let text = r#" -// Hello -// this is a multiline -// comment -// - -// But this is not - -fn main() { - // We should - // also - // fold - // this one. - //! But this one is different - //! because it has another flavor - /* As does this - multiline comment */ -}"#; - - let fold_kinds = &[ - FoldKind::Comment, - FoldKind::Block, - FoldKind::Comment, - FoldKind::Comment, - FoldKind::Comment, - ]; - do_check(text, fold_kinds); - } - - #[test] - fn test_fold_imports() { - let text = r#" -use std::{ - str, - vec, - io as iop -}; - -fn main() { -}"#; - - let folds = &[FoldKind::Imports, FoldKind::Block, FoldKind::Block]; - do_check(text, folds); - } - - #[test] - fn test_fold_import_groups() { - let text = r#" -use std::str; -use std::vec; -use std::io as iop; - -use std::mem; -use std::f64; - -use std::collections::HashMap; -// Some random comment -use std::collections::VecDeque; - -fn main() { -}"#; - - let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Block]; - do_check(text, folds); - } - - #[test] - fn test_fold_import_and_groups() { - let text = r#" -use std::str; -use std::vec; -use std::io as iop; - -use std::mem; -use std::f64; - -use std::collections::{ - HashMap, - VecDeque, -}; -// Some random comment - -fn main() { -}"#; - - let folds = &[ - FoldKind::Imports, - FoldKind::Imports, - FoldKind::Imports, - FoldKind::Block, - FoldKind::Block, - ]; - do_check(text, folds); - } - -} diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs deleted file mode 100644 index 5a6af19b7..000000000 --- a/crates/ra_editor/src/lib.rs +++ /dev/null @@ -1,168 +0,0 @@ -pub mod assists; -mod extend_selection; -mod folding_ranges; -mod line_index; -mod line_index_utils; -mod structure; -#[cfg(test)] -mod test_utils; -mod typing; -mod diagnostics; - -pub use self::{ - assists::LocalEdit, - extend_selection::extend_selection, - folding_ranges::{folding_ranges, Fold, FoldKind}, - line_index::{LineCol, LineIndex}, - line_index_utils::translate_offset_with_edit, - structure::{file_structure, StructureNode}, - typing::{join_lines, on_enter, on_dot_typed, on_eq_typed}, - diagnostics::diagnostics -}; -use ra_text_edit::TextEditBuilder; -use ra_syntax::{ - SourceFile, SyntaxNode, TextRange, TextUnit, Direction, - SyntaxKind::{self, *}, - ast::{self, AstNode}, - algo::find_leaf_at_offset, -}; -use rustc_hash::FxHashSet; - -#[derive(Debug)] -pub struct HighlightedRange { - pub range: TextRange, - pub tag: &'static str, -} - -#[derive(Debug, Copy, Clone)] -pub enum Severity { - Error, - WeakWarning, -} - -#[derive(Debug)] -pub struct Diagnostic { - pub range: TextRange, - pub msg: String, - pub severity: Severity, - pub fix: Option, -} - -pub fn matching_brace(file: &SourceFile, offset: TextUnit) -> Option { - const BRACES: &[SyntaxKind] = &[ - L_CURLY, R_CURLY, L_BRACK, R_BRACK, L_PAREN, R_PAREN, L_ANGLE, R_ANGLE, - ]; - let (brace_node, brace_idx) = find_leaf_at_offset(file.syntax(), offset) - .filter_map(|node| { - let idx = BRACES.iter().position(|&brace| brace == node.kind())?; - Some((node, idx)) - }) - .next()?; - let parent = brace_node.parent()?; - let matching_kind = BRACES[brace_idx ^ 1]; - let matching_node = parent - .children() - .find(|node| node.kind() == matching_kind)?; - Some(matching_node.range().start()) -} - -pub fn highlight(root: &SyntaxNode) -> Vec { - // Visited nodes to handle highlighting priorities - let mut highlighted = FxHashSet::default(); - let mut res = Vec::new(); - for node in root.descendants() { - if highlighted.contains(&node) { - continue; - } - let tag = match node.kind() { - COMMENT => "comment", - STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", - ATTR => "attribute", - NAME_REF => "text", - NAME => "function", - INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", - LIFETIME => "parameter", - k if k.is_keyword() => "keyword", - _ => { - if let Some(macro_call) = ast::MacroCall::cast(node) { - if let Some(path) = macro_call.path() { - if let Some(segment) = path.segment() { - if let Some(name_ref) = segment.name_ref() { - highlighted.insert(name_ref.syntax()); - let range_start = name_ref.syntax().range().start(); - let mut range_end = name_ref.syntax().range().end(); - for sibling in path.syntax().siblings(Direction::Next) { - match sibling.kind() { - EXCL | IDENT => range_end = sibling.range().end(), - _ => (), - } - } - res.push(HighlightedRange { - range: TextRange::from_to(range_start, range_end), - tag: "macro", - }) - } - } - } - } - continue; - } - }; - res.push(HighlightedRange { - range: node.range(), - tag, - }) - } - res -} - -pub fn syntax_tree(file: &SourceFile) -> String { - ::ra_syntax::utils::dump_tree(file.syntax()) -} - -#[cfg(test)] -mod tests { - use ra_syntax::AstNode; - - use crate::test_utils::{add_cursor, assert_eq_dbg, assert_eq_text, extract_offset}; - - use super::*; - - #[test] - fn test_highlighting() { - let file = SourceFile::parse( - r#" -// comment -fn main() {} - println!("Hello, {}!", 92); -"#, - ); - let hls = highlight(file.syntax()); - assert_eq_dbg( - r#"[HighlightedRange { range: [1; 11), tag: "comment" }, - HighlightedRange { range: [12; 14), tag: "keyword" }, - HighlightedRange { range: [15; 19), tag: "function" }, - HighlightedRange { range: [29; 37), tag: "macro" }, - HighlightedRange { range: [38; 50), tag: "string" }, - HighlightedRange { range: [52; 54), tag: "literal" }]"#, - &hls, - ); - } - - #[test] - fn test_matching_brace() { - fn do_check(before: &str, after: &str) { - let (pos, before) = extract_offset(before); - let file = SourceFile::parse(&before); - let new_pos = match matching_brace(&file, pos) { - None => pos, - Some(pos) => pos, - }; - let actual = add_cursor(&before, new_pos); - assert_eq_text!(after, &actual); - } - - do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }"); - } - -} diff --git a/crates/ra_editor/src/line_index.rs b/crates/ra_editor/src/line_index.rs deleted file mode 100644 index 898fee7e0..000000000 --- a/crates/ra_editor/src/line_index.rs +++ /dev/null @@ -1,399 +0,0 @@ -use crate::TextUnit; -use rustc_hash::FxHashMap; -use superslice::Ext; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LineIndex { - pub(crate) newlines: Vec, - pub(crate) utf16_lines: FxHashMap>, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct LineCol { - pub line: u32, - pub col_utf16: u32, -} - -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub(crate) struct Utf16Char { - pub(crate) start: TextUnit, - pub(crate) end: TextUnit, -} - -impl Utf16Char { - fn len(&self) -> TextUnit { - self.end - self.start - } -} - -impl LineIndex { - pub fn new(text: &str) -> LineIndex { - let mut utf16_lines = FxHashMap::default(); - let mut utf16_chars = Vec::new(); - - let mut newlines = vec![0.into()]; - let mut curr_row = 0.into(); - let mut curr_col = 0.into(); - let mut line = 0; - for c in text.chars() { - curr_row += TextUnit::of_char(c); - if c == '\n' { - newlines.push(curr_row); - - // Save any utf-16 characters seen in the previous line - if utf16_chars.len() > 0 { - utf16_lines.insert(line, utf16_chars); - utf16_chars = Vec::new(); - } - - // Prepare for processing the next line - curr_col = 0.into(); - line += 1; - continue; - } - - let char_len = TextUnit::of_char(c); - if char_len.to_usize() > 1 { - utf16_chars.push(Utf16Char { - start: curr_col, - end: curr_col + char_len, - }); - } - - curr_col += char_len; - } - - // Save any utf-16 characters seen in the last line - if utf16_chars.len() > 0 { - utf16_lines.insert(line, utf16_chars); - } - - LineIndex { - newlines, - utf16_lines, - } - } - - pub fn line_col(&self, offset: TextUnit) -> LineCol { - let line = self.newlines.upper_bound(&offset) - 1; - let line_start_offset = self.newlines[line]; - let col = offset - line_start_offset; - - LineCol { - line: line as u32, - col_utf16: self.utf8_to_utf16_col(line as u32, col) as u32, - } - } - - pub fn offset(&self, line_col: LineCol) -> TextUnit { - //TODO: return Result - let col = self.utf16_to_utf8_col(line_col.line, line_col.col_utf16); - self.newlines[line_col.line as usize] + col - } - - fn utf8_to_utf16_col(&self, line: u32, mut col: TextUnit) -> usize { - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - let mut correction = TextUnit::from_usize(0); - for c in utf16_chars { - if col >= c.end { - correction += c.len() - TextUnit::from_usize(1); - } else { - // From here on, all utf16 characters come *after* the character we are mapping, - // so we don't need to take them into account - break; - } - } - - col -= correction; - } - - col.to_usize() - } - - fn utf16_to_utf8_col(&self, line: u32, col: u32) -> TextUnit { - let mut col: TextUnit = col.into(); - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - for c in utf16_chars { - if col >= c.start { - col += c.len() - TextUnit::from_usize(1); - } else { - // From here on, all utf16 characters come *after* the character we are mapping, - // so we don't need to take them into account - break; - } - } - } - - col - } -} - -#[cfg(test)] -/// Simple reference implementation to use in proptests -pub fn to_line_col(text: &str, offset: TextUnit) -> LineCol { - let mut res = LineCol { - line: 0, - col_utf16: 0, - }; - for (i, c) in text.char_indices() { - if i + c.len_utf8() > offset.to_usize() { - // if it's an invalid offset, inside a multibyte char - // return as if it was at the start of the char - break; - } - if c == '\n' { - res.line += 1; - res.col_utf16 = 0; - } else { - res.col_utf16 += 1; - } - } - res -} - -#[cfg(test)] -mod test_line_index { - use super::*; - use proptest::{prelude::*, proptest, proptest_helper}; - use ra_text_edit::test_utils::{arb_text, arb_offset}; - - #[test] - fn test_line_index() { - let text = "hello\nworld"; - let index = LineIndex::new(text); - assert_eq!( - index.line_col(0.into()), - LineCol { - line: 0, - col_utf16: 0 - } - ); - assert_eq!( - index.line_col(1.into()), - LineCol { - line: 0, - col_utf16: 1 - } - ); - assert_eq!( - index.line_col(5.into()), - LineCol { - line: 0, - col_utf16: 5 - } - ); - assert_eq!( - index.line_col(6.into()), - LineCol { - line: 1, - col_utf16: 0 - } - ); - assert_eq!( - index.line_col(7.into()), - LineCol { - line: 1, - col_utf16: 1 - } - ); - assert_eq!( - index.line_col(8.into()), - LineCol { - line: 1, - col_utf16: 2 - } - ); - assert_eq!( - index.line_col(10.into()), - LineCol { - line: 1, - col_utf16: 4 - } - ); - assert_eq!( - index.line_col(11.into()), - LineCol { - line: 1, - col_utf16: 5 - } - ); - assert_eq!( - index.line_col(12.into()), - LineCol { - line: 1, - col_utf16: 6 - } - ); - - let text = "\nhello\nworld"; - let index = LineIndex::new(text); - assert_eq!( - index.line_col(0.into()), - LineCol { - line: 0, - col_utf16: 0 - } - ); - assert_eq!( - index.line_col(1.into()), - LineCol { - line: 1, - col_utf16: 0 - } - ); - assert_eq!( - index.line_col(2.into()), - LineCol { - line: 1, - col_utf16: 1 - } - ); - assert_eq!( - index.line_col(6.into()), - LineCol { - line: 1, - col_utf16: 5 - } - ); - assert_eq!( - index.line_col(7.into()), - LineCol { - line: 2, - col_utf16: 0 - } - ); - } - - fn arb_text_with_offset() -> BoxedStrategy<(TextUnit, String)> { - arb_text() - .prop_flat_map(|text| (arb_offset(&text), Just(text))) - .boxed() - } - - fn to_line_col(text: &str, offset: TextUnit) -> LineCol { - let mut res = LineCol { - line: 0, - col_utf16: 0, - }; - for (i, c) in text.char_indices() { - if i + c.len_utf8() > offset.to_usize() { - // if it's an invalid offset, inside a multibyte char - // return as if it was at the start of the char - break; - } - if c == '\n' { - res.line += 1; - res.col_utf16 = 0; - } else { - res.col_utf16 += 1; - } - } - res - } - - proptest! { - #[test] - fn test_line_index_proptest((offset, text) in arb_text_with_offset()) { - let expected = to_line_col(&text, offset); - let line_index = LineIndex::new(&text); - let actual = line_index.line_col(offset); - - assert_eq!(actual, expected); - } - } -} - -#[cfg(test)] -mod test_utf8_utf16_conv { - use super::*; - - #[test] - fn test_char_len() { - assert_eq!('メ'.len_utf8(), 3); - assert_eq!('メ'.len_utf16(), 1); - } - - #[test] - fn test_empty_index() { - let col_index = LineIndex::new( - " -const C: char = 'x'; -", - ); - assert_eq!(col_index.utf16_lines.len(), 0); - } - - #[test] - fn test_single_char() { - let col_index = LineIndex::new( - " -const C: char = 'メ'; -", - ); - - assert_eq!(col_index.utf16_lines.len(), 1); - assert_eq!(col_index.utf16_lines[&1].len(), 1); - assert_eq!( - col_index.utf16_lines[&1][0], - Utf16Char { - start: 17.into(), - end: 20.into() - } - ); - - // UTF-8 to UTF-16, no changes - assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); - - // UTF-8 to UTF-16 - assert_eq!(col_index.utf8_to_utf16_col(1, 22.into()), 20); - - // UTF-16 to UTF-8, no changes - assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextUnit::from(15)); - - // UTF-16 to UTF-8 - assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextUnit::from(21)); - } - - #[test] - fn test_string() { - let col_index = LineIndex::new( - " -const C: char = \"メ メ\"; -", - ); - - assert_eq!(col_index.utf16_lines.len(), 1); - assert_eq!(col_index.utf16_lines[&1].len(), 2); - assert_eq!( - col_index.utf16_lines[&1][0], - Utf16Char { - start: 17.into(), - end: 20.into() - } - ); - assert_eq!( - col_index.utf16_lines[&1][1], - Utf16Char { - start: 21.into(), - end: 24.into() - } - ); - - // UTF-8 to UTF-16 - assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); - - assert_eq!(col_index.utf8_to_utf16_col(1, 21.into()), 19); - assert_eq!(col_index.utf8_to_utf16_col(1, 25.into()), 21); - - assert!(col_index.utf8_to_utf16_col(2, 15.into()) == 15); - - // UTF-16 to UTF-8 - assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextUnit::from_usize(15)); - - assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextUnit::from_usize(20)); - assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextUnit::from_usize(23)); - - assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextUnit::from_usize(15)); - } - -} diff --git a/crates/ra_editor/src/line_index_utils.rs b/crates/ra_editor/src/line_index_utils.rs deleted file mode 100644 index ec3269bbb..000000000 --- a/crates/ra_editor/src/line_index_utils.rs +++ /dev/null @@ -1,363 +0,0 @@ -use ra_text_edit::{AtomTextEdit, TextEdit}; -use ra_syntax::{TextUnit, TextRange}; -use crate::{LineIndex, LineCol, line_index::Utf16Char}; - -#[derive(Debug, Clone)] -enum Step { - Newline(TextUnit), - Utf16Char(TextRange), -} - -#[derive(Debug)] -struct LineIndexStepIter<'a> { - line_index: &'a LineIndex, - next_newline_idx: usize, - utf16_chars: Option<(TextUnit, std::slice::Iter<'a, Utf16Char>)>, -} - -impl<'a> LineIndexStepIter<'a> { - fn from(line_index: &LineIndex) -> LineIndexStepIter { - let mut x = LineIndexStepIter { - line_index, - next_newline_idx: 0, - utf16_chars: None, - }; - // skip first newline since it's not real - x.next(); - x - } -} - -impl<'a> Iterator for LineIndexStepIter<'a> { - type Item = Step; - fn next(&mut self) -> Option { - self.utf16_chars - .as_mut() - .and_then(|(newline, x)| { - let x = x.next()?; - Some(Step::Utf16Char(TextRange::from_to( - *newline + x.start, - *newline + x.end, - ))) - }) - .or_else(|| { - let next_newline = *self.line_index.newlines.get(self.next_newline_idx)?; - self.utf16_chars = self - .line_index - .utf16_lines - .get(&(self.next_newline_idx as u32)) - .map(|x| (next_newline, x.iter())); - self.next_newline_idx += 1; - Some(Step::Newline(next_newline)) - }) - } -} - -#[derive(Debug)] -struct OffsetStepIter<'a> { - text: &'a str, - offset: TextUnit, -} - -impl<'a> Iterator for OffsetStepIter<'a> { - type Item = Step; - fn next(&mut self) -> Option { - let (next, next_offset) = self - .text - .char_indices() - .filter_map(|(i, c)| { - if c == '\n' { - let next_offset = self.offset + TextUnit::from_usize(i + 1); - let next = Step::Newline(next_offset); - Some((next, next_offset)) - } else { - let char_len = TextUnit::of_char(c); - if char_len.to_usize() > 1 { - let start = self.offset + TextUnit::from_usize(i); - let end = start + char_len; - let next = Step::Utf16Char(TextRange::from_to(start, end)); - let next_offset = end; - Some((next, next_offset)) - } else { - None - } - } - }) - .next()?; - let next_idx = (next_offset - self.offset).to_usize(); - self.text = &self.text[next_idx..]; - self.offset = next_offset; - Some(next) - } -} - -#[derive(Debug)] -enum NextSteps<'a> { - Use, - ReplaceMany(OffsetStepIter<'a>), - AddMany(OffsetStepIter<'a>), -} - -#[derive(Debug)] -struct TranslatedEdit<'a> { - delete: TextRange, - insert: &'a str, - diff: i64, -} - -struct Edits<'a> { - edits: &'a [AtomTextEdit], - current: Option>, - acc_diff: i64, -} - -impl<'a> Edits<'a> { - fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> { - let mut x = Edits { - edits: text_edit.as_atoms(), - current: None, - acc_diff: 0, - }; - x.advance_edit(); - x - } - fn advance_edit(&mut self) { - self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff); - match self.edits.split_first() { - Some((next, rest)) => { - let delete = self.translate_range(next.delete); - let diff = next.insert.len() as i64 - next.delete.len().to_usize() as i64; - self.current = Some(TranslatedEdit { - delete, - insert: &next.insert, - diff, - }); - self.edits = rest; - } - None => { - self.current = None; - } - } - } - - fn next_inserted_steps(&mut self) -> Option> { - let cur = self.current.as_ref()?; - let res = Some(OffsetStepIter { - offset: cur.delete.start(), - text: &cur.insert, - }); - self.advance_edit(); - res - } - - fn next_steps(&mut self, step: &Step) -> NextSteps { - let step_pos = match step { - &Step::Newline(n) => n, - &Step::Utf16Char(r) => r.end(), - }; - let res = match &mut self.current { - Some(edit) => { - if step_pos <= edit.delete.start() { - NextSteps::Use - } else if step_pos <= edit.delete.end() { - let iter = OffsetStepIter { - offset: edit.delete.start(), - text: &edit.insert, - }; - // empty slice to avoid returning steps again - edit.insert = &edit.insert[edit.insert.len()..]; - NextSteps::ReplaceMany(iter) - } else { - let iter = OffsetStepIter { - offset: edit.delete.start(), - text: &edit.insert, - }; - // empty slice to avoid returning steps again - edit.insert = &edit.insert[edit.insert.len()..]; - self.advance_edit(); - NextSteps::AddMany(iter) - } - } - None => NextSteps::Use, - }; - res - } - - fn translate_range(&self, range: TextRange) -> TextRange { - if self.acc_diff == 0 { - range - } else { - let start = self.translate(range.start()); - let end = self.translate(range.end()); - TextRange::from_to(start, end) - } - } - - fn translate(&self, x: TextUnit) -> TextUnit { - if self.acc_diff == 0 { - x - } else { - TextUnit::from((x.to_usize() as i64 + self.acc_diff) as u32) - } - } - - fn translate_step(&self, x: &Step) -> Step { - if self.acc_diff == 0 { - x.clone() - } else { - match x { - &Step::Newline(n) => Step::Newline(self.translate(n)), - &Step::Utf16Char(r) => Step::Utf16Char(self.translate_range(r)), - } - } - } -} - -#[derive(Debug)] -struct RunningLineCol { - line: u32, - last_newline: TextUnit, - col_adjust: TextUnit, -} - -impl RunningLineCol { - fn new() -> RunningLineCol { - RunningLineCol { - line: 0, - last_newline: TextUnit::from(0), - col_adjust: TextUnit::from(0), - } - } - - fn to_line_col(&self, offset: TextUnit) -> LineCol { - LineCol { - line: self.line, - col_utf16: ((offset - self.last_newline) - self.col_adjust).into(), - } - } - - fn add_line(&mut self, newline: TextUnit) { - self.line += 1; - self.last_newline = newline; - self.col_adjust = TextUnit::from(0); - } - - fn adjust_col(&mut self, range: &TextRange) { - self.col_adjust += range.len() - TextUnit::from(1); - } -} - -pub fn translate_offset_with_edit( - line_index: &LineIndex, - offset: TextUnit, - text_edit: &TextEdit, -) -> LineCol { - let mut state = Edits::from_text_edit(&text_edit); - - let mut res = RunningLineCol::new(); - - macro_rules! test_step { - ($x:ident) => { - match &$x { - Step::Newline(n) => { - if offset < *n { - return res.to_line_col(offset); - } else { - res.add_line(*n); - } - } - Step::Utf16Char(x) => { - if offset < x.end() { - // if the offset is inside a multibyte char it's invalid - // clamp it to the start of the char - let clamp = offset.min(x.start()); - return res.to_line_col(clamp); - } else { - res.adjust_col(x); - } - } - } - }; - } - - for orig_step in LineIndexStepIter::from(line_index) { - loop { - let translated_step = state.translate_step(&orig_step); - match state.next_steps(&translated_step) { - NextSteps::Use => { - test_step!(translated_step); - break; - } - NextSteps::ReplaceMany(ns) => { - for n in ns { - test_step!(n); - } - break; - } - NextSteps::AddMany(ns) => { - for n in ns { - test_step!(n); - } - } - } - } - } - - loop { - match state.next_inserted_steps() { - None => break, - Some(ns) => { - for n in ns { - test_step!(n); - } - } - } - } - - res.to_line_col(offset) -} - -#[cfg(test)] -mod test { - use super::*; - use proptest::{prelude::*, proptest, proptest_helper}; - use crate::line_index; - use ra_text_edit::test_utils::{arb_offset, arb_text_with_edit}; - use ra_text_edit::TextEdit; - - #[derive(Debug)] - struct ArbTextWithEditAndOffset { - text: String, - edit: TextEdit, - edited_text: String, - offset: TextUnit, - } - - fn arb_text_with_edit_and_offset() -> BoxedStrategy { - arb_text_with_edit() - .prop_flat_map(|x| { - let edited_text = x.edit.apply(&x.text); - let arb_offset = arb_offset(&edited_text); - (Just(x), Just(edited_text), arb_offset).prop_map(|(x, edited_text, offset)| { - ArbTextWithEditAndOffset { - text: x.text, - edit: x.edit, - edited_text, - offset, - } - }) - }) - .boxed() - } - - proptest! { - #[test] - fn test_translate_offset_with_edit(x in arb_text_with_edit_and_offset()) { - let expected = line_index::to_line_col(&x.edited_text, x.offset); - let line_index = LineIndex::new(&x.text); - let actual = translate_offset_with_edit(&line_index, x.offset, &x.edit); - - assert_eq!(actual, expected); - } - } -} diff --git a/crates/ra_editor/src/structure.rs b/crates/ra_editor/src/structure.rs deleted file mode 100644 index 8bd57555f..000000000 --- a/crates/ra_editor/src/structure.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::TextRange; - -use ra_syntax::{ - algo::visit::{visitor, Visitor}, - ast::{self, NameOwner}, - AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, -}; - -#[derive(Debug, Clone)] -pub struct StructureNode { - pub parent: Option, - pub label: String, - pub navigation_range: TextRange, - pub node_range: TextRange, - pub kind: SyntaxKind, -} - -pub fn file_structure(file: &SourceFile) -> Vec { - let mut res = Vec::new(); - let mut stack = Vec::new(); - - for event in file.syntax().preorder() { - match event { - WalkEvent::Enter(node) => { - if let Some(mut symbol) = structure_node(node) { - symbol.parent = stack.last().map(|&n| n); - stack.push(res.len()); - res.push(symbol); - } - } - WalkEvent::Leave(node) => { - if structure_node(node).is_some() { - stack.pop().unwrap(); - } - } - } - } - res -} - -fn structure_node(node: &SyntaxNode) -> Option { - fn decl(node: &N) -> Option { - let name = node.name()?; - Some(StructureNode { - parent: None, - label: name.text().to_string(), - navigation_range: name.syntax().range(), - node_range: node.syntax().range(), - kind: node.syntax().kind(), - }) - } - - visitor() - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(decl::) - .visit(|im: &ast::ImplBlock| { - let target_type = im.target_type()?; - let target_trait = im.target_trait(); - let label = match target_trait { - None => format!("impl {}", target_type.syntax().text()), - Some(t) => format!( - "impl {} for {}", - t.syntax().text(), - target_type.syntax().text(), - ), - }; - - let node = StructureNode { - parent: None, - label, - navigation_range: target_type.syntax().range(), - node_range: im.syntax().range(), - kind: im.syntax().kind(), - }; - Some(node) - }) - .accept(node)? -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::assert_eq_dbg; - - #[test] - fn test_file_structure() { - let file = SourceFile::parse( - r#" -struct Foo { - x: i32 -} - -mod m { - fn bar() {} -} - -enum E { X, Y(i32) } -type T = (); -static S: i32 = 92; -const C: i32 = 92; - -impl E {} - -impl fmt::Debug for E {} -"#, - ); - let structure = file_structure(&file); - assert_eq_dbg( - r#"[StructureNode { parent: None, label: "Foo", navigation_range: [8; 11), node_range: [1; 26), kind: STRUCT_DEF }, - StructureNode { parent: Some(0), label: "x", navigation_range: [18; 19), node_range: [18; 24), kind: NAMED_FIELD_DEF }, - StructureNode { parent: None, label: "m", navigation_range: [32; 33), node_range: [28; 53), kind: MODULE }, - StructureNode { parent: Some(2), label: "bar", navigation_range: [43; 46), node_range: [40; 51), kind: FN_DEF }, - StructureNode { parent: None, label: "E", navigation_range: [60; 61), node_range: [55; 75), kind: ENUM_DEF }, - StructureNode { parent: None, label: "T", navigation_range: [81; 82), node_range: [76; 88), kind: TYPE_DEF }, - StructureNode { parent: None, label: "S", navigation_range: [96; 97), node_range: [89; 108), kind: STATIC_DEF }, - StructureNode { parent: None, label: "C", navigation_range: [115; 116), node_range: [109; 127), kind: CONST_DEF }, - StructureNode { parent: None, label: "impl E", navigation_range: [134; 135), node_range: [129; 138), kind: IMPL_BLOCK }, - StructureNode { parent: None, label: "impl fmt::Debug for E", navigation_range: [160; 161), node_range: [140; 164), kind: IMPL_BLOCK }]"#, - &structure, - ) - } -} diff --git a/crates/ra_editor/src/test_utils.rs b/crates/ra_editor/src/test_utils.rs deleted file mode 100644 index dc2470aa3..000000000 --- a/crates/ra_editor/src/test_utils.rs +++ /dev/null @@ -1,41 +0,0 @@ -use ra_syntax::{SourceFile, TextRange, TextUnit}; - -use crate::LocalEdit; -pub use test_utils::*; - -pub fn check_action Option>( - before: &str, - after: &str, - f: F, -) { - let (before_cursor_pos, before) = extract_offset(before); - let file = SourceFile::parse(&before); - let result = f(&file, before_cursor_pos).expect("code action is not applicable"); - let actual = result.edit.apply(&before); - let actual_cursor_pos = match result.cursor_position { - None => result - .edit - .apply_to_offset(before_cursor_pos) - .expect("cursor position is affected by the edit"), - Some(off) => off, - }; - let actual = add_cursor(&actual, actual_cursor_pos); - assert_eq_text!(after, &actual); -} - -pub fn check_action_range Option>( - before: &str, - after: &str, - f: F, -) { - let (range, before) = extract_range(before); - let file = SourceFile::parse(&before); - let result = f(&file, range).expect("code action is not applicable"); - let actual = result.edit.apply(&before); - let actual_cursor_pos = match result.cursor_position { - None => result.edit.apply_to_offset(range.start()).unwrap(), - Some(off) => off, - }; - let actual = add_cursor(&actual, actual_cursor_pos); - assert_eq_text!(after, &actual); -} diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs deleted file mode 100644 index d8177f245..000000000 --- a/crates/ra_editor/src/typing.rs +++ /dev/null @@ -1,826 +0,0 @@ -use std::mem; - -use itertools::Itertools; -use ra_syntax::{ - algo::{find_node_at_offset, find_covering_node, find_leaf_at_offset, LeafAtOffset}, - ast, - AstNode, Direction, SourceFile, SyntaxKind, - SyntaxKind::*, - SyntaxNode, TextRange, TextUnit, -}; - -use crate::{LocalEdit, TextEditBuilder}; - -pub fn join_lines(file: &SourceFile, range: TextRange) -> LocalEdit { - let range = if range.is_empty() { - let syntax = file.syntax(); - let text = syntax.text().slice(range.start()..); - let pos = match text.find('\n') { - None => { - return LocalEdit { - label: "join lines".to_string(), - edit: TextEditBuilder::default().finish(), - cursor_position: None, - }; - } - Some(pos) => pos, - }; - TextRange::offset_len(range.start() + pos, TextUnit::of_char('\n')) - } else { - range - }; - - let node = find_covering_node(file.syntax(), range); - let mut edit = TextEditBuilder::default(); - for node in node.descendants() { - let text = match node.leaf_text() { - Some(text) => text, - None => continue, - }; - let range = match range.intersection(&node.range()) { - Some(range) => range, - None => continue, - } - node.range().start(); - for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') { - let pos: TextUnit = (pos as u32).into(); - let off = node.range().start() + range.start() + pos; - if !edit.invalidates_offset(off) { - remove_newline(&mut edit, node, text.as_str(), off); - } - } - } - - LocalEdit { - label: "join lines".to_string(), - edit: edit.finish(), - cursor_position: None, - } -} - -pub fn on_enter(file: &SourceFile, offset: TextUnit) -> Option { - let comment = find_leaf_at_offset(file.syntax(), offset) - .left_biased() - .and_then(ast::Comment::cast)?; - - if let ast::CommentFlavor::Multiline = comment.flavor() { - return None; - } - - let prefix = comment.prefix(); - if offset < comment.syntax().range().start() + TextUnit::of_str(prefix) + TextUnit::from(1) { - return None; - } - - let indent = node_indent(file, comment.syntax())?; - let inserted = format!("\n{}{} ", indent, prefix); - let cursor_position = offset + TextUnit::of_str(&inserted); - let mut edit = TextEditBuilder::default(); - edit.insert(offset, inserted); - Some(LocalEdit { - label: "on enter".to_string(), - edit: edit.finish(), - cursor_position: Some(cursor_position), - }) -} - -fn node_indent<'a>(file: &'a SourceFile, node: &SyntaxNode) -> Option<&'a str> { - let ws = match find_leaf_at_offset(file.syntax(), node.range().start()) { - LeafAtOffset::Between(l, r) => { - assert!(r == node); - l - } - LeafAtOffset::Single(n) => { - assert!(n == node); - return Some(""); - } - LeafAtOffset::None => unreachable!(), - }; - if ws.kind() != WHITESPACE { - return None; - } - let text = ws.leaf_text().unwrap(); - let pos = text.as_str().rfind('\n').map(|it| it + 1).unwrap_or(0); - Some(&text[pos..]) -} - -pub fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option { - let let_stmt: &ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; - if let_stmt.has_semi() { - return None; - } - if let Some(expr) = let_stmt.initializer() { - let expr_range = expr.syntax().range(); - if expr_range.contains(offset) && offset != expr_range.start() { - return None; - } - if file - .syntax() - .text() - .slice(offset..expr_range.start()) - .contains('\n') - { - return None; - } - } else { - return None; - } - let offset = let_stmt.syntax().range().end(); - let mut edit = TextEditBuilder::default(); - edit.insert(offset, ";".to_string()); - Some(LocalEdit { - label: "add semicolon".to_string(), - edit: edit.finish(), - cursor_position: None, - }) -} - -pub fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option { - let before_dot_offset = offset - TextUnit::of_char('.'); - - let whitespace = find_leaf_at_offset(file.syntax(), before_dot_offset).left_biased()?; - - // find whitespace just left of the dot - ast::Whitespace::cast(whitespace)?; - - // make sure there is a method call - let method_call = whitespace - .siblings(Direction::Prev) - // first is whitespace - .skip(1) - .next()?; - - ast::MethodCallExpr::cast(method_call)?; - - // find how much the _method call is indented - let method_chain_indent = method_call - .parent()? - .siblings(Direction::Prev) - .skip(1) - .next()? - .leaf_text() - .map(|x| last_line_indent_in_whitespace(x))?; - - let current_indent = TextUnit::of_str(last_line_indent_in_whitespace(whitespace.leaf_text()?)); - // TODO: indent is always 4 spaces now. A better heuristic could look on the previous line(s) - - let target_indent = TextUnit::of_str(method_chain_indent) + TextUnit::from_usize(4); - - let diff = target_indent - current_indent; - - let indent = "".repeat(diff.to_usize()); - - let cursor_position = offset + diff; - let mut edit = TextEditBuilder::default(); - edit.insert(before_dot_offset, indent); - Some(LocalEdit { - label: "indent dot".to_string(), - edit: edit.finish(), - cursor_position: Some(cursor_position), - }) -} - -/// Finds the last line in the whitespace -fn last_line_indent_in_whitespace(ws: &str) -> &str { - ws.split('\n').last().unwrap_or("") -} - -fn remove_newline( - edit: &mut TextEditBuilder, - node: &SyntaxNode, - node_text: &str, - offset: TextUnit, -) { - if node.kind() != WHITESPACE || node_text.bytes().filter(|&b| b == b'\n').count() != 1 { - // The node is either the first or the last in the file - let suff = &node_text[TextRange::from_to( - offset - node.range().start() + TextUnit::of_char('\n'), - TextUnit::of_str(node_text), - )]; - let spaces = suff.bytes().take_while(|&b| b == b' ').count(); - - edit.replace( - TextRange::offset_len(offset, ((spaces + 1) as u32).into()), - " ".to_string(), - ); - return; - } - - // Special case that turns something like: - // - // ``` - // my_function({<|> - // - // }) - // ``` - // - // into `my_function()` - if join_single_expr_block(edit, node).is_some() { - return; - } - // ditto for - // - // ``` - // use foo::{<|> - // bar - // }; - // ``` - if join_single_use_tree(edit, node).is_some() { - return; - } - - // The node is between two other nodes - let prev = node.prev_sibling().unwrap(); - let next = node.next_sibling().unwrap(); - if is_trailing_comma(prev.kind(), next.kind()) { - // Removes: trailing comma, newline (incl. surrounding whitespace) - edit.delete(TextRange::from_to(prev.range().start(), node.range().end())); - } else if prev.kind() == COMMA && next.kind() == R_CURLY { - // Removes: comma, newline (incl. surrounding whitespace) - let space = if let Some(left) = prev.prev_sibling() { - compute_ws(left, next) - } else { - " " - }; - edit.replace( - TextRange::from_to(prev.range().start(), node.range().end()), - space.to_string(), - ); - } else if let (Some(_), Some(next)) = (ast::Comment::cast(prev), ast::Comment::cast(next)) { - // Removes: newline (incl. surrounding whitespace), start of the next comment - edit.delete(TextRange::from_to( - node.range().start(), - next.syntax().range().start() + TextUnit::of_str(next.prefix()), - )); - } else { - // Remove newline but add a computed amount of whitespace characters - edit.replace(node.range(), compute_ws(prev, next).to_string()); - } -} - -fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool { - match (left, right) { - (COMMA, R_PAREN) | (COMMA, R_BRACK) => true, - _ => false, - } -} - -fn join_single_expr_block(edit: &mut TextEditBuilder, node: &SyntaxNode) -> Option<()> { - let block = ast::Block::cast(node.parent()?)?; - let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?; - let expr = single_expr(block)?; - edit.replace( - block_expr.syntax().range(), - expr.syntax().text().to_string(), - ); - Some(()) -} - -fn single_expr(block: &ast::Block) -> Option<&ast::Expr> { - let mut res = None; - for child in block.syntax().children() { - if let Some(expr) = ast::Expr::cast(child) { - if expr.syntax().text().contains('\n') { - return None; - } - if mem::replace(&mut res, Some(expr)).is_some() { - return None; - } - } else { - match child.kind() { - WHITESPACE | L_CURLY | R_CURLY => (), - _ => return None, - } - } - } - res -} - -fn join_single_use_tree(edit: &mut TextEditBuilder, node: &SyntaxNode) -> Option<()> { - let use_tree_list = ast::UseTreeList::cast(node.parent()?)?; - let (tree,) = use_tree_list.use_trees().collect_tuple()?; - edit.replace( - use_tree_list.syntax().range(), - tree.syntax().text().to_string(), - ); - Some(()) -} - -fn compute_ws(left: &SyntaxNode, right: &SyntaxNode) -> &'static str { - match left.kind() { - L_PAREN | L_BRACK => return "", - L_CURLY => { - if let USE_TREE = right.kind() { - return ""; - } - } - _ => (), - } - match right.kind() { - R_PAREN | R_BRACK => return "", - R_CURLY => { - if let USE_TREE = left.kind() { - return ""; - } - } - DOT => return "", - _ => (), - } - " " -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{ - add_cursor, assert_eq_text, check_action, extract_offset, extract_range, -}; - - fn check_join_lines(before: &str, after: &str) { - check_action(before, after, |file, offset| { - let range = TextRange::offset_len(offset, 0.into()); - let res = join_lines(file, range); - Some(res) - }) - } - - #[test] - fn test_join_lines_comma() { - check_join_lines( - r" -fn foo() { - <|>foo(1, - ) -} -", - r" -fn foo() { - <|>foo(1) -} -", - ); - } - - #[test] - fn test_join_lines_lambda_block() { - check_join_lines( - r" -pub fn reparse(&self, edit: &AtomTextEdit) -> File { - <|>self.incremental_reparse(edit).unwrap_or_else(|| { - self.full_reparse(edit) - }) -} -", - r" -pub fn reparse(&self, edit: &AtomTextEdit) -> File { - <|>self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit)) -} -", - ); - } - - #[test] - fn test_join_lines_block() { - check_join_lines( - r" -fn foo() { - foo(<|>{ - 92 - }) -}", - r" -fn foo() { - foo(<|>92) -}", - ); - } - - #[test] - fn test_join_lines_use_items_left() { - // No space after the '{' - check_join_lines( - r" -<|>use ra_syntax::{ - TextUnit, TextRange, -};", - r" -<|>use ra_syntax::{TextUnit, TextRange, -};", - ); - } - - #[test] - fn test_join_lines_use_items_right() { - // No space after the '}' - check_join_lines( - r" -use ra_syntax::{ -<|> TextUnit, TextRange -};", - r" -use ra_syntax::{ -<|> TextUnit, TextRange};", - ); - } - - #[test] - fn test_join_lines_use_items_right_comma() { - // No space after the '}' - check_join_lines( - r" -use ra_syntax::{ -<|> TextUnit, TextRange, -};", - r" -use ra_syntax::{ -<|> TextUnit, TextRange};", - ); - } - - #[test] - fn test_join_lines_use_tree() { - check_join_lines( - r" -use ra_syntax::{ - algo::<|>{ - find_leaf_at_offset, - }, - ast, -};", - r" -use ra_syntax::{ - algo::<|>find_leaf_at_offset, - ast, -};", - ); - } - - #[test] - fn test_join_lines_normal_comments() { - check_join_lines( - r" -fn foo() { - // Hello<|> - // world! -} -", - r" -fn foo() { - // Hello<|> world! -} -", - ); - } - - #[test] - fn test_join_lines_doc_comments() { - check_join_lines( - r" -fn foo() { - /// Hello<|> - /// world! -} -", - r" -fn foo() { - /// Hello<|> world! -} -", - ); - } - - #[test] - fn test_join_lines_mod_comments() { - check_join_lines( - r" -fn foo() { - //! Hello<|> - //! world! -} -", - r" -fn foo() { - //! Hello<|> world! -} -", - ); - } - - #[test] - fn test_join_lines_multiline_comments_1() { - check_join_lines( - r" -fn foo() { - // Hello<|> - /* world! */ -} -", - r" -fn foo() { - // Hello<|> world! */ -} -", - ); - } - - #[test] - fn test_join_lines_multiline_comments_2() { - check_join_lines( - r" -fn foo() { - // The<|> - /* quick - brown - fox! */ -} -", - r" -fn foo() { - // The<|> quick - brown - fox! */ -} -", - ); - } - - fn check_join_lines_sel(before: &str, after: &str) { - let (sel, before) = extract_range(before); - let file = SourceFile::parse(&before); - let result = join_lines(&file, sel); - let actual = result.edit.apply(&before); - assert_eq_text!(after, &actual); - } - - #[test] - fn test_join_lines_selection_fn_args() { - check_join_lines_sel( - r" -fn foo() { - <|>foo(1, - 2, - 3, - <|>) -} - ", - r" -fn foo() { - foo(1, 2, 3) -} - ", - ); - } - - #[test] - fn test_join_lines_selection_struct() { - check_join_lines_sel( - r" -struct Foo <|>{ - f: u32, -}<|> - ", - r" -struct Foo { f: u32 } - ", - ); - } - - #[test] - fn test_join_lines_selection_dot_chain() { - check_join_lines_sel( - r" -fn foo() { - join(<|>type_params.type_params() - .filter_map(|it| it.name()) - .map(|it| it.text())<|>) -}", - r" -fn foo() { - join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text())) -}", - ); - } - - #[test] - fn test_join_lines_selection_lambda_block_body() { - check_join_lines_sel( - r" -pub fn handle_find_matching_brace() { - params.offsets - .map(|offset| <|>{ - world.analysis().matching_brace(&file, offset).unwrap_or(offset) - }<|>) - .collect(); -}", - r" -pub fn handle_find_matching_brace() { - params.offsets - .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset)) - .collect(); -}", - ); - } - - #[test] - fn test_on_eq_typed() { - fn do_check(before: &str, after: &str) { - let (offset, before) = extract_offset(before); - let file = SourceFile::parse(&before); - let result = on_eq_typed(&file, offset).unwrap(); - let actual = result.edit.apply(&before); - assert_eq_text!(after, &actual); - } - - // do_check(r" - // fn foo() { - // let foo =<|> - // } - // ", r" - // fn foo() { - // let foo =; - // } - // "); - do_check( - r" -fn foo() { - let foo =<|> 1 + 1 -} -", - r" -fn foo() { - let foo = 1 + 1; -} -", - ); - // do_check(r" - // fn foo() { - // let foo =<|> - // let bar = 1; - // } - // ", r" - // fn foo() { - // let foo =; - // let bar = 1; - // } - // "); - } - - #[test] - fn test_on_dot_typed() { - fn do_check(before: &str, after: &str) { - let (offset, before) = extract_offset(before); - let file = SourceFile::parse(&before); - if let Some(result) = on_eq_typed(&file, offset) { - let actual = result.edit.apply(&before); - assert_eq_text!(after, &actual); - }; - } - // indent if continuing chain call - do_check( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .<|> - } -", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - . - } -", - ); - - // do not indent if already indented - do_check( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .<|> - } -", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - . - } -", - ); - - // indent if the previous line is already indented - do_check( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - .<|> - } -", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - . - } -", - ); - - // don't indent if indent matches previous line - do_check( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - .<|> - } -", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - . - } -", - ); - - // don't indent if there is no method call on previous line - do_check( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - .<|> - } -", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - . - } -", - ); - - // indent to match previous expr - do_check( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) -.<|> - } -", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - . - } -", - ); - } - - #[test] - fn test_on_enter() { - fn apply_on_enter(before: &str) -> Option { - let (offset, before) = extract_offset(before); - let file = SourceFile::parse(&before); - let result = on_enter(&file, offset)?; - let actual = result.edit.apply(&before); - let actual = add_cursor(&actual, result.cursor_position.unwrap()); - Some(actual) - } - - fn do_check(before: &str, after: &str) { - let actual = apply_on_enter(before).unwrap(); - assert_eq_text!(after, &actual); - } - - fn do_check_noop(text: &str) { - assert!(apply_on_enter(text).is_none()) - } - - do_check( - r" -/// Some docs<|> -fn foo() { -} -", - r" -/// Some docs -/// <|> -fn foo() { -} -", - ); - do_check( - r" -impl S { - /// Some<|> docs. - fn foo() {} -} -", - r" -impl S { - /// Some - /// <|> docs. - fn foo() {} -} -", - ); - do_check_noop(r"<|>//! docz"); - } -} -- cgit v1.2.3