A simple CPU rendered GUI IDE experience.
add ctrl + / and some other
bendn 6 weeks ago
parent 60978d4 · commit 14ddb82
-rw-r--r--Cargo.toml2
-rw-r--r--src/edi.rs25
-rw-r--r--src/edi/st.rs5
-rw-r--r--src/lsp.rs15
-rw-r--r--src/main.rs51
-rw-r--r--src/rnd.rs28
-rw-r--r--src/sni.rs13
-rw-r--r--src/text.rs123
8 files changed, 206 insertions, 56 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 5e13982..cd3dcb1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -63,6 +63,8 @@ atools = "0.1.10"
swizzle = "0.1.0"
walkdir = "2.5.0"
niri = { package = "niri-ipc", version = "25.11.0" }
+libc = "0.2.180"
+serde_bencode = "0.2.4"
[profile.dev.package]
rust-analyzer.opt-level = 3
diff --git a/src/edi.rs b/src/edi.rs
index 7b45f5d..1d194d9 100644
--- a/src/edi.rs
+++ b/src/edi.rs
@@ -68,20 +68,25 @@ pub struct Requests {
RequestError<lsp_request!("textDocument/definition")>,
>,
}
-#[derive(Default)]
+#[derive(Default, serde_derive::Serialize, serde_derive::Deserialize)]
pub struct Editor {
pub files: HashMap<PathBuf, Editor>,
pub text: TextArea,
pub origin: Option<PathBuf>, // ie active
+ #[serde(skip)]
pub state: State,
+ #[serde(skip)]
pub bar: Bar,
pub workspace: Option<PathBuf>,
+ #[serde(skip)]
pub lsp: Option<(
&'static Client,
std::thread::JoinHandle<()>,
Option<Sender<Arc<Window>>>,
)>,
+ #[serde(skip)]
pub requests: Requests,
+ #[serde(skip)]
pub tree: Option<Vec<PathBuf>>,
pub chist: ClickHistory,
pub hist: Hist,
@@ -245,6 +250,10 @@ impl Editor {
// }
pub fn save(&mut self) {
+ // std::fs::write(
+ // "jayson",
+ // serde_json::to_string_pretty(&self).unwrap(),
+ // );
self.bar.last_action = "saved".into();
lsp!(self + p).map(|(l, o)| {
let v = l.runtime.block_on(l.format(o)).unwrap();
@@ -731,6 +740,14 @@ impl Editor {
_ => {}
}
match o {
+ Some(Do::Comment(x)) => {
+ if x == (0..0) {
+ self.text.comment(self.text.cursor..self.text.cursor);
+ } else {
+ self.text.comment(x);
+ }
+ change!(self);
+ }
Some(Do::SpawnTerminal) => {
trm::toggle(
self.workspace
@@ -1262,6 +1279,7 @@ impl Editor {
x: &Path,
w: &mut Arc<Window>,
) -> anyhow::Result<()> {
+ if let Some(x) = self.files.get(x) {}
self.origin = Some(x.canonicalize()?.to_path_buf());
let r = self.text.r;
self.text = TextArea::default();
@@ -1274,7 +1292,6 @@ impl Editor {
last_edit: Instant::now(),
changed: false,
};
-
(
self.text.r,
self.text.cursor,
@@ -1292,7 +1309,6 @@ impl Editor {
Self::modify(self.origin.as_deref()),
"open".to_string(),
);
-
lsp!(self + p).map(|(x, origin)| {
self.requests = default();
x.open(&origin, new).unwrap();
@@ -1305,6 +1321,9 @@ impl Editor {
});
Ok(())
}
+ pub fn store(&mut self) {
+ // serde_bencode::to_bytes(self);
+ }
}
use NamedKey::*;
diff --git a/src/edi/st.rs b/src/edi/st.rs
index f487a0e..ddbfbd2 100644
--- a/src/edi/st.rs
+++ b/src/edi/st.rs
@@ -37,6 +37,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(Range<usize> => 0..0)],
K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End) if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
M(MouseButton::Left if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
M(MouseButton::Left if ctrl()) => _ [GoToDefinition],
@@ -87,7 +88,9 @@ Selection(x) => {
K(Key::Named(Backspace)) => Default [Delete(Range<usize> => x)],
K(Key::Character(y) if y == "x" && ctrl()) => Default [Cut(Range<usize> => x)],
K(Key::Character(y) if y == "c" && ctrl()) => Default [Copy(Range<usize> => x)],
- K(Key::Character(y)) => Default [Insert((Range<usize>, SmolStr) => (x, y))],
+ K(Key::Character(y) if y == "/" && ctrl()) => Default [Comment(Range<usize> => x)],
+
+ K(Key::Character(y) if !ctrl()) => Default [Insert((Range<usize>, SmolStr) => (x, y))],
K(_) => Default [Edit],
},
Save => {
diff --git a/src/lsp.rs b/src/lsp.rs
index b80a37e..5b8158b 100644
--- a/src/lsp.rs
+++ b/src/lsp.rs
@@ -469,10 +469,7 @@ impl Client {
&'static self,
f: &Path,
) -> impl Future<
- Output = Result<
- Option<Vec<TextEdit>>,
- RequestError<Formatting>,
- >,
+ Output = Result<Option<Vec<TextEdit>>, RequestError<Formatting>>,
> {
self.request::<lsp_request!("textDocument/formatting")>(
&DocumentFormattingParams {
@@ -489,8 +486,14 @@ impl Client {
},
)
.unwrap()
- .0.map(|x| {
- x.map(|x|x.map(|mut x| { x.sort_tedits(); x }))
+ .0
+ .map(|x| {
+ x.map(|x| {
+ x.map(|mut x| {
+ x.sort_tedits();
+ x
+ })
+ })
})
}
pub fn rq_semantic_tokens(
diff --git a/src/main.rs b/src/main.rs
index 0557ca2..8130ba2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -32,7 +32,7 @@
try_blocks,
portable_simd
)]
-#![allow(incomplete_features, irrefutable_let_patterns)]
+#![allow(incomplete_features, irrefutable_let_patterns, static_mut_refs)]
mod act;
mod edi;
// mod new;
@@ -40,6 +40,8 @@ mod rnd;
mod sym;
mod trm;
+use std::fmt::{Debug, Display};
+use std::mem::MaybeUninit;
use std::num::NonZeroU32;
use std::sync::LazyLock;
use std::time::Instant;
@@ -50,9 +52,11 @@ use diff_match_patch_rs::PatchInput;
use dsb::cell::Style;
use dsb::{Cell, F};
use fimg::Image;
+use libc::{atexit, signal};
use lsp::{PathURI, Rq};
use lsp_types::*;
use rust_fsm::StateMachine;
+use serde::{Deserialize, Deserializer, Serialize};
use swash::{FontRef, Instance};
use winit::event::{
ElementState, Event, Ime, MouseButton, MouseScrollDelta, WindowEvent,
@@ -83,12 +87,29 @@ 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(Debug)]
+#[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,
}
@@ -103,7 +124,7 @@ impl Default for Hist {
}
}
}
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Serialize, Deserialize)]
struct ClickHistory {
pub his: Vec<(usize, usize)>,
pub red: Vec<(usize, usize)>,
@@ -131,18 +152,18 @@ impl Hist {
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(
+ forth: d
+ .patch_make(PatchInput::new_text_text(
&x.rope.to_string(),
&self.last.rope.to_string(),
))
.unwrap(),
- d.patch_make(PatchInput::new_text_text(
+ back: 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),
@@ -199,12 +220,24 @@ static mut CLICKING: bool = false;
const BG: [u8; 3] = col!("#1f2430");
const FG: [u8; 3] = [204, 202, 194];
const BORDER: [u8; 3] = col!("#ffffff");
+
+static mut __ED: MaybeUninit<Editor> = MaybeUninit::uninit();
+extern "C" fn cleanup() {
+ unsafe { __ED.assume_init_mut().store() };
+}
+extern "C" fn sigint(_: i32) {
+ std::process::exit(12);
+}
+
#[implicit_fn::implicit_fn]
pub(crate) fn entry(event_loop: EventLoop<()>) {
+ unsafe { __ED.write(Editor::new()) };
+ assert_eq!(unsafe { atexit(cleanup) }, 0);
+ unsafe { signal(libc::SIGINT, sigint as *const () as usize) };
+ let ed = unsafe { __ED.assume_init_mut() };
let ppem = 20.0;
let ls = 20.0;
- let ed = Editor::new();
- let ed = Box::leak(Box::new(ed));
+ // let ed = Box::leak(Box::new(ed));
let mut fonts = dsb::Fonts::new(
F::FontRef(*FONT, &[(2003265652, 550.0)]),
diff --git a/src/rnd.rs b/src/rnd.rs
index f19f866..34da8e1 100644
--- a/src/rnd.rs
+++ b/src/rnd.rs
@@ -4,10 +4,10 @@ use std::sync::{Arc, LazyLock};
use std::time::Instant;
use atools::prelude::*;
-use dsb::{Cell, Fonts};
use dsb::cell::Style;
-use fimg::{Image, OverlayAt};
+use dsb::{Cell, Fonts};
use fimg::pixels::Blend;
+use fimg::{Image, OverlayAt};
use lsp_types::*;
use rust_fsm::StateMachine;
use softbuffer::Surface;
@@ -20,7 +20,10 @@ use crate::edi::st::State;
use crate::edi::{Editor, lsp_m};
use crate::lsp::Rq;
use crate::text::{CoerceOption, col};
-use crate::{BG, BORDER, CompletionAction, CompletionState, FG, FONT, com, filter, lsp, sig};
+use crate::{
+ BG, BORDER, CompletionAction, CompletionState, FG, FONT, com, filter,
+ lsp, sig,
+};
#[implicit_fn::implicit_fn]
pub fn render(
@@ -259,14 +262,14 @@ pub fn render(
};
let mut place_around = |(_x, _y): (usize, usize),
- i: Image<&mut [u8], 3>,
- c: &[Cell],
- columns: usize,
- ppem_: f32,
- ls_: f32,
- ox: f32,
- oy: f32,
- toy: f32| {
+ i: Image<&mut [u8], 3>,
+ c: &[Cell],
+ columns: usize,
+ ppem_: f32,
+ ls_: f32,
+ ox: f32,
+ oy: f32,
+ toy: f32| {
let met = super::FONT.metrics(&[]);
let fac = ppem / met.units_per_em as f32;
let position = (
@@ -563,7 +566,8 @@ pub fn render(
}) => {
let c = com::s(x, 40, &filter(&text));
if c.len() == 0 {
- ed.requests.complete
+ ed.requests
+ .complete
.consume(CompletionAction::NoResult)
.unwrap();
None
diff --git a/src/sni.rs b/src/sni.rs
index 19c1775..9c44e5c 100644
--- a/src/sni.rs
+++ b/src/sni.rs
@@ -2,14 +2,23 @@ use std::ops::Range;
use helix_core::snippets::parser::SnippetElement;
-#[derive(Debug, Clone)]
+#[derive(
+ Debug, Clone, serde_derive::Serialize, serde_derive::Deserialize,
+)]
pub struct Snippet {
pub stops: Vec<(Stop, StopP)>,
pub last: Option<StopP>,
pub index: usize,
}
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(
+ Debug,
+ Clone,
+ PartialEq,
+ Eq,
+ serde_derive::Serialize,
+ serde_derive::Deserialize,
+)]
pub enum StopP {
Just(usize),
Range(Range<usize>),
diff --git a/src/text.rs b/src/text.rs
index 56e6b36..9ad013c 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -21,6 +21,7 @@ use lsp_types::{
SemanticTokensLegend, SnippetTextEdit, TextEdit,
};
use ropey::{Rope, RopeSlice};
+use serde::{Deserialize, Serialize};
use tree_house::Language;
use winit::keyboard::{NamedKey, SmolStr};
@@ -182,16 +183,47 @@ macro_rules! col {
($(crate::text::col!($x),)+)
}};
}
-#[derive(Clone, Debug)]
+#[derive(Debug)]
+struct E;
+impl Display for E {
+ fn fmt(&self, f: &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 {
- pub changes: (Patches<u8>, Patches<u8>),
+ #[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: [(usize, usize, 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.changes.1))
+ writeln!(f, "{}", d.patch_to_text(&self.back))
}
}
@@ -201,7 +233,7 @@ impl Diff {
// println!("{}", d.patch_to_text(&self.changes.0));
t.rope = Rope::from_str(
&d.patch_apply(
- &if redo { self.changes.1 } else { self.changes.0 },
+ &if redo { self.back } else { self.forth },
&t.rope.to_string(),
)
.unwrap()
@@ -215,8 +247,18 @@ impl Diff {
}
}
-#[derive(Default, Clone)]
+pub fn deserialize_from_string<'de, D: serde::de::Deserializer<'de>>(
+ de: D,
+) -> Result<Rope, D::Error> {
+ let s = serde::de::Deserialize::deserialize(de)?;
+ Ok(Rope::from_str(s))
+}
+#[derive(Default, Clone, Serialize, Deserialize)]
pub struct TextArea {
+ #[serde(
+ serialize_with = "crate::serialize_display",
+ deserialize_with = "deserialize_from_string"
+ )]
pub rope: Rope,
pub cursor: usize,
pub column: usize,
@@ -233,10 +275,14 @@ pub struct TextArea {
pub vo: usize,
pub ho: usize,
+ #[serde(skip)]
pub r: usize,
+ #[serde(skip)]
pub c: usize,
+ #[serde(skip)]
pub tabstops: Option<Snippet>,
+ #[serde(skip)]
pub decorations: Decorations,
}
@@ -442,19 +488,24 @@ impl TextArea {
pub fn mapped_index_at(&'_ self, (x, y): (usize, usize)) -> usize {
match self.visual_index_at((x, y)) {
Some(Mapping::Char(_, _, index)) => index,
- Some(Mapping::Fake(mark, real, ..)) => real,
+ Some(Mapping::Fake(_, real, ..)) => real,
None => self.eol(self.vo + y),
}
}
pub fn remove(&mut self, r: Range<usize>) -> Result<(), ropey::Error> {
self.rope.try_remove(r.clone())?;
- self.tabstops.as_mut().map(|x| {
- x.manipulate(|x| {
- // if your region gets removed, what happens to your tabstop? big question.
- if x > r.end { x - r.len() } else { x }
- });
- });
+ let manip = |x| {
+ // if your region gets removed, what happens to your tabstop? big question.
+ if r.contains(&x) {
+ // for now, simply move it.
+ r.start
+ } else {
+ if x >= r.end { x - r.len() } else { x }
+ }
+ };
+ self.tabstops.as_mut().map(|x| x.manipulate(manip));
+ self.cursor = manip(self.cursor);
Ok(())
}
@@ -464,19 +515,19 @@ impl TextArea {
with: &str,
) -> Result<(), ropey::Error> {
self.rope.try_insert(c, with)?;
- self.tabstops.as_mut().map(|x| {
- x.manipulate(
- |x| {
- if x < c { x } else { x + with.chars().count() }
- },
- )
- });
+ let manip = |x| {
+ if x < c { x } else { x + with.chars().count() }
+ };
+ self.tabstops.as_mut().map(|x| x.manipulate(manip));
+ self.cursor = manip(self.cursor);
Ok(())
}
pub fn insert(&mut self, c: &str) -> Result<(), ropey::Error> {
+ let oc = self.cursor;
self.insert_at(self.cursor, c)?;
- self.cursor += c.chars().count();
+ assert_eq!(self.cursor, oc + c.chars().count());
+ self.cursor = oc + c.chars().count();
self.setc();
self.set_ho();
Ok(())
@@ -617,11 +668,15 @@ impl TextArea {
self.setc();
self.set_ho();
}
+ #[implicit_fn]
+ fn indentation_of(&self, n: usize) -> usize {
+ let Some(l) = self.rope.get_line(n) else { return 0 };
+ l.chars().filter(*_ != '\n').take_while(_.is_whitespace()).count()
+ }
#[implicit_fn]
fn indentation(&self) -> usize {
- let l = self.cl();
- l.chars().filter(*_ != '\n').take_while(_.is_whitespace()).count()
+ self.indentation_of(self.cursor().1)
}
#[implicit_fn]
@@ -652,7 +707,6 @@ impl TextArea {
self.setc();
self.set_ho();
}
-
pub fn set_ho(&mut self) {
let x = self.cursor_visual().0;
if x < self.ho + 4 {
@@ -1354,6 +1408,29 @@ impl TextArea {
self.setc();
r
}
+ pub fn comment(&mut self, selection: std::ops::Range<usize>) {
+ let a = self.rope.char_to_line(selection.start);
+ let b = self.rope.char_to_line(selection.end);
+ let lns = (a..=b)
+ .filter(|l| {
+ !self.rope.line(*l).chars().all(char::is_whitespace)
+ })
+ .collect::<Vec<_>>();
+ let at =
+ lns.iter().map(|l| self.indentation_of(*l)).min().unwrap_or(0);
+ for l in lns {
+ let c = self.rope.line_to_char(l);
+ if let Some(n) = self.rope.line(l).to_string().find("//") {
+ if self.rope.char(n + c + 2).is_whitespace() {
+ _ = self.remove(c + n..c + n + 3);
+ } else {
+ _ = self.remove(c + n..c + n + 2);
+ }
+ } else {
+ _ = self.insert_at(c + at, "// ");
+ }
+ }
+ }
}
pub fn is_word(r: char) -> bool {