Detailed changes
@@ -491,8 +491,8 @@
"bindings": {
"ctrl-[": "editor::Outdent",
"ctrl-]": "editor::Indent",
- "shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above
- "shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below
+ "shift-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // Insert Cursor Above
+ "shift-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // Insert Cursor Below
"ctrl-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
@@ -539,10 +539,10 @@
"bindings": {
"cmd-[": "editor::Outdent",
"cmd-]": "editor::Indent",
- "cmd-ctrl-p": "editor::AddSelectionAbove", // Insert cursor above
- "cmd-alt-up": "editor::AddSelectionAbove",
- "cmd-ctrl-n": "editor::AddSelectionBelow", // Insert cursor below
- "cmd-alt-down": "editor::AddSelectionBelow",
+ "cmd-ctrl-p": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }], // Insert cursor above
+ "cmd-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }],
+ "cmd-ctrl-n": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }], // Insert cursor below
+ "cmd-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
"cmd-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
@@ -500,8 +500,8 @@
"bindings": {
"ctrl-[": "editor::Outdent",
"ctrl-]": "editor::Indent",
- "ctrl-shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above
- "ctrl-shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below
+ "ctrl-shift-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // Insert Cursor Above
+ "ctrl-shift-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // Insert Cursor Below
"ctrl-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
@@ -24,8 +24,8 @@
"ctrl-<": "editor::ScrollCursorCenter", // editor:scroll-to-cursor
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
- "alt-shift-down": "editor::AddSelectionBelow", // editor:add-selection-below
- "alt-shift-up": "editor::AddSelectionAbove", // editor:add-selection-above
+ "alt-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // editor:add-selection-below
+ "alt-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // editor:add-selection-above
"ctrl-j": "editor::JoinLines", // editor:join-lines
"ctrl-shift-d": "editor::DuplicateLineDown", // editor:duplicate-lines
"ctrl-up": "editor::MoveLineUp", // editor:move-line-up
@@ -28,8 +28,8 @@
{
"context": "Editor",
"bindings": {
- "ctrl-alt-up": "editor::AddSelectionAbove",
- "ctrl-alt-down": "editor::AddSelectionBelow",
+ "ctrl-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }],
+ "ctrl-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }],
"ctrl-shift-up": "editor::MoveLineUp",
"ctrl-shift-down": "editor::MoveLineDown",
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
@@ -25,8 +25,8 @@
"cmd-<": "editor::ScrollCursorCenter",
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
- "ctrl-shift-down": "editor::AddSelectionBelow",
- "ctrl-shift-up": "editor::AddSelectionAbove",
+ "ctrl-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
+ "ctrl-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }],
"alt-enter": "editor::Newline",
"cmd-shift-d": "editor::DuplicateLineDown",
"ctrl-cmd-up": "editor::MoveLineUp",
@@ -28,8 +28,8 @@
{
"context": "Editor",
"bindings": {
- "ctrl-shift-up": "editor::AddSelectionAbove",
- "ctrl-shift-down": "editor::AddSelectionBelow",
+ "ctrl-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }],
+ "ctrl-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }],
"cmd-ctrl-up": "editor::MoveLineUp",
"cmd-ctrl-down": "editor::MoveLineDown",
"cmd-shift-space": "editor::SelectAll",
@@ -498,8 +498,8 @@
"ctrl-c": "editor::ToggleComments",
"d": "vim::HelixDelete",
"c": "vim::Substitute",
- "shift-c": "editor::AddSelectionBelow",
- "alt-shift-c": "editor::AddSelectionAbove"
+ "shift-c": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
+ "alt-shift-c": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }]
}
},
{
@@ -318,6 +318,24 @@ pub struct GoToPreviousDiagnostic {
pub severity: GoToDiagnosticSeverityFilter,
}
+/// Adds a cursor above the current selection.
+#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
+#[action(namespace = editor)]
+#[serde(deny_unknown_fields)]
+pub struct AddSelectionAbove {
+ #[serde(default = "default_true")]
+ pub skip_soft_wrap: bool,
+}
+
+/// Adds a cursor below the current selection.
+#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
+#[action(namespace = editor)]
+#[serde(deny_unknown_fields)]
+pub struct AddSelectionBelow {
+ #[serde(default = "default_true")]
+ pub skip_soft_wrap: bool,
+}
+
actions!(
debugger,
[
@@ -345,10 +363,6 @@ actions!(
/// Accepts a partial edit prediction.
#[action(deprecated_aliases = ["editor::AcceptPartialCopilotSuggestion"])]
AcceptPartialEditPrediction,
- /// Adds a cursor above the current selection.
- AddSelectionAbove,
- /// Adds a cursor below the current selection.
- AddSelectionBelow,
/// Applies all diff hunks in the editor.
ApplyAllDiffHunks,
/// Applies the diff hunk at the current position.
@@ -1401,6 +1401,26 @@ impl DisplaySnapshot {
pub fn excerpt_header_height(&self) -> u32 {
self.block_snapshot.excerpt_header_height
}
+
+ /// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
+ /// the start of the buffer row that is a given number of buffer rows away
+ /// from the provided point.
+ ///
+ /// This moves by buffer rows instead of display rows, a distinction that is
+ /// important when soft wrapping is enabled.
+ pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
+ let start = self.display_point_to_fold_point(point, Bias::Left);
+ let target = start.row() as isize + times;
+ let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
+
+ self.clip_point(
+ self.fold_point_to_display_point(
+ self.fold_snapshot()
+ .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
+ ),
+ Bias::Right,
+ )
+ }
}
#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
@@ -14236,23 +14236,29 @@ impl Editor {
pub fn add_selection_above(
&mut self,
- _: &AddSelectionAbove,
+ action: &AddSelectionAbove,
window: &mut Window,
cx: &mut Context<Self>,
) {
- self.add_selection(true, window, cx);
+ self.add_selection(true, action.skip_soft_wrap, window, cx);
}
pub fn add_selection_below(
&mut self,
- _: &AddSelectionBelow,
+ action: &AddSelectionBelow,
window: &mut Window,
cx: &mut Context<Self>,
) {
- self.add_selection(false, window, cx);
+ self.add_selection(false, action.skip_soft_wrap, window, cx);
}
- fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
+ fn add_selection(
+ &mut self,
+ above: bool,
+ skip_soft_wrap: bool,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -14339,12 +14345,19 @@ impl Editor {
};
let mut maybe_new_selection = None;
+ let direction = if above { -1 } else { 1 };
+
while row != end_row {
- if above {
+ if skip_soft_wrap {
+ row = display_map
+ .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
+ .row();
+ } else if above {
row.0 -= 1;
} else {
row.0 += 1;
}
+
if let Some(new_selection) = self.selections.build_columnar_selection(
&display_map,
row,
@@ -25681,6 +25681,83 @@ async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppC
);
}
+#[gpui::test]
+async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ cx.set_state(indoc!(
+ r#"ΛThis is a very long line that will be wrapped when soft wrapping is enabled
+ Second line here"#
+ ));
+
+ cx.update_editor(|editor, window, cx| {
+ // Enable soft wrapping with a narrow width to force soft wrapping and
+ // confirm that more than 2 rows are being displayed.
+ editor.set_wrap_width(Some(100.0.into()), cx);
+ assert!(editor.display_text(cx).lines().count() > 2);
+
+ editor.add_selection_below(
+ &AddSelectionBelow {
+ skip_soft_wrap: true,
+ },
+ window,
+ cx,
+ );
+
+ assert_eq!(
+ editor.selections.display_ranges(cx),
+ &[
+ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
+ DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
+ ]
+ );
+
+ editor.add_selection_above(
+ &AddSelectionAbove {
+ skip_soft_wrap: true,
+ },
+ window,
+ cx,
+ );
+
+ assert_eq!(
+ editor.selections.display_ranges(cx),
+ &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
+ );
+
+ editor.add_selection_below(
+ &AddSelectionBelow {
+ skip_soft_wrap: false,
+ },
+ window,
+ cx,
+ );
+
+ assert_eq!(
+ editor.selections.display_ranges(cx),
+ &[
+ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
+ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
+ ]
+ );
+
+ editor.add_selection_above(
+ &AddSelectionAbove {
+ skip_soft_wrap: false,
+ },
+ window,
+ cx,
+ );
+
+ assert_eq!(
+ editor.selections.display_ranges(cx),
+ &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
+ );
+ });
+}
+
#[gpui::test(iterations = 10)]
async fn test_document_colors(cx: &mut TestAppContext) {
let expected_color = Rgba {
@@ -392,6 +392,11 @@ impl SelectionsCollection {
.collect()
}
+ /// Attempts to build a selection in the provided `DisplayRow` within the
+ /// same range as the provided range of `Pixels`.
+ /// Returns `None` if the range is not empty but it starts past the line's
+ /// length, meaning that the line isn't long enough to be contained within
+ /// part of the provided range.
pub fn build_columnar_selection(
&mut self,
display_map: &DisplaySnapshot,
@@ -1525,29 +1525,6 @@ fn wrapping_right_single(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayP
}
}
-/// Given a point, returns the start of the buffer row that is a given number of
-/// buffer rows away from the current position.
-///
-/// This moves by buffer rows instead of display rows, a distinction that is
-/// important when soft wrapping is enabled.
-pub(crate) fn start_of_relative_buffer_row(
- map: &DisplaySnapshot,
- point: DisplayPoint,
- times: isize,
-) -> DisplayPoint {
- let start = map.display_point_to_fold_point(point, Bias::Left);
- let target = start.row() as isize + times;
- let new_row = (target.max(0) as u32).min(map.fold_snapshot().max_point().row());
-
- map.clip_point(
- map.fold_point_to_display_point(
- map.fold_snapshot()
- .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
- ),
- Bias::Right,
- )
-}
-
fn up_down_buffer_rows(
map: &DisplaySnapshot,
mut point: DisplayPoint,
@@ -2127,7 +2104,7 @@ pub(crate) fn end_of_line(
times: usize,
) -> DisplayPoint {
if times > 1 {
- point = start_of_relative_buffer_row(map, point, times as isize - 1);
+ point = map.start_of_relative_buffer_row(point, times as isize - 1);
}
if display_lines {
map.clip_point(
@@ -2732,17 +2709,17 @@ fn sneak_backward(
}
fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
- let correct_line = start_of_relative_buffer_row(map, point, times as isize);
+ let correct_line = map.start_of_relative_buffer_row(point, times as isize);
first_non_whitespace(map, false, correct_line)
}
fn previous_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
- let correct_line = start_of_relative_buffer_row(map, point, -(times as isize));
+ let correct_line = map.start_of_relative_buffer_row(point, -(times as isize));
first_non_whitespace(map, false, correct_line)
}
fn go_to_column(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
- let correct_line = start_of_relative_buffer_row(map, point, 0);
+ let correct_line = map.start_of_relative_buffer_row(point, 0);
right(map, correct_line, times.saturating_sub(1))
}
@@ -2752,7 +2729,7 @@ pub(crate) fn next_line_end(
times: usize,
) -> DisplayPoint {
if times > 1 {
- point = start_of_relative_buffer_row(map, point, times as isize - 1);
+ point = map.start_of_relative_buffer_row(point, times as isize - 1);
}
end_of_line(map, false, point, 1)
}
@@ -679,7 +679,7 @@ impl Vim {
editor.edit_with_autoindent(edits, cx);
editor.change_selections(Default::default(), window, cx, |s| {
s.move_cursors_with(|map, cursor, _| {
- let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1);
+ let previous_line = map.start_of_relative_buffer_row(cursor, -1);
let insert_point = motion::end_of_line(map, false, previous_line, 1);
(insert_point, SelectionGoal::None)
});
@@ -15,10 +15,7 @@ use workspace::searchable::Direction;
use crate::{
Vim,
- motion::{
- Motion, MotionKind, first_non_whitespace, next_line_end, start_of_line,
- start_of_relative_buffer_row,
- },
+ motion::{Motion, MotionKind, first_non_whitespace, next_line_end, start_of_line},
object::Object,
state::{Mark, Mode, Operator},
};
@@ -406,7 +403,9 @@ impl Vim {
// Move to the next or previous buffer row, ensuring that
// wrapped lines are handled correctly.
let direction = if tail.row() > head.row() { -1 } else { 1 };
- row = start_of_relative_buffer_row(map, DisplayPoint::new(row, 0), direction).row();
+ row = map
+ .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
+ .row();
}
s.select(selections);
@@ -185,8 +185,18 @@ pub fn app_menus(cx: &mut App) -> Vec<Menu> {
editor::actions::SelectPreviousSyntaxNode,
),
MenuItem::separator(),
- MenuItem::action("Add Cursor Above", editor::actions::AddSelectionAbove),
- MenuItem::action("Add Cursor Below", editor::actions::AddSelectionBelow),
+ MenuItem::action(
+ "Add Cursor Above",
+ editor::actions::AddSelectionAbove {
+ skip_soft_wrap: true,
+ },
+ ),
+ MenuItem::action(
+ "Add Cursor Below",
+ editor::actions::AddSelectionBelow {
+ skip_soft_wrap: true,
+ },
+ ),
MenuItem::action(
"Select Next Occurrence",
editor::actions::SelectNext {
@@ -266,8 +266,18 @@ impl Render for QuickActionBar {
)
.action("Expand Selection", Box::new(SelectLargerSyntaxNode))
.action("Shrink Selection", Box::new(SelectSmallerSyntaxNode))
- .action("Add Cursor Above", Box::new(AddSelectionAbove))
- .action("Add Cursor Below", Box::new(AddSelectionBelow))
+ .action(
+ "Add Cursor Above",
+ Box::new(AddSelectionAbove {
+ skip_soft_wrap: true,
+ }),
+ )
+ .action(
+ "Add Cursor Below",
+ Box::new(AddSelectionBelow {
+ skip_soft_wrap: true,
+ }),
+ )
.separator()
.action("Go to Symbol", Box::new(ToggleOutline))
.action("Go to Line/Column", Box::new(ToggleGoToLine))