Skip to content

Commit

Permalink
makes undo merge similar edits
Browse files Browse the repository at this point in the history
  • Loading branch information
Kl4rry committed May 29, 2024
1 parent fad08e7 commit 45a78af
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 52 deletions.
21 changes: 13 additions & 8 deletions crates/ferrite-core/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ impl Buffer {
if text.is_empty() {
return;
}
// TODO collect multiple words/whitespace chars into single undo step

self.history.begin(self.cursor, self.dirty);

fn get_pair_char(s: &str) -> Option<&str> {
Expand All @@ -796,7 +796,7 @@ impl Buffer {

let lines = Rope::from_str(text).len_lines();

let inserted_bytes = if self.cursor.has_selection() {
let (inserted_bytes, finish) = if self.cursor.has_selection() {
let start_byte_idx = self.cursor.position.min(self.cursor.anchor);
let end_byte_idx = self.cursor.position.max(self.cursor.anchor);
if let Some(pair) = get_pair_char(text) {
Expand All @@ -810,7 +810,7 @@ impl Buffer {
self.cursor.position = self.cursor.position.min(self.cursor.anchor);
self.cursor.anchor = self.cursor.position;
}
text.len()
(text.len(), false)
} else if auto_indent && lines > 1 {
let indent = self.guess_indent(self.cursor.position);
let min_indent_width = Rope::from_str(&indent).width(0);
Expand Down Expand Up @@ -861,15 +861,15 @@ impl Buffer {
self.history
.insert(&mut self.rope, self.cursor.position + text.len(), pair);
}*/
input.len()
(input.len(), true)
} else {
self.history
.insert(&mut self.rope, self.cursor.position, text);
/*if let Some(pair) = get_pair_char(text) {
self.history
.insert(&mut self.rope, self.cursor.position + text.len(), pair);
}*/
text.len()
(text.len(), false)
};

self.cursor.position += inserted_bytes;
Expand All @@ -881,7 +881,10 @@ impl Buffer {
if self.clamp_cursor {
self.center_on_cursor();
}
self.history.finish();

if finish {
self.history.finish();
}
}

pub fn backspace(&mut self) {
Expand Down Expand Up @@ -939,7 +942,6 @@ impl Buffer {
if self.clamp_cursor {
self.center_on_cursor();
}
self.history.finish();
}

pub fn backspace_word(&mut self) {
Expand Down Expand Up @@ -992,7 +994,6 @@ impl Buffer {
if self.clamp_cursor {
self.center_on_cursor();
}
self.history.finish();
}

pub fn delete_word(&mut self) {
Expand Down Expand Up @@ -1116,6 +1117,7 @@ impl Buffer {
if self.clamp_cursor {
self.center_on_cursor();
}
self.history.finish();
}

pub fn tab(&mut self, back: bool) {
Expand Down Expand Up @@ -1325,11 +1327,13 @@ impl Buffer {

pub fn paste(&mut self) {
self.insert_text(&clipboard::get_contents(), true);
self.history.finish();
}

pub fn paste_primary(&mut self, col: usize, line: usize) {
self.set_cursor_pos(col, line);
self.insert_text(&clipboard::get_primary(), true);
self.history.finish();
}

// TODO make this not use eof
Expand Down Expand Up @@ -1372,6 +1376,7 @@ impl Buffer {
self.history.save();
self.queue_syntax_update();

self.history.finish();
Ok(())
}

Expand Down
18 changes: 9 additions & 9 deletions crates/ferrite-core/src/buffer/format.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::time::Duration;

use ferrite_utility::graphemes::ensure_grapheme_boundary_next_byte;
use ferrite_utility::graphemes::RopeGraphemeExt;
use ropey::Rope;
use subprocess::{Exec, PopenError, Redirection};

Expand Down Expand Up @@ -98,10 +98,10 @@ impl Buffer {
let len = self.rope.len_bytes();
self.history.replace(&mut self.rope, 0..len, &new_rope);

let pos = ensure_grapheme_boundary_next_byte(
self.rope.slice(..),
self.cursor.position.min(self.rope.len_bytes()),
);
// TODO position curser better then using byte offset
let pos = self
.rope
.ensure_grapheme_boundary_next_byte(self.cursor.position.min(self.rope.len_bytes()));

self.cursor.position = pos;
self.cursor.anchor = pos;
Expand All @@ -128,10 +128,10 @@ impl Buffer {
let len = self.rope.len_bytes();
self.history.replace(&mut self.rope, 0..len, &new_rope);

let pos = ensure_grapheme_boundary_next_byte(
self.rope.slice(..),
self.cursor.position.min(self.rope.len_bytes()),
);
// TODO position curser better then using byte offset
let pos = self
.rope
.ensure_grapheme_boundary_next_byte(self.cursor.position.min(self.rope.len_bytes()));

self.cursor.position = pos;
self.cursor.anchor = pos;
Expand Down
142 changes: 108 additions & 34 deletions crates/ferrite-core/src/buffer/history.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,58 @@
use std::{mem, ops::Range};

use ferrite_utility::graphemes::ensure_grapheme_boundary_next_byte;
use ferrite_utility::graphemes::RopeGraphemeExt;
use ropey::Rope;

use super::Cursor;

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
enum EditClass {
Word,
WhiteSpace,
Other,
Remove,
}

impl EditClass {
fn mergeable(first: &EditClass, second: &EditClass) -> bool {
match (first, second) {
(EditClass::WhiteSpace, EditClass::WhiteSpace) => true,
(EditClass::Word, EditClass::Word) => true,
(EditClass::Remove, EditClass::Remove) => true,
(EditClass::WhiteSpace, EditClass::Word) => true,
_ => false,
}
}
}

impl From<&str> for EditClass {
fn from(value: &str) -> Self {
if Rope::from_str(value).is_word_char() {
return EditClass::Word;
}
if Rope::from_str(value).is_whitespace() {
return EditClass::WhiteSpace;
}
EditClass::Other
}
}

#[derive(Debug, Clone)]
enum EditKind {
Insert { byte_idx: usize, text: String },
Remove { range: Range<usize> },
Replace { range: Range<usize>, text: String },
Remove { range: Range<usize> },
}

impl EditKind {
fn get_class(&self) -> EditClass {
match self {
EditKind::Insert { text, .. } => EditClass::from(text.as_str()),
EditKind::Replace { text, .. } => EditClass::from(text.as_str()),
EditKind::Remove { .. } => EditClass::Remove,
}
}

fn apply(&self, rope: &mut Rope) -> EditKind {
match self {
Self::Insert { byte_idx, text } => {
Expand All @@ -21,14 +61,6 @@ impl EditKind {
range: *byte_idx..(*byte_idx + text.len()),
}
}
Self::Remove { range } => {
let text = rope.byte_slice(range.clone()).to_string();
rope.remove(rope.byte_to_char(range.start)..rope.byte_to_char(range.end));
Self::Insert {
byte_idx: range.start,
text,
}
}
Self::Replace { range, text } => {
let old = rope.byte_slice(range.clone()).to_string();
let char_range = rope.byte_to_char(range.start)..rope.byte_to_char(range.end);
Expand All @@ -39,13 +71,22 @@ impl EditKind {
text: old,
}
}
Self::Remove { range } => {
let text = rope.byte_slice(range.clone()).to_string();
rope.remove(rope.byte_to_char(range.start)..rope.byte_to_char(range.end));
Self::Insert {
byte_idx: range.start,
text,
}
}
}
}
}

#[derive(Debug, Clone)]
struct Frame {
finished: bool,
edit_class: EditClass,
cursor: Cursor,
edits: Vec<EditKind>,
dirty: bool,
Expand All @@ -70,10 +111,9 @@ impl History {
fn edit(&mut self, rope: &mut Rope, edit: EditKind) {
match self.stack.last_mut() {
Some(frame) => {
frame.edit_class = edit.get_class();
let inverse = edit.apply(rope);
if !frame.finished {
frame.edits.push(inverse);
}
frame.edits.push(inverse);
}
None => tracing::error!("Edited rope before starting new edit frame"),
}
Expand Down Expand Up @@ -105,6 +145,7 @@ impl History {

self.stack.push(Frame {
finished: false,
edit_class: EditClass::Other,
cursor,
edits: Vec::new(),
dirty,
Expand All @@ -116,7 +157,7 @@ impl History {

pub fn finish(&mut self) {
// maybe should be current_frame
if let Some(frame) = self.stack.last_mut() {
if let Some(frame) = self.stack.get_mut(self.current_frame as usize) {
if !frame.finished {
frame.finished = true;
}
Expand All @@ -128,33 +169,66 @@ impl History {
return;
}

let frame = &mut self.stack[self.current_frame as usize];
for edit in frame.edits.iter_mut().rev() {
*edit = edit.apply(rope);
}
mem::swap(&mut frame.cursor, cursor);
mem::swap(&mut frame.dirty, dirty);
cursor.position = ensure_grapheme_boundary_next_byte(rope.slice(..), cursor.position);
cursor.anchor = ensure_grapheme_boundary_next_byte(rope.slice(..), cursor.anchor);
let mut last_class = None;

while let Some(frame) = &mut self.stack.get_mut(self.current_frame as usize) {
for edit in frame.edits.iter_mut().rev() {
*edit = edit.apply(rope);
}
mem::swap(&mut frame.cursor, cursor);
mem::swap(&mut frame.dirty, dirty);
cursor.position = rope.ensure_grapheme_boundary_next_byte(cursor.position);
cursor.anchor = rope.ensure_grapheme_boundary_next_byte(cursor.anchor);
self.current_frame -= 1;

if frame.finished {
break;
}

self.current_frame -= 1;
if let Some(frame) = &mut self.stack.get_mut(self.current_frame as usize) {
let earlier_class = frame.edit_class;
if let Some(last_class) = last_class {
if !EditClass::mergeable(&earlier_class, &last_class) {
break;
}
}
last_class = Some(earlier_class);
}
}
}

pub fn redo(&mut self, rope: &mut Rope, cursor: &mut Cursor, dirty: &mut bool) {
if self.current_frame + 1 >= self.stack.len() as i64 {
return;
}
let mut last_class = None;

self.current_frame += 1;
loop {
if self.current_frame + 1 >= self.stack.len() as i64 {
return;
}
self.current_frame += 1;
let frame = &mut self.stack[self.current_frame as usize];

let frame = &mut self.stack[self.current_frame as usize];
for edit in &mut frame.edits {
*edit = edit.apply(rope);
for edit in &mut frame.edits {
*edit = edit.apply(rope);
}
mem::swap(&mut frame.cursor, cursor);
mem::swap(&mut frame.dirty, dirty);
cursor.position = rope.ensure_grapheme_boundary_next_byte(cursor.position);
cursor.anchor = rope.ensure_grapheme_boundary_next_byte(cursor.anchor);

if frame.finished {
break;
}

if let Some(frame) = &mut self.stack.get_mut(self.current_frame as usize + 1) {
let earlier_class = frame.edit_class;
if let Some(last_class) = last_class {
if !EditClass::mergeable(&last_class, &earlier_class) {
break;
}
}
last_class = Some(earlier_class);
}
}
mem::swap(&mut frame.cursor, cursor);
mem::swap(&mut frame.dirty, dirty);
cursor.position = ensure_grapheme_boundary_next_byte(rope.slice(..), cursor.position);
cursor.anchor = ensure_grapheme_boundary_next_byte(rope.slice(..), cursor.anchor);
}

pub fn save(&mut self) {
Expand Down
2 changes: 1 addition & 1 deletion crates/ferrite-utility/src/graphemes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ pub fn ensure_grapheme_boundary_prev(slice: RopeSlice, char_idx: usize) -> usize
/// or the next grapheme boundary byte index if not.
#[must_use]
#[inline]
pub fn ensure_grapheme_boundary_next_byte(slice: RopeSlice, byte_idx: usize) -> usize {
fn ensure_grapheme_boundary_next_byte(slice: RopeSlice, byte_idx: usize) -> usize {
if byte_idx == 0 {
byte_idx
} else {
Expand Down

0 comments on commit 45a78af

Please sign in to comment.