editor: Ignore soft wrapped lines when adding selection above or below (#40190)
- Add `skip_soft_wrap` field to both `AddSelectionAbove` and `AddSelectionBelow` actions. When set to `true`, which is now the default this will skip soft wrapped lines when extending the selections. - Move the `start_of_relative_buffer_row` function from the `vim::motion` module to the `editor::display_map::DisplaySnapshot` implementation as a method. - Update the default behavior for both `editor: add selection above` and `editor: add selection below` commands in order to skip over soft wrapped lines by default, mirroring VS Code's default behavior. - Update existing keymaps to specify this `skip_soft_wrap` value for both `AddSelectionAbove` and `AddSelectionBelow` actions. Closes #16979 Release Notes: - Updated both the `editor: add selection above` and `editor: add selection below` commands to ignore soft wrapped lines. If you wish to restore the old behavior, add the following to your keymap file: ``` { "context": "Editor", "bindings": { "cmd-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }], "cmd-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }] } } ``` --------- Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user