use std::fmt::Debug; use std::iter::{empty, once, repeat_n}; use std::os::fd::AsFd; use std::pin::pin; use std::str::FromStr; use std::sync::Arc; use std::vec::Vec; use Default::default; use annotate_snippets::Renderer; use dsb::Cell; use dsb::cell::Style; use implicit_fn::implicit_fn; use itertools::Itertools; use lsp_types::{ Diagnostic, DiagnosticSeverity, TextDocumentIdentifier, TextDocumentPositionParams, }; use markdown::mdast::{self, Node}; use ropey::Rope; use serde_derive::{Deserialize, Serialize}; use url::Url; const D: Cell = Cell { letter: None, style: Style::new(FG, BG) }; use crate::rnd::{CellBuffer, simplify_path}; use crate::text::RopeExt; use crate::{FG, FONT, text}; struct Builder { c: usize, to: Vec, scratch: Vec, } enum Action { Put(Vec), Just(Vec), 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); } fn put_wrapping( &mut self, mut c: impl Iterator, 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); } fn put(&mut self, c: impl Iterator + 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 n == 0 { break; } 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); // 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, inherit: Style, l: Option, ) -> Vec { n.children() .iter() .flat_map(|i| *i) .flat_map(|x| self.run(x, inherit, l)) .chain(i) .collect() } fn extend( &self, n: &Node, inherit: Style, l: Option, ) -> Vec { self.extend_(n, empty(), inherit, l) } fn run( &self, node: &mdast::Node, mut inherit: Style, l: Option, ) -> Vec { 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, l) } Node::Link(_) => { inherit.flags |= Style::UNDERLINE; inherit.fg = [244, 196, 98]; self.extend(node, inherit, l) } Node::LinkReference(_) => { inherit.flags |= Style::UNDERLINE; inherit.fg = [244, 196, 98]; self.extend(node, inherit, l) } Node::Heading(_) => { inherit.flags |= Style::BOLD; inherit.fg = [255, 255, 255]; self.extend_(node, [Action::Finish], inherit, l) } Node::Strong(_) => self.extend(node, inherit | Style::BOLD, l), 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(self.c).zip(0..) { if e != '\n' { cell[y * self.c + x].letter = Some(e); } } } for ((x1, y1), (x2, y2), s, txt) in std::iter::from_coroutine(pin!( text::hl( code.lang .as_deref() .and_then( |x| text::LOADER.language_for_name(x) ) .or(l) .unwrap_or( text::LOADER .language_for_name("rust") .unwrap() ), &r, .., self.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); }, ) }, ); } assert_eq!(self.to.len() % self.c, 0); vec![Action::Finish, Action::Just(cell)] } Node::ThematicBreak(_) => { vec![Action::Finish, Action::Finish] } Node::Paragraph(paragraph) => paragraph .children .iter() .flat_map(|c| self.run(&c, inherit, l)) .chain([Action::Finish, Action::Finish]) .collect(), n => self.extend(n, inherit, l), } } } pub fn p(x: &str) -> Option { markdown::to_mdast(x, &markdown::ParseOptions::gfm()).ok() } fn len(node: &mdast::Node) -> Vec { 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 { len(node) .into_iter() .chunk_by(|&x| x != usize::MAX) .into_iter() .filter_map(|(b, g)| b.then(|| g.sum::())) .collect::>() } #[implicit_fn::implicit_fn] pub fn markdown2( c: usize, x: &Node, l: Option, ) -> Vec { let mut r = Builder { c, to: vec![], scratch: vec![] }; for (is_put, act) in r .run(&x, Style::new(FG, BG), l) .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::>() .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.get(..c).is_some() { r.to.drain(..c); } if r.to.iter().rev().take(c).all(_.letter.is_none()) && let Some(n) = r.to.len().checked_sub(c) && r.to.get(n..).is_some() { r.to.drain(n..); } r.to } pub const BG: [u8; 3] = text::col!("#191E27"); #[test] fn t() { use std::time::Instant; use dsb::F; use fimg::Image; let ppem = 18.0; let lh = 10.0; let (w, h) = (400, 8000); let (c, r) = dsb::fit(&crate::FONT, ppem, lh, (w, h)); let cells = markdown2(c, &p(include_str!("vec.md")).unwrap()); dbg!(l(&p(include_str!("vec.md")).unwrap())); dbg!(cells.len() / c); dbg!(w, h); dbg!(c, r); let mut fonts = dsb::Fonts::new( F::FontRef(*crate::FONT, &[(2003265652, 550.0)]), F::FontRef(*crate::BFONT, &[]), F::FontRef(*crate::IFONT, &[(2003265652, 550.0)]), F::FontRef(*crate::IFONT, &[]), ); let now = Instant::now(); let mut x = Image::build(w as _, h as _).fill(BG); unsafe { dsb::render( &cells, (c, r), ppem, &mut fonts, lh, true, x.as_mut(), (0, 0), ) }; println!("{:?}", now.elapsed()); x.as_ref().save("x"); } #[derive(Debug, Serialize, Deserialize, Hash)] pub struct Hovr { pub(crate) span: Option<[(VisualX, usize); 2]>, pub(crate) item: crate::rnd::CellBuffer, #[serde(skip)] pub(crate) range: Option, #[serde(skip, default = "tdp")] pub(crate) tdpp: TextDocumentPositionParams, } fn tdp() -> TextDocumentPositionParams { TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri: Url::from_str("/home/").unwrap(), }, position: default(), } } #[derive(Serialize, Deserialize, Hash)] pub struct DiagnosticHovr { pub(crate) span: Option<[(VisualX, usize); 2]>, pub(crate) t: CellBuffer, pub diag: Diagnostic, } impl DiagnosticHovr { #[implicit_fn] pub fn new( span: Option<[(VisualX, usize); 2]>, diag: Diagnostic, window: &Arc, r: usize, rope: &Rope, ) -> Self { let fw_15 = { let ppem = 15.0; let (fw, _) = dsb::dims(&FONT, ppem); fw }; let w = (window.surface_size().width as f32 / fw_15) as u16 - 5; let dawg = diag.data .as_ref() .unwrap_or_default() .get("rendered") .and_then(serde_json::Value::as_str) .map(String::from) .unwrap_or_else(|| { Renderer::styled() .decor_style( annotate_snippets::renderer::DecorStyle::Unicode, ).term_width(w as _) .render(&[to_snippet(&diag, rope)]) }); let mut t = pattypan::term::Terminal::new((w, r as u16 - 5), false); for b in simplify_path(&dawg.replace('\n', "\r\n").replace("βΈ¬", ":")) .bytes() { t.rx(b, std::fs::File::open("/dev/null").unwrap().as_fd()); } let y_lim = t .cells .rows() .rev() .position(|x| !x.iter().all(_.letter.is_none())) .map(|x| t.cells.r() as usize - x) .unwrap_or(20); let c = t.cells.c() as usize; let Some(x_lim) = t .cells .rows() .map(_.iter().rev().take_while(_.letter.is_none()).count()) .map(|x| c - x) .max() else { panic!() }; let n = t .cells .rows() .take(y_lim) .flat_map(|x| &x[..x_lim]) .copied() .collect(); Self { t: CellBuffer { c: x_lim, vo: 0, cells: n }, diag, span } } } impl std::fmt::Debug for DiagnosticHovr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DiagnosticHovr") .field("span", &self.span) .field("diag", &self.diag) .finish() } } pub type VisualX = usize; #[derive(Debug, Serialize, Deserialize, Hash)] pub enum Hoverable { Lsp(Hovr), Diagnostic(DiagnosticHovr), } impl Hoverable { pub fn tdpp(&self) -> Option { match self { Hoverable::Lsp(hovr) => Some(hovr.tdpp.clone()), Hoverable::Diagnostic(_) => None, } } } #[derive(Debug, Serialize, Deserialize, Default)] pub struct Hovring { pub of: Vec, #[serde(skip)] pub rndr: Option, } impl Debug for Rendered { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Rendered") .field("hash", &self.hash) .field("scroll", &self.scroll) .finish() } } pub struct Rendered { pub image: fimg::Image, 3>, pub hash: u64, pub scroll: u32, // in pixels } impl Hovring { pub fn rndr() {} } pub const HOV_HEIGHT: usize = 500; pub fn to_snippet<'a: 'c, 'b: 'c, 'c>( Diagnostic { range, severity, // code, // code_description, // source, message, .. }: &'a Diagnostic, t: &'b Rope, ) -> annotate_snippets::Group<'c> { use annotate_snippets::*; match severity { Some(DiagnosticSeverity::WARNING) => Level::WARNING, Some(DiagnosticSeverity::INFORMATION) => Level::INFO, Some(DiagnosticSeverity::HINT) => Level::HELP, _ => Level::ERROR, } .primary_title(message) .element(Snippet::source(t).annotation( AnnotationKind::Primary.span(t.l_range(*range).unwrap()), )) }