diff --git a/CHANGELOG.md b/CHANGELOG.md index ee16649cd..109f93af3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,23 @@ You can find its changes [documented below](#060---2025-10-06). This release has an [MSRV] of 1.82. +### Migration + +The `move_*` methods on `PlainEditorDrv` now have a `bool` parameter, pass `false` for their previous behavior. The `select_*` selection extending methods on `PlainEditorDrv` have been removed, instead use the corresponding `move_*` methods passing `true`. + +### Changed + +#### Parley + +-- Breaking change: The `move_*` methods on `PlainEditorDrv` now take an `extend: bool` parameter. For their previous behavior use `false`. When `extend` is `true` they extend the selection range, following the behaviour of the removed `select_*` methods. ([#459][] by [@ickshonpe][]) + +### Removed + +#### Parley + +- The methods `extend_selection_to_point`, `extend_selection_to_byte`, `select_to_text_start`, `select_to_hard_line_start`, `select_to_text_end`, `select_to_hard_line_end`, `select_to_line_end`, `select_up`, `select_down`, `select_left`, `select_right`, `select_word_left` and `select_word_right` have been removed from `PlainEditorDriver`. ([#459][] by [@ickshonpe][]) + + ## [0.6.0] - 2025-10-06 This release has an [MSRV] of 1.82. @@ -283,6 +300,7 @@ This release has an [MSRV][] of 1.70. [@wfdewith]: https://github.com/wfdewith [@xorgy]: https://github.com/xorgy [@xStrom]: https://github.com/xStrom +[@ickshonpe]: https:://github.com/ickshonpe [#54]: https://github.com/linebender/parley/pull/54 [#55]: https://github.com/linebender/parley/pull/55 @@ -368,6 +386,7 @@ This release has an [MSRV][] of 1.70. [#413]: https://github.com/linebender/parley/pull/413 [#414]: https://github.com/linebender/parley/pull/414 [#418]: https://github.com/linebender/parley/pull/418 +[#459]: https://github.com/linebender/parley/pull/459 [Unreleased]: https://github.com/linebender/parley/compare/v0.5.0...HEAD [0.5.0]: https://github.com/linebender/parley/compare/v0.4.0...v0.5.0 diff --git a/examples/vello_editor/src/text.rs b/examples/vello_editor/src/text.rs index 9edf626f2..16649a020 100644 --- a/examples/vello_editor/src/text.rs +++ b/examples/vello_editor/src/text.rs @@ -159,55 +159,29 @@ impl Editor { } Key::Named(NamedKey::ArrowLeft) => { if action_mod { - if shift { - drv.select_word_left(); - } else { - drv.move_word_left(); - } - } else if shift { - drv.select_left(); + drv.move_word_left(shift); } else { - drv.move_left(); + drv.move_left(shift); } } Key::Named(NamedKey::ArrowRight) => { if action_mod { - if shift { - drv.select_word_right(); - } else { - drv.move_word_right(); - } - } else if shift { - drv.select_right(); + drv.move_word_right(shift); } else { - drv.move_right(); + drv.move_right(shift); } } Key::Named(NamedKey::ArrowUp) => { - if shift { - drv.select_up(); - } else { - drv.move_up(); - } + drv.move_up(shift); } Key::Named(NamedKey::ArrowDown) => { - if shift { - drv.select_down(); - } else { - drv.move_down(); - } + drv.move_down(shift); } Key::Named(NamedKey::Home) => { if action_mod { - if shift { - drv.select_to_text_start(); - } else { - drv.move_to_text_start(); - } - } else if shift { - drv.select_to_line_start(); + drv.move_to_text_start(shift); } else { - drv.move_to_line_start(); + drv.move_to_line_start(shift); } } Key::Named(NamedKey::End) => { @@ -215,15 +189,9 @@ impl Editor { let mut drv = this.driver(); if action_mod { - if shift { - drv.select_to_text_end(); - } else { - drv.move_to_text_end(); - } - } else if shift { - drv.select_to_line_end(); + drv.move_to_text_end(shift); } else { - drv.move_to_line_end(); + drv.move_to_line_end(shift); } } Key::Named(NamedKey::Delete) => { @@ -277,7 +245,7 @@ impl Editor { if state.modifiers.shift() { drv.shift_click_extension(cursor_pos.0, cursor_pos.1); } else { - drv.move_to_point(cursor_pos.0, cursor_pos.1); + drv.move_to_point(cursor_pos.0, cursor_pos.1, false); } } } @@ -291,7 +259,7 @@ impl Editor { ); self.cursor_reset(); self.driver() - .extend_selection_to_point(cursor_pos.0, cursor_pos.1); + .move_to_point(cursor_pos.0, cursor_pos.1, true); } _ => {} } diff --git a/parley/src/editing/editor.rs b/parley/src/editing/editor.rs index d8c494d21..72859f51b 100644 --- a/parley/src/editing/editor.rs +++ b/parley/src/editing/editor.rs @@ -435,131 +435,160 @@ where // --- MARK: Cursor Movement --- /// Move the cursor to the cluster boundary nearest this point in the layout. - pub fn move_to_point(&mut self, x: f32, y: f32) { + /// If `extend` is true, the current selection will be extended. + /// + /// If the initial selection was created from a word or line, then the new + /// selection will be extended at the same granularity. + pub fn move_to_point(&mut self, x: f32, y: f32, extend: bool) { self.refresh_layout(); - self.editor - .set_selection(Selection::from_point(&self.editor.layout, x, y)); + self.editor.set_selection(if extend { + // FIXME: This is usually the wrong way to handle selection extension for mouse moves, but not a regression. + self.editor + .selection + .extend_to_point(&self.editor.layout, x, y) + } else { + Selection::from_point(&self.editor.layout, x, y) + }); } /// Move the cursor to a byte index. /// /// No-op if index is not a char boundary. - pub fn move_to_byte(&mut self, index: usize) { + /// If `extend` is true, the current selection will be extended. + pub fn move_to_byte(&mut self, index: usize, extend: bool) { if self.editor.buffer.is_char_boundary(index) { self.refresh_layout(); - self.editor - .set_selection(self.editor.cursor_at(index).into()); + self.editor.set_selection(if extend { + self.editor.selection.extend(self.editor.cursor_at(index)) + } else { + self.editor.cursor_at(index).into() + }); } } /// Move the cursor to the start of the buffer. - pub fn move_to_text_start(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_to_text_start(&mut self, extend: bool) { self.refresh_layout(); self.editor.set_selection(self.editor.selection.move_lines( &self.editor.layout, isize::MIN, - false, + extend, )); } /// Move the cursor to just after the previous hard line break (such as `\n`). - pub fn move_to_hard_line_start(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_to_hard_line_start(&mut self, extend: bool) { self.refresh_layout(); self.editor.set_selection( self.editor .selection - .hard_line_start(&self.editor.layout, false), + .hard_line_start(&self.editor.layout, extend), ); } /// Move the cursor to the start of the physical line. - pub fn move_to_line_start(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_to_line_start(&mut self, extend: bool) { self.refresh_layout(); - self.editor - .set_selection(self.editor.selection.line_start(&self.editor.layout, false)); + self.editor.set_selection( + self.editor + .selection + .line_start(&self.editor.layout, extend), + ); } /// Move the cursor to the end of the buffer. - pub fn move_to_text_end(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_to_text_end(&mut self, extend: bool) { self.refresh_layout(); self.editor.set_selection(self.editor.selection.move_lines( &self.editor.layout, isize::MAX, - false, + extend, )); } /// Move the cursor to just before the next hard line break (such as `\n`). - pub fn move_to_hard_line_end(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_to_hard_line_end(&mut self, extend: bool) { self.refresh_layout(); self.editor.set_selection( self.editor .selection - .hard_line_end(&self.editor.layout, false), + .hard_line_end(&self.editor.layout, extend), ); } /// Move the cursor to the end of the physical line. - pub fn move_to_line_end(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_to_line_end(&mut self, extend: bool) { self.refresh_layout(); self.editor - .set_selection(self.editor.selection.line_end(&self.editor.layout, false)); + .set_selection(self.editor.selection.line_end(&self.editor.layout, extend)); } /// Move up to the closest physical cluster boundary on the previous line, preserving the horizontal position for repeated movements. - pub fn move_up(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_up(&mut self, extend: bool) { self.refresh_layout(); self.editor.set_selection( self.editor .selection - .previous_line(&self.editor.layout, false), + .previous_line(&self.editor.layout, extend), ); } /// Move down to the closest physical cluster boundary on the next line, preserving the horizontal position for repeated movements. - pub fn move_down(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_down(&mut self, extend: bool) { self.refresh_layout(); self.editor - .set_selection(self.editor.selection.next_line(&self.editor.layout, false)); + .set_selection(self.editor.selection.next_line(&self.editor.layout, extend)); } /// Move to the next cluster left in visual order. - pub fn move_left(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_left(&mut self, extend: bool) { self.refresh_layout(); self.editor.set_selection( self.editor .selection - .previous_visual(&self.editor.layout, false), + .previous_visual(&self.editor.layout, extend), ); } /// Move to the next cluster right in visual order. - pub fn move_right(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_right(&mut self, extend: bool) { self.refresh_layout(); self.editor.set_selection( self.editor .selection - .next_visual(&self.editor.layout, false), + .next_visual(&self.editor.layout, extend), ); } /// Move to the next word boundary left. - pub fn move_word_left(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_word_left(&mut self, extend: bool) { self.refresh_layout(); self.editor.set_selection( self.editor .selection - .previous_visual_word(&self.editor.layout, false), + .previous_visual_word(&self.editor.layout, extend), ); } /// Move to the next word boundary right. - pub fn move_word_right(&mut self) { + /// If `extend` is true, the current selection will be extended. + pub fn move_word_right(&mut self, extend: bool) { self.refresh_layout(); self.editor.set_selection( self.editor .selection - .next_visual_word(&self.editor.layout, false), + .next_visual_word(&self.editor.layout, extend), ); } @@ -577,114 +606,6 @@ where self.editor.set_selection(self.editor.selection.collapse()); } - /// Move the selection focus point to the start of the buffer. - pub fn select_to_text_start(&mut self) { - self.refresh_layout(); - self.editor.set_selection(self.editor.selection.move_lines( - &self.editor.layout, - isize::MIN, - true, - )); - } - - /// Move the selection focus point to just after the previous hard line break (such as `\n`). - pub fn select_to_hard_line_start(&mut self) { - self.refresh_layout(); - self.editor.set_selection( - self.editor - .selection - .hard_line_start(&self.editor.layout, true), - ); - } - - /// Move the selection focus point to the start of the physical line. - pub fn select_to_line_start(&mut self) { - self.refresh_layout(); - self.editor - .set_selection(self.editor.selection.line_start(&self.editor.layout, true)); - } - - /// Move the selection focus point to the end of the buffer. - pub fn select_to_text_end(&mut self) { - self.refresh_layout(); - self.editor.set_selection(self.editor.selection.move_lines( - &self.editor.layout, - isize::MAX, - true, - )); - } - - /// Move the selection focus point to just before the next hard line break (such as `\n`). - pub fn select_to_hard_line_end(&mut self) { - self.refresh_layout(); - self.editor.set_selection( - self.editor - .selection - .hard_line_end(&self.editor.layout, true), - ); - } - - /// Move the selection focus point to the end of the physical line. - pub fn select_to_line_end(&mut self) { - self.refresh_layout(); - self.editor - .set_selection(self.editor.selection.line_end(&self.editor.layout, true)); - } - - /// Move the selection focus point up to the nearest cluster boundary on the previous line, preserving the horizontal position for repeated movements. - pub fn select_up(&mut self) { - self.refresh_layout(); - self.editor.set_selection( - self.editor - .selection - .previous_line(&self.editor.layout, true), - ); - } - - /// Move the selection focus point down to the nearest cluster boundary on the next line, preserving the horizontal position for repeated movements. - pub fn select_down(&mut self) { - self.refresh_layout(); - self.editor - .set_selection(self.editor.selection.next_line(&self.editor.layout, true)); - } - - /// Move the selection focus point to the next cluster left in visual order. - pub fn select_left(&mut self) { - self.refresh_layout(); - self.editor.set_selection( - self.editor - .selection - .previous_visual(&self.editor.layout, true), - ); - } - - /// Move the selection focus point to the next cluster right in visual order. - pub fn select_right(&mut self) { - self.refresh_layout(); - self.editor - .set_selection(self.editor.selection.next_visual(&self.editor.layout, true)); - } - - /// Move the selection focus point to the next word boundary left. - pub fn select_word_left(&mut self) { - self.refresh_layout(); - self.editor.set_selection( - self.editor - .selection - .previous_visual_word(&self.editor.layout, true), - ); - } - - /// Move the selection focus point to the next word boundary right. - pub fn select_word_right(&mut self) { - self.refresh_layout(); - self.editor.set_selection( - self.editor - .selection - .next_visual_word(&self.editor.layout, true), - ); - } - /// Select the word at the point. pub fn select_word_at_point(&mut self, x: f32, y: f32) { self.refresh_layout(); @@ -712,20 +633,6 @@ where self.editor.set_selection(hard_line); } - /// Move the selection focus point to the cluster boundary closest to point. - /// - /// If the initial selection was created from a word or line, then the new - /// selection will be extended at the same granularity. - pub fn extend_selection_to_point(&mut self, x: f32, y: f32) { - self.refresh_layout(); - // FIXME: This is usually the wrong way to handle selection extension for mouse moves, but not a regression. - self.editor.set_selection( - self.editor - .selection - .extend_to_point(&self.editor.layout, x, y), - ); - } - /// Move the selection focus point to the cluster boundary closest to point. pub fn shift_click_extension(&mut self, x: f32, y: f32) { self.refresh_layout(); @@ -737,17 +644,6 @@ where ); } - /// Move the selection focus point to a byte index. - /// - /// No-op if index is not a char boundary. - pub fn extend_selection_to_byte(&mut self, index: usize) { - if self.editor.buffer.is_char_boundary(index) { - self.refresh_layout(); - self.editor - .set_selection(self.editor.selection.extend(self.editor.cursor_at(index))); - } - } - /// Select a range of byte indices. /// /// No-op if either index is not a char boundary. diff --git a/parley/src/tests/test_editor.rs b/parley/src/tests/test_editor.rs index 5579e0119..2efb7293b 100644 --- a/parley/src/tests/test_editor.rs +++ b/parley/src/tests/test_editor.rs @@ -13,16 +13,16 @@ fn editor_simple_move() { let mut editor = env.editor("Hi, all!\nNext"); env.check_editor_snapshot(&mut editor); let mut drv = env.driver(&mut editor); - drv.move_right(); - drv.move_right(); - drv.move_right(); + drv.move_right(false); + drv.move_right(false); + drv.move_right(false); env.check_editor_snapshot(&mut editor); - env.driver(&mut editor).move_down(); + env.driver(&mut editor).move_down(false); env.check_editor_snapshot(&mut editor); - env.driver(&mut editor).move_left(); + env.driver(&mut editor).move_left(false); env.check_editor_snapshot(&mut editor); - env.driver(&mut editor).move_up(); + env.driver(&mut editor).move_up(false); env.check_editor_snapshot(&mut editor); } @@ -39,28 +39,28 @@ fn editor_select_hard_line() { let mut env = TestEnv::new(test_name!(), None); let mut editor = env.editor("First\nNew Hard Line with soft break!\nLast"); editor.set_width(Some(40.)); - env.driver(&mut editor).move_right(); + env.driver(&mut editor).move_right(false); // We can select the first line. - env.driver(&mut editor).select_to_hard_line_end(); + env.driver(&mut editor).move_to_hard_line_end(true); env.check_editor_snapshot(&mut editor); - env.driver(&mut editor).move_to_hard_line_start(); + env.driver(&mut editor).move_to_hard_line_start(false); env.check_editor_snapshot(&mut editor); - env.driver(&mut editor).move_down(); - env.driver(&mut editor).move_to_hard_line_end(); + env.driver(&mut editor).move_down(false); + env.driver(&mut editor).move_to_hard_line_end(false); env.check_editor_snapshot(&mut editor); - env.driver(&mut editor).select_to_hard_line_start(); + env.driver(&mut editor).move_to_hard_line_start(true); env.check_editor_snapshot(&mut editor); - env.driver(&mut editor).move_right(); + env.driver(&mut editor).move_right(false); // Cursor is logically after the newline; there's not really any great answer here. - env.driver(&mut editor).select_to_hard_line_start(); + env.driver(&mut editor).move_to_hard_line_start(true); env.check_editor_snapshot(&mut editor); // We can select the last line. - env.driver(&mut editor).move_right(); - env.driver(&mut editor).move_right(); - env.driver(&mut editor).move_to_hard_line_end(); + env.driver(&mut editor).move_right(false); + env.driver(&mut editor).move_right(false); + env.driver(&mut editor).move_to_hard_line_end(false); env.check_editor_snapshot(&mut editor); - env.driver(&mut editor).select_to_hard_line_start(); + env.driver(&mut editor).move_to_hard_line_start(true); env.check_editor_snapshot(&mut editor); }