A simple CPU rendered GUI IDE experience.
git gutter
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | src/edi.rs | 61 | ||||
| -rw-r--r-- | src/edi/st.rs | 4 | ||||
| -rw-r--r-- | src/git.rs | 30 | ||||
| -rw-r--r-- | src/lsp.rs | 1 | ||||
| -rw-r--r-- | src/main.rs | 3 | ||||
| -rw-r--r-- | src/rnd.rs | 90 | ||||
| -rw-r--r-- | src/text.rs | 7 |
8 files changed, 154 insertions, 45 deletions
@@ -63,6 +63,9 @@ niri = { package = "niri-ipc", version = "25.11.0" } libc = "0.2.180" rustc-hash = "=2.1.1" bendy = { version = "0.6.1", features = ["serde"] } +git2 = "0.20.4" +imara-diff = "0.2.0" +vecto = "0.1.1" [profile.dev.package] rust-analyzer.opt-level = 3 @@ -110,6 +110,8 @@ pub struct Requests { (usize, usize), RequestError<lsp_request!("textDocument/definition")>, >, + #[serde(skip)] + pub git_diff: Rq<imara_diff::Diff, imara_diff::Diff, (), ()>, } #[derive( Default, Debug, serde_derive::Serialize, serde_derive::Deserialize, @@ -136,6 +138,9 @@ pub struct Editor { pub chist: ClickHistory, pub hist: Hist, pub mtime: Option<std::time::SystemTime>, + // #[serde(skip)] + // pub git_diff: + // Option<std::rc::Rc<std::cell::RefCell<imara_diff::Diff>>>, } macro_rules! lsp { ($self:ident) => { @@ -158,15 +163,37 @@ macro_rules! inlay { } macro_rules! change { ($self:ident) => { + change!(@$self, None) + }; + ($self:ident, $w:expr) => { + change!(@$self, Some($w)) + }; + (@$self:ident, $w:expr) => { lsp!($self + p).map(|(x, origin)| { x.edit(&origin, $self.text.rope.to_string()).unwrap(); x.rq_semantic_tokens( &mut $self.requests.semantic_tokens, origin, - None, + $w, ) .unwrap(); inlay!($self); + let o_ = $self.origin.clone(); + let w = $self.workspace.clone(); + let r = $self.text.rope.clone(); + let t = + x.runtime.spawn_blocking(move || { + try { + crate::git::diff( + o_?.strip_prefix(w.as_deref()?).ok()?, + &w?, + &r, + ) + .ok()? + } + .ok_or(()) + }); + $self.requests.git_diff.request(t); }); }; } @@ -459,6 +486,7 @@ impl Editor { &r, ); self.requests.hovering.poll(|x, _| x.ok().flatten(), &r); + self.requests.git_diff.poll(|x, _| x.ok(), &r); } #[implicit_fn] pub fn cursor_moved( @@ -830,11 +858,11 @@ impl Editor { take(&mut self.requests.sig_help); self.text.cursor.alone(); } - Some(Do::Comment) => { + Some(Do::Comment(p)) => { ceach!(self.text.cursor, |cursor| { Some( if let Some(x) = cursor.sel - && matches!(self.state, State::Selection) + && matches!(p, State::Selection) { self.text.comment(x.into()); } else { @@ -844,7 +872,7 @@ impl Editor { ) }); self.text.cursor.clear_selections(); - change!(self); + change!(self, window.clone()); } Some(Do::SpawnTerminal) => { trm::toggle( @@ -960,8 +988,10 @@ impl Editor { if Some(&f) != self.origin.as_ref() { self.open(&f, window.clone())?; } - let p = self.text - .l_position(x.location.range.start).ok_or(anyhow::anyhow!("rah"))?; + let p = self + .text + .l_position(x.location.range.start) + .ok_or(anyhow::anyhow!("rah"))?; if p != 0 { self.text.cursor.just(p, &self.text.rope); } @@ -1186,7 +1216,7 @@ impl Editor { if self.hist.record(&self.text) && let Some((lsp, path)) = lsp!(self + p) { - change!(self); + change!(self, window.clone()); } lsp!(self + p).map(|(lsp, o)| { let window = window.clone(); @@ -1316,7 +1346,7 @@ impl Editor { } } if self.hist.record(&self.text) { - change!(self); + change!(self, window.clone()); } self.requests.sig_help = Rq::new( lsp.runtime.spawn( @@ -1340,13 +1370,13 @@ impl Editor { self.hist.test_push(&self.text); self.hist.undo(&mut self.text); self.bar.last_action = "undid".to_string(); - change!(self); + change!(self, window.clone()); } Some(Do::Redo) => { self.hist.test_push(&self.text); self.hist.redo(&mut self.text); self.bar.last_action = "redid".to_string(); - change!(self); + change!(self, window.clone()); } Some(Do::Quit) => return ControlFlow::Break(()), Some(Do::SetCursor(x)) => { @@ -1400,7 +1430,7 @@ impl Editor { self.text.insert(&c).unwrap(); self.text.cursor.clear_selections(); self.hist.push_if_changed(&self.text); - change!(self); + change!(self, window.clone()); } Some(Do::Delete) => { self.hist.push_if_changed(&self.text); @@ -1410,7 +1440,7 @@ impl Editor { }); self.text.cursor.clear_selections(); self.hist.push_if_changed(&self.text); - change!(self); + change!(self, window.clone()); } Some(Do::Copy) => { @@ -1433,7 +1463,7 @@ impl Editor { clipp::copy(clip); self.text.cursor.clear_selections(); self.hist.push_if_changed(&self.text); - change!(self); + change!(self, window.clone()); } Some(Do::Cut) => { self.hist.push_if_changed(&self.text); @@ -1460,7 +1490,7 @@ impl Editor { }); self.text.cursor.clear_selections(); self.hist.push_if_changed(&self.text); - change!(self); + change!(self, window.clone()); } Some(Do::Paste) => { self.hist.push_if_changed(&self.text); @@ -1497,7 +1527,7 @@ impl Editor { self.text.insert(&clipp::paste()).unwrap(); } self.hist.push_if_changed(&self.text); - change!(self); + change!(self, window.clone()); } Some(Do::OpenFile(x)) => { _ = self.open(Path::new(&x), window.clone()); @@ -1718,6 +1748,7 @@ impl Editor { } Ok(()) } + pub fn store(&mut self) -> anyhow::Result<()> { let ws = self.workspace.clone(); let tree = self.tree.clone(); diff --git a/src/edi/st.rs b/src/edi/st.rs index 62b57b3..882f2fe 100644 --- a/src/edi/st.rs +++ b/src/edi/st.rs @@ -47,7 +47,7 @@ Default => { K(Key::Character(x) if x == "." && ctrl()) => _ [CodeAction], K(Key::Character(x) if x == "0" && ctrl()) => _ [MatchingBrace], K(Key::Character(x) if x == "`" && ctrl()) => _ [SpawnTerminal], - K(Key::Character(y) if y == "/" && ctrl()) => Default [Comment], + K(Key::Character(y) if y == "/" && ctrl()) => Default [Comment(State => State::Default)], K(Key::Named(F1)) => Procure((default(), InputRequest::RenameSymbol)), K(Key::Named(k @ (ArrowUp | ArrowDown)) if alt()) => _ [InsertCursor(Direction => { if k == ArrowUp {Direction::Above} else { Direction::Below } @@ -104,7 +104,7 @@ Selection => { K(Key::Named(Backspace)) => Default [Delete], K(Key::Character(y) if y == "x" && ctrl()) => Default [Cut], K(Key::Character(y) if y == "c" && ctrl()) => Default [Copy], - K(Key::Character(y) if y == "/" && ctrl()) => Default [Comment], + K(Key::Character(y) if y == "/" && ctrl()) => Default [Comment(State::Selection)], M(_) => _, K(Key::Character(y) if !ctrl()) => Default [Insert(SmolStr => y)], @@ -1 +1,31 @@ +use std::path::Path; +use git2::Repository; +use imara_diff::InternedInput; +use ropey::Rope; + +pub fn load(p: &Path, ws: &Path) -> Result<Vec<u8>, git2::Error> { + let r = Repository::open(ws).unwrap(); + let o = + r.head()?.peel_to_commit()?.tree()?.get_path(p)?.to_object(&r)?; + let blob = o + .as_blob() + .ok_or(git2::Error::from_str("failure to blob"))? + .content() + .to_vec(); + Ok(blob) +} + +pub fn diff( + p: &Path, + ws: &Path, + now: &Rope, +) -> Result<imara_diff::Diff, anyhow::Error> { + let f = String::from_utf8(load(p, ws)?)?; + let now = now.to_string(); + let i = InternedInput::new(&*f, &now); + let mut diff = + imara_diff::Diff::compute(imara_diff::Algorithm::Myers, &i); + diff.postprocess_lines(&i); + Ok(diff) +} @@ -595,6 +595,7 @@ impl Client { SemanticTokensResult::Tokens(x) => x.data.into_boxed_slice(), }; + w.map(|x| x.request_redraw()); Ok(r) }); diff --git a/src/main.rs b/src/main.rs index 2c921d8..0f63484 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,9 +38,8 @@ #![allow(incomplete_features, irrefutable_let_patterns, static_mut_refs)] mod act; mod edi; -mod meta; -// mod new; mod git; +mod meta; mod rnd; mod sym; mod trm; @@ -75,7 +75,13 @@ pub fn render( letter: None, }); let x = match &ed.state { - State::Selection => Some(text.cursor.iter().filter_map(|x| x.sel).map(std::ops::Range::from).collect()), + State::Selection => Some( + text.cursor + .iter() + .filter_map(|x| x.sel) + .map(std::ops::Range::from) + .collect(), + ), _ => None, }; text.line_numbers( @@ -130,7 +136,7 @@ pub fn render( x.style.flags |= Style::UNDERLINE; x.style.fg = col!("#FFD173"); }); - } } + } } if let Some((lsp, p)) = lsp_m!(ed + p) && let uri = Url::from_file_path(p).unwrap() && let Some(diag) = lsp.diagnostics.get(&uri, &lsp.diagnostics.guard()) { #[derive(Copy, Clone, Debug)] enum EType { @@ -263,6 +269,44 @@ pub fn render( ) }; + let text = text.clone(); + if let Some(d) = &ed.requests.git_diff.result { + for h in d.hunks() { + // let b = text.rope.byte_to_line(h.after.start as _); + if h.after.start == h.after.end && (text.vo..text.vo + r).contains(&(h.after.start as usize)) { + let l = h.after.start - text.vo as u32; + let f = fh + ls * fac; + i.tri::<f32>( + (0.0f32, (l as f32 - 0.15) * f - (ls * fac)), + (6.0f32, (l as f32) * f - (ls * fac)), + (0.0f32, (l as f32 + 0.15) * f - (ls * fac)), + col!("#F27983") + ); + } + for l in h.after.clone() { + if (text.vo..text.vo + r).contains(&(l as usize)) { + let l = l - text.vo as u32; + let col = if h.is_pure_insertion() { + col!("#87D96C") + } else { + col!("#80BFFF") + }; + for x in 0..(fw / 2.0) as u32 { + for y in (l as f32 * (fh + ls * fac)) as u32 + ..(((l + 1) as f32) * (fh + ls * fac)) + as u32 + { + if let Some(x) = i.get_pixel_mut(x, y) { + *x = col; + } + // unsafe { i.pixel(x, y, &col)}; + } + } + } + } + } + } + let mut place_around = |(_x, _y): (usize, usize), i: Image<&mut [u8], 3>, c: &[Cell], @@ -378,10 +422,10 @@ pub fn render( (95, (r.saturating_sub(5)) as _), false, ); - for b in simplify_path(&x - .replace('\n', "\r\n") - .replace("⸬", ":")) - .bytes() + for b in simplify_path( + &x.replace('\n', "\r\n").replace("⸬", ":"), + ) + .bytes() { t.rx( b, @@ -735,12 +779,10 @@ pub fn render( ); } }; - if matches!(ed.state, State::Default | State::Selection) { - - } + if matches!(ed.state, State::Default | State::Selection) {} text.cursor.each_ref(|c| { - let(x,y)=text.visual_xy(*c).unwrap(); - draw_at(x, y, &cursor); + let (x, y) = text.visual_xy(*c).unwrap(); + draw_at(x, y, &cursor); }); // let (x, y) = text.cursor_visual(); let image = Image::<_, 4>::build(2, (fh).ceil() as u32) @@ -753,7 +795,7 @@ pub fn render( }; draw_at(x, y, &image); } - + window.pre_present_notify(); let buffer = surface.buffer_mut().unwrap(); let x = unsafe { @@ -770,15 +812,17 @@ pub fn render( } pub fn simplify_path(x: &str) -> String { - static DEP: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\.cargo\/git\/checkouts\/(?<name>[^/]+)\-[a-f0-9]+\/[a-f0-9]+").unwrap()); - static DEP2: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\.cargo\/registry\/src/index.crates.io-[0-9a-f]+/(?<name>[^/]+)\-(?<version>[0-9]+\.[0-9]+\.[0-9]+)").unwrap()); - static RUST_SRC: LazyLock<Regex> = LazyLock::new(|| Regex::new(r".rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library").unwrap()); - let x = x.replace(env!("HOME"), " "); - [ - (&*RUST_SRC, " "), - (&*DEP, " /$name"), - (&*DEP2, " /$name"), - ].into_iter().fold(x, |acc, (r, repl)| { - r.replace(&acc, repl).into_owned() - }) + static DEP: LazyLock<Regex> = LazyLock::new(|| { + Regex::new(r"\.cargo\/git\/checkouts\/(?<name>[^/]+)\-[a-f0-9]+\/[a-f0-9]+").unwrap() + }); + static DEP2: LazyLock<Regex> = LazyLock::new(|| { + Regex::new(r"\.cargo\/registry\/src/index.crates.io-[0-9a-f]+/(?<name>[^/]+)\-(?<version>[0-9]+\.[0-9]+\.[0-9]+)").unwrap() + }); + static RUST_SRC: LazyLock<Regex> = LazyLock::new(|| { + Regex::new(r".rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library").unwrap() + }); + let x = x.replace(env!("HOME"), " "); + [(&*RUST_SRC, " "), (&*DEP, " /$name"), (&*DEP2, " /$name")] + .into_iter() + .fold(x, |acc, (r, repl)| r.replace(&acc, repl).into_owned()) } diff --git a/src/text.rs b/src/text.rs index 580dab3..26c6e5a 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1143,10 +1143,11 @@ impl TextArea { self.tree_sit(path, &mut cells); } if let Some(tabstops) = &self.tabstops { - for (_, tabstop) in - tabstops.stops.iter().skip(tabstops.index - 1) + for [a, b] in + tabstops.stops.iter().skip(tabstops.index - 1).flat_map( + |(_, tabstop)| self.visual_position(tabstop.r()), + ) { - let [a, b] = self.visual_position(tabstop.r()).unwrap(); for char in cells.get_range(a, b) { char.style.bg = [55, 86, 81]; } |