A simple CPU rendered GUI IDE experience.
-rw-r--r--Cargo.toml2
-rw-r--r--src/bar.rs17
-rw-r--r--src/main.rs103
-rw-r--r--src/text.rs26
4 files changed, 129 insertions, 19 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 296025b..600964e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,8 @@ rust-fsm = { version = "0.8.0", path = "../rust-fsm/rust-fsm", features = [
"diagram",
] }
clipp = "0.1.0"
+parking_lot = "0.12.4"
+diff-match-patch-rs = "0.5.1"
[build-dependencies]
cc = "*"
diff --git a/src/bar.rs b/src/bar.rs
index 2475ba1..f596a51 100644
--- a/src/bar.rs
+++ b/src/bar.rs
@@ -33,20 +33,9 @@ impl Bar {
use super::State;
match state {
State::Default if super::ctrl() => {
- let x = s("C + { ")
- .chain(once(('S', Style::BOLD)))
- .chain(s("ave, "))
- .chain(once(('Q', Style::BOLD)))
- .chain(s("uit, "))
- .chain(once(('V', Style::BOLD)))
- .chain(s("aste }"));
-
- x.zip(row).for_each(|((x, z), y)| {
- *y = Cell {
- letter: Some(x),
- style: Style { flags: z, ..y.style },
- ..*y
- }
+ let x = "C + { S, Q, V, Z, Y }".chars();
+ x.zip(row).for_each(|(x, y)| {
+ y.letter = Some(x);
});
}
State::Default => {
diff --git a/src/main.rs b/src/main.rs
index 5940be9..e9ade1c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,18 +10,20 @@
portable_simd
)]
#![allow(incomplete_features, redundant_semicolons)]
+use std::collections::VecDeque;
use std::convert::identity;
use std::fs::File;
use std::hint::assert_unchecked;
use std::iter::zip;
use std::num::NonZeroU32;
use std::simd::prelude::*;
-use std::sync::LazyLock;
+use std::sync::{LazyLock, Mutex};
use std::time::Instant;
use Default::default;
use array_chunks::*;
use atools::prelude::*;
+use diff_match_patch_rs::{Efficient, PatchInput};
use dsb::cell::Style;
use dsb::{Cell, F};
use fimg::Image;
@@ -34,7 +36,7 @@ use winit::event_loop::{ControlFlow, EventLoop};
use winit::keyboard::{Key, ModifiersState, NamedKey, SmolStr};
use crate::bar::Bar;
-use crate::text::TextArea;
+use crate::text::{Diff, TextArea};
mod bar;
mod text;
mod winit_app;
@@ -42,6 +44,62 @@ fn main() {
entry(EventLoop::new().unwrap())
}
+struct Hist {
+ pub history: Vec<Diff>,
+ pub redo_history: Vec<Diff>,
+ pub last: TextArea,
+ pub last_edit: std::time::Instant,
+ pub changed: bool,
+}
+impl Hist {
+ pub fn push(&mut self, x: &TextArea) {
+ let d = diff_match_patch_rs::DiffMatchPatch::new();
+ self.history.push(Diff {
+ changes: (
+ d.patch_make(PatchInput::new_text_text(
+ &x.rope.to_string(),
+ &self.last.rope.to_string(),
+ ))
+ .unwrap(),
+ d.patch_make(PatchInput::new_text_text(
+ &self.last.rope.to_string(),
+ &x.rope.to_string(),
+ ))
+ .unwrap(),
+ ),
+ data: [
+ (self.last.cursor, self.last.column, self.last.vo),
+ (x.cursor, x.column, x.vo),
+ ],
+ });
+ self.redo_history.clear();
+ self.last = x.clone();
+ self.changed = false;
+ }
+ pub fn undo(&mut self) -> Option<Diff> {
+ self.history.pop().map(|x| {
+ self.redo_history.push(x.clone());
+ x
+ })
+ }
+ pub fn redo(&mut self) -> Option<Diff> {
+ self.redo_history.pop().map(|x| {
+ self.history.push(x.clone());
+ x
+ })
+ }
+ pub fn test_push(&mut self, x: &TextArea) {
+ if self.last_edit.elapsed().as_millis() > 500 && self.changed {
+ self.push(x);
+ }
+ }
+ pub fn record(&mut self, x: &TextArea) {
+ self.test_push(x);
+ self.last_edit = Instant::now();
+ self.changed = true;
+ }
+}
+
static mut MODIFIERS: ModifiersState = ModifiersState::empty();
const BG: [u8; 3] = [31, 36, 48];
@@ -71,6 +129,13 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
text.insert(&std::fs::read_to_string(x).unwrap());
text.cursor = 0;
});
+ let mut hist = Hist {
+ history: vec![],
+ redo_history: vec![],
+ last: text.clone(),
+ last_edit: Instant::now(),
+ changed: false,
+ };
macro_rules! save {
() => {{
std::fs::write(
@@ -268,7 +333,6 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
} => {
let met = FONT.metrics(&[]);
let fac = ppem / met.units_per_em as f32;
- dbg!(cursor_position);
cursor_position = (
(position.x / (fw) as f64).round() as usize,
(position.y / (fh + ls * fac) as f64).floor()
@@ -363,6 +427,15 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
Some(Do::Edit) => {
handle2(event.logical_key, &mut text);
text.scroll_to_cursor();
+ hist.record(&text);
+ }
+ Some(Do::Undo) => {
+ hist.test_push(&text);
+ hist.undo().map(|x| x.apply(&mut text, false));
+ }
+ Some(Do::Redo) => {
+ hist.test_push(&text);
+ hist.redo().map(|x| x.apply(&mut text, true));
}
Some(Do::Quit) => elwt.exit(),
Some(Do::StartSelection) => {
@@ -383,24 +456,30 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
text.scroll_to_cursor();
}
Some(Do::Insert((x, c))) => {
+ hist.push(&text);
text.rope.remove(x.clone());
text.cursor = x.start;
text.setc();
text.insert_(c);
}
Some(Do::Delete(x)) => {
+ hist.push(&text);
+ text.cursor = x.start;
text.rope.remove(x);
}
Some(Do::Copy(x)) => {
clipp::copy(text.rope.slice(x).to_string());
}
Some(Do::Cut(x)) => {
+ hist.push(&text);
clipp::copy(
text.rope.slice(x.clone()).to_string(),
);
- text.rope.remove(x);
+ text.rope.remove(x.clone());
+ text.cursor = x.start;
}
Some(Do::Paste) => {
+ hist.push(&text);
text.insert(&clipp::paste());
}
Some(
@@ -499,6 +578,8 @@ Default => {
K(Key::Character(x) if x == "s" && ctrl()) => Save [Save],
K(Key::Character(x) if x == "q" && ctrl()) => Dead [Quit],
K(Key::Character(x) if x == "v" && ctrl()) => Default [Paste],
+ K(Key::Character(x) if x == "z" && ctrl()) => Default [Undo],
+ K(Key::Character(x) if x == "y" && ctrl()) => Default [Redo],
K(Key::Named(NamedKey::ArrowUp | NamedKey::ArrowLeft | NamedKey::ArrowDown | NamedKey::ArrowRight | NamedKey::Home | NamedKey::End) if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
M(MouseButton => MouseButton::Left if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
M(MouseButton => MouseButton::Left) => Default [MoveCursor],
@@ -524,3 +605,17 @@ Save => {
InputFname(t) => K(Key::Named(NamedKey::Enter)) => Default [SaveTo(String => t.rope.to_string())],
InputFname(t) => K(k) => InputFname(TextArea => handle(k, t)),
}
+#[test]
+fn x() {
+ let d = diff_match_patch_rs::DiffMatchPatch::new();
+ let diffs = d
+ .diff_main::<Efficient>("previous state th", "previous state")
+ .unwrap();
+ dbg!(&diffs);
+
+ let patch = d.patch_make(PatchInput::new_diffs(&diffs)).unwrap();
+ let x = d.patch_apply(&patch, "previous state th").unwrap().0;
+ assert_eq!(x, "previous state");
+ // diff = -th
+ // undo = previous state
+}
diff --git a/src/text.rs b/src/text.rs
index f8b9ee4..64ab23b 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -3,6 +3,7 @@ use std::ops::Range;
use std::sync::LazyLock;
use atools::Chunked;
+use diff_match_patch_rs::{DiffMatchPatch, Patch, Patches};
use dsb::Cell;
use dsb::cell::Style;
use ropey::Rope;
@@ -34,13 +35,36 @@ const fn color(x: &[u8; 6]) -> [u8; 3] {
|[a, b]| a * 16 + b
)
}
+#[derive(Clone)]
+pub struct Diff {
+ pub changes: (Patches<u8>, Patches<u8>),
+ pub data: [(usize, usize, usize); 2],
+}
+
+impl Diff {
+ pub fn apply(self, t: &mut TextArea, redo: bool) {
+ let d = DiffMatchPatch::new();
+ t.rope = Rope::from_str(
+ &d.patch_apply(
+ &if redo { self.changes.1 } else { self.changes.0 },
+ &t.rope.to_string(),
+ )
+ .unwrap()
+ .0,
+ );
+ let (cu, co, vo) = self.data[redo as usize];
+ t.cursor = cu;
+ t.column = co;
+ t.vo = vo;
+ }
+}
#[derive(Default)]
pub struct TextArea {
pub rope: Rope,
pub cursor: usize,
highlighter: Highlighter,
- column: usize,
+ pub column: usize,
/// ┌─────────────────┐
/// │#invisible text │
/// │╶╶╶view offset╶╶╶│