A simple CPU rendered GUI IDE experience.
add ctrl + / and some other
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/edi.rs | 25 | ||||
| -rw-r--r-- | src/edi/st.rs | 5 | ||||
| -rw-r--r-- | src/lsp.rs | 15 | ||||
| -rw-r--r-- | src/main.rs | 51 | ||||
| -rw-r--r-- | src/rnd.rs | 28 | ||||
| -rw-r--r-- | src/sni.rs | 13 | ||||
| -rw-r--r-- | src/text.rs | 123 |
8 files changed, 206 insertions, 56 deletions
@@ -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 @@ -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 => { @@ -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)]), @@ -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 @@ -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 { |