Skip to content

Commit

Permalink
Added very basic multi-cursor infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
curlpipe committed Nov 3, 2024
1 parent 5739d6a commit 2e5eb13
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 78 deletions.
24 changes: 24 additions & 0 deletions kaolinite/src/document/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ impl Document {

/// Move up by 1 page
pub fn move_page_up(&mut self) {
self.clear_cursors();
// Set x to 0
self.cursor.loc.x = 0;
self.char_ptr = 0;
Expand All @@ -201,6 +202,7 @@ impl Document {

/// Move down by 1 page
pub fn move_page_down(&mut self) {
self.clear_cursors();
// Set x to 0
self.cursor.loc.x = 0;
self.char_ptr = 0;
Expand Down Expand Up @@ -244,6 +246,7 @@ impl Document {

/// Function to select to a specific x position
pub fn select_to_x(&mut self, x: usize) {
self.clear_cursors();
let line = self.line(self.loc().y).unwrap_or_default();
// If the move position is out of bounds, move to the end of the line
if line.chars().count() < x {
Expand All @@ -269,6 +272,7 @@ impl Document {

/// Function to select to a specific y position
pub fn select_to_y(&mut self, y: usize) {
self.clear_cursors();
// Bounds checking
if self.loc().y != y && y <= self.len_lines() {
self.cursor.loc.y = y;
Expand Down Expand Up @@ -399,4 +403,24 @@ impl Document {
pub fn cancel_selection(&mut self) {
self.cursor.selection_end = self.cursor.loc;
}

/// Create a new alternative cursor
pub fn new_cursor(&mut self, loc: Loc) {
if let Some(idx) = self.has_cursor(loc) {
self.secondary_cursors.remove(idx);
} else if self.out_of_range(loc.x, loc.y).is_ok() {
self.secondary_cursors.push(loc);
}
}

/// Clear all secondary cursors
pub fn clear_cursors(&mut self) {
self.secondary_cursors.clear();
}

/// Determine if there is a secondary cursor at a certain position
#[must_use]
pub fn has_cursor(&self, loc: Loc) -> Option<usize> {
self.secondary_cursors.iter().position(|c| *c == loc)
}
}
2 changes: 2 additions & 0 deletions kaolinite/src/document/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ impl Document {
eol: false,
read_only: false,
},
secondary_cursors: vec![],
}
}

Expand Down Expand Up @@ -77,6 +78,7 @@ impl Document {
tab_width: 4,
old_cursor: 0,
in_redo: false,
secondary_cursors: vec![],
})
}

Expand Down
2 changes: 2 additions & 0 deletions kaolinite/src/document/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub struct Document {
pub in_redo: bool,
/// The number of spaces a tab should be rendered as
pub tab_width: usize,
/// Secondary cursor (for multi-cursors)
pub secondary_cursors: Vec<Loc>,
}

impl Document {
Expand Down
22 changes: 22 additions & 0 deletions src/editor/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ impl Editor {
for c in text.chars() {
let at_x = self.doc().character_idx(&Loc { y: idx, x: x_pos });
let is_selected = self.doc().is_loc_selected(Loc { y: idx, x: at_x });
// Render the correct colour
if is_selected {
if cache_bg != selection_bg {
display!(self, selection_bg);
Expand All @@ -168,7 +169,28 @@ impl Editor {
cache_fg = colour;
}
}
// Render multi-cursors
let multi_cursor_here =
self.doc().has_cursor(Loc { y: idx, x: at_x }).is_some();
if multi_cursor_here {
display!(
self,
SetAttribute(Attribute::Underlined),
Bg(Color::White),
Fg(Color::Black)
);
}
// Render the character
display!(self, c);
// Reset any multi-cursor display
if multi_cursor_here {
display!(
self,
SetAttribute(Attribute::NoUnderline),
cache_bg,
cache_fg
);
}
x_pos += 1;
}
}
Expand Down
169 changes: 91 additions & 78 deletions src/editor/mouse.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::config;
/// For handling mouse events
use crate::config;
use crate::ui::size;
use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
use crossterm::event::{KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use kaolinite::{utils::width, Loc};
use mlua::Lua;
use std::time::{Duration, Instant};
Expand Down Expand Up @@ -47,95 +47,108 @@ impl Editor {

/// Handles a mouse event (dragging / clicking)
pub fn handle_mouse_event(&mut self, lua: &Lua, event: MouseEvent) {
match event.kind {
// Single click
MouseEventKind::Down(MouseButton::Left) => {
// Determine if there has been a click within 500ms
if let Some((time, last_event)) = self.last_click {
let now = Instant::now();
let short_period = now.duration_since(time) <= Duration::from_millis(500);
let same_location =
last_event.column == event.column && last_event.row == event.row;
if short_period && same_location {
self.in_dbl_click = true;
self.handle_double_click(lua, event);
return;
match event.modifiers {
KeyModifiers::NONE => match event.kind {
// Single click
MouseEventKind::Down(MouseButton::Left) => {
// Determine if there has been a click within 500ms
if let Some((time, last_event)) = self.last_click {
let now = Instant::now();
let short_period = now.duration_since(time) <= Duration::from_millis(500);
let same_location =
last_event.column == event.column && last_event.row == event.row;
if short_period && same_location {
self.in_dbl_click = true;
self.handle_double_click(lua, event);
return;
}
}
}
match self.find_mouse_location(lua, event) {
MouseLocation::File(mut loc) => {
loc.x = self.doc_mut().character_idx(&loc);
self.doc_mut().move_to(&loc);
self.doc_mut().old_cursor = self.doc().loc().x;
match self.find_mouse_location(lua, event) {
MouseLocation::File(mut loc) => {
loc.x = self.doc_mut().character_idx(&loc);
self.doc_mut().move_to(&loc);
self.doc_mut().old_cursor = self.doc().loc().x;
}
MouseLocation::Tabs(i) => {
self.ptr = i;
self.update_cwd();
}
MouseLocation::Out => (),
}
MouseLocation::Tabs(i) => {
self.ptr = i;
self.update_cwd();
}
MouseEventKind::Down(MouseButton::Right) => {
// Select the current line
if let MouseLocation::File(loc) = self.find_mouse_location(lua, event) {
self.doc_mut().select_line_at(loc.y);
}
MouseLocation::Out => (),
}
}
MouseEventKind::Down(MouseButton::Right) => {
// Select the current line
if let MouseLocation::File(loc) = self.find_mouse_location(lua, event) {
self.doc_mut().select_line_at(loc.y);
// Double click detection
MouseEventKind::Up(MouseButton::Left) => {
self.in_dbl_click = false;
let now = Instant::now();
// Register this click as having happened
self.last_click = Some((now, event));
}
}
// Double click detection
MouseEventKind::Up(MouseButton::Left) => {
self.in_dbl_click = false;
let now = Instant::now();
// Register this click as having happened
self.last_click = Some((now, event));
}
// Mouse drag
MouseEventKind::Drag(MouseButton::Left) => match self.find_mouse_location(lua, event) {
MouseLocation::File(mut loc) => {
loc.x = self.doc_mut().character_idx(&loc);
if self.in_dbl_click {
if loc.x >= self.doc().cursor.selection_end.x {
// Find boundary of next word
let next = self.doc().next_word_close(loc);
self.doc_mut().select_to(&Loc { x: next, y: loc.y });
} else {
// Find boundary of previous word
let next = self.doc().prev_word_close(loc);
self.doc_mut().select_to(&Loc { x: next, y: loc.y });
// Mouse drag
MouseEventKind::Drag(MouseButton::Left) => {
match self.find_mouse_location(lua, event) {
MouseLocation::File(mut loc) => {
loc.x = self.doc_mut().character_idx(&loc);
if self.in_dbl_click {
if loc.x >= self.doc().cursor.selection_end.x {
// Find boundary of next word
let next = self.doc().next_word_close(loc);
self.doc_mut().select_to(&Loc { x: next, y: loc.y });
} else {
// Find boundary of previous word
let next = self.doc().prev_word_close(loc);
self.doc_mut().select_to(&Loc { x: next, y: loc.y });
}
} else {
loc.x = self.doc_mut().character_idx(&loc);
self.doc_mut().select_to(&loc);
}
}
} else {
loc.x = self.doc_mut().character_idx(&loc);
self.doc_mut().select_to(&loc);
MouseLocation::Tabs(_) | MouseLocation::Out => (),
}
}
MouseLocation::Tabs(_) | MouseLocation::Out => (),
},
MouseEventKind::Drag(MouseButton::Right) => {
match self.find_mouse_location(lua, event) {
MouseLocation::File(mut loc) => {
loc.x = self.doc_mut().character_idx(&loc);
self.doc_mut().select_to_y(loc.y);
MouseEventKind::Drag(MouseButton::Right) => {
match self.find_mouse_location(lua, event) {
MouseLocation::File(mut loc) => {
loc.x = self.doc_mut().character_idx(&loc);
self.doc_mut().select_to_y(loc.y);
}
MouseLocation::Tabs(_) | MouseLocation::Out => (),
}
MouseLocation::Tabs(_) | MouseLocation::Out => (),
}
}
// Mouse scroll behaviour
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
if let MouseLocation::File(_) = self.find_mouse_location(lua, event) {
let scroll_amount = config!(self.config, terminal).scroll_amount;
for _ in 0..scroll_amount {
if event.kind == MouseEventKind::ScrollDown {
self.doc_mut().scroll_down();
} else {
self.doc_mut().scroll_up();
// Mouse scroll behaviour
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
if let MouseLocation::File(_) = self.find_mouse_location(lua, event) {
let scroll_amount = config!(self.config, terminal).scroll_amount;
for _ in 0..scroll_amount {
if event.kind == MouseEventKind::ScrollDown {
self.doc_mut().scroll_down();
} else {
self.doc_mut().scroll_up();
}
}
}
}
}
MouseEventKind::ScrollLeft => {
self.doc_mut().move_left();
}
MouseEventKind::ScrollRight => {
self.doc_mut().move_right();
MouseEventKind::ScrollLeft => {
self.doc_mut().move_left();
}
MouseEventKind::ScrollRight => {
self.doc_mut().move_right();
}
_ => (),
},
// Multi cursor behaviour
KeyModifiers::CONTROL => {
if let MouseEventKind::Down(MouseButton::Left) = event.kind {
if let MouseLocation::File(loc) = self.find_mouse_location(lua, event) {
self.doc_mut().new_cursor(loc);
}
}
}
_ => (),
}
Expand Down

0 comments on commit 2e5eb13

Please sign in to comment.