A simple CPU rendered GUI IDE experience.
Diffstat (limited to 'src/text/hist.rs')
-rw-r--r--src/text/hist.rs240
1 files changed, 240 insertions, 0 deletions
diff --git a/src/text/hist.rs b/src/text/hist.rs
new file mode 100644
index 0000000..ed0d571
--- /dev/null
+++ b/src/text/hist.rs
@@ -0,0 +1,240 @@
+use core::ops::Range;
+use std::fmt::{Debug, Display};
+use std::mem::take;
+use std::ops::Deref;
+use std::time::Instant;
+
+use Default::default;
+
+use super::cursor::Cursors;
+use crate::text::TextArea;
+
+#[derive(
+ Clone,
+ PartialEq,
+ Eq,
+ serde_derive::Serialize,
+ serde_derive::Deserialize,
+)]
+pub enum Action {
+ Inserted { at: usize, insert: String },
+ Removed { range: Range<usize>, removed: Option<String> },
+}
+impl Debug for Action {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Inserted { at, insert } =>
+ write!(f, "A(@{at} + {insert})"),
+ Self::Removed { range, removed: None } =>
+ write!(f, "A(@{range:?} -)"),
+ Self::Removed { range, removed: Some(rem) } =>
+ write!(f, "A(@{range:?} - {rem})"),
+ }
+ }
+}
+
+#[derive(
+ Default,
+ Clone,
+ Debug,
+ PartialEq,
+ Eq,
+ serde_derive::Serialize,
+ serde_derive::Deserialize,
+)]
+pub struct Changes {
+ pub inner: Vec<Action>,
+}
+
+impl Display for Changes {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}", self.inner)
+ }
+}
+impl Display for Diff {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}", self.0)
+ }
+}
+impl Deref for Changes {
+ type Target = [Action];
+
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+impl Deref for Diff {
+ type Target = [Action];
+
+ fn deref(&self) -> &Self::Target {
+ &*self.0
+ }
+}
+impl Changes {
+ pub fn rev(&mut self) {
+ self.inner.reverse();
+ self.inner.iter_mut().for_each(|action| {
+ *action = match action {
+ Action::Inserted { at, insert } => {
+ let end = *at + insert.len();
+ Action::Removed { range: *at..end, removed: None }
+ }
+ Action::Removed { range, removed: Some(insert) } =>
+ Action::Inserted {
+ at: range.start,
+ insert: insert.clone(),
+ },
+ _ => unreachable!(),
+ }
+ });
+ }
+ pub fn apply(
+ mut self,
+ t: &mut TextArea,
+ redo: bool,
+ ) -> ropey::Result<()> {
+ if !redo {
+ self.rev();
+ }
+ let pc = take(&mut t.changes);
+ for c in self.inner {
+ match c {
+ Action::Inserted { at, insert } =>
+ t.insert_at(at, &insert),
+ Action::Removed { range, .. } => t.remove(range),
+ }?;
+ }
+ t.changes = pc;
+ Ok(())
+ }
+}
+#[derive(
+ Clone,
+ PartialEq,
+ Debug,
+ serde_derive::Serialize,
+ serde_derive::Deserialize,
+)]
+pub struct Diff(pub Changes, pub [Cursors; 2]);
+impl Diff {
+ pub fn apply(
+ mut self,
+ t: &mut TextArea,
+ redo: bool,
+ ) -> Result<(), ropey::Error> {
+ self.0.apply(t, redo)?;
+ t.cursor = take(&mut self.1[redo as usize]);
+ t.scroll_to_cursor_centering();
+ Ok(())
+ }
+}
+
+#[derive(serde::Deserialize, serde::Serialize, Debug)]
+pub struct Hist {
+ pub history: Vec<Diff>,
+ pub redo_history: Vec<Diff>,
+ pub last: Changes,
+ pub lc: super::Cursors,
+ #[serde(
+ skip_deserializing,
+ serialize_with = "crate::serialize_debug",
+ default = "Instant::now"
+ )]
+ pub last_edit: std::time::Instant,
+ pub changed: bool,
+}
+impl Default for Hist {
+ fn default() -> Self {
+ Self {
+ history: vec![],
+ redo_history: vec![],
+ last: default(),
+ lc: default(),
+ last_edit: Instant::now(),
+ changed: false,
+ }
+ }
+}
+#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
+pub struct ClickHistory {
+ pub his: Vec<(usize, usize)>,
+ pub red: Vec<(usize, usize)>,
+}
+
+impl ClickHistory {
+ pub fn push(&mut self, x: (usize, usize)) {
+ if self.his.last() != Some(&x) {
+ self.his.push(x);
+ self.red.clear();
+ }
+ }
+ pub fn back(&mut self) -> Option<(usize, usize)> {
+ self.his.pop().inspect(|x| {
+ self.red.push(*x);
+ })
+ }
+ pub fn forth(&mut self) -> Option<(usize, usize)> {
+ self.red.pop().inspect(|x| {
+ self.his.push(*x);
+ })
+ }
+}
+impl Hist {
+ pub fn push(&mut self, x: &mut TextArea) {
+ // assert_eq!(x.changes[..self.last.len()], *self.last);
+ // x.changes.inner.drain(..self.last.len());
+ let c = take(&mut x.changes);
+ self.history.push(Diff(c, [take(&mut self.lc), x.cursor.clone()]));
+ self.lc = x.cursor.clone();
+ println!("push {}", self.history.last().unwrap());
+ self.redo_history.clear();
+ take(&mut self.last);
+ self.last_edit = Instant::now();
+ self.changed = false;
+ }
+ fn undo_(&mut self) -> Option<Diff> {
+ self.history.pop().inspect(|x| self.redo_history.push(x.clone()))
+ }
+ fn redo_(&mut self) -> Option<Diff> {
+ self.redo_history.pop().inspect(|x| self.history.push(x.clone()))
+ }
+ pub fn undo(&mut self, t: &mut TextArea) -> ropey::Result<Option<()>> {
+ self.push_if_changed(t);
+ self.undo_()
+ .map(|x| {
+ let r = x.apply(t, false);
+ self.lc = t.cursor.clone();
+ self.last = t.changes.clone();
+ r
+ })
+ .transpose()
+ }
+ pub fn redo(&mut self, t: &mut TextArea) -> ropey::Result<Option<()>> {
+ self.redo_()
+ .map(|x| {
+ let r = x.apply(t, true);
+ self.lc = t.cursor.clone();
+ self.last = t.changes.clone();
+ r
+ })
+ .transpose()
+ }
+ pub fn push_if_changed(&mut self, x: &mut TextArea) {
+ if self.changed || self.last != x.changes {
+ self.push(x);
+ }
+ }
+ pub fn test_push(&mut self, x: &mut TextArea) {
+ if self.last_edit.elapsed().as_millis() > 500 {
+ self.push_if_changed(x);
+ }
+ }
+ pub fn record(&mut self, new: &TextArea) -> bool {
+ (new.changes != self.last)
+ .then(|| {
+ self.last_edit = Instant::now();
+ self.changed = true;
+ })
+ .is_some()
+ }
+}