diff --git a/zed/src/editor/buffer/selection.rs b/zed/src/editor/buffer/selection.rs index 1d7459482c22a16407bf379ddff3017357c51566..0999298357758cb00d29f155eb24a98a8907a8b6 100644 --- a/zed/src/editor/buffer/selection.rs +++ b/zed/src/editor/buffer/selection.rs @@ -73,7 +73,11 @@ impl Selection { } } - pub fn buffer_rows_for_display_rows(&self, map: &DisplayMap, ctx: &AppContext) -> Range { + pub fn buffer_rows_for_display_rows( + &self, + map: &DisplayMap, + ctx: &AppContext, + ) -> (Range, Range) { let display_start = self.start.to_display_point(map, ctx).unwrap(); let buffer_start = DisplayPoint::new(display_start.row(), 0) .to_buffer_point(map, Bias::Left, ctx) @@ -93,6 +97,9 @@ impl Selection { .to_buffer_point(map, Bias::Left, ctx) .unwrap(); - buffer_start.row..buffer_end.row + 1 + ( + buffer_start.row..buffer_end.row + 1, + display_start.row()..display_end.row() + 1, + ) } } diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index b48c16c23b941a6cab44dca439ede1406e1e855c..6bd727dc4fc93532ac7511d0976d37800681c77c 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -46,6 +46,8 @@ pub fn init(app: &mut MutableAppContext) { Some("BufferView"), ), Binding::new("cmd-shift-D", "buffer:duplicate_line", Some("BufferView")), + Binding::new("ctrl-cmd-up", "buffer:move_line_up", Some("BufferView")), + Binding::new("ctrl-cmd-down", "buffer:move_line_down", Some("BufferView")), Binding::new("cmd-x", "buffer:cut", Some("BufferView")), Binding::new("cmd-c", "buffer:copy", Some("BufferView")), Binding::new("cmd-v", "buffer:paste", Some("BufferView")), @@ -133,6 +135,8 @@ pub fn init(app: &mut MutableAppContext) { BufferView::delete_to_end_of_line, ); app.add_action("buffer:duplicate_line", BufferView::duplicate_line); + app.add_action("buffer:move_line_up", BufferView::move_line_up); + app.add_action("buffer:move_line_down", BufferView::move_line_down); app.add_action("buffer:cut", BufferView::cut); app.add_action("buffer:copy", BufferView::copy); app.add_action("buffer:paste", BufferView::paste); @@ -639,7 +643,7 @@ impl BufferView { let mut selections = self.selections(app).iter().peekable(); while let Some(selection) = selections.next() { - let mut rows = selection.buffer_rows_for_display_rows(map, app); + let (mut rows, _) = selection.buffer_rows_for_display_rows(map, app); let goal_display_column = selection .head() .to_display_point(map, app) @@ -648,7 +652,7 @@ impl BufferView { // Accumulate contiguous regions of rows that we want to delete. while let Some(next_selection) = selections.peek() { - let next_rows = next_selection.buffer_rows_for_display_rows(map, app); + let (next_rows, _) = next_selection.buffer_rows_for_display_rows(map, app); if next_rows.start <= rows.end { rows.end = next_rows.end; selections.next().unwrap(); @@ -696,10 +700,10 @@ impl BufferView { goal_column: None, }) .collect(); - self.update_selections(new_selections, true, ctx); self.buffer .update(ctx, |buffer, ctx| buffer.edit(edit_ranges, "", Some(ctx))) .unwrap(); + self.update_selections(new_selections, true, ctx); self.end_transaction(ctx); } @@ -726,9 +730,9 @@ impl BufferView { let mut selections_iter = selections.iter_mut().peekable(); while let Some(selection) = selections_iter.next() { // Avoid duplicating the same lines twice. - let mut rows = selection.buffer_rows_for_display_rows(map, app); + let (mut rows, _) = selection.buffer_rows_for_display_rows(map, app); while let Some(next_selection) = selections_iter.peek() { - let next_rows = next_selection.buffer_rows_for_display_rows(map, app); + let (next_rows, _) = next_selection.buffer_rows_for_display_rows(map, app); if next_rows.start <= rows.end - 1 { rows.end = next_rows.end; selections_iter.next().unwrap(); @@ -765,6 +769,156 @@ impl BufferView { self.end_transaction(ctx); } + pub fn move_line_up(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + + let app = ctx.as_ref(); + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(ctx); + + let mut edits = Vec::new(); + let selections = self.selections(ctx.as_ref()).to_vec(); + let mut selections_iter = selections.iter().peekable(); + while let Some(selection) = selections_iter.next() { + // Accumulate contiguous regions of rows that we want to move. + let (mut buffer_rows, mut display_rows) = + selection.buffer_rows_for_display_rows(map, app); + while let Some(next_selection) = selections_iter.peek() { + let (next_buffer_rows, next_display_rows) = + next_selection.buffer_rows_for_display_rows(map, app); + if next_buffer_rows.start <= buffer_rows.end { + buffer_rows.end = next_buffer_rows.end; + display_rows.end = next_display_rows.end; + selections_iter.next().unwrap(); + } else { + break; + } + } + + // Cut the text from the previous line and paste it at the end of the selected region. + if display_rows.start != 0 { + let selection_line_end = Point::new( + buffer_rows.end - 1, + buffer.line_len(buffer_rows.end - 1).unwrap(), + ) + .to_offset(buffer) + .unwrap(); + let prev_line_display_start = DisplayPoint::new(display_rows.start - 1, 0); + let prev_line_display_end = DisplayPoint::new( + prev_line_display_start.row(), + map.line_len(prev_line_display_start.row(), app).unwrap(), + ); + let prev_line_start = prev_line_display_start + .to_buffer_offset(map, Bias::Left, app) + .unwrap(); + let prev_line_end = prev_line_display_end + .to_buffer_offset(map, Bias::Right, app) + .unwrap(); + + let mut text = String::new(); + text.push('\n'); + text.extend( + buffer + .text_for_range(prev_line_start..prev_line_end) + .unwrap(), + ); + edits.push((prev_line_start..prev_line_end + 1, String::new())); + edits.push((selection_line_end..selection_line_end, text)); + } + } + + self.buffer.update(ctx, |buffer, ctx| { + for (range, text) in edits.into_iter().rev() { + buffer.edit(Some(range), text, Some(ctx)).unwrap(); + } + }); + self.update_selections(selections, true, ctx); + self.end_transaction(ctx); + } + + pub fn move_line_down(&mut self, _: &(), ctx: &mut ViewContext) { + self.start_transaction(ctx); + + let mut selections = self.selections(ctx.as_ref()).to_vec(); + { + // Temporarily bias selections right to allow moved lines to push them down when the + // selections are at the beginning of a line. + let buffer = self.buffer.read(ctx); + for selection in &mut selections { + selection.start = selection.start.bias_right(buffer).unwrap(); + selection.end = selection.end.bias_right(buffer).unwrap(); + } + } + self.update_selections(selections.clone(), false, ctx); + + let app = ctx.as_ref(); + let buffer = self.buffer.read(ctx); + let map = self.display_map.read(ctx); + + let mut edits = Vec::new(); + let selections = self.selections(ctx.as_ref()).to_vec(); + let mut selections_iter = selections.iter().peekable(); + while let Some(selection) = selections_iter.next() { + // Accumulate contiguous regions of rows that we want to move. + let (mut buffer_rows, mut display_rows) = + selection.buffer_rows_for_display_rows(map, app); + while let Some(next_selection) = selections_iter.peek() { + let (next_buffer_rows, next_display_rows) = + next_selection.buffer_rows_for_display_rows(map, app); + if next_buffer_rows.start <= buffer_rows.end { + buffer_rows.end = next_buffer_rows.end; + display_rows.end = next_display_rows.end; + selections_iter.next().unwrap(); + } else { + break; + } + } + + // Cut the text from the following line and paste it at the start of the selected region. + if buffer_rows.end <= buffer.max_point().row { + let selection_line_start = + Point::new(buffer_rows.start, 0).to_offset(buffer).unwrap(); + let next_line_display_end = DisplayPoint::new( + display_rows.end, + map.line_len(display_rows.end, app).unwrap(), + ); + let next_line_start = Point::new(buffer_rows.end, 0).to_offset(buffer).unwrap(); + let next_line_end = next_line_display_end + .to_buffer_offset(map, Bias::Right, app) + .unwrap(); + + let mut text = String::new(); + text.extend( + buffer + .text_for_range(next_line_start..next_line_end) + .unwrap(), + ); + text.push('\n'); + edits.push((selection_line_start..selection_line_start, text)); + edits.push((next_line_start - 1..next_line_end, String::new())); + } + } + + self.buffer.update(ctx, |buffer, ctx| { + for (range, text) in edits.into_iter().rev() { + buffer.edit(Some(range), text, Some(ctx)).unwrap(); + } + }); + + let mut selections = self.selections(ctx.as_ref()).to_vec(); + { + // Restore bias on selections. + let buffer = self.buffer.read(ctx); + for selection in &mut selections { + selection.start = selection.start.bias_left(buffer).unwrap(); + selection.end = selection.end.bias_left(buffer).unwrap(); + } + } + self.update_selections(selections, true, ctx); + + self.end_transaction(ctx); + } + pub fn cut(&mut self, _: &(), ctx: &mut ViewContext) { self.start_transaction(ctx); let mut text = String::new(); @@ -1175,9 +1329,11 @@ impl BufferView { } pub fn move_to_beginning(&mut self, _: &(), ctx: &mut ViewContext) { + let buffer = self.buffer.read(ctx); + let cursor = buffer.anchor_before(Point::new(0, 0)).unwrap(); let selection = Selection { - start: Anchor::Start, - end: Anchor::Start, + start: cursor.clone(), + end: cursor, reversed: false, goal_column: None, }; @@ -1191,9 +1347,11 @@ impl BufferView { } pub fn move_to_end(&mut self, _: &(), ctx: &mut ViewContext) { + let buffer = self.buffer.read(ctx); + let cursor = buffer.anchor_before(buffer.max_point()).unwrap(); let selection = Selection { - start: Anchor::End, - end: Anchor::End, + start: cursor.clone(), + end: cursor, reversed: false, goal_column: None, }; diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 536971a87d13cffc33fc9e63a0d64ae3e2f02099..fd63a3e2ec478acb0b32c4bca19d6d4b83a93b95 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -169,6 +169,13 @@ impl FoldMap { false } + pub fn to_buffer_offset(&self, point: DisplayPoint, app: &AppContext) -> Result { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&point, SeekBias::Right); + let overshoot = point.0 - cursor.start().display.lines; + (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(app)) + } + pub fn to_display_offset( &self, point: DisplayPoint, diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index e8d2b6c6e5e199eb50111edb1d70cc43c29d7586..77a3defb124faf98aed9304b5b1b561bb9af92e8 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -179,6 +179,11 @@ impl DisplayPoint { .to_buffer_point(self.collapse_tabs(map, bias, app)?.0)) } + pub fn to_buffer_offset(self, map: &DisplayMap, bias: Bias, app: &AppContext) -> Result { + map.fold_map + .to_buffer_offset(self.collapse_tabs(map, bias, app)?.0, app) + } + fn expand_tabs(mut self, map: &DisplayMap, app: &AppContext) -> Result { let chars = map .fold_map