Enable external change tracking

This commit is contained in:
Jeremy Soller 2023-11-13 12:37:07 -07:00
parent e942e649ed
commit 7830f4107c
No known key found for this signature in database
GPG key ID: DCFCA852D3906975
4 changed files with 253 additions and 209 deletions

View file

@ -11,8 +11,8 @@ 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, Cursor, Edit, FontSystem, LayoutCursor, Action, Affinity, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem,
Shaping, LayoutCursor, Shaping,
}; };
/// A wrapper of [`Buffer`] for easy editing /// A wrapper of [`Buffer`] for easy editing
@ -24,6 +24,7 @@ pub struct Editor {
select_opt: Option<Cursor>, select_opt: Option<Cursor>,
cursor_moved: bool, cursor_moved: bool,
tab_width: usize, tab_width: usize,
change: Option<Change>,
} }
impl Editor { impl Editor {
@ -36,6 +37,7 @@ impl Editor {
select_opt: None, select_opt: None,
cursor_moved: false, cursor_moved: false,
tab_width: 4, tab_width: 4,
change: None,
} }
} }
@ -72,6 +74,142 @@ impl Editor {
self.buffer.set_redraw(true); self.buffer.set_redraw(true);
} }
} }
fn delete_range(&mut self, start: Cursor, end: Cursor) {
// Collect removed data for change tracking
let mut change_text = String::new();
// Delete the selection from the last line
let end_line_opt = if end.line > start.line {
// Get part of line after selection
let after = self.buffer.lines[end.line].split_off(end.index);
// Remove end line
let removed = self.buffer.lines.remove(end.line);
change_text.insert_str(0, removed.text());
Some(after)
} else {
None
};
// Delete interior lines (in reverse for safety)
for line_i in (start.line + 1..end.line).rev() {
let removed = self.buffer.lines.remove(line_i);
change_text.insert_str(0, removed.text());
}
// Delete the selection from the first line
{
// Get part after selection if start line is also end line
let after_opt = if start.line == end.line {
Some(self.buffer.lines[start.line].split_off(end.index))
} else {
None
};
// Delete selected part of line
let removed = self.buffer.lines[start.line].split_off(start.index);
change_text.insert_str(0, removed.text());
// Re-add part of line after selection
if let Some(after) = after_opt {
self.buffer.lines[start.line].append(after);
}
// Re-add valid parts of end line
if let Some(end_line) = end_line_opt {
self.buffer.lines[start.line].append(end_line);
}
}
if let Some(ref mut change) = self.change {
let item = ChangeItem::Delete(start, change_text);
change.items.push(item);
}
}
fn insert_at(
&mut self,
mut cursor: Cursor,
data: &str,
attrs_list: Option<AttrsList>,
) -> Cursor {
let mut remaining_split_len = data.len();
if remaining_split_len == 0 {
return cursor;
}
if let Some(ref mut change) = self.change {
let item = ChangeItem::Insert(cursor, data.to_string());
change.items.push(item);
}
let line: &mut BufferLine = &mut self.buffer.lines[cursor.line];
let insert_line = cursor.line + 1;
// Collect text after insertion as a line
let after: BufferLine = line.split_off(cursor.index);
let after_len = after.text().len();
// Collect attributes
let mut final_attrs = attrs_list.unwrap_or_else(|| {
AttrsList::new(line.attrs_list().get_span(cursor.index.saturating_sub(1)))
});
// Append the inserted text, line by line
// we want to see a blank entry if the string ends with a newline
let addendum = once("").filter(|_| data.ends_with('\n'));
let mut lines_iter = data.split_inclusive('\n').chain(addendum);
if let Some(data_line) = lines_iter.next() {
let mut these_attrs = final_attrs.split_off(data_line.len());
remaining_split_len -= data_line.len();
core::mem::swap(&mut these_attrs, &mut final_attrs);
line.append(BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
these_attrs,
Shaping::Advanced,
));
} else {
panic!("str::lines() did not yield any elements");
}
if let Some(data_line) = lines_iter.next_back() {
remaining_split_len -= data_line.len();
let mut tmp = BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
final_attrs.split_off(remaining_split_len),
Shaping::Advanced,
);
tmp.append(after);
self.buffer.lines.insert(insert_line, tmp);
cursor.line += 1;
} else {
line.append(after);
}
for data_line in lines_iter.rev() {
remaining_split_len -= data_line.len();
let tmp = BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
final_attrs.split_off(remaining_split_len),
Shaping::Advanced,
);
self.buffer.lines.insert(insert_line, tmp);
cursor.line += 1;
}
assert_eq!(remaining_split_len, 0);
// Append the text after insertion
cursor.index = self.buffer.lines[cursor.line].text().len() - after_len;
cursor
}
} }
impl Edit for Editor { impl Edit for Editor {
@ -198,123 +336,25 @@ impl Edit for Editor {
// Reset cursor to start of selection // Reset cursor to start of selection
self.cursor = start; self.cursor = start;
// Delete the selection from the last line // Delete from start to end of selection
let end_line_opt = if end.line > start.line { self.delete_range(start, end);
// Get part of line after selection
let after = self.buffer.lines[end.line].split_off(end.index);
// Remove end line
self.buffer.lines.remove(end.line);
Some(after)
} else {
None
};
// Delete interior lines (in reverse for safety)
for line_i in (start.line + 1..end.line).rev() {
self.buffer.lines.remove(line_i);
}
// Delete the selection from the first line
{
// Get part after selection if start line is also end line
let after_opt = if start.line == end.line {
Some(self.buffer.lines[start.line].split_off(end.index))
} else {
None
};
// Delete selected part of line
self.buffer.lines[start.line].split_off(start.index);
// Re-add part of line after selection
if let Some(after) = after_opt {
self.buffer.lines[start.line].append(after);
}
// Re-add valid parts of end line
if let Some(end_line) = end_line_opt {
self.buffer.lines[start.line].append(end_line);
}
}
true true
} }
fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>) { fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>) {
self.delete_selection(); self.delete_selection();
let mut remaining_split_len = data.len(); let new_cursor = self.insert_at(self.cursor, data, attrs_list);
if remaining_split_len == 0 { self.set_cursor(new_cursor);
return; }
}
let line: &mut BufferLine = &mut self.buffer.lines[self.cursor.line]; fn start_change(&mut self) {
let insert_line = self.cursor.line + 1; //TODO: what to do if overwriting change?
self.change = Some(Change::default());
}
// Collect text after insertion as a line fn finish_change(&mut self) -> Option<Change> {
let after: BufferLine = line.split_off(self.cursor.index); self.change.take()
let after_len = after.text().len();
// Collect attributes
let mut final_attrs = attrs_list.unwrap_or_else(|| {
AttrsList::new(
line.attrs_list()
.get_span(self.cursor.index.saturating_sub(1)),
)
});
// Append the inserted text, line by line
// we want to see a blank entry if the string ends with a newline
let addendum = once("").filter(|_| data.ends_with('\n'));
let mut lines_iter = data.split_inclusive('\n').chain(addendum);
if let Some(data_line) = lines_iter.next() {
let mut these_attrs = final_attrs.split_off(data_line.len());
remaining_split_len -= data_line.len();
core::mem::swap(&mut these_attrs, &mut final_attrs);
line.append(BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
these_attrs,
Shaping::Advanced,
));
} else {
panic!("str::lines() did not yield any elements");
}
if let Some(data_line) = lines_iter.next_back() {
remaining_split_len -= data_line.len();
let mut tmp = BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
final_attrs.split_off(remaining_split_len),
Shaping::Advanced,
);
tmp.append(after);
self.buffer.lines.insert(insert_line, tmp);
self.cursor.line += 1;
} else {
line.append(after);
}
for data_line in lines_iter.rev() {
remaining_split_len -= data_line.len();
let tmp = BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
final_attrs.split_off(remaining_split_len),
Shaping::Advanced,
);
self.buffer.lines.insert(insert_line, tmp);
self.cursor.line += 1;
}
assert_eq!(remaining_split_len, 0);
// Append the text after insertion
self.cursor.index = self.buffer.lines[self.cursor.line].text().len() - after_len;
self.cursor_moved = true;
} }
fn action(&mut self, font_system: &mut FontSystem, action: Action) { fn action(&mut self, font_system: &mut FontSystem, action: Action) {
@ -322,7 +362,7 @@ impl Edit for Editor {
match action { match action {
Action::Previous => { Action::Previous => {
let line = &mut self.buffer.lines[self.cursor.line]; let line = &self.buffer.lines[self.cursor.line];
if self.cursor.index > 0 { if self.cursor.index > 0 {
// Find previous character index // Find previous character index
let mut prev_index = 0; let mut prev_index = 0;
@ -346,7 +386,7 @@ impl Edit for Editor {
self.cursor_x_opt = None; self.cursor_x_opt = None;
} }
Action::Next => { Action::Next => {
let line = &mut self.buffer.lines[self.cursor.line]; let line = &self.buffer.lines[self.cursor.line];
if self.cursor.index < line.text().len() { if self.cursor.index < line.text().len() {
for (i, c) in line.text().grapheme_indices(true) { for (i, c) in line.text().grapheme_indices(true) {
if i == self.cursor.index { if i == self.cursor.index {
@ -449,7 +489,7 @@ impl Edit for Editor {
self.cursor_x_opt = None; self.cursor_x_opt = None;
} }
Action::SoftHome => { Action::SoftHome => {
let line: &mut BufferLine = &mut self.buffer.lines[self.cursor.line]; let line = &self.buffer.lines[self.cursor.line];
self.cursor.index = line self.cursor.index = line
.text() .text()
.char_indices() .char_indices()
@ -516,14 +556,7 @@ impl Edit for Editor {
} }
} }
Action::Enter => { Action::Enter => {
self.delete_selection(); self.insert_string("\n", None);
let new_line = self.buffer.lines[self.cursor.line].split_off(self.cursor.index);
self.cursor.line += 1;
self.cursor.index = 0;
self.buffer.lines.insert(self.cursor.line, new_line);
// Ensure line is properly shaped and laid out (for potential immediate commands) // Ensure line is properly shaped and laid out (for potential immediate commands)
self.buffer.line_layout(font_system, self.cursor.line); self.buffer.line_layout(font_system, self.cursor.line);
@ -531,70 +564,60 @@ impl Edit for Editor {
Action::Backspace => { Action::Backspace => {
if self.delete_selection() { if self.delete_selection() {
// Deleted selection // Deleted selection
} else if self.cursor.index > 0 { } else {
let line = &mut self.buffer.lines[self.cursor.line]; // Save current cursor as end
let end = self.cursor;
// Get text line after cursor if self.cursor.index > 0 {
let after = line.split_off(self.cursor.index); // Move cursor to previous character index
let line = &self.buffer.lines[self.cursor.line];
// Find previous character index self.cursor.index = line.text()[..self.cursor.index]
let mut prev_index = 0; .char_indices()
for (i, _) in line.text().char_indices() { .rev()
if i < self.cursor.index { .next()
prev_index = i; .map_or(0, |(i, _)| i);
} else { } else if self.cursor.line > 0 {
break; // Move cursor to previous line
} self.cursor.line -= 1;
let line = &self.buffer.lines[self.cursor.line];
self.cursor.index = line.text().len();
} }
self.cursor.index = prev_index; if self.cursor != end {
// Delete range
// Remove character self.delete_range(self.cursor, end);
line.split_off(self.cursor.index); }
// Add text after cursor
line.append(after);
} else if self.cursor.line > 0 {
let mut line_index = self.cursor.line;
let old_line = self.buffer.lines.remove(line_index);
line_index -= 1;
let line = &mut self.buffer.lines[line_index];
self.cursor.line = line_index;
self.cursor.index = line.text().len();
line.append(old_line);
} }
} }
Action::Delete => { Action::Delete => {
if self.delete_selection() { if self.delete_selection() {
// Deleted selection // Deleted selection
} else if self.cursor.index < self.buffer.lines[self.cursor.line].text().len() { } else {
let line = &mut self.buffer.lines[self.cursor.line]; // Save current cursor as end
let mut end = self.cursor;
let range_opt = line if self.cursor.index < self.buffer.lines[self.cursor.line].text().len() {
.text() let line = &self.buffer.lines[self.cursor.line];
.grapheme_indices(true)
.take_while(|(i, _)| *i <= self.cursor.index)
.last()
.map(|(i, c)| i..(i + c.len()));
if let Some(range) = range_opt { let range_opt = line
self.cursor.index = range.start; .text()
.grapheme_indices(true)
.take_while(|(i, _)| *i <= self.cursor.index)
.last()
.map(|(i, c)| i..(i + c.len()));
// Get text after deleted EGC if let Some(range) = range_opt {
let after = line.split_off(range.end); self.cursor.index = range.start;
end.index = range.end;
// Delete EGC }
line.split_off(range.start); } else if self.cursor.line + 1 < self.buffer.lines.len() {
end.line += 1;
// Add text after deleted EGC end.index = 0;
line.append(after); }
if self.cursor != end {
self.delete_range(self.cursor, end);
} }
} else if self.cursor.line + 1 < self.buffer.lines.len() {
let old_line = self.buffer.lines.remove(self.cursor.line + 1);
self.buffer.lines[self.cursor.line].append(old_line);
} }
} }
Action::Indent => { Action::Indent => {
@ -618,12 +641,11 @@ impl Edit for Editor {
// For every line in selection // For every line in selection
for line_i in start.line..=end.line { for line_i in start.line..=end.line {
let line = &mut self.buffer.lines[line_i];
// Determine indexes of last indent and first character after whitespace // Determine indexes of last indent and first character after whitespace
let mut after_whitespace = 0; let mut after_whitespace = 0;
let mut required_indent = 0; let mut required_indent = 0;
{ {
let line = &self.buffer.lines[line_i];
let text = line.text(); let text = line.text();
for (count, (index, c)) in text.char_indices().enumerate() { for (count, (index, c)) in text.char_indices().enumerate() {
if !c.is_whitespace() { if !c.is_whitespace() {
@ -636,28 +658,22 @@ impl Edit for Editor {
// No indent required (not possible?) // No indent required (not possible?)
if required_indent == 0 { if required_indent == 0 {
continue; required_indent = self.tab_width;
} }
// Save line after last whitespace self.insert_at(
let after = line.split_off(after_whitespace); Cursor::new(line_i, after_whitespace),
&" ".repeat(required_indent),
// Add required indent None,
line.append(BufferLine::new( );
" ".repeat(required_indent),
AttrsList::new(line.attrs_list().defaults()),
Shaping::Advanced,
));
// Re-add line after last whitespace
line.append(after);
// Adjust cursor // Adjust cursor
if self.cursor.line == line_i { if self.cursor.line == line_i {
if self.cursor.index >= after_whitespace { //TODO: should we be forcing cursor index to current indent location?
self.cursor.index += required_indent; if self.cursor.index < after_whitespace {
self.cursor_moved = true; self.cursor.index = after_whitespace;
} }
self.cursor.index += required_indent;
} }
// Adjust selection // Adjust selection
@ -697,12 +713,11 @@ impl Edit for Editor {
// For every line in selection // For every line in selection
for line_i in start.line..=end.line { for line_i in start.line..=end.line {
let line = &mut self.buffer.lines[line_i];
// Determine indexes of last indent and first character after whitespace // Determine indexes of last indent and first character after whitespace
let mut last_indent = 0; let mut last_indent = 0;
let mut after_whitespace = 0; let mut after_whitespace = 0;
{ {
let line = &self.buffer.lines[line_i];
let text = line.text(); let text = line.text();
for (count, (index, c)) in text.char_indices().enumerate() { for (count, (index, c)) in text.char_indices().enumerate() {
if !c.is_whitespace() { if !c.is_whitespace() {
@ -720,20 +735,16 @@ impl Edit for Editor {
continue; continue;
} }
// Save line after last whitespace // Delete one indent
let after = line.split_off(after_whitespace); self.delete_range(
Cursor::new(line_i, last_indent),
// Drop part of line after last indent Cursor::new(line_i, after_whitespace),
line.split_off(last_indent); );
// Re-add line after last whitespace
line.append(after);
// Adjust cursor // Adjust cursor
if self.cursor.line == line_i { if self.cursor.line == line_i {
if self.cursor.index > last_indent { if self.cursor.index > last_indent {
self.cursor.index -= after_whitespace - last_indent; self.cursor.index -= after_whitespace - last_indent;
self.cursor_moved = true;
} }
} }
@ -786,7 +797,7 @@ impl Edit for Editor {
self.buffer.set_scroll(scroll); self.buffer.set_scroll(scroll);
} }
Action::PreviousWord => { Action::PreviousWord => {
let line: &mut BufferLine = &mut self.buffer.lines[self.cursor.line]; let line = &self.buffer.lines[self.cursor.line];
if self.cursor.index > 0 { if self.cursor.index > 0 {
self.cursor.index = line self.cursor.index = line
.text() .text()
@ -805,7 +816,7 @@ impl Edit for Editor {
self.cursor_x_opt = None; self.cursor_x_opt = None;
} }
Action::NextWord => { Action::NextWord => {
let line: &mut BufferLine = &mut self.buffer.lines[self.cursor.line]; let line = &self.buffer.lines[self.cursor.line];
if self.cursor.index < line.text().len() { if self.cursor.index < line.text().len() {
self.cursor.index = line self.cursor.index = line
.text() .text()

View file

@ -1,5 +1,5 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::string::String; use alloc::{string::String, vec::Vec};
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
use crate::Color; use crate::Color;
@ -93,6 +93,17 @@ pub enum Action {
GotoLine(usize), GotoLine(usize),
} }
#[derive(Clone, Debug)]
pub enum ChangeItem {
Delete(Cursor, String),
Insert(Cursor, String),
}
#[derive(Clone, Debug, Default)]
pub struct Change {
items: Vec<ChangeItem>,
}
/// 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
@ -147,6 +158,12 @@ pub trait Edit {
/// attributes, or with the previous character's attributes if None is given. /// attributes, or with the previous character's attributes if None is given.
fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>); fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>);
/// Start collecting change
fn start_change(&mut self);
/// Get completed change
fn finish_change(&mut self) -> Option<Change>;
/// Perform an [Action] on the editor /// Perform an [Action] on the editor
fn action(&mut self, font_system: &mut FontSystem, action: Action); fn action(&mut self, font_system: &mut FontSystem, action: Action);

View file

@ -8,8 +8,8 @@ use syntect::highlighting::{
use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet}; use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
use crate::{ use crate::{
Action, AttrsList, BorrowedWithFontSystem, Buffer, Color, Cursor, Edit, Editor, FontSystem, Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, Editor,
Shaping, Style, Weight, Wrap, FontSystem, Shaping, Style, Weight, Wrap,
}; };
pub use syntect::highlighting::Theme as SyntaxTheme; pub use syntect::highlighting::Theme as SyntaxTheme;
@ -304,6 +304,14 @@ impl<'a> Edit for SyntaxEditor<'a> {
self.editor.insert_string(data, attrs_list); self.editor.insert_string(data, attrs_list);
} }
fn start_change(&mut self) {
self.editor.start_change();
}
fn finish_change(&mut self) -> Option<Change> {
self.editor.finish_change()
}
fn action(&mut self, font_system: &mut FontSystem, action: Action) { fn action(&mut self, font_system: &mut FontSystem, action: Action) {
self.editor.action(font_system, action); self.editor.action(font_system, action);
} }

View file

@ -4,7 +4,7 @@ use modit::{Event, Key, Motion, Parser, TextObject, WordIter};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use crate::{ use crate::{
Action, AttrsList, BorrowedWithFontSystem, Buffer, Color, Cursor, Edit, FontSystem, Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, FontSystem,
SyntaxEditor, SyntaxTheme, SyntaxEditor, SyntaxTheme,
}; };
@ -230,6 +230,14 @@ impl<'a> Edit for ViEditor<'a> {
self.editor.insert_string(data, attrs_list); self.editor.insert_string(data, attrs_list);
} }
fn start_change(&mut self) {
self.editor.start_change();
}
fn finish_change(&mut self) -> Option<Change> {
self.editor.finish_change()
}
fn action(&mut self, font_system: &mut FontSystem, action: Action) { fn action(&mut self, font_system: &mut FontSystem, action: Action) {
let editor = &mut self.editor; let editor = &mut self.editor;
log::info!("Action {:?}", action); log::info!("Action {:?}", action);