A simple CPU rendered GUI IDE experience.
git gutter
bendn 10 days ago
parent 2d0c081 · commit d6ba699
-rw-r--r--Cargo.toml3
-rw-r--r--src/edi.rs61
-rw-r--r--src/edi/st.rs4
-rw-r--r--src/git.rs30
-rw-r--r--src/lsp.rs1
-rw-r--r--src/main.rs3
-rw-r--r--src/rnd.rs90
-rw-r--r--src/text.rs7
8 files changed, 154 insertions, 45 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 5bfcaf2..4e209e9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
diff --git a/src/edi.rs b/src/edi.rs
index 8e7fde8..95f73c5 100644
--- a/src/edi.rs
+++ b/src/edi.rs
@@ -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)],
diff --git a/src/git.rs b/src/git.rs
index 8b13789..c47cd2b 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -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)
+}
diff --git a/src/lsp.rs b/src/lsp.rs
index 0ebfeb3..bbf6891 100644
--- a/src/lsp.rs
+++ b/src/lsp.rs
@@ -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;
diff --git a/src/rnd.rs b/src/rnd.rs
index e091de5..1cf42f4 100644
--- a/src/rnd.rs
+++ b/src/rnd.rs
@@ -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];
}