mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 09:26:19 +04:00
Merge cc659f2cba
into b8313da5a8
This commit is contained in:
commit
c5de1a5493
@ -11,7 +11,7 @@
|
|||||||
next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
|
next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
|
||||||
prev_grapheme_boundary,
|
prev_grapheme_boundary,
|
||||||
},
|
},
|
||||||
line_ending::rope_is_line_ending,
|
line_ending::{line_end_char_index, rope_is_line_ending},
|
||||||
position::char_idx_at_visual_block_offset,
|
position::char_idx_at_visual_block_offset,
|
||||||
syntax::LanguageConfiguration,
|
syntax::LanguageConfiguration,
|
||||||
text_annotations::TextAnnotations,
|
text_annotations::TextAnnotations,
|
||||||
@ -52,6 +52,40 @@ pub fn move_horizontally(
|
|||||||
range.put_cursor(slice, new_pos, behaviour == Movement::Extend)
|
range.put_cursor(slice, new_pos, behaviour == Movement::Extend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_horizontally_same_line(
|
||||||
|
slice: RopeSlice,
|
||||||
|
range: Range,
|
||||||
|
dir: Direction,
|
||||||
|
count: usize,
|
||||||
|
behaviour: Movement,
|
||||||
|
text_fmt: &TextFormat,
|
||||||
|
annotations: &mut TextAnnotations,
|
||||||
|
) -> Range {
|
||||||
|
let pos = range.cursor(slice);
|
||||||
|
let line = slice.char_to_line(pos);
|
||||||
|
|
||||||
|
let new_range = move_horizontally(slice, range, dir, count, behaviour, text_fmt, annotations);
|
||||||
|
let new_pos = new_range.cursor(slice);
|
||||||
|
let new_line = slice.char_to_line(new_pos);
|
||||||
|
|
||||||
|
match new_line.cmp(&line) {
|
||||||
|
std::cmp::Ordering::Equal => {
|
||||||
|
// we'll end up in same line - move there
|
||||||
|
new_range
|
||||||
|
}
|
||||||
|
std::cmp::Ordering::Less => {
|
||||||
|
// we'll end up in a line before - move to the beginning of the line
|
||||||
|
let line_beginning = slice.line_to_char(line);
|
||||||
|
range.put_cursor(slice, line_beginning, behaviour == Movement::Extend)
|
||||||
|
}
|
||||||
|
std::cmp::Ordering::Greater => {
|
||||||
|
// we'll end up in a line after - move to the end of the line
|
||||||
|
let line_end = line_end_char_index(&slice, line);
|
||||||
|
range.put_cursor(slice, line_end, behaviour == Movement::Extend)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_vertically_visual(
|
pub fn move_vertically_visual(
|
||||||
slice: RopeSlice,
|
slice: RopeSlice,
|
||||||
range: Range,
|
range: Range,
|
||||||
@ -165,6 +199,190 @@ pub fn move_vertically(
|
|||||||
new_range
|
new_range
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MoveFn =
|
||||||
|
fn(RopeSlice, Range, Direction, usize, Movement, &TextFormat, &mut TextAnnotations) -> Range;
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)] // just an internal helper function
|
||||||
|
fn move_anchored(
|
||||||
|
move_fn: MoveFn,
|
||||||
|
slice: RopeSlice,
|
||||||
|
range: Range,
|
||||||
|
dir: Direction,
|
||||||
|
count: usize,
|
||||||
|
behaviour: Movement,
|
||||||
|
text_fmt: &TextFormat,
|
||||||
|
annotations: &mut TextAnnotations,
|
||||||
|
) -> Range {
|
||||||
|
/// Store an indicator in a given [`Range`] to be able to remember the previous strategy
|
||||||
|
/// (stay on newlines or avoid them) after encountering a newline.
|
||||||
|
fn set_indicator_to_stay_on_newline(range: &mut Range) {
|
||||||
|
let softwrapped_lines: u32 = range.old_visual_position.unzip().0.unwrap_or(0);
|
||||||
|
range.old_visual_position = Some((softwrapped_lines, u32::MAX));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the indicator that might previously have been set with
|
||||||
|
/// [`set_indicator_to_stay_on_newline()`].
|
||||||
|
fn get_indicator_to_stay_on_newline(range: &Range) -> bool {
|
||||||
|
match range.old_visual_position {
|
||||||
|
None => false,
|
||||||
|
Some((_, u32::MAX)) => true,
|
||||||
|
Some((_, _)) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Figure out if a certain position/index is in a visual empty line.
|
||||||
|
///
|
||||||
|
/// If the given `pos` is a newline character and it is alone in its line or visual line,
|
||||||
|
/// this function will return `true`, otherwise `false.
|
||||||
|
fn is_in_visual_empty_line(
|
||||||
|
slice: RopeSlice,
|
||||||
|
text_fmt: &TextFormat,
|
||||||
|
annotations: &TextAnnotations,
|
||||||
|
pos: usize,
|
||||||
|
) -> bool {
|
||||||
|
let line = slice.char_to_line(pos);
|
||||||
|
|
||||||
|
// if this line only contains a newline char, it is empty
|
||||||
|
if rope_is_line_ending(slice.line(line)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here without soft wrap, this line is not empty
|
||||||
|
if !text_fmt.soft_wrap {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if pos is not the last character, there have to be other chars in the same visual line
|
||||||
|
if pos != line_end_char_index(&slice, line) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the previous char (has to exist) is not in the same row, this is an empty visual line
|
||||||
|
let prev = prev_grapheme_boundary(slice, pos);
|
||||||
|
let pos_visual_row = visual_offset_from_block(slice, pos, pos, text_fmt, annotations)
|
||||||
|
.0
|
||||||
|
.row;
|
||||||
|
let prev_visual_row = visual_offset_from_block(slice, prev, prev, text_fmt, annotations)
|
||||||
|
.0
|
||||||
|
.row;
|
||||||
|
pos_visual_row != prev_visual_row
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move to the next newline character, in direction of movement.
|
||||||
|
fn move_to_next_newline(
|
||||||
|
slice: RopeSlice,
|
||||||
|
range: Range,
|
||||||
|
dir: Direction,
|
||||||
|
count: usize,
|
||||||
|
behaviour: Movement,
|
||||||
|
text_fmt: &TextFormat,
|
||||||
|
annotations: &mut TextAnnotations,
|
||||||
|
) -> Range {
|
||||||
|
// Move to new position.
|
||||||
|
// Note: We can't use the given `move_fn` here. If we move visually backwards and soft-wrap
|
||||||
|
// is enabled, we would end up in the same line, and get the same newline character that we
|
||||||
|
// are actually coming from.
|
||||||
|
let new_range = move_vertically(slice, range, dir, count, behaviour, text_fmt, annotations);
|
||||||
|
let new_pos = new_range.cursor(slice);
|
||||||
|
let new_line = slice.char_to_line(new_pos);
|
||||||
|
|
||||||
|
// move to newline char in this line
|
||||||
|
let newline_pos = line_end_char_index(&slice, new_line);
|
||||||
|
new_range.put_cursor(slice, newline_pos, behaviour == Movement::Extend)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move a range's cursor to the previous grapheme in the same line, if the cursor is on a
|
||||||
|
/// newline character in a non-empty (visual or non-visual) line.
|
||||||
|
fn try_to_avoid_newline(
|
||||||
|
slice: RopeSlice,
|
||||||
|
range: Range,
|
||||||
|
behaviour: Movement,
|
||||||
|
text_fmt: &TextFormat,
|
||||||
|
annotations: &mut TextAnnotations,
|
||||||
|
) -> Range {
|
||||||
|
let pos = range.cursor(slice);
|
||||||
|
let line = slice.char_to_line(pos);
|
||||||
|
let end_char_index = line_end_char_index(&slice, line);
|
||||||
|
let pos_is_in_empty_line = is_in_visual_empty_line(slice, text_fmt, annotations, pos);
|
||||||
|
let pos_is_at_end_of_line = pos == end_char_index;
|
||||||
|
|
||||||
|
if !pos_is_at_end_of_line || pos_is_in_empty_line {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
// move away from newline character
|
||||||
|
let new_pos = prev_grapheme_boundary(slice, end_char_index);
|
||||||
|
let old_visual_position = range.old_visual_position;
|
||||||
|
let mut new_range = range.put_cursor(slice, new_pos, behaviour == Movement::Extend);
|
||||||
|
new_range.old_visual_position = old_visual_position;
|
||||||
|
new_range
|
||||||
|
}
|
||||||
|
|
||||||
|
let pos = range.cursor(slice);
|
||||||
|
let line = slice.char_to_line(pos);
|
||||||
|
let pos_is_at_end_of_line = pos == line_end_char_index(&slice, line);
|
||||||
|
let pos_is_in_empty_line = is_in_visual_empty_line(slice, text_fmt, annotations, pos);
|
||||||
|
|
||||||
|
let new_range = move_fn(slice, range, dir, count, behaviour, text_fmt, annotations);
|
||||||
|
|
||||||
|
// Stay on newline characters if the cursor currently is on one. If the current line is empty
|
||||||
|
// (i.e. it only contains a newline character), only stay on newlines if also done so before.
|
||||||
|
let stayed_on_newline_before = get_indicator_to_stay_on_newline(&range);
|
||||||
|
let stay_on_newline =
|
||||||
|
pos_is_at_end_of_line && (stayed_on_newline_before || !pos_is_in_empty_line);
|
||||||
|
|
||||||
|
if stay_on_newline {
|
||||||
|
let mut updated_range =
|
||||||
|
move_to_next_newline(slice, range, dir, count, behaviour, text_fmt, annotations);
|
||||||
|
set_indicator_to_stay_on_newline(&mut updated_range);
|
||||||
|
updated_range
|
||||||
|
} else {
|
||||||
|
try_to_avoid_newline(slice, new_range, behaviour, text_fmt, annotations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_vertically_anchored(
|
||||||
|
slice: RopeSlice,
|
||||||
|
range: Range,
|
||||||
|
dir: Direction,
|
||||||
|
count: usize,
|
||||||
|
behaviour: Movement,
|
||||||
|
text_fmt: &TextFormat,
|
||||||
|
annotations: &mut TextAnnotations,
|
||||||
|
) -> Range {
|
||||||
|
move_anchored(
|
||||||
|
move_vertically,
|
||||||
|
slice,
|
||||||
|
range,
|
||||||
|
dir,
|
||||||
|
count,
|
||||||
|
behaviour,
|
||||||
|
text_fmt,
|
||||||
|
annotations,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_vertically_anchored_visual(
|
||||||
|
slice: RopeSlice,
|
||||||
|
range: Range,
|
||||||
|
dir: Direction,
|
||||||
|
count: usize,
|
||||||
|
behaviour: Movement,
|
||||||
|
text_fmt: &TextFormat,
|
||||||
|
annotations: &mut TextAnnotations,
|
||||||
|
) -> Range {
|
||||||
|
move_anchored(
|
||||||
|
move_vertically_visual,
|
||||||
|
slice,
|
||||||
|
range,
|
||||||
|
dir,
|
||||||
|
count,
|
||||||
|
behaviour,
|
||||||
|
text_fmt,
|
||||||
|
annotations,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_next_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
|
pub fn move_next_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
|
||||||
word_move(slice, range, count, WordMotionTarget::NextWordStart)
|
word_move(slice, range, count, WordMotionTarget::NextWordStart)
|
||||||
}
|
}
|
||||||
@ -738,6 +956,260 @@ fn test_vertical_move() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vertical_anchored_move_from_newline_stays_on_newline() {
|
||||||
|
let text = Rope::from("aaa\na\n\naaaa\n");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
let pos = pos_at_coords(slice, (0, 3).into(), true);
|
||||||
|
let mut range = Range::new(pos, pos);
|
||||||
|
|
||||||
|
let vmove = |range, direction, count| {
|
||||||
|
move_vertically_anchored(
|
||||||
|
slice,
|
||||||
|
range,
|
||||||
|
direction,
|
||||||
|
count,
|
||||||
|
Movement::Move,
|
||||||
|
&TextFormat::default(),
|
||||||
|
&mut TextAnnotations::default(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 1).into());
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (2, 0).into());
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 4).into());
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 0).into());
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 0).into());
|
||||||
|
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 4).into());
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (2, 0).into());
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 1).into());
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 3).into());
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 3).into());
|
||||||
|
|
||||||
|
range = vmove(range, Direction::Forward, 3);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 4).into());
|
||||||
|
range = vmove(range, Direction::Backward, 3);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 3).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vertical_anchored_move_from_non_newline_avoids_newline() {
|
||||||
|
let text = Rope::from("aaa\na\n\naaaa\n");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
let pos = pos_at_coords(slice, (0, 2).into(), true);
|
||||||
|
let mut range = Range::new(pos, pos);
|
||||||
|
|
||||||
|
let vmove = |range, direction, count| {
|
||||||
|
move_vertically_anchored(
|
||||||
|
slice,
|
||||||
|
range,
|
||||||
|
direction,
|
||||||
|
count,
|
||||||
|
Movement::Move,
|
||||||
|
&TextFormat::default(),
|
||||||
|
&mut TextAnnotations::default(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 0).into());
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (2, 0).into());
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 2).into());
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 0).into());
|
||||||
|
range = vmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 0).into());
|
||||||
|
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 2).into());
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (2, 0).into());
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 0).into());
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 2).into());
|
||||||
|
range = vmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 2).into());
|
||||||
|
|
||||||
|
range = vmove(range, Direction::Forward, 3);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 2).into());
|
||||||
|
range = vmove(range, Direction::Backward, 3);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 2).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vertical_visual_anchored_move_from_newline_stays_on_newline() {
|
||||||
|
let text_fmt = TextFormat {
|
||||||
|
soft_wrap: true,
|
||||||
|
viewport_width: 6,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = Rope::from("a\naaaaaabb\naaaaaab\n\naa\n");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
let pos = pos_at_coords(slice, (0, 1).into(), true);
|
||||||
|
let mut range = Range::new(pos, pos);
|
||||||
|
|
||||||
|
let vvmove = |range, direction, count| -> Range {
|
||||||
|
move_vertically_anchored_visual(
|
||||||
|
slice,
|
||||||
|
range,
|
||||||
|
direction,
|
||||||
|
count,
|
||||||
|
Movement::Move,
|
||||||
|
&text_fmt,
|
||||||
|
&mut TextAnnotations::default(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 8).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (2, 7).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 0).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 2).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (5, 0).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (5, 0).into());
|
||||||
|
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 2).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 0).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (2, 7).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 8).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 1).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 1).into());
|
||||||
|
|
||||||
|
range = vvmove(range, Direction::Forward, 4);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 2).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 3);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 8).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vertical_visual_anchored_move_from_non_newline_avoids_newline() {
|
||||||
|
let text_fmt = TextFormat {
|
||||||
|
soft_wrap: true,
|
||||||
|
viewport_width: 6,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = Rope::from("aaaaaabb\naaa\n\naaaaaa\n aaaabb\na");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
let pos = pos_at_coords(slice, (0, 3).into(), true);
|
||||||
|
let mut range = Range::new(pos, pos);
|
||||||
|
|
||||||
|
let vvmove = |range, direction, count| -> Range {
|
||||||
|
move_vertically_anchored_visual(
|
||||||
|
slice,
|
||||||
|
range,
|
||||||
|
direction,
|
||||||
|
count,
|
||||||
|
Movement::Move,
|
||||||
|
&text_fmt,
|
||||||
|
&mut TextAnnotations::default(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
// wrapped word, stay in same line
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 7).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 2).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (2, 0).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 3).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
// wrapped newline, stay in same line
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 6).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
// line was visually empty, continue avoiding newlines
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 3).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 6).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (5, 0).into());
|
||||||
|
range = vvmove(range, Direction::Forward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (5, 0).into());
|
||||||
|
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 6).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (4, 3).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 6).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 3).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (2, 0).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 2).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 7).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 3).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 3).into());
|
||||||
|
|
||||||
|
range = vvmove(range, Direction::Forward, 4);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (3, 3).into());
|
||||||
|
range = vvmove(range, Direction::Backward, 3);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (0, 7).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn horizontal_movement_in_same_line() {
|
||||||
|
let text = Rope::from("a\na\naaaa");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
let pos = pos_at_coords(slice, (1, 0).into(), true);
|
||||||
|
let mut range = Range::new(pos, pos);
|
||||||
|
|
||||||
|
let hmove = |range, direction, count| -> Range {
|
||||||
|
move_horizontally_same_line(
|
||||||
|
slice,
|
||||||
|
range,
|
||||||
|
direction,
|
||||||
|
count,
|
||||||
|
Movement::Move,
|
||||||
|
&TextFormat::default(),
|
||||||
|
&mut TextAnnotations::default(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
range = hmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 0).into());
|
||||||
|
range = hmove(range, Direction::Forward, 2);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 1).into());
|
||||||
|
range = hmove(range, Direction::Forward, 2);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 1).into());
|
||||||
|
range = hmove(range, Direction::Backward, 1);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 0).into());
|
||||||
|
range = hmove(range, Direction::Backward, 2);
|
||||||
|
assert_eq!(coords_at_pos(slice, range.head), (1, 0).into());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn horizontal_moves_through_single_line_text() {
|
fn horizontal_moves_through_single_line_text() {
|
||||||
let text = Rope::from(SINGLE_LINE_SAMPLE);
|
let text = Rope::from(SINGLE_LINE_SAMPLE);
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
indent::{self, IndentStyle},
|
indent::{self, IndentStyle},
|
||||||
line_ending::{get_line_ending_of_str, line_end_char_index},
|
line_ending::{get_line_ending_of_str, line_end_char_index},
|
||||||
match_brackets,
|
match_brackets,
|
||||||
movement::{self, move_vertically_visual, Direction},
|
movement::{self, Direction, Movement},
|
||||||
object, pos_at_coords,
|
object, pos_at_coords,
|
||||||
regex::{self, Regex},
|
regex::{self, Regex},
|
||||||
search::{self, CharMatcher},
|
search::{self, CharMatcher},
|
||||||
@ -52,7 +52,6 @@
|
|||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Context as _};
|
use anyhow::{anyhow, bail, ensure, Context as _};
|
||||||
use insert::*;
|
use insert::*;
|
||||||
use movement::Movement;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
args,
|
args,
|
||||||
@ -279,16 +278,28 @@ pub fn doc(&self) -> &str {
|
|||||||
no_op, "Do nothing",
|
no_op, "Do nothing",
|
||||||
move_char_left, "Move left",
|
move_char_left, "Move left",
|
||||||
move_char_right, "Move right",
|
move_char_right, "Move right",
|
||||||
|
move_same_line_char_left, "Move left within in the same line only",
|
||||||
|
move_same_line_char_right, "Move right within in the same line only",
|
||||||
move_line_up, "Move up",
|
move_line_up, "Move up",
|
||||||
move_line_down, "Move down",
|
move_line_down, "Move down",
|
||||||
|
move_anchored_line_up, "Move up with newline anchoring behaviour",
|
||||||
|
move_anchored_line_down, "Move down with newline anchoring behaviour",
|
||||||
move_visual_line_up, "Move up",
|
move_visual_line_up, "Move up",
|
||||||
move_visual_line_down, "Move down",
|
move_visual_line_down, "Move down",
|
||||||
|
move_anchored_visual_line_up, "Move up with newline anchoring behaviour",
|
||||||
|
move_anchored_visual_line_down, "Move down with newline anchoring behaviour",
|
||||||
extend_char_left, "Extend left",
|
extend_char_left, "Extend left",
|
||||||
extend_char_right, "Extend right",
|
extend_char_right, "Extend right",
|
||||||
|
extend_same_line_char_left, "Extend left within the same line only",
|
||||||
|
extend_same_line_char_right, "Extend right within the same line only",
|
||||||
extend_line_up, "Extend up",
|
extend_line_up, "Extend up",
|
||||||
extend_line_down, "Extend down",
|
extend_line_down, "Extend down",
|
||||||
|
extend_anchored_line_up, "Extend up with newline anchoring behaviour",
|
||||||
|
extend_anchored_line_down, "Extend down with newline anchoring behaviour",
|
||||||
extend_visual_line_up, "Extend up",
|
extend_visual_line_up, "Extend up",
|
||||||
extend_visual_line_down, "Extend down",
|
extend_visual_line_down, "Extend down",
|
||||||
|
extend_anchored_visual_line_up, "Extend up with newline anchoring behaviour",
|
||||||
|
extend_anchored_visual_line_down, "Extend down with newline anchoring behaviour",
|
||||||
copy_selection_on_next_line, "Copy selection on next line",
|
copy_selection_on_next_line, "Copy selection on next line",
|
||||||
copy_selection_on_prev_line, "Copy selection on previous line",
|
copy_selection_on_prev_line, "Copy selection on previous line",
|
||||||
move_next_word_start, "Move to start of next word",
|
move_next_word_start, "Move to start of next word",
|
||||||
@ -371,6 +382,7 @@ pub fn doc(&self) -> &str {
|
|||||||
ensure_selections_forward, "Ensure all selections face forward",
|
ensure_selections_forward, "Ensure all selections face forward",
|
||||||
insert_mode, "Insert before selection",
|
insert_mode, "Insert before selection",
|
||||||
append_mode, "Append after selection",
|
append_mode, "Append after selection",
|
||||||
|
append_mode_same_line, "Append after selection within the same line only",
|
||||||
command_mode, "Enter command mode",
|
command_mode, "Enter command mode",
|
||||||
file_picker, "Open file picker",
|
file_picker, "Open file picker",
|
||||||
file_picker_in_current_buffer_directory, "Open file picker at current buffer's directory",
|
file_picker_in_current_buffer_directory, "Open file picker at current buffer's directory",
|
||||||
@ -696,7 +708,10 @@ fn move_impl(cx: &mut Context, move_fn: MoveFn, dir: Direction, behaviour: Movem
|
|||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
use helix_core::movement::{move_horizontally, move_vertically};
|
use helix_core::movement::{
|
||||||
|
move_horizontally, move_horizontally_same_line, move_vertically, move_vertically_anchored,
|
||||||
|
move_vertically_anchored_visual, move_vertically_visual,
|
||||||
|
};
|
||||||
|
|
||||||
fn move_char_left(cx: &mut Context) {
|
fn move_char_left(cx: &mut Context) {
|
||||||
move_impl(cx, move_horizontally, Direction::Backward, Movement::Move)
|
move_impl(cx, move_horizontally, Direction::Backward, Movement::Move)
|
||||||
@ -706,6 +721,24 @@ fn move_char_right(cx: &mut Context) {
|
|||||||
move_impl(cx, move_horizontally, Direction::Forward, Movement::Move)
|
move_impl(cx, move_horizontally, Direction::Forward, Movement::Move)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_same_line_char_left(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_horizontally_same_line,
|
||||||
|
Direction::Backward,
|
||||||
|
Movement::Move,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_same_line_char_right(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_horizontally_same_line,
|
||||||
|
Direction::Forward,
|
||||||
|
Movement::Move,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn move_line_up(cx: &mut Context) {
|
fn move_line_up(cx: &mut Context) {
|
||||||
move_impl(cx, move_vertically, Direction::Backward, Movement::Move)
|
move_impl(cx, move_vertically, Direction::Backward, Movement::Move)
|
||||||
}
|
}
|
||||||
@ -714,6 +747,24 @@ fn move_line_down(cx: &mut Context) {
|
|||||||
move_impl(cx, move_vertically, Direction::Forward, Movement::Move)
|
move_impl(cx, move_vertically, Direction::Forward, Movement::Move)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_anchored_line_up(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_vertically_anchored,
|
||||||
|
Direction::Backward,
|
||||||
|
Movement::Move,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_anchored_line_down(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_vertically_anchored,
|
||||||
|
Direction::Forward,
|
||||||
|
Movement::Move,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn move_visual_line_up(cx: &mut Context) {
|
fn move_visual_line_up(cx: &mut Context) {
|
||||||
move_impl(
|
move_impl(
|
||||||
cx,
|
cx,
|
||||||
@ -732,6 +783,24 @@ fn move_visual_line_down(cx: &mut Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_anchored_visual_line_up(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_vertically_anchored_visual,
|
||||||
|
Direction::Backward,
|
||||||
|
Movement::Move,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_anchored_visual_line_down(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_vertically_anchored_visual,
|
||||||
|
Direction::Forward,
|
||||||
|
Movement::Move,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn extend_char_left(cx: &mut Context) {
|
fn extend_char_left(cx: &mut Context) {
|
||||||
move_impl(cx, move_horizontally, Direction::Backward, Movement::Extend)
|
move_impl(cx, move_horizontally, Direction::Backward, Movement::Extend)
|
||||||
}
|
}
|
||||||
@ -740,6 +809,24 @@ fn extend_char_right(cx: &mut Context) {
|
|||||||
move_impl(cx, move_horizontally, Direction::Forward, Movement::Extend)
|
move_impl(cx, move_horizontally, Direction::Forward, Movement::Extend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extend_same_line_char_left(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_horizontally_same_line,
|
||||||
|
Direction::Backward,
|
||||||
|
Movement::Extend,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_same_line_char_right(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_horizontally_same_line,
|
||||||
|
Direction::Forward,
|
||||||
|
Movement::Extend,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn extend_line_up(cx: &mut Context) {
|
fn extend_line_up(cx: &mut Context) {
|
||||||
move_impl(cx, move_vertically, Direction::Backward, Movement::Extend)
|
move_impl(cx, move_vertically, Direction::Backward, Movement::Extend)
|
||||||
}
|
}
|
||||||
@ -748,6 +835,24 @@ fn extend_line_down(cx: &mut Context) {
|
|||||||
move_impl(cx, move_vertically, Direction::Forward, Movement::Extend)
|
move_impl(cx, move_vertically, Direction::Forward, Movement::Extend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extend_anchored_line_up(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_vertically_anchored,
|
||||||
|
Direction::Backward,
|
||||||
|
Movement::Extend,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_anchored_line_down(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_vertically_anchored,
|
||||||
|
Direction::Forward,
|
||||||
|
Movement::Extend,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn extend_visual_line_up(cx: &mut Context) {
|
fn extend_visual_line_up(cx: &mut Context) {
|
||||||
move_impl(
|
move_impl(
|
||||||
cx,
|
cx,
|
||||||
@ -766,6 +871,24 @@ fn extend_visual_line_down(cx: &mut Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extend_anchored_visual_line_up(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_vertically_anchored_visual,
|
||||||
|
Direction::Backward,
|
||||||
|
Movement::Extend,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_anchored_visual_line_down(cx: &mut Context) {
|
||||||
|
move_impl(
|
||||||
|
cx,
|
||||||
|
move_vertically_anchored_visual,
|
||||||
|
Direction::Forward,
|
||||||
|
Movement::Extend,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn goto_line_end_impl(view: &mut View, doc: &mut Document, movement: Movement) {
|
fn goto_line_end_impl(view: &mut View, doc: &mut Document, movement: Movement) {
|
||||||
let text = doc.text().slice(..);
|
let text = doc.text().slice(..);
|
||||||
|
|
||||||
@ -2912,6 +3035,30 @@ fn append_mode(cx: &mut Context) {
|
|||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn append_mode_same_line(cx: &mut Context) {
|
||||||
|
enter_insert_mode(cx);
|
||||||
|
let (view, doc) = current!(cx.editor);
|
||||||
|
doc.restore_cursor = true;
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
|
||||||
|
let selection = doc.selection(view.id).clone().transform(|range| {
|
||||||
|
let pos = range.cursor(text);
|
||||||
|
let line = text.char_to_line(pos);
|
||||||
|
let end_char_index = line_end_char_index(&text, line);
|
||||||
|
let pos_is_at_end_of_line = pos == end_char_index;
|
||||||
|
|
||||||
|
if pos_is_at_end_of_line {
|
||||||
|
range
|
||||||
|
} else {
|
||||||
|
Range::new(
|
||||||
|
range.from(),
|
||||||
|
graphemes::next_grapheme_boundary(doc.text().slice(..), range.to()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
doc.set_selection(view.id, selection);
|
||||||
|
}
|
||||||
|
|
||||||
fn file_picker(cx: &mut Context) {
|
fn file_picker(cx: &mut Context) {
|
||||||
let root = find_workspace().0;
|
let root = find_workspace().0;
|
||||||
if !root.exists() {
|
if !root.exists() {
|
||||||
|
Loading…
Reference in New Issue
Block a user