add `same_line` movement commands (#768)

Commands added:
* `move_same_line_char_left`
* `move_same_line_char_right`
* `extend_same_line_char_left`
* `extend_same_line_char_right`
* `append_mode_same_line`

These new commands move cursors, while making them stay in the same
line. So if a cursor would wrap around into another line, instead it
won't move and stay at its current position.
pull/10576/head
Pantos 7 months ago
parent f305c7299d
commit b5b2cc77e4

@ -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,
@ -738,6 +772,37 @@ mod test {
); );
} }
#[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 @@ use helix_core::{
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 helix_view::{
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,12 +278,16 @@ impl MappableCommand {
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_visual_line_up, "Move up", move_visual_line_up, "Move up",
move_visual_line_down, "Move down", move_visual_line_down, "Move down",
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_visual_line_up, "Extend up", extend_visual_line_up, "Extend up",
@ -371,6 +374,7 @@ impl MappableCommand {
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 +700,9 @@ 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_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 +712,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)
} }
@ -740,6 +764,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)
} }
@ -2912,6 +2954,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…
Cancel
Save