@@ -68,7 +68,8 @@ action!(Backspace);
action!(Delete);
action!(Input, String);
action!(Newline);
-action!(Tab);
+action!(Tab, Direction);
+action!(Indent);
action!(Outdent);
action!(DeleteLine);
action!(DeleteToPreviousWordStart);
@@ -171,13 +172,15 @@ pub fn init(cx: &mut MutableAppContext) {
Some("Editor && showing_code_actions"),
),
Binding::new("enter", ConfirmRename, Some("Editor && renaming")),
- Binding::new("tab", Tab, Some("Editor")),
+ Binding::new("tab", Tab(Direction::Next), Some("Editor")),
+ Binding::new("shift-tab", Tab(Direction::Prev), Some("Editor")),
Binding::new(
"tab",
ConfirmCompletion(None),
Some("Editor && showing_completions"),
),
- Binding::new("shift-tab", Outdent, Some("Editor")),
+ Binding::new("cmd-[", Outdent, Some("Editor")),
+ Binding::new("cmd-]", Indent, Some("Editor")),
Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")),
Binding::new("alt-backspace", DeleteToPreviousWordStart, Some("Editor")),
Binding::new("alt-h", DeleteToPreviousWordStart, Some("Editor")),
@@ -305,6 +308,7 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::backspace);
cx.add_action(Editor::delete);
cx.add_action(Editor::tab);
+ cx.add_action(Editor::indent);
cx.add_action(Editor::outdent);
cx.add_action(Editor::delete_line);
cx.add_action(Editor::delete_to_previous_word_start);
@@ -2861,75 +2865,101 @@ impl Editor {
});
}
- pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
- if self.move_to_next_snippet_tabstop(cx) {
- return;
+ pub fn tab(&mut self, &Tab(direction): &Tab, cx: &mut ViewContext<Self>) {
+ match direction {
+ Direction::Prev => {
+ if !self.snippet_stack.is_empty() {
+ self.move_to_prev_snippet_tabstop(cx);
+ return;
+ }
+
+ self.outdent(&Outdent, cx);
+ }
+ Direction::Next => {
+ if self.move_to_next_snippet_tabstop(cx) {
+ return;
+ }
+
+ let tab_size = cx.global::<Settings>().tab_size;
+ let mut selections = self.local_selections::<Point>(cx);
+ if selections.iter().all(|s| s.is_empty()) {
+ self.transact(cx, |this, cx| {
+ this.buffer.update(cx, |buffer, cx| {
+ for selection in &mut selections {
+ let char_column = buffer
+ .read(cx)
+ .text_for_range(
+ Point::new(selection.start.row, 0)..selection.start,
+ )
+ .flat_map(str::chars)
+ .count();
+ let chars_to_next_tab_stop = tab_size - (char_column % tab_size);
+ buffer.edit(
+ [selection.start..selection.start],
+ " ".repeat(chars_to_next_tab_stop),
+ cx,
+ );
+ selection.start.column += chars_to_next_tab_stop as u32;
+ selection.end = selection.start;
+ }
+ });
+ this.update_selections(selections, Some(Autoscroll::Fit), cx);
+ });
+ } else {
+ self.indent(&Indent, cx);
+ }
+ }
}
+ }
+ pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext<Self>) {
let tab_size = cx.global::<Settings>().tab_size;
let mut selections = self.local_selections::<Point>(cx);
self.transact(cx, |this, cx| {
let mut last_indent = None;
this.buffer.update(cx, |buffer, cx| {
for selection in &mut selections {
- if selection.is_empty() {
- let char_column = buffer
- .read(cx)
- .text_for_range(Point::new(selection.start.row, 0)..selection.start)
- .flat_map(str::chars)
- .count();
- let chars_to_next_tab_stop = tab_size - (char_column % tab_size);
+ let mut start_row = selection.start.row;
+ let mut end_row = selection.end.row + 1;
+
+ // If a selection ends at the beginning of a line, don't indent
+ // that last line.
+ if selection.end.column == 0 {
+ end_row -= 1;
+ }
+
+ // Avoid re-indenting a row that has already been indented by a
+ // previous selection, but still update this selection's column
+ // to reflect that indentation.
+ if let Some((last_indent_row, last_indent_len)) = last_indent {
+ if last_indent_row == selection.start.row {
+ selection.start.column += last_indent_len;
+ start_row += 1;
+ }
+ if last_indent_row == selection.end.row {
+ selection.end.column += last_indent_len;
+ }
+ }
+
+ for row in start_row..end_row {
+ let indent_column = buffer.read(cx).indent_column_for_line(row) as usize;
+ let columns_to_next_tab_stop = tab_size - (indent_column % tab_size);
+ let row_start = Point::new(row, 0);
buffer.edit(
- [selection.start..selection.start],
- " ".repeat(chars_to_next_tab_stop),
+ [row_start..row_start],
+ " ".repeat(columns_to_next_tab_stop),
cx,
);
- selection.start.column += chars_to_next_tab_stop as u32;
- selection.end = selection.start;
- } else {
- let mut start_row = selection.start.row;
- let mut end_row = selection.end.row + 1;
- // If a selection ends at the beginning of a line, don't indent
- // that last line.
- if selection.end.column == 0 {
- end_row -= 1;
+ // Update this selection's endpoints to reflect the indentation.
+ if row == selection.start.row {
+ selection.start.column += columns_to_next_tab_stop as u32;
}
-
- // Avoid re-indenting a row that has already been indented by a
- // previous selection, but still update this selection's column
- // to reflect that indentation.
- if let Some((last_indent_row, last_indent_len)) = last_indent {
- if last_indent_row == selection.start.row {
- selection.start.column += last_indent_len;
- start_row += 1;
- }
- if last_indent_row == selection.end.row {
- selection.end.column += last_indent_len;
- }
+ if row == selection.end.row {
+ selection.end.column += columns_to_next_tab_stop as u32;
}
- for row in start_row..end_row {
- let indent_column =
- buffer.read(cx).indent_column_for_line(row) as usize;
- let columns_to_next_tab_stop = tab_size - (indent_column % tab_size);
- let row_start = Point::new(row, 0);
- buffer.edit(
- [row_start..row_start],
- " ".repeat(columns_to_next_tab_stop),
- cx,
- );
-
- // Update this selection's endpoints to reflect the indentation.
- if row == selection.start.row {
- selection.start.column += columns_to_next_tab_stop as u32;
- }
- if row == selection.end.row {
- selection.end.column += columns_to_next_tab_stop as u32;
- }
-
- last_indent = Some((row, columns_to_next_tab_stop as u32));
- }
+ last_indent = Some((row, columns_to_next_tab_stop as u32));
}
}
});
@@ -2939,11 +2969,6 @@ impl Editor {
}
pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
- if !self.snippet_stack.is_empty() {
- self.move_to_prev_snippet_tabstop(cx);
- return;
- }
-
let tab_size = cx.global::<Settings>().tab_size;
let selections = self.local_selections::<Point>(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -7458,7 +7483,7 @@ mod tests {
);
// indent from mid-tabstop to full tabstop
- view.tab(&Tab, cx);
+ view.tab(&Tab(Direction::Next), cx);
assert_eq!(view.text(cx), " one two\nthree\n four");
assert_eq!(
view.selected_display_ranges(cx),
@@ -7469,7 +7494,7 @@ mod tests {
);
// outdent from 1 tabstop to 0 tabstops
- view.outdent(&Outdent, cx);
+ view.tab(&Tab(Direction::Prev), cx);
assert_eq!(view.text(cx), "one two\nthree\n four");
assert_eq!(
view.selected_display_ranges(cx),
@@ -7483,13 +7508,13 @@ mod tests {
view.select_display_ranges(&[DisplayPoint::new(1, 1)..DisplayPoint::new(2, 0)], cx);
// indent and outdent affect only the preceding line
- view.tab(&Tab, cx);
+ view.tab(&Tab(Direction::Next), cx);
assert_eq!(view.text(cx), "one two\n three\n four");
assert_eq!(
view.selected_display_ranges(cx),
&[DisplayPoint::new(1, 5)..DisplayPoint::new(2, 0)]
);
- view.outdent(&Outdent, cx);
+ view.tab(&Tab(Direction::Prev), cx);
assert_eq!(view.text(cx), "one two\nthree\n four");
assert_eq!(
view.selected_display_ranges(cx),
@@ -7498,7 +7523,7 @@ mod tests {
// Ensure that indenting/outdenting works when the cursor is at column 0.
view.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
- view.tab(&Tab, cx);
+ view.tab(&Tab(Direction::Next), cx);
assert_eq!(view.text(cx), "one two\n three\n four");
assert_eq!(
view.selected_display_ranges(cx),
@@ -7506,7 +7531,7 @@ mod tests {
);
view.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
- view.outdent(&Outdent, cx);
+ view.tab(&Tab(Direction::Prev), cx);
assert_eq!(view.text(cx), "one two\nthree\n four");
assert_eq!(
view.selected_display_ranges(cx),