From 2491522c87d654ad2ec4f509d0f7bca44eea2a6d Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 17:40:50 +0000 Subject: [PATCH 01/23] feat: basic MouseClicks struct --- helix-term/src/ui/editor.rs | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 5d028415e..2bb4d4d01 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -46,6 +46,50 @@ pub struct EditorView { terminal_focused: bool, } +/// Tracks the character positions where the mouse was last clicked +/// +/// If we double click, select that word +/// If we triple click, select full line +struct MouseClicks { + clicks: [Option; 2], + count: usize, +} + +impl MouseClicks { + fn new() -> Self { + Self { + clicks: [None, None], + count: 0, + } + } + + fn register_click(&mut self, char_idx: usize) { + match self.count { + 0 => { + self.clicks[0] = Some(char_idx); + self.count = 1; + } + 1 => { + self.clicks[1] = Some(char_idx); + self.count = 2; + } + 2 => { + self.clicks[0] = self.clicks[1]; + self.clicks[1] = Some(char_idx); + } + _ => unreachable!("Mouse click count should never exceed 2"), + } + } + + fn has_double_click(&mut self) -> Option { + self.clicks[0].filter(|first_click| Some(first_click) != self.clicks[1].as_ref()) + } + + fn has_single_click(&mut self) -> Option { + self.clicks[1] + } +} + #[derive(Debug, Clone)] pub enum InsertEvent { Key(KeyEvent), From 897569fa64fddcbf2dc506a4389a6728fecfb93e Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:01:16 +0000 Subject: [PATCH 02/23] fix: correct functions for double and triple click --- helix-term/src/ui/editor.rs | 73 ++++++++++++++----------------------- helix-view/src/editor.rs | 48 ++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 45 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2bb4d4d01..3dff8110b 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -46,50 +46,6 @@ pub struct EditorView { terminal_focused: bool, } -/// Tracks the character positions where the mouse was last clicked -/// -/// If we double click, select that word -/// If we triple click, select full line -struct MouseClicks { - clicks: [Option; 2], - count: usize, -} - -impl MouseClicks { - fn new() -> Self { - Self { - clicks: [None, None], - count: 0, - } - } - - fn register_click(&mut self, char_idx: usize) { - match self.count { - 0 => { - self.clicks[0] = Some(char_idx); - self.count = 1; - } - 1 => { - self.clicks[1] = Some(char_idx); - self.count = 2; - } - 2 => { - self.clicks[0] = self.clicks[1]; - self.clicks[1] = Some(char_idx); - } - _ => unreachable!("Mouse click count should never exceed 2"), - } - } - - fn has_double_click(&mut self) -> Option { - self.clicks[0].filter(|first_click| Some(first_click) != self.clicks[1].as_ref()) - } - - fn has_single_click(&mut self) -> Option { - self.clicks[1] - } -} - #[derive(Debug, Clone)] pub enum InsertEvent { Key(KeyEvent), @@ -1218,12 +1174,31 @@ impl EditorView { let editor = &mut cxt.editor; if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) { + let message; let prev_view_id = view!(editor).id; let doc = doc_mut!(editor, &view!(editor, view_id).doc); - if modifiers == KeyModifiers::ALT { + if editor.mouse_clicks.is_triple_click(pos) { + message = format!("triple click: {:?}, {}", editor.mouse_clicks, pos); + + // let text = doc.text().slice(..); + + // let (start_line, end_line) = + // Range::point(triple_click_idx).line_range(text); + + // let start = text.line_to_char(start_line); + // let end = text.line_to_char( + // (end_line + 1) // newline of end_line + // .min(text.len_lines()), + // ); + + // doc.set_selection(view_id, Selection::single(start, end)) + } else if editor.mouse_clicks.is_double_click(pos) { + message = format!("double click: {:?}, {}", editor.mouse_clicks, pos); + } else if modifiers == KeyModifiers::ALT { let selection = doc.selection(view_id).clone(); doc.set_selection(view_id, selection.push(Range::point(pos))); + message = format!("(alt) single click: {:?}, {}", editor.mouse_clicks, pos); } else if editor.mode == Mode::Select { // Discards non-primary selections for consistent UX with normal mode let primary = doc.selection(view_id).primary().put_cursor( @@ -1231,9 +1206,14 @@ impl EditorView { pos, true, ); + message = + format!("(select) single click: {:?}, {}", editor.mouse_clicks, pos); + editor.mouse_down_range = Some(primary); doc.set_selection(view_id, Selection::single(primary.anchor, primary.head)); } else { + message = format!("single click: {:?}, {}", editor.mouse_clicks, pos); + doc.set_selection(view_id, Selection::point(pos)); } @@ -1243,6 +1223,9 @@ impl EditorView { editor.focus(view_id); editor.ensure_cursor_in_view(view_id); + editor.mouse_clicks.register_click(pos); + + editor.set_status(message); return EventResult::Consumed(None); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 151b26538..af4edb99c 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -81,6 +81,51 @@ where ) } +/// Tracks the character positions where the mouse was last clicked +/// +/// If we double click, select that word +/// If we triple click, select full line +#[derive(Debug)] +pub struct MouseClicks { + clicks: [Option; 2], + count: usize, +} + +impl MouseClicks { + fn new() -> Self { + Self { + clicks: [None, None], + count: 0, + } + } + + pub fn register_click(&mut self, char_idx: usize) { + match self.count { + 0 => { + self.clicks[0] = Some(char_idx); + self.count = 1; + } + 1 => { + self.clicks[1] = Some(char_idx); + self.count = 2; + } + 2 => { + self.clicks[0] = self.clicks[1]; + self.clicks[1] = Some(char_idx); + } + _ => unreachable!("Mouse click count should never exceed 2"), + } + } + + pub fn is_triple_click(&mut self, pos: usize) -> bool { + Some(pos) == self.clicks[0] && Some(pos) == self.clicks[1] + } + + pub fn is_double_click(&mut self, pos: usize) -> bool { + Some(pos) == self.clicks[1] && Some(pos) != self.clicks[0] + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct GutterConfig { @@ -1101,6 +1146,8 @@ pub struct Editor { pub mouse_down_range: Option, pub cursor_cache: CursorCache, + + pub mouse_clicks: MouseClicks, } pub type Motion = Box; @@ -1223,6 +1270,7 @@ impl Editor { handlers, mouse_down_range: None, cursor_cache: CursorCache::default(), + mouse_clicks: MouseClicks::new(), } } From 2b76e839ad001d86af33f24222f47bc14f677a2b Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:07:25 +0000 Subject: [PATCH 03/23] refactor: use enum for mouse click type --- helix-term/src/ui/editor.rs | 77 +++++++++++++++++++------------------ helix-view/src/editor.rs | 26 ++++++++++--- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 3dff8110b..e8ba1d409 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -25,7 +25,7 @@ use helix_core::{ use helix_view::{ annotations::diagnostics::DiagnosticFilter, document::{Mode, SavePoint, SCRATCH_BUFFER_NAME}, - editor::{CompleteAction, CursorShapeConfig}, + editor::{CompleteAction, CursorShapeConfig, MouseClick}, graphics::{Color, CursorKind, Modifier, Rect, Style}, input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, keyboard::{KeyCode, KeyModifiers}, @@ -1174,47 +1174,51 @@ impl EditorView { let editor = &mut cxt.editor; if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) { + let click_type = editor.mouse_clicks.register_click(pos); + let message; let prev_view_id = view!(editor).id; let doc = doc_mut!(editor, &view!(editor, view_id).doc); - if editor.mouse_clicks.is_triple_click(pos) { - message = format!("triple click: {:?}, {}", editor.mouse_clicks, pos); + match click_type { + MouseClick::Triple => { + message = format!("triple click: {:?}, {}", editor.mouse_clicks, pos); + } + MouseClick::Double => { + message = format!("double click: {:?}, {}", editor.mouse_clicks, pos); + } + MouseClick::Single => { + if modifiers == KeyModifiers::ALT { + let selection = doc.selection(view_id).clone(); + doc.set_selection(view_id, selection.push(Range::point(pos))); + message = format!( + "(alt) single click: {:?}, {}", + editor.mouse_clicks, pos + ); + } else if editor.mode == Mode::Select { + // Discards non-primary selections for consistent UX with normal mode + let primary = doc.selection(view_id).primary().put_cursor( + doc.text().slice(..), + pos, + true, + ); + message = format!( + "(select) single click: {:?}, {}", + editor.mouse_clicks, pos + ); - // let text = doc.text().slice(..); + editor.mouse_down_range = Some(primary); + doc.set_selection( + view_id, + Selection::single(primary.anchor, primary.head), + ); + } else { + message = + format!("single click: {:?}, {}", editor.mouse_clicks, pos); - // let (start_line, end_line) = - // Range::point(triple_click_idx).line_range(text); - - // let start = text.line_to_char(start_line); - // let end = text.line_to_char( - // (end_line + 1) // newline of end_line - // .min(text.len_lines()), - // ); - - // doc.set_selection(view_id, Selection::single(start, end)) - } else if editor.mouse_clicks.is_double_click(pos) { - message = format!("double click: {:?}, {}", editor.mouse_clicks, pos); - } else if modifiers == KeyModifiers::ALT { - let selection = doc.selection(view_id).clone(); - doc.set_selection(view_id, selection.push(Range::point(pos))); - message = format!("(alt) single click: {:?}, {}", editor.mouse_clicks, pos); - } else if editor.mode == Mode::Select { - // Discards non-primary selections for consistent UX with normal mode - let primary = doc.selection(view_id).primary().put_cursor( - doc.text().slice(..), - pos, - true, - ); - message = - format!("(select) single click: {:?}, {}", editor.mouse_clicks, pos); - - editor.mouse_down_range = Some(primary); - doc.set_selection(view_id, Selection::single(primary.anchor, primary.head)); - } else { - message = format!("single click: {:?}, {}", editor.mouse_clicks, pos); - - doc.set_selection(view_id, Selection::point(pos)); + doc.set_selection(view_id, Selection::point(pos)); + } + } } if view_id != prev_view_id { @@ -1223,7 +1227,6 @@ impl EditorView { editor.focus(view_id); editor.ensure_cursor_in_view(view_id); - editor.mouse_clicks.register_click(pos); editor.set_status(message); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index af4edb99c..9c54b02dd 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -91,6 +91,12 @@ pub struct MouseClicks { count: usize, } +pub enum MouseClick { + Single, + Double, + Triple, +} + impl MouseClicks { fn new() -> Self { Self { @@ -99,7 +105,15 @@ impl MouseClicks { } } - pub fn register_click(&mut self, char_idx: usize) { + pub fn register_click(&mut self, char_idx: usize) -> MouseClick { + let click_type = if self.is_triple_click(char_idx) { + MouseClick::Triple + } else if self.is_double_click(char_idx) { + MouseClick::Double + } else { + MouseClick::Single + }; + match self.count { 0 => { self.clicks[0] = Some(char_idx); @@ -113,15 +127,17 @@ impl MouseClicks { self.clicks[0] = self.clicks[1]; self.clicks[1] = Some(char_idx); } - _ => unreachable!("Mouse click count should never exceed 2"), - } + _ => unreachable!("Mouse click count will never exceed 2"), + }; + + click_type } - pub fn is_triple_click(&mut self, pos: usize) -> bool { + fn is_triple_click(&mut self, pos: usize) -> bool { Some(pos) == self.clicks[0] && Some(pos) == self.clicks[1] } - pub fn is_double_click(&mut self, pos: usize) -> bool { + fn is_double_click(&mut self, pos: usize) -> bool { Some(pos) == self.clicks[1] && Some(pos) != self.clicks[0] } } From 30b592fed944ed624d9ddab9fdf248e24cfcfd50 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:10:23 +0000 Subject: [PATCH 04/23] refactor: clean up code and remove debugging statements --- helix-term/src/ui/editor.rs | 27 +++------------------------ helix-view/src/editor.rs | 1 + 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index e8ba1d409..f86de5b81 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1174,27 +1174,16 @@ impl EditorView { let editor = &mut cxt.editor; if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) { - let click_type = editor.mouse_clicks.register_click(pos); - - let message; let prev_view_id = view!(editor).id; let doc = doc_mut!(editor, &view!(editor, view_id).doc); - match click_type { - MouseClick::Triple => { - message = format!("triple click: {:?}, {}", editor.mouse_clicks, pos); - } - MouseClick::Double => { - message = format!("double click: {:?}, {}", editor.mouse_clicks, pos); - } + match editor.mouse_clicks.register_click(pos) { + MouseClick::Triple => {} + MouseClick::Double => {} MouseClick::Single => { if modifiers == KeyModifiers::ALT { let selection = doc.selection(view_id).clone(); doc.set_selection(view_id, selection.push(Range::point(pos))); - message = format!( - "(alt) single click: {:?}, {}", - editor.mouse_clicks, pos - ); } else if editor.mode == Mode::Select { // Discards non-primary selections for consistent UX with normal mode let primary = doc.selection(view_id).primary().put_cursor( @@ -1202,20 +1191,12 @@ impl EditorView { pos, true, ); - message = format!( - "(select) single click: {:?}, {}", - editor.mouse_clicks, pos - ); - editor.mouse_down_range = Some(primary); doc.set_selection( view_id, Selection::single(primary.anchor, primary.head), ); } else { - message = - format!("single click: {:?}, {}", editor.mouse_clicks, pos); - doc.set_selection(view_id, Selection::point(pos)); } } @@ -1228,8 +1209,6 @@ impl EditorView { editor.focus(view_id); editor.ensure_cursor_in_view(view_id); - editor.set_status(message); - return EventResult::Consumed(None); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 9c54b02dd..f09fedb1d 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -105,6 +105,7 @@ impl MouseClicks { } } + /// Registers a click for a certain character, and returns the type of this click pub fn register_click(&mut self, char_idx: usize) -> MouseClick { let click_type = if self.is_triple_click(char_idx) { MouseClick::Triple From 6c19d2c1c4f6e0665ceb44e75e5e5315b87d3b4b Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:31:49 +0000 Subject: [PATCH 05/23] feat: implement double click and single click selection --- helix-core/src/textobject.rs | 7 ++++++- helix-term/src/ui/editor.rs | 24 ++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index 7576b3a78..1225b70a7 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -11,7 +11,12 @@ use crate::syntax::LanguageConfiguration; use crate::Range; use crate::{surround, Syntax}; -fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction, long: bool) -> usize { +pub fn find_word_boundary( + slice: RopeSlice, + mut pos: usize, + direction: Direction, + long: bool, +) -> usize { use CharCategory::{Eol, Whitespace}; let iter = match direction { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f86de5b81..03f5ba97f 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -14,11 +14,13 @@ use crate::{ }; use helix_core::{ + chars::CharCategory, diagnostic::NumberOrString, graphemes::{next_grapheme_boundary, prev_grapheme_boundary}, movement::Direction, syntax::{self, HighlightEvent}, text_annotations::TextAnnotations, + textobject::find_word_boundary, unicode::width::UnicodeWidthStr, visual_offset_from_block, Change, Position, Range, Selection, Transaction, }; @@ -1176,10 +1178,9 @@ impl EditorView { if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) { let prev_view_id = view!(editor).id; let doc = doc_mut!(editor, &view!(editor, view_id).doc); + let text = doc.text().slice(..); match editor.mouse_clicks.register_click(pos) { - MouseClick::Triple => {} - MouseClick::Double => {} MouseClick::Single => { if modifiers == KeyModifiers::ALT { let selection = doc.selection(view_id).clone(); @@ -1200,6 +1201,25 @@ impl EditorView { doc.set_selection(view_id, Selection::point(pos)); } } + MouseClick::Double => { + let word_start = + find_word_boundary(text, pos, Direction::Backward, false); + let word_end = match text + .get_char(pos) + .map(helix_core::chars::categorize_char) + { + None | Some(CharCategory::Whitespace | CharCategory::Eol) => pos, + _ => find_word_boundary(text, pos + 1, Direction::Forward, false), + }; + + doc.set_selection(view_id, Selection::single(word_start, word_end)); + } + MouseClick::Triple => { + let current_line = text.char_to_line(pos); + let from = text.line_to_char(current_line); + let to = text.line_to_char(current_line + 1); + doc.set_selection(view_id, Selection::single(from, to)); + } } if view_id != prev_view_id { From b3831c032e682a371fe8f3966200cc40379af487 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:35:29 +0000 Subject: [PATCH 06/23] refactor: move impl to a proper mod --- helix-term/src/ui/editor.rs | 25 +++++++------- helix-view/src/editor.rs | 64 +--------------------------------- helix-view/src/input.rs | 69 +++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 75 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 03f5ba97f..c05f1acbd 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -27,9 +27,9 @@ use helix_core::{ use helix_view::{ annotations::diagnostics::DiagnosticFilter, document::{Mode, SavePoint, SCRATCH_BUFFER_NAME}, - editor::{CompleteAction, CursorShapeConfig, MouseClick}, + editor::{CompleteAction, CursorShapeConfig}, graphics::{Color, CursorKind, Modifier, Rect, Style}, - input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, + input::{KeyEvent, MouseButton, MouseClick, MouseEvent, MouseEventKind}, keyboard::{KeyCode, KeyModifiers}, Document, Editor, Theme, View, }; @@ -1180,11 +1180,11 @@ impl EditorView { let doc = doc_mut!(editor, &view!(editor, view_id).doc); let text = doc.text().slice(..); - match editor.mouse_clicks.register_click(pos) { + let selection = match editor.mouse_clicks.register_click(pos) { MouseClick::Single => { if modifiers == KeyModifiers::ALT { let selection = doc.selection(view_id).clone(); - doc.set_selection(view_id, selection.push(Range::point(pos))); + selection.push(Range::point(pos)) } else if editor.mode == Mode::Select { // Discards non-primary selections for consistent UX with normal mode let primary = doc.selection(view_id).primary().put_cursor( @@ -1193,12 +1193,10 @@ impl EditorView { true, ); editor.mouse_down_range = Some(primary); - doc.set_selection( - view_id, - Selection::single(primary.anchor, primary.head), - ); + + Selection::single(primary.anchor, primary.head) } else { - doc.set_selection(view_id, Selection::point(pos)); + Selection::point(pos) } } MouseClick::Double => { @@ -1212,15 +1210,18 @@ impl EditorView { _ => find_word_boundary(text, pos + 1, Direction::Forward, false), }; - doc.set_selection(view_id, Selection::single(word_start, word_end)); + Selection::single(word_start, word_end) } MouseClick::Triple => { let current_line = text.char_to_line(pos); let from = text.line_to_char(current_line); let to = text.line_to_char(current_line + 1); - doc.set_selection(view_id, Selection::single(from, to)); + + Selection::single(from, to) } - } + }; + + doc.set_selection(view_id, selection); if view_id != prev_view_id { self.clear_completion(editor); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index f09fedb1d..7ea12384d 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -8,7 +8,7 @@ use crate::{ graphics::{CursorKind, Rect}, handlers::Handlers, info::Info, - input::KeyEvent, + input::{KeyEvent, MouseClicks}, register::Registers, theme::{self, Theme}, tree::{self, Tree}, @@ -81,68 +81,6 @@ where ) } -/// Tracks the character positions where the mouse was last clicked -/// -/// If we double click, select that word -/// If we triple click, select full line -#[derive(Debug)] -pub struct MouseClicks { - clicks: [Option; 2], - count: usize, -} - -pub enum MouseClick { - Single, - Double, - Triple, -} - -impl MouseClicks { - fn new() -> Self { - Self { - clicks: [None, None], - count: 0, - } - } - - /// Registers a click for a certain character, and returns the type of this click - pub fn register_click(&mut self, char_idx: usize) -> MouseClick { - let click_type = if self.is_triple_click(char_idx) { - MouseClick::Triple - } else if self.is_double_click(char_idx) { - MouseClick::Double - } else { - MouseClick::Single - }; - - match self.count { - 0 => { - self.clicks[0] = Some(char_idx); - self.count = 1; - } - 1 => { - self.clicks[1] = Some(char_idx); - self.count = 2; - } - 2 => { - self.clicks[0] = self.clicks[1]; - self.clicks[1] = Some(char_idx); - } - _ => unreachable!("Mouse click count will never exceed 2"), - }; - - click_type - } - - fn is_triple_click(&mut self, pos: usize) -> bool { - Some(pos) == self.clicks[0] && Some(pos) == self.clicks[1] - } - - fn is_double_click(&mut self, pos: usize) -> bool { - Some(pos) == self.clicks[1] && Some(pos) != self.clicks[0] - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct GutterConfig { diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index d359db703..ca65e74f1 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -59,6 +59,75 @@ pub enum MouseButton { /// Middle mouse button. Middle, } + +/// Tracks the character positions where the mouse was last clicked +/// +/// If we double click, select that word +/// If we triple click, select full line +#[derive(Debug)] +pub struct MouseClicks { + clicks: [Option; 2], + count: usize, +} + +pub enum MouseClick { + Single, + Double, + Triple, +} + +impl MouseClicks { + pub fn new() -> Self { + Self { + clicks: [None, None], + count: 0, + } + } + + /// Registers a click for a certain character, and returns the type of this click + pub fn register_click(&mut self, char_idx: usize) -> MouseClick { + let click_type = if self.is_triple_click(char_idx) { + MouseClick::Triple + } else if self.is_double_click(char_idx) { + MouseClick::Double + } else { + MouseClick::Single + }; + + match self.count { + 0 => { + self.clicks[0] = Some(char_idx); + self.count = 1; + } + 1 => { + self.clicks[1] = Some(char_idx); + self.count = 2; + } + 2 => { + self.clicks[0] = self.clicks[1]; + self.clicks[1] = Some(char_idx); + } + _ => unreachable!("Mouse click count will never exceed 2"), + }; + + click_type + } + + fn is_triple_click(&mut self, pos: usize) -> bool { + Some(pos) == self.clicks[0] && Some(pos) == self.clicks[1] + } + + fn is_double_click(&mut self, pos: usize) -> bool { + Some(pos) == self.clicks[1] && Some(pos) != self.clicks[0] + } +} + +impl Default for MouseClicks { + fn default() -> Self { + Self::new() + } +} + /// Represents a key event. // We use a newtype here because we want to customize Deserialize and Display. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] From 78bfdb680f1356e42a9cf5c7cac7f7d0785cfb65 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:45:18 +0000 Subject: [PATCH 07/23] refactor: rename variables --- helix-view/src/input.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index ca65e74f1..bcc55a078 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -113,12 +113,12 @@ impl MouseClicks { click_type } - fn is_triple_click(&mut self, pos: usize) -> bool { - Some(pos) == self.clicks[0] && Some(pos) == self.clicks[1] + fn is_triple_click(&mut self, char_idx: usize) -> bool { + Some(char_idx) == self.clicks[0] && Some(char_idx) == self.clicks[1] } - fn is_double_click(&mut self, pos: usize) -> bool { - Some(pos) == self.clicks[1] && Some(pos) != self.clicks[0] + fn is_double_click(&mut self, char_idx: usize) -> bool { + Some(char_idx) == self.clicks[1] && Some(char_idx) != self.clicks[0] } } From 64390df1591f415378b2c5e3dc0a1d4fe5db5a97 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:53:00 +0000 Subject: [PATCH 08/23] refactor: extract duplicated logic --- helix-core/src/textobject.rs | 23 ++++++++++++----------- helix-term/src/ui/editor.rs | 13 ++----------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index 1225b70a7..fd0b656d7 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -11,12 +11,7 @@ use crate::syntax::LanguageConfiguration; use crate::Range; use crate::{surround, Syntax}; -pub fn find_word_boundary( - slice: RopeSlice, - mut pos: usize, - direction: Direction, - long: bool, -) -> usize { +fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction, long: bool) -> usize { use CharCategory::{Eol, Whitespace}; let iter = match direction { @@ -73,6 +68,16 @@ impl Display for TextObject { } } +pub fn find_word_boundaries(slice: RopeSlice, pos: usize, is_long: bool) -> (usize, usize) { + let word_start = find_word_boundary(slice, pos, Direction::Backward, is_long); + let word_end = match slice.get_char(pos).map(categorize_char) { + None | Some(CharCategory::Whitespace | CharCategory::Eol) => pos, + _ => find_word_boundary(slice, pos + 1, Direction::Forward, is_long), + }; + + (word_start, word_end) +} + // count doesn't do anything yet pub fn textobject_word( slice: RopeSlice, @@ -83,11 +88,7 @@ pub fn textobject_word( ) -> Range { let pos = range.cursor(slice); - let word_start = find_word_boundary(slice, pos, Direction::Backward, long); - let word_end = match slice.get_char(pos).map(categorize_char) { - None | Some(CharCategory::Whitespace | CharCategory::Eol) => pos, - _ => find_word_boundary(slice, pos + 1, Direction::Forward, long), - }; + let (word_start, word_end) = find_word_boundaries(slice, pos, long); // Special case. if word_start == word_end { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index c05f1acbd..da2273c2c 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -14,13 +14,12 @@ use crate::{ }; use helix_core::{ - chars::CharCategory, diagnostic::NumberOrString, graphemes::{next_grapheme_boundary, prev_grapheme_boundary}, movement::Direction, syntax::{self, HighlightEvent}, text_annotations::TextAnnotations, - textobject::find_word_boundary, + textobject::find_word_boundaries, unicode::width::UnicodeWidthStr, visual_offset_from_block, Change, Position, Range, Selection, Transaction, }; @@ -1200,15 +1199,7 @@ impl EditorView { } } MouseClick::Double => { - let word_start = - find_word_boundary(text, pos, Direction::Backward, false); - let word_end = match text - .get_char(pos) - .map(helix_core::chars::categorize_char) - { - None | Some(CharCategory::Whitespace | CharCategory::Eol) => pos, - _ => find_word_boundary(text, pos + 1, Direction::Forward, false), - }; + let (word_start, word_end) = find_word_boundaries(text, pos, false); Selection::single(word_start, word_end) } From c43074abdbfe32a76ca4cc777105999cd5352228 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:30:07 +0000 Subject: [PATCH 09/23] fix: 4th click on the same spot will reset the selection --- helix-view/src/input.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index bcc55a078..0bc910e3c 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -84,10 +84,14 @@ impl MouseClicks { } } - /// Registers a click for a certain character, and returns the type of this click + /// Registers a click for a certain character index, and returns the type of this click pub fn register_click(&mut self, char_idx: usize) -> MouseClick { let click_type = if self.is_triple_click(char_idx) { - MouseClick::Triple + self.clicks = [None, None]; + self.count = 0; + + return MouseClick::Triple; + // MouseClick::Triple } else if self.is_double_click(char_idx) { MouseClick::Double } else { @@ -107,18 +111,18 @@ impl MouseClicks { self.clicks[0] = self.clicks[1]; self.clicks[1] = Some(char_idx); } - _ => unreachable!("Mouse click count will never exceed 2"), + _ => unreachable!(), }; click_type } fn is_triple_click(&mut self, char_idx: usize) -> bool { - Some(char_idx) == self.clicks[0] && Some(char_idx) == self.clicks[1] + Some(char_idx) == self.clicks[1] && Some(char_idx) == self.clicks[0] } fn is_double_click(&mut self, char_idx: usize) -> bool { - Some(char_idx) == self.clicks[1] && Some(char_idx) != self.clicks[0] + Some(char_idx) == self.clicks[0] && Some(char_idx) != self.clicks[1] } } From a9a1e69f09d6380576cc174450f607cf3bdfdec0 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:32:39 +0000 Subject: [PATCH 10/23] chore: remove comment --- helix-view/src/input.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 0bc910e3c..831043532 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -91,7 +91,6 @@ impl MouseClicks { self.count = 0; return MouseClick::Triple; - // MouseClick::Triple } else if self.is_double_click(char_idx) { MouseClick::Double } else { From a60dc2d08578b7af06d9f1fb2903ec889d225cb0 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:34:06 +0000 Subject: [PATCH 11/23] refactor: reverse && --- helix-view/src/input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 831043532..f73081f81 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -117,7 +117,7 @@ impl MouseClicks { } fn is_triple_click(&mut self, char_idx: usize) -> bool { - Some(char_idx) == self.clicks[1] && Some(char_idx) == self.clicks[0] + Some(char_idx) == self.clicks[0] && Some(char_idx) == self.clicks[1] } fn is_double_click(&mut self, char_idx: usize) -> bool { From 7add058abc930ec81196ddbce1ffbc4bb65069df Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:36:48 +0000 Subject: [PATCH 12/23] chore: add documentation --- helix-view/src/input.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index f73081f81..00af402a4 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -71,8 +71,11 @@ pub struct MouseClicks { } pub enum MouseClick { + /// A single click Single, + /// A click where the same character was pressed 2 times in a row Double, + /// A click where the same character pressed 3 times in a row Triple, } From 7537b0c25b43cb9cf45accdc21577e1a54b9411e Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Mon, 13 Jan 2025 08:40:18 +0000 Subject: [PATCH 13/23] refactor: use u8 for count --- helix-view/src/input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 00af402a4..fab3024fc 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -67,7 +67,7 @@ pub enum MouseButton { #[derive(Debug)] pub struct MouseClicks { clicks: [Option; 2], - count: usize, + count: u8, } pub enum MouseClick { From 6fbe9fe0bf79766e5a2e91a8931b069953ebff20 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:01:39 +0000 Subject: [PATCH 14/23] refactor: variable names --- helix-term/src/ui/editor.rs | 6 +++--- helix-view/src/input.rs | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index da2273c2c..73d5ded95 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1205,10 +1205,10 @@ impl EditorView { } MouseClick::Triple => { let current_line = text.char_to_line(pos); - let from = text.line_to_char(current_line); - let to = text.line_to_char(current_line + 1); + let line_start = text.line_to_char(current_line); + let line_end = text.line_to_char(current_line + 1); - Selection::single(from, to) + Selection::single(line_start, line_end) } }; diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index fab3024fc..76c8250b3 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -61,9 +61,6 @@ pub enum MouseButton { } /// Tracks the character positions where the mouse was last clicked -/// -/// If we double click, select that word -/// If we triple click, select full line #[derive(Debug)] pub struct MouseClicks { clicks: [Option; 2], From 82401d87b71d45830984c46d3af0cf1c32f2b444 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:16:14 +0000 Subject: [PATCH 15/23] fix: double click not registering after the first one --- helix-view/src/input.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 76c8250b3..b5e359653 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -107,8 +107,8 @@ impl MouseClicks { self.count = 2; } 2 => { - self.clicks[0] = self.clicks[1]; - self.clicks[1] = Some(char_idx); + self.clicks[1] = self.clicks[0]; + self.clicks[0] = Some(char_idx); } _ => unreachable!(), }; From 056e2ffbb255653881c4d933a95c0e827b650636 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:06:13 +0000 Subject: [PATCH 16/23] fix: clicking the same char indexes in different views triggered double / triple click --- helix-term/src/ui/editor.rs | 2 +- helix-view/src/input.rs | 32 ++++++++++++++++++++------------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 73d5ded95..6f9188356 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1179,7 +1179,7 @@ impl EditorView { let doc = doc_mut!(editor, &view!(editor, view_id).doc); let text = doc.text().slice(..); - let selection = match editor.mouse_clicks.register_click(pos) { + let selection = match editor.mouse_clicks.register_click(pos, view_id) { MouseClick::Single => { if modifiers == KeyModifiers::ALT { let selection = doc.selection(view_id).clone(); diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index b5e359653..c09a4e474 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -5,6 +5,7 @@ use serde::de::{self, Deserialize, Deserializer}; use std::fmt; pub use crate::keyboard::{KeyCode, KeyModifiers, MediaKeyCode, ModifierKeyCode}; +use crate::ViewId; #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)] pub enum Event { @@ -63,12 +64,12 @@ pub enum MouseButton { /// Tracks the character positions where the mouse was last clicked #[derive(Debug)] pub struct MouseClicks { - clicks: [Option; 2], + clicks: [Option<(usize, ViewId)>; 2], count: u8, } pub enum MouseClick { - /// A single click + /// A click where the pressed character is different to the character previously pressed Single, /// A click where the same character was pressed 2 times in a row Double, @@ -76,6 +77,9 @@ pub enum MouseClick { Triple, } +/// Stores information about the state of the mouse clicks on specific character indexes +/// +/// Stores the positions of the 2 most recently clicked characters impl MouseClicks { pub fn new() -> Self { Self { @@ -85,13 +89,15 @@ impl MouseClicks { } /// Registers a click for a certain character index, and returns the type of this click - pub fn register_click(&mut self, char_idx: usize) -> MouseClick { - let click_type = if self.is_triple_click(char_idx) { + pub fn register_click(&mut self, char_idx: usize, view_id: ViewId) -> MouseClick { + let click_type = if self.is_triple_click(char_idx, view_id) { + // Clicking 4th time on the same character should be the same as clicking for the 1st time + // So we reset the state self.clicks = [None, None]; self.count = 0; return MouseClick::Triple; - } else if self.is_double_click(char_idx) { + } else if self.is_double_click(char_idx, view_id) { MouseClick::Double } else { MouseClick::Single @@ -99,16 +105,16 @@ impl MouseClicks { match self.count { 0 => { - self.clicks[0] = Some(char_idx); + self.clicks[0] = Some((char_idx, view_id)); self.count = 1; } 1 => { - self.clicks[1] = Some(char_idx); + self.clicks[1] = Some((char_idx, view_id)); self.count = 2; } 2 => { self.clicks[1] = self.clicks[0]; - self.clicks[0] = Some(char_idx); + self.clicks[0] = Some((char_idx, view_id)); } _ => unreachable!(), }; @@ -116,12 +122,14 @@ impl MouseClicks { click_type } - fn is_triple_click(&mut self, char_idx: usize) -> bool { - Some(char_idx) == self.clicks[0] && Some(char_idx) == self.clicks[1] + /// Whether the character indexed was pressed 3 times in a row + fn is_triple_click(&mut self, char_idx: usize, view_id: ViewId) -> bool { + Some((char_idx, view_id)) == self.clicks[0] && Some((char_idx, view_id)) == self.clicks[1] } - fn is_double_click(&mut self, char_idx: usize) -> bool { - Some(char_idx) == self.clicks[0] && Some(char_idx) != self.clicks[1] + /// The 2 most recent clicks were on the same character, and the 1 click before that was on a different character + fn is_double_click(&mut self, char_idx: usize, view_id: ViewId) -> bool { + Some((char_idx, view_id)) == self.clicks[0] && Some((char_idx, view_id)) != self.clicks[1] } } From 395f4bf877717ade9cfb16e3e7b0e7126f5fb1a2 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:10:04 +0000 Subject: [PATCH 17/23] docs: document the clicks field --- helix-view/src/input.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index c09a4e474..eee9a998a 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -61,9 +61,14 @@ pub enum MouseButton { Middle, } -/// Tracks the character positions where the mouse was last clicked +/// Tracks the character positions and views where we last saw a mouse click #[derive(Debug)] pub struct MouseClicks { + /// The last 2 clicks on specific characters in the editor: + /// (character index clicked, view id) + // We store the view id to ensure that if we click on + // the 3rd character in view #1 and 3rd character in view #2, + // it won't register as a double click. clicks: [Option<(usize, ViewId)>; 2], count: u8, } From 7694517d0f1eb9b67aa0bac49b5920112a6e6033 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:12:05 +0000 Subject: [PATCH 18/23] docs: make docs more informative --- helix-view/src/input.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index eee9a998a..cd8071d24 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -127,12 +127,12 @@ impl MouseClicks { click_type } - /// Whether the character indexed was pressed 3 times in a row + /// If we click this character, would that be a triple click? fn is_triple_click(&mut self, char_idx: usize, view_id: ViewId) -> bool { Some((char_idx, view_id)) == self.clicks[0] && Some((char_idx, view_id)) == self.clicks[1] } - /// The 2 most recent clicks were on the same character, and the 1 click before that was on a different character + /// If we click this character, would that be a double click? fn is_double_click(&mut self, char_idx: usize, view_id: ViewId) -> bool { Some((char_idx, view_id)) == self.clicks[0] && Some((char_idx, view_id)) != self.clicks[1] } From c4672ed902067948e545a77346ba7784cc5c4fd2 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:59:53 +0000 Subject: [PATCH 19/23] test: add tests for MouseClicks --- helix-view/src/input.rs | 76 +++++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index cd8071d24..a2ae84fbe 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -73,6 +73,7 @@ pub struct MouseClicks { count: u8, } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum MouseClick { /// A click where the pressed character is different to the character previously pressed Single, @@ -94,15 +95,15 @@ impl MouseClicks { } /// Registers a click for a certain character index, and returns the type of this click - pub fn register_click(&mut self, char_idx: usize, view_id: ViewId) -> MouseClick { - let click_type = if self.is_triple_click(char_idx, view_id) { + pub fn register_click(&mut self, click: usize, view_id: ViewId) -> MouseClick { + let click_type = if self.is_triple_click(click, view_id) { // Clicking 4th time on the same character should be the same as clicking for the 1st time // So we reset the state self.clicks = [None, None]; self.count = 0; return MouseClick::Triple; - } else if self.is_double_click(char_idx, view_id) { + } else if self.is_double_click(click, view_id) { MouseClick::Double } else { MouseClick::Single @@ -110,16 +111,16 @@ impl MouseClicks { match self.count { 0 => { - self.clicks[0] = Some((char_idx, view_id)); + self.clicks[0] = Some((click, view_id)); self.count = 1; } 1 => { - self.clicks[1] = Some((char_idx, view_id)); + self.clicks[1] = Some((click, view_id)); self.count = 2; } 2 => { self.clicks[1] = self.clicks[0]; - self.clicks[0] = Some((char_idx, view_id)); + self.clicks[0] = Some((click, view_id)); } _ => unreachable!(), }; @@ -128,13 +129,18 @@ impl MouseClicks { } /// If we click this character, would that be a triple click? - fn is_triple_click(&mut self, char_idx: usize, view_id: ViewId) -> bool { - Some((char_idx, view_id)) == self.clicks[0] && Some((char_idx, view_id)) == self.clicks[1] + fn is_triple_click(&mut self, click: usize, view_id: ViewId) -> bool { + Some((click, view_id)) == self.clicks[0] && Some((click, view_id)) == self.clicks[1] } /// If we click this character, would that be a double click? - fn is_double_click(&mut self, char_idx: usize, view_id: ViewId) -> bool { - Some((char_idx, view_id)) == self.clicks[0] && Some((char_idx, view_id)) != self.clicks[1] + fn is_double_click(&mut self, click: usize, view_id: ViewId) -> bool { + dbg!(self.clicks, click, view_id); + Some((click, view_id)) == self.clicks[0] + && self.clicks[1].map_or(true, |(prev_click, prev_view_id)| { + (click != prev_click && prev_view_id == view_id) + || (click == prev_click && prev_view_id != view_id) + }) } } @@ -690,6 +696,8 @@ pub fn parse_macro(keys_str: &str) -> anyhow::Result> { #[cfg(test)] mod test { + use slotmap::HopSlotMap; + use super::*; #[test] @@ -1046,4 +1054,52 @@ mod test { assert!(parse_macro("abc>123").is_err()); assert!(parse_macro("wd").is_err()); } + + #[test] + fn clicking_4th_time_resets_mouse_clicks() { + let mut mouse_clicks = MouseClicks::new(); + let view = ViewId::default(); + + assert_eq!(mouse_clicks.register_click(4, view), MouseClick::Single); + assert_eq!(mouse_clicks.register_click(4, view), MouseClick::Double); + assert_eq!(mouse_clicks.register_click(4, view), MouseClick::Triple); + + assert_eq!(mouse_clicks.register_click(4, view), MouseClick::Single); + } + + #[test] + fn clicking_different_characters_resets_mouse_clicks() { + let mut mouse_clicks = MouseClicks::new(); + let view = ViewId::default(); + + assert_eq!(mouse_clicks.register_click(4, view), MouseClick::Single); + assert_eq!(mouse_clicks.register_click(4, view), MouseClick::Double); + + assert_eq!(mouse_clicks.register_click(8, view), MouseClick::Single); + + assert_eq!(mouse_clicks.register_click(1, view), MouseClick::Single); + assert_eq!(mouse_clicks.register_click(1, view), MouseClick::Double); + assert_eq!(mouse_clicks.register_click(1, view), MouseClick::Triple); + } + + #[test] + fn switching_views_resets_mouse_clicks() { + let mut mouse_clicks = MouseClicks::new(); + let mut view_ids: HopSlotMap = HopSlotMap::with_key(); + let view1 = view_ids.insert(()); + let view2 = view_ids.insert(()); + + assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); + + assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Single); + + assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); + + assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Single); + assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Double); + + // assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); + // assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Double); + // assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Triple); + } } From 931b8d9a31b2f2aee4a172f1716c655b2e85e495 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:23:27 +0000 Subject: [PATCH 20/23] refactor: simplify internal MouseClicks code --- helix-view/src/input.rs | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index a2ae84fbe..94c81b5cc 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -70,7 +70,6 @@ pub struct MouseClicks { // the 3rd character in view #1 and 3rd character in view #2, // it won't register as a double click. clicks: [Option<(usize, ViewId)>; 2], - count: u8, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -90,17 +89,20 @@ impl MouseClicks { pub fn new() -> Self { Self { clicks: [None, None], - count: 0, } } + fn push(&mut self, click: usize, view_id: ViewId) { + self.clicks[1] = self.clicks[0]; + self.clicks[0] = Some((click, view_id)); + } + /// Registers a click for a certain character index, and returns the type of this click pub fn register_click(&mut self, click: usize, view_id: ViewId) -> MouseClick { let click_type = if self.is_triple_click(click, view_id) { // Clicking 4th time on the same character should be the same as clicking for the 1st time // So we reset the state self.clicks = [None, None]; - self.count = 0; return MouseClick::Triple; } else if self.is_double_click(click, view_id) { @@ -109,21 +111,7 @@ impl MouseClicks { MouseClick::Single }; - match self.count { - 0 => { - self.clicks[0] = Some((click, view_id)); - self.count = 1; - } - 1 => { - self.clicks[1] = Some((click, view_id)); - self.count = 2; - } - 2 => { - self.clicks[1] = self.clicks[0]; - self.clicks[0] = Some((click, view_id)); - } - _ => unreachable!(), - }; + self.push(click, view_id); click_type } @@ -135,11 +123,9 @@ impl MouseClicks { /// If we click this character, would that be a double click? fn is_double_click(&mut self, click: usize, view_id: ViewId) -> bool { - dbg!(self.clicks, click, view_id); Some((click, view_id)) == self.clicks[0] && self.clicks[1].map_or(true, |(prev_click, prev_view_id)| { - (click != prev_click && prev_view_id == view_id) - || (click == prev_click && prev_view_id != view_id) + !(click == prev_click && prev_view_id == view_id) }) } } @@ -696,8 +682,6 @@ pub fn parse_macro(keys_str: &str) -> anyhow::Result> { #[cfg(test)] mod test { - use slotmap::HopSlotMap; - use super::*; #[test] @@ -1084,19 +1068,24 @@ mod test { #[test] fn switching_views_resets_mouse_clicks() { + use slotmap::HopSlotMap; + let mut mouse_clicks = MouseClicks::new(); let mut view_ids: HopSlotMap = HopSlotMap::with_key(); let view1 = view_ids.insert(()); let view2 = view_ids.insert(()); + dbg!(&mouse_clicks, view1); assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); + dbg!(&mouse_clicks, view2); assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Single); + dbg!(&mouse_clicks, view1); assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); - assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Single); - assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Double); + // assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Single); + // assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Double); // assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); // assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Double); From 0d21faf7124ee8096b31c5e5682d3b1e01dda3c9 Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:26:33 +0000 Subject: [PATCH 21/23] docs: better description of MouseClicks --- helix-view/src/input.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 94c81b5cc..d17e1b3e4 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -82,9 +82,8 @@ pub enum MouseClick { Triple, } -/// Stores information about the state of the mouse clicks on specific character indexes -/// -/// Stores the positions of the 2 most recently clicked characters +/// A fixed-size queue of length 2, storing the most recently clicked characters +/// as well as the views for which they were clicked. impl MouseClicks { pub fn new() -> Self { Self { @@ -92,7 +91,8 @@ impl MouseClicks { } } - fn push(&mut self, click: usize, view_id: ViewId) { + /// Add a click to the beginning of the queue, discarding the last click + fn insert(&mut self, click: usize, view_id: ViewId) { self.clicks[1] = self.clicks[0]; self.clicks[0] = Some((click, view_id)); } @@ -111,7 +111,7 @@ impl MouseClicks { MouseClick::Single }; - self.push(click, view_id); + self.insert(click, view_id); click_type } From 26c230cea089c268357b72b65bee52082e9210ee Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:28:56 +0000 Subject: [PATCH 22/23] fix: failing tests --- helix-view/src/input.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index d17e1b3e4..e9ea8ce2b 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -1075,20 +1075,17 @@ mod test { let view1 = view_ids.insert(()); let view2 = view_ids.insert(()); - dbg!(&mouse_clicks, view1); assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); - dbg!(&mouse_clicks, view2); assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Single); - dbg!(&mouse_clicks, view1); assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); - // assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Single); - // assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Double); + assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Single); + assert_eq!(mouse_clicks.register_click(4, view2), MouseClick::Double); - // assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); - // assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Double); - // assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Triple); + assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Single); + assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Double); + assert_eq!(mouse_clicks.register_click(4, view1), MouseClick::Triple); } } From 6576e923336fadd38c691fd8e068e35a2c37ae4c Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:44:31 +0000 Subject: [PATCH 23/23] refactor: do not use unnecessary type hint --- helix-view/src/input.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index e9ea8ce2b..dc05ea78f 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -1068,10 +1068,8 @@ mod test { #[test] fn switching_views_resets_mouse_clicks() { - use slotmap::HopSlotMap; - let mut mouse_clicks = MouseClicks::new(); - let mut view_ids: HopSlotMap = HopSlotMap::with_key(); + let mut view_ids = slotmap::HopSlotMap::with_key(); let view1 = view_ids.insert(()); let view2 = view_ids.insert(());