diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-07 06:26:09 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-07 06:26:09 +0000 |
commit | a6071c9f4c8441b4b8f2e970bc055d66cc9be5f0 (patch) | |
tree | 67fb82094ed6542f77f4ad1cc8b7bdbfb37bf6b1 /crates/ra_editor/src | |
parent | c69bb8a7e737e09c667f9e343d0f1d3e4c13b8f7 (diff) | |
parent | f3c708ab7babc4e94250cbfbaae0fdd3919284ce (diff) |
Merge #442
442: WIP: indent on typing dot r=matklad a=simonvandel
Fixes #439.
The unit test passes, but I can't seem to make VS code perform the action. The existing action on "=" doesn't work either on my end either though.
I didn't add any smart way of detecting the current indent level. Any ideas how I would do that?
Co-authored-by: Simon Vandel Sillesen <[email protected]>
Diffstat (limited to 'crates/ra_editor/src')
-rw-r--r-- | crates/ra_editor/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_editor/src/typing.rs | 169 |
2 files changed, 166 insertions, 5 deletions
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index ac283e2e0..a3c85ed5d 100644 --- a/crates/ra_editor/src/lib.rs +++ b/crates/ra_editor/src/lib.rs | |||
@@ -16,7 +16,7 @@ pub use self::{ | |||
16 | line_index::{LineCol, LineIndex}, | 16 | line_index::{LineCol, LineIndex}, |
17 | line_index_utils::translate_offset_with_edit, | 17 | line_index_utils::translate_offset_with_edit, |
18 | structure::{file_structure, StructureNode}, | 18 | structure::{file_structure, StructureNode}, |
19 | typing::{join_lines, on_enter, on_eq_typed}, | 19 | typing::{join_lines, on_enter, on_dot_typed, on_eq_typed}, |
20 | diagnostics::diagnostics | 20 | diagnostics::diagnostics |
21 | }; | 21 | }; |
22 | use ra_text_edit::TextEditBuilder; | 22 | use ra_text_edit::TextEditBuilder; |
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs index 1b568e96c..12500854c 100644 --- a/crates/ra_editor/src/typing.rs +++ b/crates/ra_editor/src/typing.rs | |||
@@ -1,17 +1,17 @@ | |||
1 | use std::mem; | 1 | use std::mem; |
2 | 2 | ||
3 | use itertools::Itertools; | ||
3 | use ra_syntax::{ | 4 | use ra_syntax::{ |
4 | algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset}, | 5 | algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset}, |
5 | ast, | 6 | ast, |
6 | text_utils::intersect, | 7 | text_utils::intersect, |
7 | AstNode, SourceFileNode, SyntaxKind, | 8 | AstNode, Direction, SourceFileNode, SyntaxKind, |
8 | SyntaxKind::*, | 9 | SyntaxKind::*, |
9 | SyntaxNodeRef, TextRange, TextUnit, | 10 | SyntaxNodeRef, TextRange, TextUnit, |
10 | }; | 11 | }; |
11 | use ra_text_edit::text_utils::contains_offset_nonstrict; | 12 | use ra_text_edit::text_utils::contains_offset_nonstrict; |
12 | use itertools::Itertools; | ||
13 | 13 | ||
14 | use crate::{find_node_at_offset, TextEditBuilder, LocalEdit}; | 14 | use crate::{find_node_at_offset, LocalEdit, TextEditBuilder}; |
15 | 15 | ||
16 | pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit { | 16 | pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit { |
17 | let range = if range.is_empty() { | 17 | let range = if range.is_empty() { |
@@ -136,6 +136,56 @@ pub fn on_eq_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> | |||
136 | }) | 136 | }) |
137 | } | 137 | } |
138 | 138 | ||
139 | pub fn on_dot_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> { | ||
140 | let before_dot_offset = offset - TextUnit::of_char('.'); | ||
141 | |||
142 | let whitespace = find_leaf_at_offset(file.syntax(), before_dot_offset).left_biased()?; | ||
143 | |||
144 | // find whitespace just left of the dot | ||
145 | ast::Whitespace::cast(whitespace)?; | ||
146 | |||
147 | // make sure there is a method call | ||
148 | let method_call = whitespace | ||
149 | .siblings(Direction::Prev) | ||
150 | // first is whitespace | ||
151 | .skip(1) | ||
152 | .next()?; | ||
153 | |||
154 | ast::MethodCallExprNode::cast(method_call)?; | ||
155 | |||
156 | // find how much the _method call is indented | ||
157 | let method_chain_indent = method_call | ||
158 | .parent()? | ||
159 | .siblings(Direction::Prev) | ||
160 | .skip(1) | ||
161 | .next()? | ||
162 | .leaf_text() | ||
163 | .map(|x| last_line_indent_in_whitespace(x))?; | ||
164 | |||
165 | let current_indent = TextUnit::of_str(last_line_indent_in_whitespace(whitespace.leaf_text()?)); | ||
166 | // TODO: indent is always 4 spaces now. A better heuristic could look on the previous line(s) | ||
167 | |||
168 | let target_indent = TextUnit::of_str(method_chain_indent) + TextUnit::from_usize(4); | ||
169 | |||
170 | let diff = target_indent - current_indent; | ||
171 | |||
172 | let indent = "".repeat(diff.to_usize()); | ||
173 | |||
174 | let cursor_position = offset + diff; | ||
175 | let mut edit = TextEditBuilder::default(); | ||
176 | edit.insert(before_dot_offset, indent); | ||
177 | Some(LocalEdit { | ||
178 | label: "indent dot".to_string(), | ||
179 | edit: edit.finish(), | ||
180 | cursor_position: Some(cursor_position), | ||
181 | }) | ||
182 | } | ||
183 | |||
184 | /// Finds the last line in the whitespace | ||
185 | fn last_line_indent_in_whitespace(ws: &str) -> &str { | ||
186 | ws.split('\n').last().unwrap_or("") | ||
187 | } | ||
188 | |||
139 | fn remove_newline( | 189 | fn remove_newline( |
140 | edit: &mut TextEditBuilder, | 190 | edit: &mut TextEditBuilder, |
141 | node: SyntaxNodeRef, | 191 | node: SyntaxNodeRef, |
@@ -283,7 +333,9 @@ fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str { | |||
283 | #[cfg(test)] | 333 | #[cfg(test)] |
284 | mod tests { | 334 | mod tests { |
285 | use super::*; | 335 | use super::*; |
286 | use crate::test_utils::{add_cursor, check_action, extract_offset, extract_range, assert_eq_text}; | 336 | use crate::test_utils::{ |
337 | add_cursor, assert_eq_text, check_action, extract_offset, extract_range, | ||
338 | }; | ||
287 | 339 | ||
288 | fn check_join_lines(before: &str, after: &str) { | 340 | fn check_join_lines(before: &str, after: &str) { |
289 | check_action(before, after, |file, offset| { | 341 | check_action(before, after, |file, offset| { |
@@ -615,6 +667,115 @@ fn foo() { | |||
615 | } | 667 | } |
616 | 668 | ||
617 | #[test] | 669 | #[test] |
670 | fn test_on_dot_typed() { | ||
671 | fn do_check(before: &str, after: &str) { | ||
672 | let (offset, before) = extract_offset(before); | ||
673 | let file = SourceFileNode::parse(&before); | ||
674 | if let Some(result) = on_eq_typed(&file, offset) { | ||
675 | let actual = result.edit.apply(&before); | ||
676 | assert_eq_text!(after, &actual); | ||
677 | }; | ||
678 | } | ||
679 | // indent if continuing chain call | ||
680 | do_check( | ||
681 | r" | ||
682 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
683 | self.child_impl(db, name) | ||
684 | .<|> | ||
685 | } | ||
686 | ", | ||
687 | r" | ||
688 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
689 | self.child_impl(db, name) | ||
690 | . | ||
691 | } | ||
692 | ", | ||
693 | ); | ||
694 | |||
695 | // do not indent if already indented | ||
696 | do_check( | ||
697 | r" | ||
698 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
699 | self.child_impl(db, name) | ||
700 | .<|> | ||
701 | } | ||
702 | ", | ||
703 | r" | ||
704 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
705 | self.child_impl(db, name) | ||
706 | . | ||
707 | } | ||
708 | ", | ||
709 | ); | ||
710 | |||
711 | // indent if the previous line is already indented | ||
712 | do_check( | ||
713 | r" | ||
714 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
715 | self.child_impl(db, name) | ||
716 | .first() | ||
717 | .<|> | ||
718 | } | ||
719 | ", | ||
720 | r" | ||
721 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
722 | self.child_impl(db, name) | ||
723 | .first() | ||
724 | . | ||
725 | } | ||
726 | ", | ||
727 | ); | ||
728 | |||
729 | // don't indent if indent matches previous line | ||
730 | do_check( | ||
731 | r" | ||
732 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
733 | self.child_impl(db, name) | ||
734 | .first() | ||
735 | .<|> | ||
736 | } | ||
737 | ", | ||
738 | r" | ||
739 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
740 | self.child_impl(db, name) | ||
741 | .first() | ||
742 | . | ||
743 | } | ||
744 | ", | ||
745 | ); | ||
746 | |||
747 | // don't indent if there is no method call on previous line | ||
748 | do_check( | ||
749 | r" | ||
750 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
751 | .<|> | ||
752 | } | ||
753 | ", | ||
754 | r" | ||
755 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
756 | . | ||
757 | } | ||
758 | ", | ||
759 | ); | ||
760 | |||
761 | // indent to match previous expr | ||
762 | do_check( | ||
763 | r" | ||
764 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
765 | self.child_impl(db, name) | ||
766 | .<|> | ||
767 | } | ||
768 | ", | ||
769 | r" | ||
770 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | ||
771 | self.child_impl(db, name) | ||
772 | . | ||
773 | } | ||
774 | ", | ||
775 | ); | ||
776 | } | ||
777 | |||
778 | #[test] | ||
618 | fn test_on_enter() { | 779 | fn test_on_enter() { |
619 | fn apply_on_enter(before: &str) -> Option<String> { | 780 | fn apply_on_enter(before: &str) -> Option<String> { |
620 | let (offset, before) = extract_offset(before); | 781 | let (offset, before) = extract_offset(before); |