From 1c399e49a3e52a72d239d77dc88e601f5da9233c Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Thu, 8 Dec 2022 11:09:56 -0600 Subject: [PATCH 1/2] Reset mode when changing buffers This is similar to the change in e4c9d4082a139aac3aea4506918171b96e81f5b9: reset the editor to normal mode when changing buffers. Usually the editor is already in normal mode but it's possible to setup insert-mode keybindings that change buffers. --- helix-term/src/commands.rs | 11 ++++++++--- helix-view/src/editor.rs | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1843e7a2be4e..a97277fbca1c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -694,6 +694,7 @@ fn goto_buffer(editor: &mut Editor, direction: Direction) { let id = *id; + normal_mode_impl(editor); editor.switch(id, Action::Replace); } @@ -2672,12 +2673,16 @@ fn open_above(cx: &mut Context) { } fn normal_mode(cx: &mut Context) { - if cx.editor.mode == Mode::Normal { + normal_mode_impl(cx.editor); +} + +fn normal_mode_impl(editor: &mut Editor) { + if editor.mode == Mode::Normal { return; } - cx.editor.mode = Mode::Normal; - let (view, doc) = current!(cx.editor); + editor.mode = Mode::Normal; + let (view, doc) = current!(editor); try_restore_indent(doc, view); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 973cf82ea109..1e429ec69252 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1025,6 +1025,9 @@ impl Editor { let (view, doc) = current!(self); let view_id = view.id; + // Append any outstanding changes to history in the old document. + doc.append_changes_to_history(view); + if remove_empty_scratch { // Copy `doc.id` into a variable before calling `self.documents.remove`, which requires a mutable // borrow, invalidating direct access to `doc.id`. From 4e094972743a8a85474df33802e761edd3a7148b Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Thu, 8 Dec 2022 21:20:56 -0600 Subject: [PATCH 2/2] Move normal mode entering code to Editor This should be called internally in the Editor when changing documents (Editor::switch) or changing focuses (Editor::focus). --- helix-term/src/commands.rs | 62 +---------------------------------- helix-view/src/editor.rs | 67 +++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 62 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a97277fbca1c..d090d488ffa5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -694,7 +694,6 @@ fn goto_buffer(editor: &mut Editor, direction: Direction) { let id = *id; - normal_mode_impl(editor); editor.switch(id, Action::Replace); } @@ -2673,66 +2672,7 @@ fn open_above(cx: &mut Context) { } fn normal_mode(cx: &mut Context) { - normal_mode_impl(cx.editor); -} - -fn normal_mode_impl(editor: &mut Editor) { - if editor.mode == Mode::Normal { - return; - } - - editor.mode = Mode::Normal; - let (view, doc) = current!(editor); - - try_restore_indent(doc, view); - - // if leaving append mode, move cursor back by 1 - if doc.restore_cursor { - let text = doc.text().slice(..); - let selection = doc.selection(view.id).clone().transform(|range| { - Range::new( - range.from(), - graphemes::prev_grapheme_boundary(text, range.to()), - ) - }); - - doc.set_selection(view.id, selection); - doc.restore_cursor = false; - } -} - -fn try_restore_indent(doc: &mut Document, view: &mut View) { - use helix_core::chars::char_is_whitespace; - use helix_core::Operation; - - fn inserted_a_new_blank_line(changes: &[Operation], pos: usize, line_end_pos: usize) -> bool { - if let [Operation::Retain(move_pos), Operation::Insert(ref inserted_str), Operation::Retain(_)] = - changes - { - move_pos + inserted_str.len() == pos - && inserted_str.starts_with('\n') - && inserted_str.chars().skip(1).all(char_is_whitespace) - && pos == line_end_pos // ensure no characters exists after current position - } else { - false - } - } - - let doc_changes = doc.changes().changes(); - let text = doc.text().slice(..); - let range = doc.selection(view.id).primary(); - let pos = range.cursor(text); - let line_end_pos = line_end_char_index(&text, range.cursor_line(text)); - - if inserted_a_new_blank_line(doc_changes, pos, line_end_pos) { - // Removes tailing whitespaces. - let transaction = - Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { - let line_start_pos = text.line_to_char(range.cursor_line(text)); - (line_start_pos, pos, None) - }); - apply_transaction(&transaction, doc, view); - } + cx.editor.enter_normal_mode(); } // Store a jump on the jumplist. diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 1e429ec69252..c13a66736948 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1005,6 +1005,8 @@ impl Editor { return; } + self.enter_normal_mode(); + match action { Action::Replace => { let (view, doc) = current_ref!(self); @@ -1265,7 +1267,7 @@ impl Editor { // if leaving the view: mode should reset and the cursor should be // within view if prev_id != view_id { - self.mode = Mode::Normal; + self.enter_normal_mode(); self.ensure_cursor_in_view(view_id); // Update jumplist selections with new document changes. @@ -1430,4 +1432,67 @@ impl Editor { Ok(()) } + + /// Switches the editor into normal mode. + pub fn enter_normal_mode(&mut self) { + use helix_core::{graphemes, Range}; + + if self.mode == Mode::Normal { + return; + } + + self.mode = Mode::Normal; + let (view, doc) = current!(self); + + try_restore_indent(doc, view); + + // if leaving append mode, move cursor back by 1 + if doc.restore_cursor { + let text = doc.text().slice(..); + let selection = doc.selection(view.id).clone().transform(|range| { + Range::new( + range.from(), + graphemes::prev_grapheme_boundary(text, range.to()), + ) + }); + + doc.set_selection(view.id, selection); + doc.restore_cursor = false; + } + } +} + +fn try_restore_indent(doc: &mut Document, view: &mut View) { + use helix_core::{ + chars::char_is_whitespace, line_ending::line_end_char_index, Operation, Transaction, + }; + + fn inserted_a_new_blank_line(changes: &[Operation], pos: usize, line_end_pos: usize) -> bool { + if let [Operation::Retain(move_pos), Operation::Insert(ref inserted_str), Operation::Retain(_)] = + changes + { + move_pos + inserted_str.len() == pos + && inserted_str.starts_with('\n') + && inserted_str.chars().skip(1).all(char_is_whitespace) + && pos == line_end_pos // ensure no characters exists after current position + } else { + false + } + } + + let doc_changes = doc.changes().changes(); + let text = doc.text().slice(..); + let range = doc.selection(view.id).primary(); + let pos = range.cursor(text); + let line_end_pos = line_end_char_index(&text, range.cursor_line(text)); + + if inserted_a_new_blank_line(doc_changes, pos, line_end_pos) { + // Removes tailing whitespaces. + let transaction = + Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + let line_start_pos = text.line_to_char(range.cursor_line(text)); + (line_start_pos, pos, None) + }); + crate::apply_transaction(&transaction, doc, view); + } }