Skip to content

Commit

Permalink
Support line selection
Browse files Browse the repository at this point in the history
  • Loading branch information
jackpot51 committed Nov 28, 2023
1 parent 9a975ad commit cbd567d
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 142 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
9 changes: 2 additions & 7 deletions examples/editor-libcosmic/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(&current_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));
}
Expand Down
142 changes: 43 additions & 99 deletions src/edit/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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),
};

Expand Down Expand Up @@ -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;
}
}
}

Expand All @@ -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),
};

Expand Down Expand Up @@ -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;
}
}
}

Expand All @@ -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 {
Expand All @@ -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);
}

Expand Down Expand Up @@ -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() {
Expand Down
45 changes: 43 additions & 2 deletions src/edit/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::cmp;

#[cfg(feature = "swash")]
use crate::Color;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions src/edit/syntect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit cbd567d

Please sign in to comment.