on master help sign in
don't move cursor while forward deleting in append mode
25d4ebePascal Kuthe
helix-core/src/transaction.rs+5
@@ -570,6 +570,11 @@ impl Transaction {
570 Self::from(changeset)
570 Self::from(changeset)
571 }
571 }
572
572
573+ pub fn insert_at_eof(mut self, text: Tendril) -> Transaction {
574+ self.changes.insert(text);
575+ self
576+ }
577+
573 /// Generate a transaction with a change per selection range.
578 /// Generate a transaction with a change per selection range.
574 pub fn change_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
579 pub fn change_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
575 where
580 where
helix-term/src/commands.rs+96−45
@@ -795,42 +795,50 @@ fn extend_to_line_start(cx: &mut Context) {
795}
795}
796
796
797fn kill_to_line_start(cx: &mut Context) {
797fn kill_to_line_start(cx: &mut Context) {
798− delete_by_selection_insert_mode(cx, move |text, range| {
798+ delete_by_selection_insert_mode(
799+ cx,
800+ move |text, range| {
799− let line = range.cursor_line(text);
801+ let line = range.cursor_line(text);
800− let first_char = text.line_to_char(line);
802+ let first_char = text.line_to_char(line);
801− let anchor = range.cursor(text);
803+ let anchor = range.cursor(text);
802− let head = if anchor == first_char && line != 0 {
804+ let head = if anchor == first_char && line != 0 {
803− // select until previous line
805+ // select until previous line
804− line_end_char_index(&text, line - 1)
806+ line_end_char_index(&text, line - 1)
805− } else if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
807+ } else if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
806− if first_char + pos < anchor {
808+ if first_char + pos < anchor {
807− // select until first non-blank in line if cursor is after it
809+ // select until first non-blank in line if cursor is after it
808− first_char + pos
810+ first_char + pos
811+ } else {
812+ // select until start of line
813+ first_char
814+ }
809 } else {
815 } else {
810 // select until start of line
816 // select until start of line
811 first_char
817 first_char
812− }
818+ };
813− } else {
814− // select until start of line
815− first_char
816− };
817− (head, anchor)
819+ (head, anchor)
820+ },
821+ Direction::Backward,
818− });
822+ );
819}
823}
820
824
821fn kill_to_line_end(cx: &mut Context) {
825fn kill_to_line_end(cx: &mut Context) {
822− delete_by_selection_insert_mode(cx, |text, range| {
826+ delete_by_selection_insert_mode(
827+ cx,
828+ |text, range| {
823− let line = range.cursor_line(text);
829+ let line = range.cursor_line(text);
824− let line_end_pos = line_end_char_index(&text, line);
830+ let line_end_pos = line_end_char_index(&text, line);
825− let pos = range.cursor(text);
831+ let pos = range.cursor(text);
826
832
827− // if the cursor is on the newline char delete that
833+ // if the cursor is on the newline char delete that
828− if pos == line_end_pos {
834+ if pos == line_end_pos {
829− (pos, text.line_to_char(line + 1))
835+ (pos, text.line_to_char(line + 1))
830− } else {
836+ } else {
831− (pos, line_end_pos)
837+ (pos, line_end_pos)
838+ }
832− }
839+ },
840+ Direction::Forward,
833− });
841+ );
834}
842}
835
843
836fn goto_first_nonwhitespace(cx: &mut Context) {
844fn goto_first_nonwhitespace(cx: &mut Context) {
@@ -2322,13 +2330,44 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
2322fn delete_by_selection_insert_mode(
2330fn delete_by_selection_insert_mode(
2323 cx: &mut Context,
2331 cx: &mut Context,
2324 mut f: impl FnMut(RopeSlice, &Range) -> Deletion,
2332 mut f: impl FnMut(RopeSlice, &Range) -> Deletion,
2333+ direction: Direction,
2325) {
2334) {
2326 let (view, doc) = current!(cx.editor);
2335 let (view, doc) = current!(cx.editor);
2327 let text = doc.text().slice(..);
2336 let text = doc.text().slice(..);
2337+ let mut selection = SmallVec::new();
2338+ let mut insert_newline = false;
2339+ let text_len = text.len_chars();
2328− let transaction =
2340+ let mut transaction =
2329 Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
2341 Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
2330− f(text, range)
2342+ let (start, end) = f(text, range);
2343+ if direction == Direction::Forward {
2344+ let mut range = *range;
2345+ if range.head > range.anchor {
2346+ insert_newline |= end == text_len;
2347+ // move the cursor to the right so that the selection
2348+ // doesn't shrink when deleting forward (so the text appears to
2349+ // move to left)
2350+ // += 1 is enough here as the range is normalized to grapheme boundaries
2351+ // later anyway
2352+ range.head += 1;
2353+ }
2354+ selection.push(range);
2355+ }
2356+ (start, end)
2331 });
2357 });
2358+
2359+ // in case we delete the last character and the cursor would be moved to the EOF char
2360+ // insert a newline, just like when entering append mode
2361+ if insert_newline {
2362+ transaction = transaction.insert_at_eof(doc.line_ending.as_str().into());
2363+ }
2364+
2365+ if direction == Direction::Forward {
2366+ doc.set_selection(
2367+ view.id,
2368+ Selection::new(selection, doc.selection(view.id).primary_index()),
2369+ );
2370+ }
2332 doc.apply(&transaction, view.id);
2371 doc.apply(&transaction, view.id);
2333 lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
2372 lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
2334}
2373}
@@ -3490,28 +3529,40 @@ pub mod insert {
3490
3529
3491 pub fn delete_char_forward(cx: &mut Context) {
3530 pub fn delete_char_forward(cx: &mut Context) {
3492 let count = cx.count();
3531 let count = cx.count();
3493− delete_by_selection_insert_mode(cx, |text, range| {
3532+ delete_by_selection_insert_mode(
3533+ cx,
3534+ |text, range| {
3494− let pos = range.cursor(text);
3535+ let pos = range.cursor(text);
3495− (pos, graphemes::nth_next_grapheme_boundary(text, pos, count))
3536+ (pos, graphemes::nth_next_grapheme_boundary(text, pos, count))
3537+ },
3538+ Direction::Forward,
3496− })
3539+ )
3497 }
3540 }
3498
3541
3499 pub fn delete_word_backward(cx: &mut Context) {
3542 pub fn delete_word_backward(cx: &mut Context) {
3500 let count = cx.count();
3543 let count = cx.count();
3501− delete_by_selection_insert_mode(cx, |text, range| {
3544+ delete_by_selection_insert_mode(
3545+ cx,
3546+ |text, range| {
3502− let anchor = movement::move_prev_word_start(text, *range, count).from();
3547+ let anchor = movement::move_prev_word_start(text, *range, count).from();
3503− let next = Range::new(anchor, range.cursor(text));
3548+ let next = Range::new(anchor, range.cursor(text));
3504− let range = exclude_cursor(text, next, *range);
3549+ let range = exclude_cursor(text, next, *range);
3505− (range.from(), range.to())
3550+ (range.from(), range.to())
3551+ },
3552+ Direction::Backward,
3506− });
3553+ );
3507 }
3554 }
3508
3555
3509 pub fn delete_word_forward(cx: &mut Context) {
3556 pub fn delete_word_forward(cx: &mut Context) {
3510 let count = cx.count();
3557 let count = cx.count();
3511− delete_by_selection_insert_mode(cx, |text, range| {
3558+ delete_by_selection_insert_mode(
3559+ cx,
3560+ |text, range| {
3512− let head = movement::move_next_word_end(text, *range, count).to();
3561+ let head = movement::move_next_word_end(text, *range, count).to();
3513− (range.cursor(text), head)
3562+ (range.cursor(text), head)
3563+ },
3564+ Direction::Forward,
3514− });
3565+ );
3515 }
3566 }
3516}
3567}
3517
3568
helix-term/tests/test/commands.rs+23
@@ -429,3 +429,26 @@ async fn test_delete_word_forward() -> anyhow::Result<()> {
429 Ok(())
429 Ok(())
430}
430}
431
431
432+#[tokio::test(flavor = "multi_thread")]
433+async fn test_delete_char_forward() -> anyhow::Result<()> {
434+ test((
435+ platform_line(indoc! {"\
436+ #[abc|]#def
437+ #(abc|)#ef
438+ #(abc|)#f
439+ #(abc|)#
440+ "})
441+ .as_str(),
442+ "a<del><esc>",
443+ platform_line(indoc! {"\
444+ #[abc|]#ef
445+ #(abc|)#f
446+ #(abc|)#
447+ #(abc|)#
448+ "})
449+ .as_str(),
450+ ))
451+ .await?;
452+
453+ Ok(())
454+}