Support line selection

This commit is contained in:
Jeremy Soller 2023-11-28 10:42:50 -07:00
parent 9a975ad79a
commit cbd567d238
No known key found for this signature in database
GPG key ID: DCFCA852D3906975
6 changed files with 118 additions and 142 deletions

View file

@ -15,7 +15,7 @@ fontdb = { version = "0.16.0", default-features = false }
hashbrown = { version = "0.14.1", optional = true, default-features = false } hashbrown = { version = "0.14.1", optional = true, default-features = false }
libm = "0.2.8" libm = "0.2.8"
log = "0.4.20" log = "0.4.20"
modit = { version = "0.1.0", optional = true } modit = { version = "0.1.1", optional = true }
rangemap = "1.4.0" rangemap = "1.4.0"
rustc-hash = { version = "1.1.0", default-features = false } rustc-hash = { version = "1.1.0", default-features = false }
rustybuzz = { version = "0.11.0", default-features = false, features = ["libm"] } rustybuzz = { version = "0.11.0", default-features = false, features = ["libm"] }

View file

@ -383,13 +383,8 @@ fn update_attrs<T: Edit>(editor: &mut T, attrs: Attrs) {
fn update_alignment<T: Edit>(editor: &mut T, align: Align) { fn update_alignment<T: Edit>(editor: &mut T, align: Align) {
let current_line = editor.cursor().line; let current_line = editor.cursor().line;
if let Some(select) = editor.select_opt() { if let Some((start, end)) = editor.selection_bounds() {
let (start, end) = match select.line.cmp(&current_line) { if let Some(lines) = editor.buffer_mut().lines.get_mut(start.line..=end.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) {
for line in lines.iter_mut() { for line in lines.iter_mut() {
line.set_align(Some(align)); line.set_align(Some(align));
} }

View file

@ -2,17 +2,14 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use core::{ use core::{cmp, iter::once};
cmp::{self, Ordering},
iter::once,
};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
use crate::Color; use crate::Color;
use crate::{ use crate::{
Action, Affinity, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem, Action, Affinity, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem,
LayoutCursor, Shaping, LayoutCursor, Selection, Shaping,
}; };
/// A wrapper of [`Buffer`] for easy editing /// A wrapper of [`Buffer`] for easy editing
@ -21,7 +18,7 @@ pub struct Editor {
buffer: Buffer, buffer: Buffer,
cursor: Cursor, cursor: Cursor,
cursor_x_opt: Option<i32>, cursor_x_opt: Option<i32>,
select_opt: Option<Cursor>, selection: Selection,
cursor_moved: bool, cursor_moved: bool,
auto_indent: bool, auto_indent: bool,
tab_width: u16, tab_width: u16,
@ -35,7 +32,7 @@ impl Editor {
buffer, buffer,
cursor: Cursor::default(), cursor: Cursor::default(),
cursor_x_opt: None, cursor_x_opt: None,
select_opt: None, selection: Selection::None,
cursor_moved: false, cursor_moved: false,
auto_indent: false, auto_indent: false,
tab_width: 4, tab_width: 4,
@ -249,13 +246,13 @@ impl Edit for Editor {
} }
} }
fn select_opt(&self) -> Option<Cursor> { fn selection(&self) -> Selection {
self.select_opt self.selection
} }
fn set_select_opt(&mut self, select_opt: Option<Cursor>) { fn set_selection(&mut self, selection: Selection) {
if self.select_opt != select_opt { if self.selection != selection {
self.select_opt = select_opt; self.selection = selection;
self.buffer.set_redraw(true); self.buffer.set_redraw(true);
} }
} }
@ -293,21 +290,7 @@ impl Edit for Editor {
} }
fn copy_selection(&self) -> Option<String> { fn copy_selection(&self) -> Option<String> {
let select = self.select_opt?; let (start, end) = self.selection_bounds()?;
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 mut selection = String::new(); let mut selection = String::new();
// Take the selection from the first line // Take the selection from the first line
@ -337,25 +320,11 @@ impl Edit for Editor {
} }
fn delete_selection(&mut self) -> bool { fn delete_selection(&mut self) -> bool {
let select = match self.select_opt.take() { let (start, end) = match self.selection_bounds() {
Some(some) => some, Some(some) => some,
None => return false, 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 // Reset cursor to start of selection
self.cursor = start; self.cursor = start;
@ -575,23 +544,25 @@ impl Edit for Editor {
// TODO more efficient // TODO more efficient
let lines = px / self.buffer.metrics().line_height as i32; let lines = px / self.buffer.metrics().line_height as i32;
match lines.cmp(&0) { match lines.cmp(&0) {
Ordering::Less => { cmp::Ordering::Less => {
for _ in 0..-lines { for _ in 0..-lines {
self.action(font_system, Action::Up); self.action(font_system, Action::Up);
} }
} }
Ordering::Greater => { cmp::Ordering::Greater => {
for _ in 0..lines { for _ in 0..lines {
self.action(font_system, Action::Down); self.action(font_system, Action::Down);
} }
} }
Ordering::Equal => {} cmp::Ordering::Equal => {}
} }
} }
Action::Escape => { Action::Escape => {
if self.select_opt.take().is_some() { match self.selection {
self.buffer.set_redraw(true); Selection::None => {}
_ => self.buffer.set_redraw(true),
} }
self.selection = Selection::None;
} }
Action::Insert(character) => { Action::Insert(character) => {
if character.is_control() && !['\t', '\n', '\u{92}'].contains(&character) { if character.is_control() && !['\t', '\n', '\u{92}'].contains(&character) {
@ -688,20 +659,8 @@ impl Edit for Editor {
} }
Action::Indent => { Action::Indent => {
// Get start and end of selection // Get start and end of selection
let (start, end) = match self.select_opt { let (start, end) = match self.selection_bounds() {
Some(select) => match select.line.cmp(&self.cursor.line) { Some(some) => some,
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)
}
}
},
None => (self.cursor, self.cursor), None => (self.cursor, self.cursor),
}; };
@ -746,9 +705,17 @@ impl Edit for Editor {
} }
// Adjust selection // Adjust selection
if let Some(ref mut select) = self.select_opt { match self.selection {
if select.line == line_i && select.index >= after_whitespace { Selection::None => {}
select.index += required_indent; 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 => { Action::Unindent => {
// Get start and end of selection // Get start and end of selection
let (start, end) = match self.select_opt { let (start, end) = match self.selection_bounds() {
Some(select) => match select.line.cmp(&self.cursor.line) { Some(some) => some,
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)
}
}
},
None => (self.cursor, self.cursor), None => (self.cursor, self.cursor),
}; };
@ -814,9 +769,12 @@ impl Edit for Editor {
} }
// Adjust selection // Adjust selection
if let Some(ref mut select) = self.select_opt { match self.selection {
if select.line == line_i && select.index > last_indent { Selection::None => {}
select.index -= after_whitespace - last_indent; 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 } => { 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 let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) {
if new_cursor != self.cursor { if new_cursor != self.cursor {
@ -837,8 +795,8 @@ impl Edit for Editor {
} }
} }
Action::Drag { x, y } => { Action::Drag { x, y } => {
if self.select_opt.is_none() { if self.selection == Selection::None {
self.select_opt = Some(self.cursor); self.selection = Selection::Normal(self.cursor);
self.buffer.set_redraw(true); self.buffer.set_redraw(true);
} }
@ -1012,21 +970,7 @@ impl Edit for Editor {
}; };
// Highlight selection (TODO: HIGHLIGHT COLOR!) // Highlight selection (TODO: HIGHLIGHT COLOR!)
if let Some(select) = self.select_opt { if let Some((start, end)) = self.selection_bounds() {
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 line_i >= start.line && line_i <= end.line { if line_i >= start.line && line_i <= end.line {
let mut range_opt = None; let mut range_opt = None;
for glyph in run.glyphs.iter() { for glyph in run.glyphs.iter() {

View file

@ -1,5 +1,6 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec}; use alloc::{string::String, vec::Vec};
use core::cmp;
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
use crate::Color; 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` /// A trait to allow easy replacements of [`Editor`], like `SyntaxEditor`
pub trait Edit { pub trait Edit {
/// Mutably borrows `self` together with an [`FontSystem`] for more convenient methods /// 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); fn set_cursor(&mut self, cursor: Cursor);
/// Get the current selection position /// Get the current selection position
fn select_opt(&self) -> Option<Cursor>; fn selection(&self) -> Selection;
/// Set the current selection position /// Set the current selection position
fn set_select_opt(&mut self, select_opt: Option<Cursor>); 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 /// Get the current automatic indentation setting
fn auto_indent(&self) -> bool; fn auto_indent(&self) -> bool;

View file

@ -9,7 +9,7 @@ use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
use crate::{ use crate::{
Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, Editor, 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; pub use syntect::highlighting::Theme as SyntaxTheme;
@ -158,12 +158,12 @@ impl<'a> Edit for SyntaxEditor<'a> {
self.editor.set_cursor(cursor); self.editor.set_cursor(cursor);
} }
fn select_opt(&self) -> Option<Cursor> { fn selection(&self) -> Selection {
self.editor.select_opt() self.editor.selection()
} }
fn set_select_opt(&mut self, select_opt: Option<Cursor>) { fn set_selection(&mut self, selection: Selection) {
self.editor.set_select_opt(select_opt); self.editor.set_selection(selection);
} }
fn auto_indent(&self) -> bool { fn auto_indent(&self) -> bool {

View file

@ -5,7 +5,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::{ use crate::{
Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, FontSystem, Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, FontSystem,
SyntaxEditor, SyntaxTheme, Selection, SyntaxEditor, SyntaxTheme,
}; };
pub use modit::{ViMode, ViParser}; pub use modit::{ViMode, ViParser};
@ -146,7 +146,7 @@ fn select_in<E: Edit>(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); editor.set_cursor(end);
} }
@ -264,12 +264,12 @@ impl<'a> Edit for ViEditor<'a> {
self.editor.set_cursor(cursor); self.editor.set_cursor(cursor);
} }
fn select_opt(&self) -> Option<Cursor> { fn selection(&self) -> Selection {
self.editor.select_opt() self.editor.selection()
} }
fn set_select_opt(&mut self, select_opt: Option<Cursor>) { fn set_selection(&mut self, selection: Selection) {
self.editor.set_select_opt(select_opt); self.editor.set_selection(selection);
} }
fn auto_indent(&self) -> bool { 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); log::info!(" Event {:?}", event);
let action = match event { let action = match event {
Event::AutoIndent => { Event::AutoIndent => {
@ -390,12 +395,17 @@ impl<'a> Edit for ViEditor<'a> {
return; return;
} }
Event::SelectClear => { Event::SelectClear => {
editor.set_select_opt(None); editor.set_selection(Selection::None);
return; return;
} }
Event::SelectStart => { Event::SelectStart => {
let cursor = editor.cursor(); 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; return;
} }
Event::SelectTextObject(text_object, include) => { Event::SelectTextObject(text_object, include) => {
@ -409,7 +419,7 @@ impl<'a> Edit for ViEditor<'a> {
Some((value, _)) => { Some((value, _)) => {
if search(editor, value, forwards) { if search(editor, value, forwards) {
let mut cursor = editor.cursor(); let mut cursor = editor.cursor();
editor.set_select_opt(Some(cursor)); editor.set_selection(Selection::Normal(cursor));
//TODO: traverse lines if necessary //TODO: traverse lines if necessary
cursor.index += value.len(); cursor.index += value.len();
editor.set_cursor(cursor); editor.set_cursor(cursor);
@ -423,7 +433,7 @@ impl<'a> Edit for ViEditor<'a> {
TextObject::Ticks => select_in(editor, '`', '`', include), TextObject::Ticks => select_in(editor, '`', '`', include),
TextObject::Word(word) => { TextObject::Word(word) => {
let mut cursor = editor.cursor(); let mut cursor = editor.cursor();
let mut select_opt = editor.select_opt(); let mut selection = editor.selection();
let buffer = editor.buffer(); let buffer = editor.buffer();
let text = buffer.lines[cursor.line].text(); let text = buffer.lines[cursor.line].text();
match WordIter::new(text, word) match WordIter::new(text, word)
@ -431,14 +441,14 @@ impl<'a> Edit for ViEditor<'a> {
{ {
Some((i, w)) => { Some((i, w)) => {
cursor.index = i; cursor.index = i;
select_opt = Some(cursor); selection = Selection::Normal(cursor);
cursor.index += w.len(); cursor.index += w.len();
} }
None => { None => {
//TODO //TODO
} }
} }
editor.set_select_opt(select_opt); editor.set_selection(selection);
editor.set_cursor(cursor); editor.set_cursor(cursor);
} }
_ => { _ => {
@ -809,21 +819,7 @@ impl<'a> Edit for ViEditor<'a> {
}; };
// Highlight selection (TODO: HIGHLIGHT COLOR!) // Highlight selection (TODO: HIGHLIGHT COLOR!)
if let Some(select) = self.select_opt() { if let Some((start, end)) = self.selection_bounds() {
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 line_i >= start.line && line_i <= end.line { if line_i >= start.line && line_i <= end.line {
let mut range_opt = None; let mut range_opt = None;
for glyph in run.glyphs.iter() { for glyph in run.glyphs.iter() {