From cbd567d2387d1d9803c8f056fab12e66fcf8f044 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 28 Nov 2023 10:42:50 -0700 Subject: [PATCH] Support line selection --- Cargo.toml | 2 +- examples/editor-libcosmic/src/main.rs | 9 +- src/edit/editor.rs | 142 ++++++++------------------ src/edit/mod.rs | 45 +++++++- src/edit/syntect.rs | 10 +- src/edit/vi.rs | 52 +++++----- 6 files changed, 118 insertions(+), 142 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ddb78a92f9..e76caaecf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ fontdb = { version = "0.16.0", default-features = false } hashbrown = { version = "0.14.1", optional = true, default-features = false } libm = "0.2.8" log = "0.4.20" -modit = { version = "0.1.0", optional = true } +modit = { version = "0.1.1", optional = true } rangemap = "1.4.0" rustc-hash = { version = "1.1.0", default-features = false } rustybuzz = { version = "0.11.0", default-features = false, features = ["libm"] } diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index 84f27d2316..18ee393b4a 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -383,13 +383,8 @@ fn update_attrs(editor: &mut T, attrs: Attrs) { fn update_alignment(editor: &mut T, align: Align) { let current_line = editor.cursor().line; - if let Some(select) = editor.select_opt() { - let (start, end) = match select.line.cmp(¤t_line) { - std::cmp::Ordering::Greater => (current_line, select.line), - std::cmp::Ordering::Less => (select.line, current_line), - std::cmp::Ordering::Equal => (current_line, current_line), - }; - if let Some(lines) = editor.buffer_mut().lines.get_mut(start..=end) { + if let Some((start, end)) = editor.selection_bounds() { + if let Some(lines) = editor.buffer_mut().lines.get_mut(start.line..=end.line) { for line in lines.iter_mut() { line.set_align(Some(align)); } diff --git a/src/edit/editor.rs b/src/edit/editor.rs index fe026ed806..93f6c281bd 100644 --- a/src/edit/editor.rs +++ b/src/edit/editor.rs @@ -2,17 +2,14 @@ #[cfg(not(feature = "std"))] use alloc::string::{String, ToString}; -use core::{ - cmp::{self, Ordering}, - iter::once, -}; +use core::{cmp, iter::once}; use unicode_segmentation::UnicodeSegmentation; #[cfg(feature = "swash")] use crate::Color; use crate::{ Action, Affinity, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem, - LayoutCursor, Shaping, + LayoutCursor, Selection, Shaping, }; /// A wrapper of [`Buffer`] for easy editing @@ -21,7 +18,7 @@ pub struct Editor { buffer: Buffer, cursor: Cursor, cursor_x_opt: Option, - select_opt: Option, + selection: Selection, cursor_moved: bool, auto_indent: bool, tab_width: u16, @@ -35,7 +32,7 @@ impl Editor { buffer, cursor: Cursor::default(), cursor_x_opt: None, - select_opt: None, + selection: Selection::None, cursor_moved: false, auto_indent: false, tab_width: 4, @@ -249,13 +246,13 @@ impl Edit for Editor { } } - fn select_opt(&self) -> Option { - self.select_opt + fn selection(&self) -> Selection { + self.selection } - fn set_select_opt(&mut self, select_opt: Option) { - if self.select_opt != select_opt { - self.select_opt = select_opt; + fn set_selection(&mut self, selection: Selection) { + if self.selection != selection { + self.selection = selection; self.buffer.set_redraw(true); } } @@ -293,21 +290,7 @@ impl Edit for Editor { } fn copy_selection(&self) -> Option { - let select = self.select_opt?; - - let (start, end) = match select.line.cmp(&self.cursor.line) { - cmp::Ordering::Greater => (self.cursor, select), - cmp::Ordering::Less => (select, self.cursor), - cmp::Ordering::Equal => { - /* select.line == self.cursor.line */ - if select.index < self.cursor.index { - (select, self.cursor) - } else { - /* select.index >= self.cursor.index */ - (self.cursor, select) - } - } - }; + let (start, end) = self.selection_bounds()?; let mut selection = String::new(); // Take the selection from the first line @@ -337,25 +320,11 @@ impl Edit for Editor { } fn delete_selection(&mut self) -> bool { - let select = match self.select_opt.take() { + let (start, end) = match self.selection_bounds() { Some(some) => some, None => return false, }; - let (start, end) = match select.line.cmp(&self.cursor.line) { - cmp::Ordering::Greater => (self.cursor, select), - cmp::Ordering::Less => (select, self.cursor), - cmp::Ordering::Equal => { - /* select.line == self.cursor.line */ - if select.index < self.cursor.index { - (select, self.cursor) - } else { - /* select.index >= self.cursor.index */ - (self.cursor, select) - } - } - }; - // Reset cursor to start of selection self.cursor = start; @@ -575,23 +544,25 @@ impl Edit for Editor { // TODO more efficient let lines = px / self.buffer.metrics().line_height as i32; match lines.cmp(&0) { - Ordering::Less => { + cmp::Ordering::Less => { for _ in 0..-lines { self.action(font_system, Action::Up); } } - Ordering::Greater => { + cmp::Ordering::Greater => { for _ in 0..lines { self.action(font_system, Action::Down); } } - Ordering::Equal => {} + cmp::Ordering::Equal => {} } } Action::Escape => { - if self.select_opt.take().is_some() { - self.buffer.set_redraw(true); + match self.selection { + Selection::None => {} + _ => self.buffer.set_redraw(true), } + self.selection = Selection::None; } Action::Insert(character) => { if character.is_control() && !['\t', '\n', '\u{92}'].contains(&character) { @@ -688,20 +659,8 @@ impl Edit for Editor { } Action::Indent => { // Get start and end of selection - let (start, end) = match self.select_opt { - Some(select) => match select.line.cmp(&self.cursor.line) { - cmp::Ordering::Greater => (self.cursor, select), - cmp::Ordering::Less => (select, self.cursor), - cmp::Ordering::Equal => { - /* select.line == self.cursor.line */ - if select.index < self.cursor.index { - (select, self.cursor) - } else { - /* select.index >= self.cursor.index */ - (self.cursor, select) - } - } - }, + let (start, end) = match self.selection_bounds() { + Some(some) => some, None => (self.cursor, self.cursor), }; @@ -746,9 +705,17 @@ impl Edit for Editor { } // Adjust selection - if let Some(ref mut select) = self.select_opt { - if select.line == line_i && select.index >= after_whitespace { - select.index += required_indent; + match self.selection { + Selection::None => {} + Selection::Normal(ref mut select) => { + if select.line == line_i && select.index >= after_whitespace { + select.index += required_indent; + } + } + Selection::Line(ref mut select) => { + if select.line == line_i && select.index >= after_whitespace { + select.index += required_indent; + } } } @@ -758,20 +725,8 @@ impl Edit for Editor { } Action::Unindent => { // Get start and end of selection - let (start, end) = match self.select_opt { - Some(select) => match select.line.cmp(&self.cursor.line) { - cmp::Ordering::Greater => (self.cursor, select), - cmp::Ordering::Less => (select, self.cursor), - cmp::Ordering::Equal => { - /* select.line == self.cursor.line */ - if select.index < self.cursor.index { - (select, self.cursor) - } else { - /* select.index >= self.cursor.index */ - (self.cursor, select) - } - } - }, + let (start, end) = match self.selection_bounds() { + Some(some) => some, None => (self.cursor, self.cursor), }; @@ -814,9 +769,12 @@ impl Edit for Editor { } // Adjust selection - if let Some(ref mut select) = self.select_opt { - if select.line == line_i && select.index > last_indent { - select.index -= after_whitespace - last_indent; + match self.selection { + Selection::None => {} + Selection::Normal(ref mut select) | Selection::Line(ref mut select) => { + if select.line == line_i && select.index > last_indent { + select.index -= after_whitespace - last_indent; + } } } @@ -825,7 +783,7 @@ impl Edit for Editor { } } Action::Click { x, y } => { - self.select_opt = None; + self.set_selection(Selection::None); if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) { if new_cursor != self.cursor { @@ -837,8 +795,8 @@ impl Edit for Editor { } } Action::Drag { x, y } => { - if self.select_opt.is_none() { - self.select_opt = Some(self.cursor); + if self.selection == Selection::None { + self.selection = Selection::Normal(self.cursor); self.buffer.set_redraw(true); } @@ -1012,21 +970,7 @@ impl Edit for Editor { }; // Highlight selection (TODO: HIGHLIGHT COLOR!) - if let Some(select) = self.select_opt { - let (start, end) = match select.line.cmp(&self.cursor.line) { - cmp::Ordering::Greater => (self.cursor, select), - cmp::Ordering::Less => (select, self.cursor), - cmp::Ordering::Equal => { - /* select.line == self.cursor.line */ - if select.index < self.cursor.index { - (select, self.cursor) - } else { - /* select.index >= self.cursor.index */ - (self.cursor, select) - } - } - }; - + if let Some((start, end)) = self.selection_bounds() { if line_i >= start.line && line_i <= end.line { let mut range_opt = None; for glyph in run.glyphs.iter() { diff --git a/src/edit/mod.rs b/src/edit/mod.rs index 7976537ec5..10b950211c 100644 --- a/src/edit/mod.rs +++ b/src/edit/mod.rs @@ -1,5 +1,6 @@ #[cfg(not(feature = "std"))] use alloc::{string::String, vec::Vec}; +use core::cmp; #[cfg(feature = "swash")] use crate::Color; @@ -130,6 +131,18 @@ impl Change { } } +/// Selection mode +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Selection { + /// No selection + None, + /// Normal selection + Normal(Cursor), + /// Select by lines + Line(Cursor), + //TODO: Select block +} + /// A trait to allow easy replacements of [`Editor`], like `SyntaxEditor` pub trait Edit { /// Mutably borrows `self` together with an [`FontSystem`] for more convenient methods @@ -159,10 +172,38 @@ pub trait Edit { fn set_cursor(&mut self, cursor: Cursor); /// Get the current selection position - fn select_opt(&self) -> Option; + fn selection(&self) -> Selection; /// Set the current selection position - fn set_select_opt(&mut self, select_opt: Option); + fn set_selection(&mut self, selection: Selection); + + /// Get the bounds of the current selection + //TODO: will not work with Block select + fn selection_bounds(&self) -> Option<(Cursor, Cursor)> { + let cursor = self.cursor(); + match self.selection() { + Selection::None => None, + Selection::Normal(select) => match select.line.cmp(&cursor.line) { + cmp::Ordering::Greater => Some((cursor, select)), + cmp::Ordering::Less => Some((select, cursor)), + cmp::Ordering::Equal => { + /* select.line == cursor.line */ + if select.index < cursor.index { + Some((select, cursor)) + } else { + /* select.index >= cursor.index */ + Some((cursor, select)) + } + } + }, + Selection::Line(select) => { + let start_line = cmp::min(select.line, cursor.line); + let end_line = cmp::max(select.line, cursor.line); + let end_index = self.buffer().lines[end_line].text().len(); + Some((Cursor::new(start_line, 0), Cursor::new(end_line, end_index))) + } + } + } /// Get the current automatic indentation setting fn auto_indent(&self) -> bool; diff --git a/src/edit/syntect.rs b/src/edit/syntect.rs index ae91963c5a..350e4a6d65 100644 --- a/src/edit/syntect.rs +++ b/src/edit/syntect.rs @@ -9,7 +9,7 @@ use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet}; use crate::{ Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, Editor, - FontSystem, Shaping, Style, Weight, Wrap, + FontSystem, Selection, Shaping, Style, Weight, Wrap, }; pub use syntect::highlighting::Theme as SyntaxTheme; @@ -158,12 +158,12 @@ impl<'a> Edit for SyntaxEditor<'a> { self.editor.set_cursor(cursor); } - fn select_opt(&self) -> Option { - self.editor.select_opt() + fn selection(&self) -> Selection { + self.editor.selection() } - fn set_select_opt(&mut self, select_opt: Option) { - self.editor.set_select_opt(select_opt); + fn set_selection(&mut self, selection: Selection) { + self.editor.set_selection(selection); } fn auto_indent(&self) -> bool { diff --git a/src/edit/vi.rs b/src/edit/vi.rs index db88372305..e52bddbbc5 100644 --- a/src/edit/vi.rs +++ b/src/edit/vi.rs @@ -5,7 +5,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::{ Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, FontSystem, - SyntaxEditor, SyntaxTheme, + Selection, SyntaxEditor, SyntaxTheme, }; pub use modit::{ViMode, ViParser}; @@ -146,7 +146,7 @@ fn select_in(editor: &mut E, start_c: char, end_c: char, include: bool) } } - editor.set_select_opt(Some(start)); + editor.set_selection(Selection::Normal(start)); editor.set_cursor(end); } @@ -264,12 +264,12 @@ impl<'a> Edit for ViEditor<'a> { self.editor.set_cursor(cursor); } - fn select_opt(&self) -> Option { - self.editor.select_opt() + fn selection(&self) -> Selection { + self.editor.selection() } - fn set_select_opt(&mut self, select_opt: Option) { - self.editor.set_select_opt(select_opt); + fn set_selection(&mut self, selection: Selection) { + self.editor.set_selection(selection); } fn auto_indent(&self) -> bool { @@ -357,7 +357,12 @@ impl<'a> Edit for ViEditor<'a> { } }; - self.parser.parse(key, false, |event| { + let has_selection = match editor.selection() { + Selection::None => false, + _ => true, + }; + + self.parser.parse(key, has_selection, |event| { log::info!(" Event {:?}", event); let action = match event { Event::AutoIndent => { @@ -390,12 +395,17 @@ impl<'a> Edit for ViEditor<'a> { return; } Event::SelectClear => { - editor.set_select_opt(None); + editor.set_selection(Selection::None); return; } Event::SelectStart => { let cursor = editor.cursor(); - editor.set_select_opt(Some(cursor)); + editor.set_selection(Selection::Normal(cursor)); + return; + } + Event::SelectLineStart => { + let cursor = editor.cursor(); + editor.set_selection(Selection::Line(cursor)); return; } Event::SelectTextObject(text_object, include) => { @@ -409,7 +419,7 @@ impl<'a> Edit for ViEditor<'a> { Some((value, _)) => { if search(editor, value, forwards) { let mut cursor = editor.cursor(); - editor.set_select_opt(Some(cursor)); + editor.set_selection(Selection::Normal(cursor)); //TODO: traverse lines if necessary cursor.index += value.len(); editor.set_cursor(cursor); @@ -423,7 +433,7 @@ impl<'a> Edit for ViEditor<'a> { TextObject::Ticks => select_in(editor, '`', '`', include), TextObject::Word(word) => { let mut cursor = editor.cursor(); - let mut select_opt = editor.select_opt(); + let mut selection = editor.selection(); let buffer = editor.buffer(); let text = buffer.lines[cursor.line].text(); match WordIter::new(text, word) @@ -431,14 +441,14 @@ impl<'a> Edit for ViEditor<'a> { { Some((i, w)) => { cursor.index = i; - select_opt = Some(cursor); + selection = Selection::Normal(cursor); cursor.index += w.len(); } None => { //TODO } } - editor.set_select_opt(select_opt); + editor.set_selection(selection); editor.set_cursor(cursor); } _ => { @@ -809,21 +819,7 @@ impl<'a> Edit for ViEditor<'a> { }; // Highlight selection (TODO: HIGHLIGHT COLOR!) - if let Some(select) = self.select_opt() { - let (start, end) = match select.line.cmp(&self.cursor().line) { - cmp::Ordering::Greater => (self.cursor(), select), - cmp::Ordering::Less => (select, self.cursor()), - cmp::Ordering::Equal => { - /* select.line == self.cursor.line */ - if select.index < self.cursor().index { - (select, self.cursor()) - } else { - /* select.index >= self.cursor.index */ - (self.cursor(), select) - } - } - }; - + if let Some((start, end)) = self.selection_bounds() { if line_i >= start.line && line_i <= end.line { let mut range_opt = None; for glyph in run.glyphs.iter() {