aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_editor')
-rw-r--r--crates/ra_editor/src/lib.rs2
-rw-r--r--crates/ra_editor/src/typing.rs169
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};
22use ra_text_edit::TextEditBuilder; 22use 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 @@
1use std::mem; 1use std::mem;
2 2
3use itertools::Itertools;
3use ra_syntax::{ 4use 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};
11use ra_text_edit::text_utils::contains_offset_nonstrict; 12use ra_text_edit::text_utils::contains_offset_nonstrict;
12use itertools::Itertools;
13 13
14use crate::{find_node_at_offset, TextEditBuilder, LocalEdit}; 14use crate::{find_node_at_offset, LocalEdit, TextEditBuilder};
15 15
16pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit { 16pub 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
139pub 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
185fn last_line_indent_in_whitespace(ws: &str) -> &str {
186 ws.split('\n').last().unwrap_or("")
187}
188
139fn remove_newline( 189fn 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)]
284mod tests { 334mod 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);