A simple CPU rendered GUI IDE experience.
redo history
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/edi.rs | 104 | ||||
| -rw-r--r-- | src/edi/st.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 146 | ||||
| -rw-r--r-- | src/rnd.rs | 2 | ||||
| -rw-r--r-- | src/text.rs | 112 | ||||
| -rw-r--r-- | src/text/hist.rs | 240 | ||||
| -rw-r--r-- | src/text/semantic_tokens.rs | 95 | ||||
| -rw-r--r-- | src/text/semantic_tokens/theme.rs | 95 | ||||
| -rw-r--r-- | src/text/theme_treesitter.rs | 20 |
10 files changed, 456 insertions, 361 deletions
@@ -22,7 +22,6 @@ run_times = "0.1.0" array_chunks = "1.0.0" rust-fsm = { git = "https://git.bendn.org/rust-fsm", features = ["diagram"] } clipp = "0.1.0" -diff-match-patch-rs = { git = "https://git.bendn.org/dmp" } regex = { version = "1.11.3", features = ["unstable", "use_std"] } tree-house = { version = "0.3.0", features = ["fixtures"] } @@ -34,12 +34,13 @@ use crate::lsp::{ use crate::meta::META; use crate::sym::{Symbols, SymbolsType}; use crate::text::cursor::{Ronge, ceach}; +use crate::text::hist::{ClickHistory, Hist}; use crate::text::{ self, CoerceOption, Mapping, RopeExt, SortTedits, TextArea, }; use crate::{ - BoolRequest, CDo, ClickHistory, CompletionAction, CompletionState, - Hist, act, alt, ctrl, filter, hash, shift, sig, sym, trm, + BoolRequest, CDo, CompletionAction, CompletionState, act, alt, ctrl, + filter, hash, shift, sig, sym, trm, }; #[allow(dead_code)] pub fn serialize_tokens<S: serde::Serializer>( @@ -308,7 +309,8 @@ impl Editor { me.tree = t; } else { me.lsp = l; - me.hist.last = me.text.clone(); + me.hist.lc = me.text.cursor.clone(); + me.hist.last = me.text.changes.clone(); me.lsp.as_ref().zip(me.origin.as_deref()).map( |((c, ..), origin)| { c.open( @@ -375,7 +377,7 @@ impl Editor { // self.text.cursor = // self.text.cursor.min(self.text.rope.len_chars()); change!(self); - self.hist.push(&self.text); + self.hist.push(&mut self.text); l.notify::<lsp_notification!("textDocument/didSave")>( &DidSaveTextDocumentParams { text_document: o.tid(), @@ -510,9 +512,9 @@ impl Editor { } Some(Do::StartSelection) => { let x = self.text.mapped_index_at(cursor_position); - self.hist.last.cursor.first_mut().position = x; self.text.cursor.first_mut().position = x; self.text.cursor.first_mut().sel = Some((x..x).into()); + self.hist.lc = self.text.cursor.clone(); } Some(Do::Hover) if let Some(hover) = @@ -756,38 +758,23 @@ impl Editor { ), ); } - self.hist.last.cursor = text.cursor.clone(); + self.hist.lc = text.cursor.clone(); self.chist.push(text.primary_cursor()); text.cursor.first().setc(&text.rope); } - Some(Do::NavForward) => { - self.chist.forth().map(|x| { - text.cursor.just( - text.rope.line_to_char(x.1) + x.0, - &text.rope, - ); - text.scroll_to_cursor(); - }); - } - Some(Do::NavBack) => { - self.chist.back().map(|x| { - text.cursor.just( - text.rope.line_to_char(x.1) + x.0, - &text.rope, - ); - text.scroll_to_cursor(); - }); - } + Some(Do::NavForward) => self.nav_forward(), + Some(Do::NavBack) => self.nav_back(), Some(Do::ExtendSelectionToMouse) => { let p = text.mapped_index_at(cursor_position); text.cursor.first_mut().extend_selection_to(p, &text.rope); } Some(Do::StartSelection) => { let p = text.mapped_index_at(cursor_position); - self.hist.last.cursor.just(p, &text.rope); + let x = *text.cursor.first(); text.cursor.first_mut().sel = Some((x..x).into()); text.cursor.first_mut().extend_selection_to(p, &text.rope); + self.hist.lc = text.cursor.clone(); } Some(Do::GoToDefinition) => { if let Some(LocationLink { @@ -814,7 +801,7 @@ impl Editor { text.mapped_index_at(cursor_position), &text.rope, ); - self.hist.last.cursor = text.cursor.clone(); + self.hist.lc = text.cursor.clone(); self.chist.push(text.primary_cursor()); text.cursor.first().setc(&text.rope); } @@ -822,6 +809,24 @@ impl Editor { _ => unreachable!(), } } + pub fn nav_back(&mut self) { + self.chist.back().map(|x| { + self.text.cursor.just( + self.text.rope.line_to_char(x.1) + x.0, + &self.text.rope, + ); + self.text.scroll_to_cursor(); + }); + } + pub fn nav_forward(&mut self) { + self.chist.forth().map(|x| { + self.text.cursor.just( + self.text.rope.line_to_char(x.1) + x.0, + &self.text.rope, + ); + self.text.scroll_to_cursor(); + }); + } pub fn scroll(&mut self, rows: f32) { let rows = if alt() { rows * 8. } else { rows * 3. }; let (vo, max) = lower::saturating::math! { if let Some(x)= &mut self.requests.hovering.result && shift() { @@ -1113,8 +1118,8 @@ impl Editor { let Some(act) = c.right() else { break 'out }; let act = act.clone(); self.state = State::Default; - self.hist.last.cursor = self.text.cursor.clone(); - self.hist.test_push(&self.text); + self.hist.lc = self.text.cursor.clone(); + self.hist.test_push(&mut self.text); let act = lsp .runtime .block_on( @@ -1142,14 +1147,14 @@ impl Editor { }; c.up(); } + Some(Do::NavBack) => self.nav_back(), + Some(Do::NavForward) => self.nav_forward(), Some( Do::Reinsert | Do::GoToDefinition | Do::MoveCursor | Do::ExtendSelectionToMouse | Do::Hover - | Do::NavBack - | Do::NavForward | Do::InsertCursorAtMouse, ) => panic!(), Some(Do::Save) => match &self.origin { @@ -1167,7 +1172,7 @@ impl Editor { } Some(Do::Edit) => { self.text.cursor.clear_selections(); - self.hist.test_push(&self.text); + self.hist.test_push(&mut self.text); let cb4 = self.text.cursor.first(); if let Key::Named(Enter | ArrowUp | ArrowDown | Tab) = event.logical_key @@ -1366,14 +1371,14 @@ impl Editor { }); } Some(Do::Undo) => { - self.hist.test_push(&self.text); - self.hist.undo(&mut self.text); + self.hist.test_push(&mut self.text); + self.hist.undo(&mut self.text).unwrap(); self.bar.last_action = "undid".to_string(); change!(self, window.clone()); } Some(Do::Redo) => { - self.hist.test_push(&self.text); - self.hist.redo(&mut self.text); + self.hist.test_push(&mut self.text); + self.hist.redo(&mut self.text).unwrap(); self.bar.last_action = "redid".to_string(); change!(self, window.clone()); } @@ -1420,7 +1425,7 @@ impl Editor { } Some(Do::Insert(c)) => { // self.text.cursor.inner.clear(); - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); ceach!(self.text.cursor, |cursor| { let Some(r) = cursor.sel else { return }; _ = self.text.remove(r.into()); @@ -1428,22 +1433,22 @@ impl Editor { }); self.text.insert(&c); self.text.cursor.clear_selections(); - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); change!(self, window.clone()); } Some(Do::Delete) => { - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); ceach!(self.text.cursor, |cursor| { let Some(r) = cursor.sel else { return }; _ = self.text.remove(r.into()); }); self.text.cursor.clear_selections(); - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); change!(self, window.clone()); } Some(Do::Copy) => { - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); unsafe { take(&mut META) }; let mut clip = String::new(); self.text.cursor.each_ref(|x| { @@ -1461,11 +1466,11 @@ impl Editor { }; clipp::copy(clip); self.text.cursor.clear_selections(); - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); change!(self, window.clone()); } Some(Do::Cut) => { - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); unsafe { take(&mut META) }; let mut clip = String::new(); self.text.cursor.each_ref(|x| { @@ -1488,11 +1493,11 @@ impl Editor { } }); self.text.cursor.clear_selections(); - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); change!(self, window.clone()); } Some(Do::Paste) => { - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); let r = clipp::paste(); if unsafe { META.hash == hash(&r) } { let bounds = unsafe { &*META.splits }; @@ -1525,7 +1530,7 @@ impl Editor { } else { self.text.insert(&clipp::paste()); } - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); change!(self, window.clone()); } Some(Do::OpenFile(x)) => { @@ -1566,7 +1571,7 @@ impl Editor { inlay!(self); } Some(Do::Boolean(BoolRequest::ReloadFile, true)) => { - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); self.text.rope = Rope::from_str( &std::fs::read_to_string( self.origin.as_ref().unwrap(), @@ -1582,7 +1587,7 @@ impl Editor { .min(self.text.rope.len_chars()); self.mtime = Self::modify(self.origin.as_deref()); self.bar.last_action = "reloaded".into(); - self.hist.push(&self.text) + self.hist.push(&mut self.text) } Some(Do::Boolean(BoolRequest::ReloadFile, false)) => {} Some(Do::InsertCursor(dir)) => { @@ -1692,7 +1697,7 @@ impl Editor { self.files = f; self.bar.last_action = "restored".into(); if self.mtime != Self::modify(self.origin.as_deref()) { - self.hist.push_if_changed(&self.text); + self.hist.push_if_changed(&mut self.text); self.text.rope = Rope::from_str( &std::fs::read_to_string( self.origin.as_ref().unwrap(), @@ -1709,7 +1714,7 @@ impl Editor { self.mtime = Self::modify(self.origin.as_deref()); self.bar.last_action = "restored -> reloaded".into(); take(&mut self.requests); - self.hist.push(&self.text) + self.hist.push(&mut self.text) } self.lsp = lsp; @@ -1723,6 +1728,7 @@ impl Editor { let new = std::fs::read_to_string(&x)?; take(&mut self.text); self.text.insert(&new); + take(&mut self.text.changes); self.text.cursor.just(0, &self.text.rope); self.bar.last_action = "open".into(); self.mtime = Self::modify(self.origin.as_deref()); diff --git a/src/edi/st.rs b/src/edi/st.rs index 90c16fb..20fcfb3 100644 --- a/src/edi/st.rs +++ b/src/edi/st.rs @@ -57,6 +57,8 @@ Default => { M(MouseButton::Left if alt()) => _ [InsertCursorAtMouse], M(MouseButton::Left if ctrl()) => _ [GoToDefinition], M(MouseButton::Left) => _ [MoveCursor], + K(Key::Character(x) if x == "-" && ctrl()) => _ [NavBack], + K(Key::Character(x) if x == "=" && ctrl()) => _ [NavBack], M(MouseButton::Back) => _ [NavBack], M(MouseButton::Forward) => _ [NavForward], C(((usize, usize)) => .. if unsafe { CLICKING }) => Selection [StartSelection], diff --git a/src/main.rs b/src/main.rs index 7d2080e..a318719 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,11 +53,9 @@ use std::hash::Hash; use std::mem::MaybeUninit; use std::num::NonZeroU32; use std::sync::LazyLock; -use std::time::Instant; use Default::default; use NamedKey::*; -use diff_match_patch_rs::PatchInput; use dsb::cell::Style; use dsb::{Cell, F}; use fimg::Image; @@ -67,7 +65,7 @@ use lsp_types::*; use rust_fsm::StateMachine; use serde::{Deserialize, Serialize}; use swash::FontRef; -use winit::dpi::{LogicalSize, PhysicalSize}; +use winit::dpi::PhysicalSize; use winit::event::{ ElementState, Event, Ime, MouseButton, MouseScrollDelta, WindowEvent, }; @@ -79,7 +77,7 @@ use winit::window::Icon; use crate::edi::Editor; use crate::edi::st::*; use crate::lsp::RqS; -use crate::text::{Diff, TextArea, col, is_word}; +use crate::text::{TextArea, col, is_word}; mod bar; pub mod com; pub mod hov; @@ -97,133 +95,6 @@ fn main() { // lsp::x(); entry(EventLoop::new().unwrap()) } -pub fn serialize_debug<S: serde::Serializer, T: Debug>( - s: &T, - ser: S, -) -> Result<S::Ok, S::Error> { - ser.serialize_str(&format!("{s:?}")) -} -pub fn serialize_display<S: serde::Serializer, T: Display>( - s: &T, - ser: S, -) -> Result<S::Ok, S::Error> { - ser.serialize_str(&format!("{s}")) -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -struct Hist { - pub history: Vec<Diff>, - pub redo_history: Vec<Diff>, - pub last: TextArea, - #[serde( - skip_deserializing, - serialize_with = "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: TextArea::default(), - last_edit: Instant::now(), - changed: false, - } - } -} -#[derive(Debug, Default, Serialize, Deserialize)] -struct ClickHistory { - pub his: Vec<(usize, usize)>, - pub red: Vec<(usize, usize)>, -} - -impl ClickHistory { - fn push(&mut self, x: (usize, usize)) { - if self.his.last() != Some(&x) { - self.his.push(x); - self.red.clear(); - } - } - fn back(&mut self) -> Option<(usize, usize)> { - self.his.pop().inspect(|x| { - self.red.push(*x); - }) - } - fn forth(&mut self) -> Option<(usize, usize)> { - self.red.pop().inspect(|x| { - self.his.push(*x); - }) - } -} -impl Hist { - fn push(&mut self, x: &TextArea) { - let d = diff_match_patch_rs::DiffMatchPatch::new(); - self.history.push(Diff { - forth: d - .patch_make(PatchInput::new_text_text( - &x.rope.to_string(), - &self.last.rope.to_string(), - )) - .unwrap(), - back: d - .patch_make(PatchInput::new_text_text( - &self.last.rope.to_string(), - &x.rope.to_string(), - )) - .unwrap(), - data: [ - (self.last.cursor.clone(), self.last.vo), - (x.cursor.clone(), x.vo), - ], - }); - println!("push {}", self.history.last().unwrap()); - self.redo_history.clear(); - self.last = x.clone(); - 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) { - self.push_if_changed(t); - self.undo_().map(|x| { - x.apply(t, false); - self.last = t.clone(); - }); - } - pub fn redo(&mut self, t: &mut TextArea) { - self.redo_().map(|x| { - x.apply(t, true); - self.last = t.clone(); - }); - } - pub fn push_if_changed(&mut self, x: &TextArea) { - if self.changed || x.rope != self.last.rope { - self.push(x); - } - } - pub fn test_push(&mut self, x: &TextArea) { - if self.last_edit.elapsed().as_millis() > 500 { - self.push_if_changed(x); - } - } - pub fn record(&mut self, new: &TextArea) -> bool { - // self.test_push(x); - if new.rope != self.last.rope { - self.last_edit = Instant::now(); - self.changed = true; - } - new.rope != self.last.rope - } -} - static mut MODIFIERS: ModifiersState = ModifiersState::empty(); static mut CLICKING: bool = false; @@ -633,3 +504,16 @@ pub fn hash(x: &impl Hash) -> u64 { use std::hash::BuildHasher; rustc_hash::FxBuildHasher::default().hash_one(x) } + +pub fn serialize_debug<S: serde::Serializer, T: Debug>( + s: &T, + ser: S, +) -> Result<S::Ok, S::Error> { + ser.serialize_str(&format!("{s:?}")) +} +pub fn serialize_display<S: serde::Serializer, T: Display>( + s: &T, + ser: S, +) -> Result<S::Ok, S::Error> { + ser.serialize_str(&format!("{s}")) +} @@ -4,8 +4,8 @@ use std::sync::{Arc, LazyLock}; use std::time::Instant; use atools::prelude::*; -use dsb::cell::Style; use dsb::Cell; +use dsb::cell::Style; use fimg::pixels::Blend; use fimg::{Image, OverlayAt}; use lsp_types::*; diff --git a/src/text.rs b/src/text.rs index ddeb205..0ba3e3c 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,6 +1,6 @@ use std::cmp::{Reverse, min}; use std::collections::BTreeSet; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::iter::repeat_n; use std::ops::{Deref, Range, RangeBounds}; use std::path::Path; @@ -10,7 +10,6 @@ use std::vec::Vec; use anyhow::anyhow; use atools::prelude::*; -use diff_match_patch_rs::{DiffMatchPatch, Patches}; use dsb::Cell; use dsb::cell::Style; use helix_core::Syntax; @@ -27,29 +26,14 @@ use cursor::{Cursor, Cursors, ceach}; pub mod inlay; use inlay::{Inlay, Marking}; pub mod semantic_tokens; -use semantic_tokens::{TokenD, theme}; +use semantic_tokens::TokenD; +pub mod hist; +pub mod theme_treesitter; +use hist::Changes; use crate::lsp::Void; use crate::sni::{Snippet, StopP}; - -theme! { - "attribute" b"#ffd173", - "comment" b"#5c6773" Style::ITALIC, - "constant" b"#DFBFFF", - "function" b"#FFD173" Style::ITALIC, - "function.macro" b"#fbc351", - "variable.builtin" b"#FFAD66", - "keyword" b"#FFAD66" Style::ITALIC | Style::BOLD, - "number" b"#dfbfff", - "operator" b"#F29E74", - "punctuation" b"#cccac2", - "string" b"#D5FF80", - "tag" b"#5CCFE6" Style::ITALIC | Style::BOLD, - "type" b"#73D0FF" Style::ITALIC | Style::BOLD, - "variable" b"#cccac2", - "variable.parameter" b"#DFBFFF", - "namespace" b"#73d0ff", -} +use crate::text::hist::Action; pub const fn color_(x: &str) -> [u8; 3] { let Some(x): Option<[u8; 7]> = x.as_bytes().try_into().ok() else { @@ -83,68 +67,6 @@ macro_rules! col { ($(crate::text::col!($x),)+) }}; } -#[derive(Debug)] -struct E; -impl Display for E { - fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } -} - -fn from_t<'de, D>(de: D) -> Result<Patches<u8>, D::Error> -where - D: serde::Deserializer<'de>, -{ - let dmp = DiffMatchPatch::new(); - let s: &str = serde::de::Deserialize::deserialize(de)?; - let p = - dmp.patch_from_text(s).map_err(|_| serde::de::Error::custom(E))?; - Ok(p) -} -fn to_t<S: serde::Serializer>( - x: &Patches<u8>, - s: S, -) -> Result<S::Ok, S::Error> { - let dmp = DiffMatchPatch::new(); - s.serialize_str(&dmp.patch_to_text(x)) -} - -#[derive( - Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize, -)] -pub struct Diff { - #[serde(deserialize_with = "from_t", serialize_with = "to_t")] - pub forth: Patches<u8>, - #[serde(deserialize_with = "from_t", serialize_with = "to_t")] - pub back: Patches<u8>, - pub data: [(Cursors, usize); 2], -} - -impl Display for Diff { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let d = DiffMatchPatch::new(); - writeln!(f, "{}", d.patch_to_text(&self.back)) - } -} -impl Diff { - pub fn apply(self, t: &mut TextArea, redo: bool) { - let d = DiffMatchPatch::new(); - // println!("{}", d.patch_to_text(&self.changes.0)); - // causes great internal strife atm - t.rope = Rope::from_str( - &d.patch_apply( - &if redo { self.back } else { self.forth }, - &t.rope.to_string(), - ) - .unwrap() - .0, - ); - let (cu, _vo) = self.data[redo as usize].clone(); - t.cursor = cu; - t.scroll_to_cursor_centering(); - // t.vo = vo; - } -} pub fn deserialize_from_string<'de, D: serde::de::Deserializer<'de>>( de: D, @@ -182,7 +104,8 @@ pub struct TextArea { #[serde(skip)] pub tabstops: Option<Snippet>, pub inlays: BTreeSet<Inlay>, - pub tokens: Vec<TokenD>, // TODO: fixperf + pub tokens: Vec<TokenD>, + pub changes: Changes, } #[derive(Serialize, Deserialize)] pub struct CellBuffer { @@ -416,6 +339,15 @@ impl TextArea { } pub fn remove(&mut self, r: Range<usize>) -> Result<(), ropey::Error> { + let removed = self + .rope + .get_slice(r.clone()) + .ok_or(ropey::Error::CharIndexOutOfBounds(4, 4))? + .to_string(); + self.changes.inner.push(Action::Removed { + removed: Some(removed), + range: r.clone(), + }); self.rope.try_remove(r.clone())?; let manip = |x| { // if your region gets removed, what happens to your tabstop? big question. @@ -453,6 +385,9 @@ impl TextArea { with: &str, ) -> Result<(), ropey::Error> { self.rope.try_insert(c, with)?; + self.changes + .inner + .push(Action::Inserted { at: c, insert: with.to_string() }); let manip = |x| { if x < c { x } else { x + with.chars().count() } }; @@ -1056,7 +991,7 @@ pub fn is_word(r: char) -> bool { } pub static LOADER: LazyLock<Loader> = LazyLock::new(|| { let x = helix_core::config::default_lang_loader(); - x.set_scopes(NAMES.map(|x| x.to_string()).to_vec()); + x.set_scopes(theme_treesitter::NAMES.map(|x| x.to_string()).to_vec()); // x.languages().for_each(|(_, x)| { // x.syntax_config(&LOADER).map(|x| { @@ -1264,7 +1199,10 @@ pub fn hl( yield ( (x1, y1), (x2, y2), - (STYLES[h.idx()], COLORS[h.idx()]), + ( + theme_treesitter::STYLES[h.idx()], + theme_treesitter::COLORS[h.idx()], + ), (text.byte_slice(at..end)), ) } 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() + } +} diff --git a/src/text/semantic_tokens.rs b/src/text/semantic_tokens.rs index fa964e6..1fa0e70 100644 --- a/src/text/semantic_tokens.rs +++ b/src/text/semantic_tokens.rs @@ -3,99 +3,10 @@ use log::error; use lsp_types::{SemanticToken, SemanticTokensLegend}; use serde_derive::{Deserialize, Serialize}; -macro_rules! modified { - ($count:literal $($x:literal . $mod:literal $color:literal $($style:expr)?,)+ $(,)?) => { - pub const MODIFIED: [(&str, &str); $count] = [ - $(($x, $mod),)+ - ]; - pub const MCOLORS: [[u8;3]; MODIFIED.len()] = car::map!([$($color),+], |x| color(x)); - pub const MSTYLE: [u8; MODIFIED.len()] = [$(($($style, )? 0, ).0 ,)+]; - } -} -use super::color; -macro_rules! theme { - ($($x:literal $color:literal $($style:expr)?),+ $(,)?) => { - #[rustfmt::skip] - pub const NAMES: [&str; [$($x),+].len()] = [$($x),+]; - #[rustfmt::skip] - pub const COLORS: [[u8; 3]; NAMES.len()] = car::map!([$($color),+], |x| color(x)); - pub const STYLES: [u8; NAMES.len()] = [$( - ($($style, )? 0, ).0 - ),+]; - }; -} -pub(crate) use theme; -theme! { -"constructor" b"#FFAD66", -"field" b"#cccac2", +pub mod theme; +pub(crate) use theme::theme; +use theme::{COLORS, MCOLORS, MODIFIED, MSTYLE, NAMES, STYLES}; -"comment" b"#5c6773" Style::ITALIC, -// "decorator" b"#cccac2", -"function" b"#FFD173" Style::ITALIC, -"interface" b"#5CCFE6", -"keyword" b"#FFAD66" Style::ITALIC | Style::BOLD, -"macro" b"#fbc351" Style::BOLD, -"method" b"#FFD173" Style::ITALIC, -// "namespace" b"#cccac2", -"number" b"#dfbfff", -"operator" b"#F29E74", -// "property" b"#cccac2", -"string" b"#D5FF80", -// "struct" b"#cccac2", -// "typeParameter" b"#cccac2", -"class" b"#73b9ff", -"enumMember" b"#73b9ff", -"enum" b"#73b9ff" Style::ITALIC | Style::BOLD, -"builtinType" b"#73d0ff" Style::ITALIC, -// "type" b"#73d0ff" Style::ITALIC | Style::BOLD, -"typeAlias" b"#69caed" Style::ITALIC | Style::BOLD, -"struct" b"#73d0ff" Style::ITALIC | Style::BOLD, -"variable" b"#cccac2", -// "angle" b"#cccac2", -// "arithmetic" b"#cccac2", -// "attributeBracket" b"#cccac2", -"parameter" b"#DFBFFF", -"namespace" b"#73d0ff", -// "attributeBracket" b"#cccac2", -// "attribute" b"#cccac2", -// "bitwise" b"#cccac2", -// "boolean" b"#cccac2", -// "brace" b"#cccac2", -// "bracket" b"#cccac2", -// "builtinAttribute" b"#cccac2", -// "character" b"#cccac2", -// "colon" b"#cccac2", -// "comma" b"#cccac2", -// "comparison" b"#cccac2", -// "constParameter" b"#cccac2", -"const" b"#DFBFFF", -// "deriveHelper" b"#cccac2", -// "derive" b"#cccac2", -// "dot" b"#cccac2", -// "escapeSequence" b"#cccac2", -// "formatSpecifier" b"#cccac2", -// "generic" b"#cccac2", -// "invalidEscapeSequence" b"#cccac2", -// "label" b"#cccac2", -// "lifetime" b"#cccac2", -// "logical" b"#cccac2", -"macroBang" b"#f28f74", -// "parenthesis" b"#cccac2", -// "procMacro" b"#cccac2", -// "punctuation" b"#cccac2", -"selfKeyword" b"#FFAD66" Style::ITALIC | Style::BOLD, -"selfTypeKeyword" b"#FFAD66" Style::ITALIC | Style::BOLD, -// "semicolon" b"#cccac2", -// "static" b"#cccac2", -// "toolModule" b"#cccac2", -// "union" b"#cccac2", -// "unresolvedReference" b"#cccac2", -} - -modified! { 2 - "function" . "unsafe" b"#F28779", - "variable" . "mutable" b"#e6dab6", -} use crate::text::TextArea; #[derive( diff --git a/src/text/semantic_tokens/theme.rs b/src/text/semantic_tokens/theme.rs new file mode 100644 index 0000000..6be578c --- /dev/null +++ b/src/text/semantic_tokens/theme.rs @@ -0,0 +1,95 @@ +use dsb::cell::Style; + +macro_rules! theme { + ($($x:literal $color:literal $($style:expr)?),+ $(,)?) => { + #[rustfmt::skip] + pub const NAMES: [&str; [$($x),+].len()] = [$($x),+]; + #[rustfmt::skip] + pub const COLORS: [[u8; 3]; NAMES.len()] = car::map!([$($color),+], |x| crate::text::color(x)); + pub const STYLES: [u8; NAMES.len()] = [$( + ($($style, )? 0, ).0 + ),+]; + }; +} + +macro_rules! modified { + ($count:literal $($x:literal . $mod:literal $color:literal $($style:expr)?,)+ $(,)?) => { + pub const MODIFIED: [(&str, &str); $count] = [ + $(($x, $mod),)+ + ]; + pub const MCOLORS: [[u8;3]; MODIFIED.len()] = car::map!([$($color),+], |x| crate::text::color(x)); + pub const MSTYLE: [u8; MODIFIED.len()] = [$(($($style, )? 0, ).0 ,)+]; + } +} +pub(crate) use theme; +theme! { +"constructor" b"#FFAD66", +"field" b"#cccac2", + +"comment" b"#5c6773" Style::ITALIC, +// "decorator" b"#cccac2", +"function" b"#FFD173" Style::ITALIC, +"interface" b"#5CCFE6", +"keyword" b"#FFAD66" Style::ITALIC | Style::BOLD, +"macro" b"#fbc351" Style::BOLD, +"method" b"#FFD173" Style::ITALIC, +// "namespace" b"#cccac2", +"number" b"#dfbfff", +"operator" b"#F29E74", +// "property" b"#cccac2", +"string" b"#D5FF80", +// "struct" b"#cccac2", +// "typeParameter" b"#cccac2", +"class" b"#73b9ff", +"enumMember" b"#73b9ff", +"enum" b"#73b9ff" Style::ITALIC | Style::BOLD, +"builtinType" b"#73d0ff" Style::ITALIC, +// "type" b"#73d0ff" Style::ITALIC | Style::BOLD, +"typeAlias" b"#69caed" Style::ITALIC | Style::BOLD, +"struct" b"#73d0ff" Style::ITALIC | Style::BOLD, +"variable" b"#cccac2", +// "angle" b"#cccac2", +// "arithmetic" b"#cccac2", +// "attributeBracket" b"#cccac2", +"parameter" b"#DFBFFF", +"namespace" b"#73d0ff", +// "attributeBracket" b"#cccac2", +// "attribute" b"#cccac2", +// "bitwise" b"#cccac2", +// "boolean" b"#cccac2", +// "brace" b"#cccac2", +// "bracket" b"#cccac2", +// "builtinAttribute" b"#cccac2", +// "character" b"#cccac2", +// "colon" b"#cccac2", +// "comma" b"#cccac2", +// "comparison" b"#cccac2", +// "constParameter" b"#cccac2", +"const" b"#DFBFFF", +// "deriveHelper" b"#cccac2", +// "derive" b"#cccac2", +// "dot" b"#cccac2", +// "escapeSequence" b"#cccac2", +// "formatSpecifier" b"#cccac2", +// "generic" b"#cccac2", +// "invalidEscapeSequence" b"#cccac2", +// "label" b"#cccac2", +// "lifetime" b"#cccac2", +// "logical" b"#cccac2", +"macroBang" b"#f28f74", +// "parenthesis" b"#cccac2", +// "procMacro" b"#cccac2", +// "punctuation" b"#cccac2", +"selfKeyword" b"#FFAD66" Style::ITALIC | Style::BOLD, +"selfTypeKeyword" b"#FFAD66" Style::ITALIC | Style::BOLD, +// "semicolon" b"#cccac2", +// "static" b"#cccac2", +// "toolModule" b"#cccac2", +// "union" b"#cccac2", +// "unresolvedReference" b"#cccac2", +} + +modified! { 2 + "function" . "unsafe" b"#F28779", + "variable" . "mutable" b"#e6dab6", +} diff --git a/src/text/theme_treesitter.rs b/src/text/theme_treesitter.rs new file mode 100644 index 0000000..89813ad --- /dev/null +++ b/src/text/theme_treesitter.rs @@ -0,0 +1,20 @@ +use dsb::cell::Style; + +super::semantic_tokens::theme! { + "attribute" b"#ffd173", + "comment" b"#5c6773" Style::ITALIC, + "constant" b"#DFBFFF", + "function" b"#FFD173" Style::ITALIC, + "function.macro" b"#fbc351", + "variable.builtin" b"#FFAD66", + "keyword" b"#FFAD66" Style::ITALIC | Style::BOLD, + "number" b"#dfbfff", + "operator" b"#F29E74", + "punctuation" b"#cccac2", + "string" b"#D5FF80", + "tag" b"#5CCFE6" Style::ITALIC | Style::BOLD, + "type" b"#73D0FF" Style::ITALIC | Style::BOLD, + "variable" b"#cccac2", + "variable.parameter" b"#DFBFFF", + "namespace" b"#73d0ff", +} |