diff --git a/crates/ferrite-core/src/buffer.rs b/crates/ferrite-core/src/buffer.rs index c0982b0..dcc5da6 100644 --- a/crates/ferrite-core/src/buffer.rs +++ b/crates/ferrite-core/src/buffer.rs @@ -826,6 +826,31 @@ impl Buffer { } } + fn select_word_raw(&mut self, view_id: ViewId, cursor_idx: usize) { + let mut start_byte_idx = self.views[view_id].cursors[cursor_idx].position; + loop { + let new_idx = self.rope.prev_grapheme_boundary_byte(start_byte_idx); + let grapheme = self.rope.byte_slice(new_idx..start_byte_idx); + if new_idx == start_byte_idx || !grapheme.is_word_char() { + break; + } + start_byte_idx = new_idx; + } + + let mut end_byte_idx = self.views[view_id].cursors[cursor_idx].position; + loop { + let new_idx = self.rope.next_grapheme_boundary_byte(end_byte_idx); + let grapheme = self.rope.byte_slice(end_byte_idx..new_idx); + if new_idx == end_byte_idx || !grapheme.is_word_char() { + break; + } + end_byte_idx = new_idx; + } + + self.views[view_id].cursors[cursor_idx].position = end_byte_idx; + self.views[view_id].cursors[cursor_idx].anchor = start_byte_idx; + } + pub fn select_word(&mut self, view_id: ViewId) { self.views[view_id].coalesce_cursors(); let has_selection = self.views[view_id] @@ -866,28 +891,7 @@ impl Buffer { } } else { for i in 0..self.views[view_id].cursors.len() { - let mut start_byte_idx = self.views[view_id].cursors[i].position; - loop { - let new_idx = self.rope.prev_grapheme_boundary_byte(start_byte_idx); - let grapheme = self.rope.byte_slice(new_idx..start_byte_idx); - if new_idx == start_byte_idx || !grapheme.is_word_char() { - break; - } - start_byte_idx = new_idx; - } - - let mut end_byte_idx = self.views[view_id].cursors[i].position; - loop { - let new_idx = self.rope.next_grapheme_boundary_byte(end_byte_idx); - let grapheme = self.rope.byte_slice(end_byte_idx..new_idx); - if new_idx == end_byte_idx || !grapheme.is_word_char() { - break; - } - end_byte_idx = new_idx; - } - - self.views[view_id].cursors[i].position = end_byte_idx; - self.views[view_id].cursors[i].anchor = start_byte_idx; + self.select_word_raw(view_id, i); } } @@ -1802,19 +1806,23 @@ impl Buffer { } } + fn select_line_raw(&mut self, view_id: ViewId, cursor_idx: usize) { + { + let line_idx = self.cursor_line_idx(view_id, cursor_idx); + let line_start = self.rope.line_to_byte(line_idx + 1); + self.views[view_id].cursors[cursor_idx].position = line_start; + } + + { + let line_idx = self.anchor_line_idx(view_id, cursor_idx); + let line_start = self.rope.line_to_byte(line_idx); + self.views[view_id].cursors[cursor_idx].anchor = line_start; + } + } + pub fn select_line(&mut self, view_id: ViewId) { for i in 0..self.views[view_id].cursors.len() { - { - let line_idx = self.cursor_line_idx(view_id, i); - let line_start = self.rope.line_to_byte(line_idx + 1); - self.views[view_id].cursors[i].position = line_start; - } - - { - let line_idx = self.anchor_line_idx(view_id, i); - let line_start = self.rope.line_to_byte(line_idx); - self.views[view_id].cursors[i].anchor = line_start; - } + self.select_line_raw(view_id, i); } self.views[view_id].coalesce_cursors(); @@ -2090,9 +2098,18 @@ impl Buffer { } } - pub fn handle_click(&mut self, view_id: ViewId, col: usize, line: usize) { - self.views[view_id].cursors.clear(); - self.set_cursor_pos(view_id, 0, col, line); + pub fn handle_click(&mut self, view_id: ViewId, spawn_cursor: bool, col: usize, line: usize) { + let cursor_idx = if spawn_cursor { + self.views[view_id].cursors.push(Cursor::default()); + self.views[view_id].cursors.len() - 1 + } else { + self.views[view_id].cursors.clear(); + 0 + }; + self.set_cursor_pos(view_id, cursor_idx, col, line); + self.views[view_id].cursors[cursor_idx].affinity = + self.cursor_grapheme_column(view_id, cursor_idx); + let click_point = Point::new(col, line); let now = Instant::now(); if now.duration_since(self.views[view_id].last_click) < Duration::from_millis(500) @@ -2100,11 +2117,15 @@ impl Buffer { { self.views[view_id].clicks_in_a_row += 1; if self.views[view_id].clicks_in_a_row == 1 { - self.select_word(view_id); - self.copy_selection_to_primary(view_id); + self.select_word_raw(view_id, cursor_idx); + if !spawn_cursor { + self.copy_selection_to_primary(view_id); + } } else if self.views[view_id].clicks_in_a_row == 2 { - self.select_line(view_id); - self.copy_selection_to_primary(view_id); + self.select_line_raw(view_id, cursor_idx); + if !spawn_cursor { + self.copy_selection_to_primary(view_id); + } } else { self.views[view_id].clicks_in_a_row = 0; } @@ -2113,7 +2134,7 @@ impl Buffer { } self.views[view_id].last_click = now; self.views[view_id].last_click_pos = click_point; - self.update_affinity(view_id); + self.views[view_id].coalesce_cursors(); } pub fn set_cursor_pos( diff --git a/crates/ferrite-core/src/buffer/input.rs b/crates/ferrite-core/src/buffer/input.rs index 403c490..4142cdf 100644 --- a/crates/ferrite-core/src/buffer/input.rs +++ b/crates/ferrite-core/src/buffer/input.rs @@ -43,7 +43,9 @@ impl Buffer { Tab { back } if !self.read_only => self.tab(view_id, back), VerticalScroll(distance) => self.vertical_scroll(view_id, distance), Escape => self.escape(view_id), - ClickCell(col, line) => self.handle_click(view_id, col, line), + ClickCell(spawn_cursor, col, line) => { + self.handle_click(view_id, spawn_cursor, col, line) + } SelectArea { cursor, anchor } => self.select_area(view_id, cursor, anchor, true), NextMatch => self.next_match(view_id), PrevMatch => self.prev_match(view_id), diff --git a/crates/ferrite-core/src/cmd.rs b/crates/ferrite-core/src/cmd.rs index c0438f4..6242e0d 100644 --- a/crates/ferrite-core/src/cmd.rs +++ b/crates/ferrite-core/src/cmd.rs @@ -95,7 +95,7 @@ pub enum Cmd { BackspaceWord, Delete, DeleteWord, - ClickCell(usize, usize), + ClickCell(bool, usize, usize), SelectArea { cursor: Point, anchor: Point, @@ -178,7 +178,7 @@ impl Cmd { BackspaceWord => "Backspace word", Delete => "Delete", DeleteWord => "Delete word", - ClickCell(_, _) => "Set cursor pos", + ClickCell(..) => "Set cursor pos", SelectArea { .. } => "Select area", PromptGoto => "Goto", Home { .. } => "Home", diff --git a/crates/ferrite-gui/src/lib.rs b/crates/ferrite-gui/src/lib.rs index 8743b5e..017a299 100644 --- a/crates/ferrite-gui/src/lib.rs +++ b/crates/ferrite-gui/src/lib.rs @@ -29,7 +29,7 @@ use winit::{ dpi::PhysicalPosition, event::{ElementState, Event, MouseButton, MouseScrollDelta, WindowEvent}, event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget}, - keyboard::Key, + keyboard::{Key, ModifiersState, NamedKey}, window::{CursorIcon, Window, WindowBuilder}, }; @@ -291,53 +291,51 @@ impl GuiApp { } } } + WindowEvent::ModifiersChanged(modifiers) => { + let modifiers = modifiers.state(); + self.modifiers.set( + KeyModifiers::CONTROL, + modifiers.contains(ModifiersState::CONTROL), + ); + self.modifiers + .set(KeyModifiers::ALT, modifiers.contains(ModifiersState::ALT)); + self.modifiers.set( + KeyModifiers::SHIFT, + modifiers.contains(ModifiersState::SHIFT), + ); + } WindowEvent::KeyboardInput { event, .. } => { tracing::trace!("{:?}", event); let mut control_flow = self.control_flow; - if !event.state.is_pressed() { - if let Key::Named(key) = event.logical_key { - match key { - winit::keyboard::NamedKey::Control => { - self.modifiers.remove(KeyModifiers::CONTROL) - } - winit::keyboard::NamedKey::Alt => { - self.modifiers.remove(KeyModifiers::ALT) - } - winit::keyboard::NamedKey::Shift => { - self.modifiers.remove(KeyModifiers::SHIFT) - } - winit::keyboard::NamedKey::Super => { - self.modifiers.remove(KeyModifiers::SUPER) - } - winit::keyboard::NamedKey::Hyper => { - self.modifiers.remove(KeyModifiers::HYPER) - } - winit::keyboard::NamedKey::Meta => { - self.modifiers.remove(KeyModifiers::META) - } - _ => (), + + if let Key::Named(key) = event.logical_key { + match key { + NamedKey::Super => { + self.modifiers + .set(KeyModifiers::SUPER, event.state.is_pressed()); + return; + } + NamedKey::Hyper => { + self.modifiers + .set(KeyModifiers::HYPER, event.state.is_pressed()); + return; } + NamedKey::Meta => { + self.modifiers + .set(KeyModifiers::META, event.state.is_pressed()); + return; + } + _ => (), } + } + + if !event.state.is_pressed() { return; } let cmd = 'block: { match event.logical_key { Key::Named(key) => { - let modifier = match key { - winit::keyboard::NamedKey::Control => KeyModifiers::CONTROL, - winit::keyboard::NamedKey::Alt => KeyModifiers::ALT, - winit::keyboard::NamedKey::Shift => KeyModifiers::SHIFT, - winit::keyboard::NamedKey::Super => KeyModifiers::SUPER, - winit::keyboard::NamedKey::Hyper => KeyModifiers::HYPER, - winit::keyboard::NamedKey::Meta => KeyModifiers::META, - _ => KeyModifiers::NONE, - }; - if !modifier.is_empty() { - self.modifiers |= modifier; - return; - } - if let Some(keycode) = convert_keycode(key) { let cmd = keymap::get_command_from_input( keycode, @@ -492,7 +490,11 @@ impl GuiApp { .saturating_sub(left_offset); let line = (line as usize + buffer.line_pos(view_id)) .saturating_sub(pane_rect.y); - break 'block Some(Cmd::ClickCell(column, line)); + break 'block Some(Cmd::ClickCell( + self.modifiers.contains(KeyModifiers::ALT), + column, + line, + )); } } } diff --git a/crates/ferrite-term/src/lib.rs b/crates/ferrite-term/src/lib.rs index 777324b..f299db7 100644 --- a/crates/ferrite-term/src/lib.rs +++ b/crates/ferrite-term/src/lib.rs @@ -213,7 +213,7 @@ impl TermApp { .saturating_sub(left_offset); let line = (event.row as usize + buffer.line_pos(view_id)) .saturating_sub(pane_rect.y); - break 'block Some(Cmd::ClickCell(column, line)); + break 'block Some(Cmd::ClickCell(false, column, line)); } } }