A simple CPU rendered GUI IDE experience.
feat: hov
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Cargo.toml | 7 | ||||
| -rw-r--r-- | src/hov.rs | 580 | ||||
| -rw-r--r-- | src/lsp.rs | 4 | ||||
| -rw-r--r-- | src/main.rs | 261 | ||||
| -rw-r--r-- | src/text.rs | 52 | ||||
| -rw-r--r-- | src/vec.md | 258 |
7 files changed, 731 insertions, 434 deletions
@@ -1 +1,2 @@ -Cargo.lock
\ No newline at end of file +Cargo.lock +target/
\ No newline at end of file @@ -49,7 +49,6 @@ arc-swap = "1.7.1" tokio = { version = "1.47.1", features = ["rt-multi-thread"] } regex-cursor = "0.1.5" papaya = "0.2.3" -minimad = { git = "https://github.com/Canop/minimad", branch = "keep-code-fence", version = "0.13.1" } markdown = "1.0.0" itertools = "0.14.0" @@ -60,3 +59,9 @@ cc = "*" debug = 2 # overflow-checks = true # debug-assertions = true + +[profile.dev] +incremental = false + +[patch.'https://github.com/bend-n/fimg'] +fimg = { "path" = "../fimg" } @@ -1,4 +1,5 @@ -use std::iter::{once, repeat}; +use std::iter::{empty, once, repeat_n}; +use std::ops::Range; use std::pin::pin; use std::time::Instant; use std::vec::Vec; @@ -7,375 +8,274 @@ use dsb::cell::Style; use dsb::{Cell, F}; use fimg::Image; use itertools::Itertools; -use minimad::*; +use markdown::mdast::{self, Node}; use ropey::Rope; +const D: Cell = + Cell { letter: None, style: Style { bg: BG, color: FG, flags: 0 } }; +use crate::{FG, text}; -use crate::{BG, FG, M, text}; -fn f(x: &Compound) -> u8 { - let mut s = 0; - if x.bold { - s |= Style::BOLD; - } - if x.italic { - s |= Style::ITALIC; +struct Builder { + c: usize, + to: Vec<Cell>, + scratch: Vec<Cell>, +} +enum Action { + Put(Vec<Cell>), + Just(Vec<Cell>), + Finish, +} +impl Builder { + fn finish(&mut self) { + assert!(self.scratch.len() < self.c); + self.to.extend(&self.scratch); + self.to.extend(repeat_n(D, self.c - self.scratch.len())); + self.scratch.clear(); + assert!(self.to.len() % self.c == 0); } - if x.strikeout { - s |= Style::STRIKETHROUGH + fn put_wrapping( + &mut self, + mut c: impl Iterator<Item = Cell>, + mut l: usize, + ) { + self.to.extend(self.scratch.drain(..)); + let fill = self.c - (self.to.len() % self.c); + self.to.extend(c.by_ref().take(fill)); + l -= fill; + let n = l - (l % self.c); + self.to.extend(c.by_ref().take(n)); + c.collect_into(&mut self.scratch); + assert!(self.to.len() % self.c == 0); + assert!(self.scratch.len() < self.c); } - s -} -fn markdown(c: usize, x: &str) -> Vec<Cell> { - let mut cells = vec![]; - // println!( - // "{:#?}", - // markdown::to_mdast( - // include_str!("../vec.md"), - // &markdown::ParseOptions::default() - // ) - // .unwrap() - // ); - use Default::default; - // std::fs::write( - // "vec.ast", - // format!( - // "{:#?}", - // minimad::parse_text( - // include_str!("../vec.md"), - // Options { keep_code_fences: true, ..default() }, - // ) - // .lines - // ), - // ) - // .unwrap(); - const D: Cell = Cell { - letter: None, - style: Style { bg: BG, color: FG, flags: 0 }, - }; - let mut l = minimad::parse_text( - x, - Options { keep_code_fences: true, ..default() }, - ) - .lines - .into_iter() - .peekable(); - 'l: while let Some(line) = l.next() { - match line { - minimad::Line::Normal(Composite { style, compounds }) => { - let mut compounded = l - .by_ref() - .peeking_take_while(|x| matches!(x, Line::Normal(Composite{style, compounds}) if !matches!(style, CompositeStyle::Header(_)))) - .flat_map(|x| match x { - Line::Normal(Composite { style, compounds }) => - compounds.into_iter().flat_map(move |x| { - x.as_str().chars().zip(repeat(f(&x)).map( - move |flags| match style { - CompositeStyle::Paragraph => - Style { - color: FG, - bg: BG, - flags, - }, - CompositeStyle::Header(x) => - Style { - color: [255; 3], - bg: BG, - flags: flags | Style::BOLD, - }, - CompositeStyle::ListItem(x) => - Style { - color: FG, - bg: BG, - flags, - }, - CompositeStyle::Code => Style { - color: [244,244,244], - bg: [5,5,5], - flags, - }, - CompositeStyle::Quote => Style { - color: [128; 3], - bg: [0; 3], - flags, - }, - }, - )) - }).chain(once((' ', Style { color: FG, bg: BG, flags : 0 }))), - _ => panic!(), - }) - .chunk_by(|x| x.0.is_whitespace()); - let mut compounded = compounded.into_iter(); - let mut out = vec![]; - - // let mut compounds = compounds.iter(); - while let Some((x, word)) = compounded.next() { - if x { - continue; - } - if out.len() > c { - panic!() - } - let word = word.collect::<Vec<_>>(); - if word.len() > c { - let mut w = word.iter().map(|&(x, style)| Cell { - letter: Some(x), - style, - }); - out.extend(w); - cells.extend( - out.drain(..out.len() - out.len() % c), - ); - // 'out: loop { - // while out.len() != c { - // let Some(x) = w.next() else { - // break 'out; - // }; - // out.push(x); - // } - // cells.extend(&out); - // out.clear(); - // } - continue; - // cells.extend(&out); - // out.clear(); - // cells.extend( - // w.by_ref() - // .take(c.saturating_sub(out.len() + 1)), - // ); - // cells.push(Cell::basic('-')); - // cells.extend(w); - } + fn put(&mut self, c: impl Iterator<Item = Cell> + Clone) { + let mut c = c.map(|x| match x.letter { + Some('\n') => x.style.empty(), + _ => x, + }); + assert!(self.scratch.len() < self.c); + loop { + let n = c + .clone() + .take_while_inclusive(|x| { + !x.letter.is_none_or(char::is_whitespace) + }) + .count(); + let next = c.by_ref().take_while_inclusive(|x| { + !x.letter.is_none_or(char::is_whitespace) + }); - if out.len() + word.len() > c { - assert_eq!(cells.len() % c, 0); - cells.extend(&out); - cells.extend(std::iter::repeat_n( - D, - c.saturating_sub(out.len()), - )); - dbg!(out.len(), c); - out.clear(); - assert_eq!(cells.len() % c, 0); - } + if n == 0 { + break; + } - out.extend(word.iter().map(|&(x, style)| Cell { - letter: Some(x), - style, - })); - if out.len() != c { - out.push(D); - } else { - cells.extend(&out); - out.clear(); - } - dbg!(out.len()); + if n + self.scratch.len() < self.c { + self.scratch.extend(next); + } else if n < self.c { + self.finish(); + self.scratch.extend(next); + } else { + self.put_wrapping(next.into_iter(), n); + } + } + // assert!(self.scratch.len() == 0); + // } + assert!(self.scratch.len() < self.c); - // } - } - // if out.len() > c { - // panic!(); - // } - // if out.len() + next.char_length() > c { - // cells.extend(&out); - // cells.extend(std::iter::repeat_n( - // Cell::default(), - // c.saturating_sub(out.len()), - // )); - // out.clear(); - // assert_eq!(cells.len() % c, 0); - // } - // out.extend( - // next.as_str() - // .chars() - // .map(|x| Cell { letter: Some(x), style }), - // ); - // } - if !out.is_empty() { - assert!(out.len() <= c, "{} < c", out.len()); - cells.extend(&out); - cells.extend(std::iter::repeat_n( - D, - c.saturating_sub(out.len()), - )); - assert_eq!(cells.len() % c, 0); - } - continue 'l; + // self.scratch.extend(after); + // self.scratch.extend(c); + assert!(self.to.len() % self.c == 0); + assert!(self.scratch.len() < self.c); + } + fn extend_( + &self, + n: &mdast::Node, + i: impl IntoIterator<Item = Action>, + inherit: Style, + ) -> Vec<Action> { + n.children() + .iter() + .flat_map(|i| *i) + .flat_map(|x| self.run(x, inherit)) + .chain(i) + .collect() + } + fn extend(&self, n: &Node, inherit: Style) -> Vec<Action> { + self.extend_(n, empty(), inherit) + } + fn run(&self, node: &mdast::Node, mut inherit: Style) -> Vec<Action> { + match node { + Node::Break(_) => vec![Action::Finish], + Node::InlineCode(x) => { + inherit.bg = [21, 24, 30]; + vec![Action::Put( + x.value.chars().map(|x| inherit.basic(x)).collect(), + )] + } + // Node::Html(Html { value: "code", .. }) => {} + Node::Delete(_) => todo!(), + Node::Emphasis(_) => { + inherit.flags |= Style::ITALIC; + self.extend(node, inherit | Style::ITALIC) + } + Node::Link(_) => { + inherit.flags |= Style::UNDERLINE; + inherit.color = [244, 196, 98]; + self.extend(node, inherit) + } + Node::LinkReference(_) => { + inherit.flags |= Style::UNDERLINE; + inherit.color = [244, 196, 98]; + self.extend(node, inherit) + } + Node::Heading(_) => { + inherit.flags |= Style::BOLD; + inherit.color = [255, 255, 255]; + self.extend_(node, [Action::Finish], inherit) } - minimad::Line::TableRow(table_row) => todo!(), - minimad::Line::TableRule(table_rule) => todo!(), - minimad::Line::HorizontalRule => vec![Cell::basic('-')], - minimad::Line::CodeFence(Composite { - compounds: [Compound { src: lang, .. }], - .. - }) => { - let mut r = Rope::new(); - while let Some(x) = l.next() { - match x { - Line::CodeFence(Composite { - compounds: [], - .. - }) => { - break; - } - Line::Normal(Composite { - compounds: [Compound { src, .. }], - .. - }) => { - r.insert(r.len_chars(), src); - r.insert_char(r.len_chars(), '\n'); - } - _ => {} - } - } - let mut cell = vec![D; c * r.len_lines()]; + + Node::Strong(_) => self.extend(node, inherit | Style::BOLD), + Node::Text(text) => vec![Action::Put( + text.value + .chars() + .map(|x| if x == '\n' { ' ' } else { x }) + .map(|x| inherit.basic(x)) + .collect(), + )], + Node::Code(code) => { + let r = Rope::from_str(&code.value); + let mut cell = vec![D; self.c * r.len_lines()]; for (l, y) in r.lines().zip(0..) { - for (e, x) in l.chars().take(c).zip(0..) { + for (e, x) in l.chars().take(self.c).zip(0..) { if e != '\n' { - cell[y * c + x].letter = Some(e); + cell[y * self.c + x].letter = Some(e); } } } for ((x1, y1), (x2, y2), s, txt) in std::iter::from_coroutine(pin!(text::hl( - text::LOADER.language_for_name(lang).unwrap_or( - text::LOADER - .language_for_name("rust") - .unwrap() - ), + text::LOADER + .language_for_name( + code.lang.as_deref().unwrap_or("rust") + ) + .unwrap_or( + text::LOADER + .language_for_name("rust") + .unwrap() + ), &r, .., - c, + self.c, ))) { - cell.get_mut(y1 * c + x1..y2 * c + x2).map(|x| { - x.iter_mut().zip(txt.chars()).for_each(|(x, c)| { - x.style |= s; - x.letter = Some(c); - }) - }); + cell.get_mut(y1 * self.c + x1..y2 * self.c + x2).map( + |x| { + x.iter_mut().zip(txt.chars()).for_each( + |(x, c)| { + x.style |= s; + x.letter = Some(c); + }, + ) + }, + ); } - cells.extend(cell); - assert_eq!(cells.len() % c, 0); - continue 'l; + assert_eq!(self.to.len() % self.c, 0); + vec![Action::Finish, Action::Just(cell)] } - _ => panic!(), - }; - // if out.len() > c { - // out.drain(c - 1..); - // out.push(Cell::basic('…')); - // } else { - // out.extend(std::iter::repeat_n( - // Cell::default(), - // c.saturating_sub(out.len()), - // )); - // } - // assert_eq!(out.len(), c); - // cells.extend(out); + Node::ThematicBreak(_) => { + vec![Action::Finish, Action::Finish] + } + + Node::Paragraph(paragraph) => paragraph + .children + .iter() + .flat_map(|c| self.run(&c, inherit)) + .chain([Action::Finish, Action::Finish]) + .collect(), + n => self.extend(n, inherit), + } } - assert_eq!(cells.len() % c, 0); - cells } - +pub fn p(x: &str) -> Option<Node> { + markdown::to_mdast(x, &markdown::ParseOptions::gfm()).ok() +} +fn len(node: &mdast::Node) -> Vec<usize> { + match node { + Node::Break(_) => vec![usize::MAX], + Node::InlineCode(x) => vec![x.value.chars().count()], + Node::Code(x) => once(usize::MAX) + .chain( + Iterator::intersperse( + x.value.lines().map(|l| l.chars().count()), + usize::MAX, + ) + .chain([usize::MAX]), + ) + .collect(), + // Node::Html(Html { value: "code", .. }) => {} + Node::Text(text) => vec![text.value.chars().count()], + Node::Paragraph(x) => + x.children.iter().flat_map(len).chain([usize::MAX]).collect(), + n => n.children().iter().flat_map(|x| *x).flat_map(len).collect(), + } +} +pub fn l(node: &Node) -> Vec<usize> { + len(node) + .into_iter() + .chunk_by(|&x| x != usize::MAX) + .into_iter() + .filter_map(|x| x.0.then(|| x.1.sum::<usize>())) + .collect::<Vec<_>>() +} +#[implicit_fn::implicit_fn] +pub fn markdown2(c: usize, x: &Node) -> Vec<Cell> { + let mut r = Builder { c, to: vec![], scratch: vec![] }; + for (is_put, act) in r + .run(&x, Style { bg: BG, color: FG, flags: 0 }) + .into_iter() + .chunk_by(|x| matches!(x, Action::Put(_))) + .into_iter() + { + if is_put { + r.put( + act.flat_map(|x| match x { + Action::Put(x) => x, + _ => panic!(), + }) + .collect::<Vec<_>>() + .into_iter(), + ); + } else { + for act in act { + match act { + Action::Put(cells) => r.put(cells.into_iter()), + Action::Just(cells) => r.to.extend(cells), + Action::Finish => r.finish(), + } + } + } + } + if r.to.iter().take(c).all(_.letter.is_none()) { + r.to.drain(..c); + } + if r.to.iter().rev().take(c).all(_.letter.is_none()) { + r.to.drain(r.to.len() - c..); + } + r.to +} +pub const BG: [u8; 3] = text::color(b"191E27"); #[test] fn t() { let ppem = 18.0; let lh = 10.0; - let (w, h) = (800, 8000); + let (w, h) = (400, 8000); let (c, r) = dsb::fit(&crate::FONT, ppem, lh, (w, h)); - std::fs::write( - "mdast2", - format!( - "{:#?}", - markdown::to_mdast( - include_str!("../vec.md"), - &markdown::ParseOptions::gfm() - ) - .unwrap() - ), - ); - let mut cells = markdown(c, include_str!("../vec.md")); - // use Default::default; - // for line in dbg!(minimad::parse_text( - // include_str!("../vec.md"), - // Options { keep_code_fences: true, ..default() }, - // )) - // .lines - // { - // fn f(x: &Compound) -> u8 { - // let mut s = 0; - // if x.bold { - // s |= Style::BOLD; - // } - // if x.italic { - // s |= Style::ITALIC; - // } - // if x.strikeout { - // s |= Style::STRIKETHROUGH - // } - // s - // } - // let mut out = match line { - // minimad::Line::Normal(Composite { style, compounds }) => - // compounds - // .iter() - // .flat_map(|c| { - // c.as_str().chars().map(move |x| { - // let flags = f(&c); - // let style = match style { - // CompositeStyle::Paragraph => Style { - // color: [128; 3], - // bg: [0; 3], - // flags, - // }, - // CompositeStyle::Header(x) => Style { - // color: [255; 3], - // bg: [0; 3], - // flags: flags | Style::BOLD, - // }, - // CompositeStyle::ListItem(x) => Style { - // color: [128; 3], - // bg: [0; 3], - // flags, - // }, - // CompositeStyle::Code => Style { - // color: [100; 3], - // bg: [0; 3], - // flags, - // }, - // CompositeStyle::Quote => Style { - // color: [128; 3], - // bg: [0; 3], - // flags, - // }, - // }; - // Cell { letter: Some(x), style } - // }) - // }) - // .collect::<Vec<_>>(), - // minimad::Line::TableRow(table_row) => todo!(), - // minimad::Line::TableRule(table_rule) => todo!(), - // minimad::Line::HorizontalRule => vec![Cell::basic('-')], - // minimad::Line::CodeFence(composite) => { - // vec![] - // } - // }; - // if out.len() > c { - // out.drain(c - 1..); - // out.push(Cell::basic('…')); - // } else { - // out.extend(std::iter::repeat_n( - // Cell::default(), - // c - out.len(), - // )); - // } - // assert_eq!(out.len(), c); - // cells.extend(out); - // } + + let cells = markdown2(c, &p(include_str!("vec.md")).unwrap()); + dbg!(l(&p(include_str!("vec.md")).unwrap())); dbg!(cells.len() / c); - // let (fw, fh) = dsb::dims(&FONT, ppem); dbg!(w, h); dbg!(c, r); - // panic!(); let mut fonts = dsb::Fonts::new( F::FontRef(*crate::FONT, &[(2003265652, 550.0)]), @@ -401,3 +301,7 @@ fn t() { println!("{:?}", now.elapsed()); x.as_ref().save("x"); } +pub struct Hovr { + pub(crate) span: Option<Range<usize>>, + pub(crate) item: crate::text::CellBuffer, +} @@ -117,6 +117,10 @@ impl Client { } pub fn rq_semantic_tokens(&self, f: &Path) -> anyhow::Result<()> { debug!("requested semantic tokens"); + let Some(b"rs") = f.extension().map(|x| x.as_encoded_bytes()) + else { + return Ok(()); + }; let mut p = self.semantic_tokens.1.lock(); if let Some((h, task)) = &*p { if !h.is_finished() { diff --git a/src/main.rs b/src/main.rs index e0f5289..4f04d36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,17 @@ // this looks pretty good though #![feature(tuple_trait, unboxed_closures, fn_traits)] #![feature( + iter_intersperse, stmt_expr_attributes, + new_range_api, + iter_collect_into, mpmc_channel, const_cmp, - // generator_trait, gen_blocks, const_default, - coroutines,iter_from_coroutine,coroutine_trait, + coroutines, + iter_from_coroutine, + coroutine_trait, cell_get_cloned, import_trait_associated_functions, if_let_guard, @@ -18,33 +22,34 @@ portable_simd )] #![allow(incomplete_features, redundant_semicolons)] -use std::convert::identity; +use std::borrow::Cow; use std::io::BufReader; -use std::mem::forget; use std::num::NonZeroU32; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; -use std::rc::Rc; use std::sync::{Arc, LazyLock, OnceLock}; use std::thread; use std::time::Instant; use Default::default; use NamedKey::*; +use atools::prelude::AASAdd; use diff_match_patch_rs::PatchInput; use dsb::cell::Style; use dsb::{Cell, F}; -use fimg::Image; +use fimg::{Image, OverlayAt}; use lsp_types::request::HoverRequest; use lsp_types::{ - Hover, HoverParams, Position, SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities, TextDocumentIdentifier, TextDocumentPositionParams, WorkspaceFolder + Hover, HoverParams, MarkedString, Position, SemanticTokensOptions, + SemanticTokensServerCapabilities, ServerCapabilities, + TextDocumentIdentifier, TextDocumentPositionParams, WorkspaceFolder, }; -use minimad::{Composite, CompositeStyle}; -use parking_lot::RwLock; +use parking_lot::Mutex; use regex::Regex; use ropey::Rope; use rust_fsm::StateMachineImpl; use swash::{FontRef, Instance}; +use tokio::task::spawn_blocking; use url::Url; use winit::event::{ ElementState, Event, MouseButton, MouseScrollDelta, WindowEvent, @@ -55,12 +60,13 @@ use winit::platform::wayland::WindowAttributesExtWayland; use winit::window::{Icon, Window}; use crate::bar::Bar; +use crate::hov::Hovr; use crate::text::{Diff, TextArea}; mod bar; +pub mod hov; mod lsp; mod text; mod winit_app; -mod hov; fn main() { env_logger::init(); // lsp::x(); @@ -185,38 +191,44 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { .as_ref() .and_then(|x| rooter(&x.parent().unwrap())) .and_then(|x| x.canonicalize().ok()); - let c = workspace.zip(origin.clone()).map(|(workspace, origin)| { - let mut c = Command::new("rust-analyzer") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn() - .unwrap(); + let c = workspace.as_ref().zip(origin.clone()).map( + |(workspace, origin)| { + let mut c = Command::new("rust-analyzer") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap(); - let (c, t, t2, changed) = lsp::run( - lsp_server::stdio::stdio_transport( - BufReader::new(c.stdout.take().unwrap()), - c.stdin.take().unwrap(), - ), - WorkspaceFolder { - uri: Url::from_file_path(&workspace).unwrap(), - name: workspace - .file_name() - .unwrap() - .to_string_lossy() - .into_owned(), - }, - ); - c.open(&origin, std::fs::read_to_string(&origin).unwrap()) - .unwrap(); - ((c, origin), (t, t2), changed) - }); - let (lsp, t, ch) = match c { - Some((a,b,c)) => { - (Some(a), Some(b), Some(c)) + let (c, t, t2, changed) = lsp::run( + lsp_server::stdio::stdio_transport( + BufReader::new(c.stdout.take().unwrap()), + c.stdin.take().unwrap(), + ), + WorkspaceFolder { + uri: Url::from_file_path(&workspace).unwrap(), + name: workspace + .file_name() + .unwrap() + .to_string_lossy() + .into_owned(), + }, + ); + c.open(&origin, std::fs::read_to_string(&origin).unwrap()) + .unwrap(); + (c, (t, t2), changed) }, - None => { (None, None, None) } - }; + ); + let (lsp, t, ch) = match c { + Some((a, b, c)) => (Some(a), Some(b), Some(c)), + None => (None, None, None), + }; + macro_rules! lsp { + () => { + lsp.as_ref().zip(origin.as_deref()) + }; + } + let hovering = &*Box::leak(Box::new(Mutex::new(None::<hov::Hovr>))); // let mut hl_result = None; @@ -236,13 +248,13 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { } macro_rules! change { () => { - lsp.as_ref().map(|(x, origin)| { + lsp!().map(|(x, origin)| { x.edit(&origin, text.rope.to_string()).unwrap(); x.rq_semantic_tokens(origin).unwrap(); }); }; } - lsp.as_ref().map(|(x, origin)| x.rq_semantic_tokens(origin).unwrap()); + lsp!().map(|(x, origin)| x.rq_semantic_tokens(origin).unwrap()); let mut mtime = modify!(); macro_rules! save { () => {{ @@ -341,6 +353,9 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { if size.height != 0 && size.width != 0 { let now = Instant::now(); + if c*r!=cells.len(){ + return; + } cells.fill(Cell { style: Style { color: BG, bg: BG, flags: 0 }, letter: None, @@ -392,7 +407,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { } }, origin.as_deref(), - lsp.as_ref().and_then(|(x, _)| { match &x.initialized { + lsp!().and_then(|(x, _)| { match &x.initialized { Some(lsp_types::InitializeResult { capabilities: ServerCapabilities { semantic_tokens_provider: @@ -411,11 +426,11 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { r - 1, origin .as_ref() - .map(|x| x.to_str().unwrap()) + .map(|x| workspace.as_ref().and_then(|w| x.strip_prefix(w).ok()).unwrap_or(&x).to_str().unwrap()) .unwrap_or("new buffer"), &state, &text, - lsp.as_ref().map(|x| &x.0) + lsp.as_ref() ); unsafe { dsb::render( @@ -427,13 +442,52 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { ls, true, i.as_mut(), - ) + ) }; + hovering.lock().as_ref().map(|x| x.span.clone().map(|sp| { + let met = FONT.metrics(&[]); + let fac = ppem / met.units_per_em as f32; + let [(_x, _y), (_x2, _)] = text.position(sp); + let [_x, _x2] = [_x, _x2].add(text.line_number_offset()+1); + let _y = _y - text.vo; + if !(cursor_position.1 == _y && (_x..=_x2).contains(&cursor_position.0)) { + return; + } + let position = ( + ((_x) as f32 * fw).round() as usize, + ((_y as f32 as f32) * (fh + ls * fac)).round() as usize, + ); + + let ppem = 18.0; + let ls = 10.0; + let r = x.item.l().min(15); + let c = x.item.displayable(r); + let (w, h) = dsb::size(&fonts.regular, ppem, ls, (x.item.c, r)); + let top = position.1.checked_sub(h).unwrap_or((((_y + 1) as f32) * (fh + ls * fac)).round() as usize,); + let left = + if position.0 + w as usize > window.inner_size().width as usize { + window.inner_size().width as usize- w as usize + } else { position.0 }; + + let mut i2 = Image::build(w as _, h as _).fill(BG); + unsafe{ dsb::render( + &c, + (x.item.c, 0), + ppem, + hov::BG, + &mut fonts, + ls, + true, + i2.as_mut(), + )}; + // dbg!(w, h, i2.width(), i2.height(), window.inner_size(), i.width(),i.height()); + unsafe { i.overlay_at(&i2.as_ref(), left as u32, top as u32) }; + i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), i2.width(), i2.height(), [0;3]); + })); let met = FONT.metrics(&[]); let fac = ppem / met.units_per_em as f32; // if x.view_o == Some(x.cells.row) || x.view_o.is_none() { - use fimg::OverlayAt; let (fw, fh) = dsb::dims(&FONT, ppem); let cursor = Image::<_, 4>::build(3, (fh).ceil() as u32) @@ -509,41 +563,76 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { text.cursor = x; *state.sel() = x..x; } - Some(Do::Hover) => { - let hover = text.index_at(cursor_position); + Some(Do::Hover) if let Some(hover) = text.raw_index_at(cursor_position) => { + assert_eq!(hover, text.index_at(cursor_position)); let (x, y) =text.xy(hover); - let s = lsp.as_ref().map(|(c, o)| { - let (rx, id) = c.request::<HoverRequest>(&HoverParams { - text_document_position_params: TextDocumentPositionParams { text_document: TextDocumentIdentifier::new(Url::from_file_path(o).unwrap()), position: Position { - line: y as _, character: x as _, - }}, - work_done_progress_params:default() }).unwrap(); - c.runtime.spawn(async { - let x = rx.await?.load::<Hover>()?; - // dbg!(&x); - match x.contents { - lsp_types::HoverContents::Scalar(marked_string) => { - println!("{marked_string:?}"); - }, - lsp_types::HoverContents::Array(marked_strings) => { - println!("{marked_strings:?}"); - - }, - lsp_types::HoverContents::Markup(markup_content) => { - println!("{}", markup_content.value); - // dbg!(minimad::Text::from(&*markup_content.value)); - }, - } - anyhow::Ok(()) - }) + let text = text.clone(); + { + let mut l = hovering.lock(); + if let Some(Hovr{ span: Some(span),..}) = &*l { + let [(_x, _y), (_x2, _)] = text.position(span.clone()); + let [_x, _x2] = [_x, _x2].add(text.line_number_offset()+1); + let _y = _y - text.vo; + if cursor_position.1 == _y && (_x.._x2).contains(&cursor_position.0) { + return + } else { + *l = None; + window.request_redraw(); + } + } + } + let hovering = hovering; + lsp!().map(|(cl, o)| { +let window = window.clone(); +static RUNNING: LazyLock< papaya::HashSet<usize, >>= LazyLock::new(||papaya::HashSet::new()); +if !RUNNING.insert(hover, &RUNNING.guard()) {return} +let (rx, _) = cl.request::<HoverRequest>(&HoverParams { +text_document_position_params: TextDocumentPositionParams { text_document: TextDocumentIdentifier::new(Url::from_file_path(o).unwrap()), position: Position { + line: y as _, character: x as _, +}}, +work_done_progress_params:default() }).unwrap(); +cl.runtime.spawn(async move { + let x = rx.await?.load::<Hover>()?; + let (w, cells) = spawn_blocking(move || { + let x = match &x.contents { + lsp_types::HoverContents::Scalar(marked_string) => { + match marked_string{ + MarkedString::LanguageString(x) =>Cow::Borrowed(&*x.value), + MarkedString::String(x) => Cow::Borrowed(&**x), + } + }, + lsp_types::HoverContents::Array(marked_strings) => { + Cow::Owned(marked_strings.iter().map(|x| match x{ + MarkedString::LanguageString(x) => &*x.value, + MarkedString::String(x) => &*x, + }).collect::<String>()) + }, + lsp_types::HoverContents::Markup(markup_content) => { + Cow::Borrowed(&*markup_content.value) + }, + }; + let x = hov::p(&x).unwrap(); + let m = hov::l(&x).into_iter().max().map(_+2).unwrap_or(usize::MAX).min(c-10); + (m, hov::markdown2(m, &x)) +}).await.unwrap(); +RUNNING.remove(&hover,&RUNNING.guard()); + let span = x.range.and_then(|x| { + Some(text.l_position(x.start).ok()?..text.l_position(x.end).ok()?) + }); + *hovering.lock()= Some( hov::Hovr { span, item: text::CellBuffer { c: w, vo: 0, cells: cells.into() }}.into()); + window.request_redraw(); + anyhow::Ok(()) +}); }); - + } + Some(Do::Hover) => { + *hovering.lock() = None; + window.request_redraw(); } None => {} x => unreachable!("{x:?}"), } } - Event::WindowEvent { event: WindowEvent::MouseInput { state: bt, button, .. }, @@ -796,12 +885,17 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { _ => {} }; }, - ); - ch.map(|ch| thread::Builder::new().name("redrawer".into()).spawn(move || { - for () in ch { - PUT.get().map(|x| x.request_redraw()); - } - })); + ); + ch.map(|ch| { + thread::Builder::new().name("redrawer".into()).spawn(move || { + for () in ch { + PUT.get().map(|x| { + x.request_redraw(); + println!("rq redraw"); + }); + } + }) + }); winit_app::run_app(event_loop, app); } @@ -886,7 +980,7 @@ impl State { } } -use std::ops::Range; +use std::ops::{Not, Range}; rust_fsm::state_machine! { #[derive(Clone, Debug)] @@ -907,8 +1001,7 @@ Default => { M(MouseButton => MouseButton::Left) => _ [MoveCursor], C(((usize, usize)) => .. if unsafe { CLICKING }) => Selection(0..0) [StartSelection], Changed => RequestBoolean(BoolRequest => BoolRequest::ReloadFile), - C(_ if false) => _ [Hover], - C(_) => _, + C(_) => _ [Hover], K(_) => _ [Edit], M(_) => _, }, diff --git a/src/text.rs b/src/text.rs index f35539f..cd22f64 100644 --- a/src/text.rs +++ b/src/text.rs @@ -2,7 +2,7 @@ use std::cmp::min; use std::fmt::{Debug, Display}; use std::ops::{Deref, Not as _, Range, RangeBounds}; use std::path::Path; -use std::pin::{Pin, pin}; +use std::pin::pin; use std::sync::{Arc, LazyLock}; use std::vec::Vec; @@ -11,17 +11,14 @@ use diff_match_patch_rs::{DiffMatchPatch, Patches}; use dsb::Cell; use dsb::cell::Style; use helix_core::Syntax; -use helix_core::syntax::{HighlightEvent, Loader, reconfigure_highlights}; +use helix_core::syntax::{HighlightEvent, Loader}; use implicit_fn::implicit_fn; use log::error; -use lsp_types::{ - SemanticToken, SemanticTokensLegend, SemanticTokensServerCapabilities, -}; +use lsp_types::{Position, SemanticToken, SemanticTokensLegend}; use ropey::{Rope, RopeSlice}; -use tree_house::{Language, fixtures}; +use tree_house::Language; use winit::keyboard::{NamedKey, SmolStr}; -use crate::MODIFIERS; use crate::text::semantic::{MCOLORS, MODIFIED, MSTYLE}; macro_rules! theme { ($n:literal $($x:literal $color:literal $($style:expr)?),+ $(,)?) => { @@ -144,7 +141,7 @@ const fn of(x: &'static str) -> usize { panic!() } -const fn color(x: &[u8; 6]) -> [u8; 3] { +pub const fn color(x: &[u8; 6]) -> [u8; 3] { car::map!( car::map!(x, |b| (b & 0xF) + 9 * (b >> 6)).chunked::<2>(), |[a, b]| a * 16 + b @@ -202,6 +199,29 @@ pub struct TextArea { pub r: usize, pub c: usize, } + +pub struct CellBuffer { + pub c: usize, + pub vo: usize, + pub cells: Box<[Cell]>, +} +impl Deref for CellBuffer { + type Target = [Cell]; + + fn deref(&self) -> &Self::Target { + &self.cells + } +} + +impl CellBuffer { + pub fn displayable(&self, r: usize) -> &[Cell] { + &self[self.vo * self.c..((self.vo + r) * self.c).min(self.len())] + } + pub fn l(&self) -> usize { + self.len() / self.c + } +} + impl Debug for TextArea { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TextArea") @@ -262,6 +282,13 @@ impl TextArea { .min(self.rope.len_chars()) } + pub fn raw_index_at(&self, (x, y): (usize, usize)) -> Option<usize> { + let x = x.checked_sub(self.line_number_offset() + 1)?; + Some(self.vo + y) + .filter(|&l| self.rope.line(l).len_chars() > x) + .and_then(|l| Some(self.rope.try_line_to_char(l).ok()? + x)) + } + pub fn insert_(&mut self, c: SmolStr) { self.rope.insert(self.cursor, &c); self.cursor += c.chars().count(); @@ -574,6 +601,12 @@ impl TextArea { &mut cell[y1 * c + x1..y2 * c + x2] } + pub fn l_position(&self, p: Position) -> Result<usize, ropey::Error> { + Ok(self.rope.try_line_to_char(p.line as _)? + + (p.character as usize) + .min(self.rope.line(p.line as _).len_chars())) + } + #[implicit_fn] pub fn write_to<'lsp>( &mut self, @@ -629,7 +662,7 @@ impl TextArea { + ch as usize + t.length as usize, )? - self.rope.try_line_to_char(ln as _)?; - (x1, x2) + (x1.min(self.c), x2.min(self.c)) }; let Ok((x1, x2)) = x else { continue; @@ -1036,7 +1069,6 @@ pub fn hl( // ); #[coroutine] static move || { - println!("`\n{text}\n`"); let syntax = Syntax::new(text.slice(..), lang, &LOADER).unwrap(); let mut h = syntax.highlighter(text.slice(..), &LOADER, r); let mut at = 0; diff --git a/src/vec.md b/src/vec.md new file mode 100644 index 0000000..7554be7 --- /dev/null +++ b/src/vec.md @@ -0,0 +1,258 @@ +```rust +alloc::vec +``` + +```rust +pub struct Vec<T, A = Global> +where + A: Allocator, +{ + buf: RawVec<T, A>, + len: usize, +} +``` + +--- + +A contiguous growable array type, written as `Vec<T>`, short for 'vector'. + +# Examples + +```rust +let mut vec = Vec::new(); +vec.push(1); +vec.push(2); + +assert_eq!(vec.len(), 2); +assert_eq!(vec[0], 1); + +assert_eq!(vec.pop(), Some(2)); +assert_eq!(vec.len(), 1); + +vec[0] = 7; +assert_eq!(vec[0], 7); + +vec.extend([1, 2, 3]); + +for x in &vec { + println!("{x}"); +} +assert_eq!(vec, [7, 1, 2, 3]); +``` + +The [`vec`](https://doc.rust-lang.org/nightly/alloc/macros/macro.vec.html) macro is provided for convenient initialization: + +```rust +let mut vec1 = vec![1, 2, 3]; +vec1.push(4); +let vec2 = Vec::from([1, 2, 3, 4]); +assert_eq!(vec1, vec2); +``` + +It can also initialize each element of a `Vec<T>` with a given value. +This may be more efficient than performing allocation and initialization +in separate steps, especially when initializing a vector of zeros: + +```rust +let vec = vec![0; 5]; +assert_eq!(vec, [0, 0, 0, 0, 0]); + +// The following is equivalent, but potentially slower: +let mut vec = Vec::with_capacity(5); +vec.resize(5, 0); +assert_eq!(vec, [0, 0, 0, 0, 0]); +``` + +For more information, see +[Capacity and Reallocation](https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#capacity-and-reallocation). + +Use a `Vec<T>` as an efficient stack: + +```rust +let mut stack = Vec::new(); + +stack.push(1); +stack.push(2); +stack.push(3); + +while let Some(top) = stack.pop() { + // Prints 3, 2, 1 + println!("{top}"); +} +``` + +# Indexing + +The `Vec` type allows access to values by index, because it implements the +[`Index`](https://doc.rust-lang.org/nightly/core/ops/index/trait.Index.html) trait. An example will be more explicit: + +```rust +let v = vec![0, 2, 4, 6]; +println!("{}", v[1]); // it will display '2' +``` + +However be careful: if you try to access an index which isn't in the `Vec`, +your software will panic! You cannot do this: + +```rust +let v = vec![0, 2, 4, 6]; +println!("{}", v[6]); // it will panic! +``` + +Use [`get`] and [`get_mut`] if you want to check whether the index is in +the `Vec`. + +# Slicing + +A `Vec` can be mutable. On the other hand, slices are read-only objects. +To get a [slice](https://doc.rust-lang.org/nightly/core/slice/index.html), use [`&`](`&`). Example: + +```rust +fn read_slice(slice: &[usize]) { + // ... +} + +let v = vec![0, 1]; +read_slice(&v); + +// ... and that's all! +// you can also do it like this: +let u: &[usize] = &v; +// or like this: +let u: &[_] = &v; +``` + +In Rust, it's more common to pass slices as arguments rather than vectors +when you just want to provide read access. The same goes for [`String`] and +[`&str`]. + +# Capacity and reallocation + +The capacity of a vector is the amount of space allocated for any future +elements that will be added onto the vector. This is not to be confused with +the _length_ of a vector, which specifies the number of actual elements +within the vector. If a vector's length exceeds its capacity, its capacity +will automatically be increased, but its elements will have to be +reallocated. + +For example, a vector with capacity 10 and length 0 would be an empty vector +with space for 10 more elements. Pushing 10 or fewer elements onto the +vector will not change its capacity or cause reallocation to occur. However, +if the vector's length is increased to 11, it will have to reallocate, which +can be slow. For this reason, it is recommended to use [`Vec::with_capacity`](https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.with_capacity) +whenever possible to specify how big the vector is expected to get. + +# Guarantees + +Due to its incredibly fundamental nature, `Vec` makes a lot of guarantees +about its design. This ensures that it's as low-overhead as possible in +the general case, and can be correctly manipulated in primitive ways +by unsafe code. Note that these guarantees refer to an unqualified `Vec<T>`. +If additional type parameters are added (e.g., to support custom allocators), +overriding their defaults may change the behavior. + +Most fundamentally, `Vec` is and always will be a (pointer, capacity, length) +triplet. No more, no less. The order of these fields is completely +unspecified, and you should use the appropriate methods to modify these. +The pointer will never be null, so this type is null-pointer-optimized. + +However, the pointer might not actually point to allocated memory. In particular, +if you construct a `Vec` with capacity 0 via [`Vec::new`](https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new), [`vec![]`](https://doc.rust-lang.org/nightly/alloc/macros/macro.vec.html), +[`Vec::with_capacity(0)`](https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.with_capacity), or by calling [`shrink_to_fit`] +on an empty Vec, it will not allocate memory. Similarly, if you store zero-sized +types inside a `Vec`, it will not allocate space for them. _Note that in this case +the `Vec` might not report a [`capacity`] of 0_. `Vec` will allocate if and only +if <code> +[size_of::\<T>](https://doc.rust-lang.org/nightly/core/mem/fn.size_of.html)() \* [capacity]() > 0</code>. In general, `Vec`'s allocation +details are very subtle --- if you intend to allocate memory using a `Vec` +and use it for something else (either to pass to unsafe code, or to build your +own memory-backed collection), be sure to deallocate this memory by using +`from_raw_parts` to recover the `Vec` and then dropping it. + +If a `Vec` _has_ allocated memory, then the memory it points to is on the heap +(as defined by the allocator Rust is configured to use by default), and its +pointer points to [`len`] initialized, contiguous elements in order (what +you would see if you coerced it to a slice), followed by <code> +[capacity] - [len]</code> +logically uninitialized, contiguous elements. + +A vector containing the elements `'a'` and `'b'` with capacity 4 can be +visualized as below. The top part is the `Vec` struct, it contains a +pointer to the head of the allocation in the heap, length and capacity. +The bottom part is the allocation on the heap, a contiguous memory block. + +```text + ptr len capacity + +--------+--------+--------+ + | 0x0123 | 2 | 4 | + +--------+--------+--------+ + | + v +Heap +--------+--------+--------+--------+ + | 'a' | 'b' | uninit | uninit | + +--------+--------+--------+--------+ +``` + +- **uninit** represents memory that is not initialized, see [`MaybeUninit`]. +- Note: the ABI is not stable and `Vec` makes no guarantees about its memory + layout (including the order of fields). + +`Vec` will never perform a "small optimization" where elements are actually +stored on the stack for two reasons: + +- It would make it more difficult for unsafe code to correctly manipulate + a `Vec`. The contents of a `Vec` wouldn't have a stable address if it were + only moved, and it would be more difficult to determine if a `Vec` had + actually allocated memory. + +- It would penalize the general case, incurring an additional branch + on every access. + +`Vec` will never automatically shrink itself, even if completely empty. This +ensures no unnecessary allocations or deallocations occur. Emptying a `Vec` +and then filling it back up to the same [`len`] should incur no calls to +the allocator. If you wish to free up unused memory, use +[`shrink_to_fit`] or [`shrink_to`]. + +[`push`] and [`insert`] will never (re)allocate if the reported capacity is +sufficient. [`push`] and [`insert`] _will_ (re)allocate if +<code> +[len] == [capacity]</code>. That is, the reported capacity is completely +accurate, and can be relied on. It can even be used to manually free the memory +allocated by a `Vec` if desired. Bulk insertion methods _may_ reallocate, even +when not necessary. + +`Vec` does not guarantee any particular growth strategy when reallocating +when full, nor when [`reserve`] is called. The current strategy is basic +and it may prove desirable to use a non-constant growth factor. Whatever +strategy is used will of course guarantee _O_(1) amortized [`push`]. + +It is guaranteed, in order to respect the intentions of the programmer, that +all of `vec![e_1, e_2, ..., e_n]`, `vec![x; n]`, and [`Vec::with_capacity(n)`] produce a `Vec` +that requests an allocation of the exact size needed for precisely `n` elements from the allocator, +and no other size (such as, for example: a size rounded up to the nearest power of 2). +The allocator will return an allocation that is at least as large as requested, but it may be larger. + +It is guaranteed that the [`Vec::capacity`] method returns a value that is at least the requested capacity +and not more than the allocated capacity. + +The method [`Vec::shrink_to_fit`](https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.shrink_to_fit) will attempt to discard excess capacity an allocator has given to a `Vec`. +If <code> +[len] == [capacity]</code>, then a `Vec<T>` can be converted +to and from a [`Box<[T]>`](https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html) without reallocating or moving the elements. +`Vec` exploits this fact as much as reasonable when implementing common conversions +such as [`into_boxed_slice`]. + +`Vec` will not specifically overwrite any data that is removed from it, +but also won't specifically preserve it. Its uninitialized memory is +scratch space that it may use however it wants. It will generally just do +whatever is most efficient or otherwise easy to implement. Do not rely on +removed data to be erased for security purposes. Even if you drop a `Vec`, its +buffer may simply be reused by another allocation. Even if you zero a `Vec`'s memory +first, that might not actually happen because the optimizer does not consider +this a side-effect that must be preserved. There is one case which we will +not break, however: using `unsafe` code to write to the excess capacity, +and then increasing the length to match, is always valid. + +Currently, `Vec` does not guarantee the order in which elements are dropped. +The order has changed in the past and may change again. |