Support line selection
This commit is contained in:
parent
9a975ad79a
commit
cbd567d238
6 changed files with 118 additions and 142 deletions
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -383,13 +383,8 @@ fn update_attrs<T: Edit>(editor: &mut T, attrs: Attrs) {
|
|||
|
||||
fn update_alignment<T: Edit>(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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<i32>,
|
||||
select_opt: Option<Cursor>,
|
||||
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<Cursor> {
|
||||
self.select_opt
|
||||
fn selection(&self) -> Selection {
|
||||
self.selection
|
||||
}
|
||||
|
||||
fn set_select_opt(&mut self, select_opt: Option<Cursor>) {
|
||||
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<String> {
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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<Cursor>;
|
||||
fn selection(&self) -> Selection;
|
||||
|
||||
/// 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
|
||||
fn auto_indent(&self) -> bool;
|
||||
|
|
|
|||
|
|
@ -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<Cursor> {
|
||||
self.editor.select_opt()
|
||||
fn selection(&self) -> Selection {
|
||||
self.editor.selection()
|
||||
}
|
||||
|
||||
fn set_select_opt(&mut self, select_opt: Option<Cursor>) {
|
||||
self.editor.set_select_opt(select_opt);
|
||||
fn set_selection(&mut self, selection: Selection) {
|
||||
self.editor.set_selection(selection);
|
||||
}
|
||||
|
||||
fn auto_indent(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -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<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);
|
||||
}
|
||||
|
||||
|
|
@ -264,12 +264,12 @@ impl<'a> Edit for ViEditor<'a> {
|
|||
self.editor.set_cursor(cursor);
|
||||
}
|
||||
|
||||
fn select_opt(&self) -> Option<Cursor> {
|
||||
self.editor.select_opt()
|
||||
fn selection(&self) -> Selection {
|
||||
self.editor.selection()
|
||||
}
|
||||
|
||||
fn set_select_opt(&mut self, select_opt: Option<Cursor>) {
|
||||
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() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue