A simple CPU rendered GUI IDE experience.
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/bar.rs | 17 | ||||
| -rw-r--r-- | src/main.rs | 103 | ||||
| -rw-r--r-- | src/text.rs | 26 |
4 files changed, 129 insertions, 19 deletions
@@ -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 = "*" @@ -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╶╶╶│ |