diff --git a/crates/libeditor/src/code_actions.rs b/crates/libeditor/src/code_actions.rs index 6c41923ddd7..c25ee973c9e 100644 --- a/crates/libeditor/src/code_actions.rs +++ b/crates/libeditor/src/code_actions.rs @@ -14,6 +14,7 @@ use libsyntax2::{ use {TextUnit, EditBuilder, Edit}; +#[derive(Debug)] pub struct ActionResult { pub edit: Edit, pub cursor_position: Option, diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs index 6bae7a3fa17..f8bc73ae9fa 100644 --- a/crates/libeditor/src/lib.rs +++ b/crates/libeditor/src/lib.rs @@ -7,11 +7,12 @@ mod symbols; mod line_index; mod edit; mod code_actions; +mod typing; use libsyntax2::{ ast::{self, NameOwner}, AstNode, - algo::{walk, find_leaf_at_offset}, + algo::{walk, find_leaf_at_offset, find_covering_node}, SyntaxKind::{self, *}, }; pub use libsyntax2::{ParsedFile, TextRange, TextUnit}; @@ -24,6 +25,7 @@ pub use self::{ ActionResult, find_node, flip_comma, add_derive, add_impl, }, + typing::join_lines, }; #[derive(Debug)] diff --git a/crates/libeditor/src/typing.rs b/crates/libeditor/src/typing.rs new file mode 100644 index 00000000000..f49dd0fdc6a --- /dev/null +++ b/crates/libeditor/src/typing.rs @@ -0,0 +1,81 @@ +use libsyntax2::{ + TextUnit, TextRange, SyntaxNodeRef, + ast, + algo::{ + walk::preorder, + find_covering_node, + }, +}; + +use {ActionResult, EditBuilder}; + +pub fn join_lines(file: &ast::ParsedFile, range: TextRange) -> ActionResult { + let range = if range.is_empty() { + let text = file.syntax().text(); + let text = &text[TextRange::from_to(range.start(), TextUnit::of_str(&text))]; + let pos = text.bytes().take_while(|&b| b != b'\n').count(); + if pos == text.len() { + return ActionResult { + edit: EditBuilder::new().finish(), + cursor_position: None + }; + } + let pos: TextUnit = (pos as u32).into(); + TextRange::offset_len( + range.start() + pos, + TextUnit::of_char('\n'), + ) + } else { + range + }; + let node = find_covering_node(file.syntax(), range); + let mut edit = EditBuilder::new(); + for node in preorder(node) { + let text = match node.leaf_text() { + Some(text) => text, + None => continue, + }; + let range = match intersect(range, 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; + remove_newline(&mut edit, node, text.as_str(), off); + } + } + + ActionResult { + edit: edit.finish(), + cursor_position: None, + } +} + +fn intersect(r1: TextRange, r2: TextRange) -> Option { + let start = r1.start().max(r2.start()); + let end = r1.end().min(r2.end()); + if start <= end { + Some(TextRange::from_to(start, end)) + } else { + None + } +} + +fn remove_newline( + edit: &mut EditBuilder, + node: SyntaxNodeRef, + node_text: &str, + offset: TextUnit, +) { + 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(), + ); +} diff --git a/crates/libeditor/tests/test.rs b/crates/libeditor/tests/test.rs index 42926ffc872..6aa260a861e 100644 --- a/crates/libeditor/tests/test.rs +++ b/crates/libeditor/tests/test.rs @@ -8,6 +8,7 @@ use libeditor::{ ParsedFile, TextUnit, TextRange, ActionResult, highlight, runnables, extend_selection, file_structure, flip_comma, add_derive, add_impl, matching_brace, + join_lines, }; #[test] @@ -177,6 +178,54 @@ fn test_matching_brace() { ); } +#[test] +fn test_join_lines_cursor() { + fn do_check(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) + }) + } + + do_check(r" +fn foo() { + <|>foo(1, + ) +} +", r" +fn foo() { + <|>foo(1, ) +} +"); +} + +#[test] +fn test_join_lines_selection() { + fn do_check(before: &str, after: &str) { + let (sel_start, before) = extract_cursor(before); + let (sel_end, before) = extract_cursor(&before); + let sel = TextRange::from_to(sel_start, sel_end); + let file = file(&before); + let result = join_lines(&file, sel); + let actual = result.edit.apply(&before); + assert_eq_text!(after, &actual); + } + + do_check(r" +fn foo() { + <|>foo(1, + 2, + 3, + <|>) +} +", r" +fn foo() { + foo(1, 2, 3, ) +} +"); +} + fn file(text: &str) -> ParsedFile { ParsedFile::parse(text) }