From f401a8c2dda89cda86bbfe95e3323e879bec39de Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Sat, 6 Apr 2024 15:29:12 +0100 Subject: [PATCH] Add select flag for motion and click actions --- examples/editor-test/src/main.rs | 10 ++- examples/editor/src/main.rs | 51 +++++++---- examples/rich-text/src/main.rs | 51 +++++++---- src/edit/editor.rs | 52 ++++++++++- src/edit/mod.rs | 6 +- src/edit/vi.rs | 145 ++++++++++++++++++++++++------- 6 files changed, 246 insertions(+), 69 deletions(-) diff --git a/examples/editor-test/src/main.rs b/examples/editor-test/src/main.rs index 85bae2be71..b4b2c1d085 100644 --- a/examples/editor-test/src/main.rs +++ b/examples/editor-test/src/main.rs @@ -116,7 +116,10 @@ fn main() { // Test delete of EGC { let cursor = editor.cursor(); - editor.action(Action::Motion(Motion::Previous)); + editor.action(Action::Motion { + motion: Motion::Previous, + select: false, + }); editor.action(Action::Delete); for c in grapheme.chars() { editor.action(Action::Insert(c)); @@ -140,7 +143,10 @@ fn main() { { let cursor = editor.cursor(); editor.action(Action::Enter); - editor.action(Action::Motion(Motion::Previous)); + editor.action(Action::Motion { + motion: Motion::Previous, + select: false, + }); editor.action(Action::Delete); assert_eq!(cursor, editor.cursor()); } diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 1ce0758c2e..0da5185a7d 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -62,6 +62,7 @@ fn main() { } let mut ctrl_pressed = false; + let mut shift_pressed = false; let mut mouse_x = 0.0; let mut mouse_y = 0.0; let mut mouse_left = ElementState::Released; @@ -174,7 +175,8 @@ fn main() { surface_buffer.present().unwrap(); } WindowEvent::ModifiersChanged(modifiers) => { - ctrl_pressed = modifiers.state().control_key() + ctrl_pressed = modifiers.state().control_key(); + shift_pressed = modifiers.state().shift_key(); } WindowEvent::KeyboardInput { event, .. } => { let KeyEvent { @@ -184,28 +186,46 @@ fn main() { if state.is_pressed() { match logical_key { Key::Named(NamedKey::ArrowLeft) => { - editor.action(Action::Motion(Motion::Left)) + editor.action(Action::Motion { + motion: Motion::Left, + select: shift_pressed, + }) } Key::Named(NamedKey::ArrowRight) => { - editor.action(Action::Motion(Motion::Right)) + editor.action(Action::Motion { + motion: Motion::Right, + select: shift_pressed, + }) } Key::Named(NamedKey::ArrowUp) => { - editor.action(Action::Motion(Motion::Up)) + editor.action(Action::Motion { + motion: Motion::Up, + select: shift_pressed, + }) } Key::Named(NamedKey::ArrowDown) => { - editor.action(Action::Motion(Motion::Down)) - } - Key::Named(NamedKey::Home) => { - editor.action(Action::Motion(Motion::Home)) - } - Key::Named(NamedKey::End) => { - editor.action(Action::Motion(Motion::End)) - } - Key::Named(NamedKey::PageUp) => { - editor.action(Action::Motion(Motion::PageUp)) + editor.action(Action::Motion { + motion: Motion::Down, + select: shift_pressed, + }) } + Key::Named(NamedKey::Home) => editor.action(Action::Motion { + motion: Motion::Home, + select: shift_pressed, + }), + Key::Named(NamedKey::End) => editor.action(Action::Motion { + motion: Motion::End, + select: shift_pressed, + }), + Key::Named(NamedKey::PageUp) => editor.action(Action::Motion { + motion: Motion::PageUp, + select: shift_pressed, + }), Key::Named(NamedKey::PageDown) => { - editor.action(Action::Motion(Motion::PageDown)) + editor.action(Action::Motion { + motion: Motion::PageDown, + select: shift_pressed, + }) } Key::Named(NamedKey::Escape) => editor.action(Action::Escape), Key::Named(NamedKey::Enter) => editor.action(Action::Enter), @@ -316,6 +336,7 @@ fn main() { editor.action(Action::Click { x: mouse_x as i32, y: mouse_y as i32, + select: shift_pressed, }); window.request_redraw(); } diff --git a/examples/rich-text/src/main.rs b/examples/rich-text/src/main.rs index 8a2f858d97..264b3e465d 100644 --- a/examples/rich-text/src/main.rs +++ b/examples/rich-text/src/main.rs @@ -129,6 +129,7 @@ fn main() { editor.with_buffer_mut(|buffer| set_buffer_text(buffer)); let mut ctrl_pressed = false; + let mut shift_pressed = false; let mut mouse_x = 0.0; let mut mouse_y = 0.0; let mut mouse_left = ElementState::Released; @@ -217,7 +218,8 @@ fn main() { surface_buffer.present().unwrap(); } WindowEvent::ModifiersChanged(modifiers) => { - ctrl_pressed = modifiers.state().control_key() + ctrl_pressed = modifiers.state().control_key(); + shift_pressed = modifiers.state().shift_key(); } WindowEvent::KeyboardInput { event, .. } => { let KeyEvent { @@ -227,28 +229,46 @@ fn main() { if state.is_pressed() { match logical_key { Key::Named(NamedKey::ArrowLeft) => { - editor.action(Action::Motion(Motion::Left)) + editor.action(Action::Motion { + motion: Motion::Left, + select: shift_pressed, + }) } Key::Named(NamedKey::ArrowRight) => { - editor.action(Action::Motion(Motion::Right)) + editor.action(Action::Motion { + motion: Motion::Right, + select: shift_pressed, + }) } Key::Named(NamedKey::ArrowUp) => { - editor.action(Action::Motion(Motion::Up)) + editor.action(Action::Motion { + motion: Motion::Up, + select: shift_pressed, + }) } Key::Named(NamedKey::ArrowDown) => { - editor.action(Action::Motion(Motion::Down)) - } - Key::Named(NamedKey::Home) => { - editor.action(Action::Motion(Motion::Home)) - } - Key::Named(NamedKey::End) => { - editor.action(Action::Motion(Motion::End)) - } - Key::Named(NamedKey::PageUp) => { - editor.action(Action::Motion(Motion::PageUp)) + editor.action(Action::Motion { + motion: Motion::Down, + select: shift_pressed, + }) } + Key::Named(NamedKey::Home) => editor.action(Action::Motion { + motion: Motion::Home, + select: shift_pressed, + }), + Key::Named(NamedKey::End) => editor.action(Action::Motion { + motion: Motion::End, + select: shift_pressed, + }), + Key::Named(NamedKey::PageUp) => editor.action(Action::Motion { + motion: Motion::PageUp, + select: shift_pressed, + }), Key::Named(NamedKey::PageDown) => { - editor.action(Action::Motion(Motion::PageDown)) + editor.action(Action::Motion { + motion: Motion::PageDown, + select: shift_pressed, + }) } Key::Named(NamedKey::Escape) => editor.action(Action::Escape), Key::Named(NamedKey::Enter) => editor.action(Action::Enter), @@ -313,6 +333,7 @@ fn main() { editor.action(Action::Click { x: mouse_x /*- line_x*/ as i32, y: mouse_y as i32, + select: shift_pressed, }); window.request_redraw(); } diff --git a/src/edit/editor.rs b/src/edit/editor.rs index 1231002cec..33a72574b7 100644 --- a/src/edit/editor.rs +++ b/src/edit/editor.rs @@ -12,7 +12,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::Color; use crate::{ Action, Attrs, AttrsList, BorrowedWithFontSystem, BufferLine, BufferRef, Change, ChangeItem, - Cursor, Edit, FontSystem, LayoutRun, Selection, Shaping, + Cursor, Edit, FontSystem, LayoutRun, Motion, Selection, Shaping, }; /// A wrapper of [`Buffer`] for easy editing @@ -565,8 +565,46 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> { let old_cursor = self.cursor; match action { - Action::Motion(motion) => { + Action::Motion { motion, select } => { let cursor = self.cursor; + if select { + if self.selection == Selection::None { + self.selection = Selection::Normal(self.cursor); + } + } else if let Some((start, end)) = self.selection_bounds() { + if start.line != end.line || start.index != end.index { + let new_cursor = match motion { + // These actions have special behavior when there is an active selection. + Motion::Previous => Some(start), + Motion::Next => Some(end), + Motion::Left => self + .with_buffer_mut(|buffer| { + buffer + .line_shape(font_system, cursor.line) + .map(|shape| shape.rtl) + }) + .map(|rtl| if rtl { end } else { start }), + Motion::Right => self + .with_buffer_mut(|buffer| { + buffer + .line_shape(font_system, cursor.line) + .map(|shape| shape.rtl) + }) + .map(|rtl| if rtl { start } else { end }), + _ => None, + }; + if let Some(new_cursor) = new_cursor { + self.cursor = new_cursor; + self.cursor_x_opt = None; + self.cursor_moved = true; + self.selection = Selection::None; + self.set_redraw(true); + return; + } + } + self.selection = Selection::None; + } + let cursor_x_opt = self.cursor_x_opt; if let Some((new_cursor, new_cursor_x_opt)) = self.with_buffer_mut(|buffer| { buffer.cursor_motion(font_system, cursor, cursor_x_opt, motion) @@ -807,8 +845,14 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> { self.with_buffer_mut(|buffer| buffer.set_redraw(true)); } } - Action::Click { x, y } => { - self.set_selection(Selection::None); + Action::Click { x, y, select } => { + if select { + if self.selection == Selection::None { + self.selection = Selection::Normal(self.cursor); + } + } else { + self.selection = Selection::None; + } if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32)) { diff --git a/src/edit/mod.rs b/src/edit/mod.rs index d9d4fd11db..213602a3b5 100644 --- a/src/edit/mod.rs +++ b/src/edit/mod.rs @@ -23,7 +23,10 @@ mod vi; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Action { /// Move the cursor with some motion - Motion(Motion), + Motion { + motion: Motion, + select: bool, + }, /// Escape, clears selection Escape, /// Insert character at cursor @@ -42,6 +45,7 @@ pub enum Action { Click { x: i32, y: i32, + select: bool, }, /// Mouse double click at specified position DoubleClick { diff --git a/src/edit/vi.rs b/src/edit/vi.rs index 08dfd480fb..2b64704232 100644 --- a/src/edit/vi.rs +++ b/src/edit/vi.rs @@ -619,19 +619,42 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer //TODO: this leaves lots of room for issues in translation, should we directly accept Key? Action::Backspace => Key::Backspace, Action::Delete => Key::Delete, - Action::Motion(Motion::Down) => Key::Down, - Action::Motion(Motion::End) => Key::End, + Action::Motion { + motion: Motion::Down, + .. + } => Key::Down, + Action::Motion { + motion: Motion::End, + .. + } => Key::End, Action::Enter => Key::Enter, Action::Escape => Key::Escape, - Action::Motion(Motion::Home) => Key::Home, + Action::Motion { + motion: Motion::Home, + .. + } => Key::Home, Action::Indent => Key::Tab, Action::Insert(c) => Key::Char(c), - Action::Motion(Motion::Left) => Key::Left, - Action::Motion(Motion::PageDown) => Key::PageDown, - Action::Motion(Motion::PageUp) => Key::PageUp, - Action::Motion(Motion::Right) => Key::Right, + Action::Motion { + motion: Motion::Left, + .. + } => Key::Left, + Action::Motion { + motion: Motion::PageDown, + .. + } => Key::PageDown, + Action::Motion { + motion: Motion::PageUp, + .. + } => Key::PageUp, + Action::Motion { + motion: Motion::Right, + .. + } => Key::Right, Action::Unindent => Key::Backtab, - Action::Motion(Motion::Up) => Key::Up, + Action::Motion { + motion: Motion::Up, .. + } => Key::Up, _ => { log::debug!("Pass through action {:?}", action); editor.action(font_system, action); @@ -737,9 +760,21 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer // Move to inserted line, preserving cursor x position if after { - editor.action(font_system, Action::Motion(Motion::Down)); + editor.action( + font_system, + Action::Motion { + motion: Motion::Down, + select: false, + }, + ); } else { - editor.action(font_system, Action::Motion(Motion::Up)); + editor.action( + font_system, + Action::Motion { + motion: Motion::Up, + select: false, + }, + ); } } } @@ -849,24 +884,43 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer //TODO: what to do for this psuedo-motion? return; } - modit::Motion::Down => Action::Motion(Motion::Down), - modit::Motion::End => Action::Motion(Motion::End), - modit::Motion::GotoLine(line) => { - Action::Motion(Motion::GotoLine(line.saturating_sub(1))) - } - modit::Motion::GotoEof => Action::Motion(Motion::GotoLine( - editor.with_buffer(|buffer| buffer.lines.len().saturating_sub(1)), - )), - modit::Motion::Home => Action::Motion(Motion::Home), + modit::Motion::Down => Action::Motion { + motion: Motion::Down, + select: false, + }, + modit::Motion::End => Action::Motion { + motion: Motion::End, + select: false, + }, + modit::Motion::GotoLine(line) => Action::Motion { + motion: Motion::GotoLine(line.saturating_sub(1)), + select: false, + }, + modit::Motion::GotoEof => Action::Motion { + motion: Motion::GotoLine( + editor.with_buffer(|buffer| buffer.lines.len().saturating_sub(1)), + ), + select: false, + }, + modit::Motion::Home => Action::Motion { + motion: Motion::Home, + select: false, + }, modit::Motion::Inside => { //TODO: what to do for this psuedo-motion? return; } - modit::Motion::Left => Action::Motion(Motion::Left), + modit::Motion::Left => Action::Motion { + motion: Motion::Left, + select: false, + }, modit::Motion::LeftInLine => { let cursor = editor.cursor(); if cursor.index > 0 { - Action::Motion(Motion::Left) + Action::Motion { + motion: Motion::Left, + select: false, + } } else { return; } @@ -979,8 +1033,14 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer editor.set_cursor(cursor); return; } - modit::Motion::PageDown => Action::Motion(Motion::PageDown), - modit::Motion::PageUp => Action::Motion(Motion::PageUp), + modit::Motion::PageDown => Action::Motion { + motion: Motion::PageDown, + select: false, + }, + modit::Motion::PageUp => Action::Motion { + motion: Motion::PageUp, + select: false, + }, modit::Motion::PreviousChar(find_c) => { let mut cursor = editor.cursor(); editor.with_buffer(|buffer| { @@ -1096,14 +1156,20 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer editor.set_cursor(cursor); return; } - modit::Motion::Right => Action::Motion(Motion::Right), + modit::Motion::Right => Action::Motion { + motion: Motion::Right, + select: false, + }, modit::Motion::RightInLine => { let cursor = editor.cursor(); if cursor.index < editor .with_buffer(|buffer| buffer.lines[cursor.line].text().len()) { - Action::Motion(Motion::Right) + Action::Motion { + motion: Motion::Right, + select: false, + } } else { return; } @@ -1113,7 +1179,10 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer if let Some(line_i) = editor.with_buffer(|buffer| { buffer.layout_runs().next().map(|first| first.line_i) }) { - Action::Motion(Motion::GotoLine(line_i)) + Action::Motion { + motion: Motion::GotoLine(line_i), + select: false, + } } else { return; } @@ -1123,7 +1192,10 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer if let Some(line_i) = editor.with_buffer(|buffer| { buffer.layout_runs().last().map(|last| last.line_i) }) { - Action::Motion(Motion::GotoLine(line_i)) + Action::Motion { + motion: Motion::GotoLine(line_i), + select: false, + } } else { return; } @@ -1134,9 +1206,12 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer let mut layout_runs = buffer.layout_runs(); if let Some(first) = layout_runs.next() { if let Some(last) = layout_runs.last() { - Some(Action::Motion(Motion::GotoLine( - (last.line_i + first.line_i) / 2, - ))) + Some(Action::Motion { + motion: Motion::GotoLine( + (last.line_i + first.line_i) / 2, + ), + select: false, + }) } else { None } @@ -1153,8 +1228,14 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer //TODO: what to do for this psuedo-motion? return; } - modit::Motion::SoftHome => Action::Motion(Motion::SoftHome), - modit::Motion::Up => Action::Motion(Motion::Up), + modit::Motion::SoftHome => Action::Motion { + motion: Motion::SoftHome, + select: false, + }, + modit::Motion::Up => Action::Motion { + motion: Motion::Up, + select: false, + }, } } };