diff --git a/Cargo.lock b/Cargo.lock index 18831d9..ae34826 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -500,6 +500,10 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "careless" +version = "0.1.0" + [[package]] name = "castaway" version = "0.1.2" @@ -1651,6 +1655,22 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inkjet" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd9fd670f1a42725a90e7a97f5e6a777fc56f9031c1ee0ebcea8f91a6bb9c2f" +dependencies = [ + "anyhow", + "cc", + "once_cell", + "serde", + "thiserror", + "toml", + "tree-sitter", + "tree-sitter-highlight", +] + [[package]] name = "inout" version = "0.1.3" @@ -2166,13 +2186,13 @@ name = "nite" version = "0.1.0" dependencies = [ "anyhow", + "careless", "funnylog", + "inkjet", "kaydle", "log", "ming", "serde", - "tree-sitter-highlight", - "tree-sitter-rust", "widestring", ] @@ -2689,7 +2709,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit", + "toml_edit 0.21.1", ] [[package]] @@ -3140,6 +3160,15 @@ dependencies = [ "syn 2.0.63", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3592,11 +3621,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.12", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -3606,7 +3650,20 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.8", ] [[package]] @@ -3653,9 +3710,9 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.22.6" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7cc499ceadd4dcdf7ec6d4cbc34ece92c3fa07821e287aedecd4416c516dca" +checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d" dependencies = [ "cc", "regex", @@ -3663,26 +3720,15 @@ dependencies = [ [[package]] name = "tree-sitter-highlight" -version = "0.22.6" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaca0fe34fa96eec6aaa8e63308dbe1bafe65a6317487c287f93938959b21907" +checksum = "042342584c5a7a0b833d9fc4e2bdab3f9868ddc6c4b339a1e01451c6720868bc" dependencies = [ - "lazy_static", "regex", "thiserror", "tree-sitter", ] -[[package]] -name = "tree-sitter-rust" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "277690f420bf90741dea984f3da038ace46c4fe6047cba57a66822226cde1c93" -dependencies = [ - "cc", - "tree-sitter", -] - [[package]] name = "ttf-parser" version = "0.20.0" @@ -4250,6 +4296,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +dependencies = [ + "memchr", +] + [[package]] name = "wio" version = "0.2.2" @@ -4471,7 +4526,3 @@ dependencies = [ "quote", "syn 1.0.109", ] - -[[patch.unused]] -name = "careless" -version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 65f962a..9b86945 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,10 @@ ming.workspace = true anyhow.workspace = true widestring = "1.1.0" log.workspace = true -tree-sitter-highlight = "0.22.6" -tree-sitter-rust = "0.21.2" serde = { workspace = true, features = ["derive"] } kaydle = "0.2.0" +inkjet = { version = "0.10.5", features = ["language-rust", "language-toml", "language-zig"], default-features = false } +careless = { git = "https://codeberg.org/minky/careless", version = "0.1.0" } [workspace] members = [ diff --git a/crates/ming/src/element.rs b/crates/ming/src/element.rs index e4c8ead..18861d2 100644 --- a/crates/ming/src/element.rs +++ b/crates/ming/src/element.rs @@ -109,6 +109,7 @@ pub trait IntoElement: Sized { } } +impl FluentBuilder for crate::TextRun {} impl FluentBuilder for T {} /// An object that can be drawn to the screen. This is the trait that distinguishes `Views` from diff --git a/crates/ming/src/text_system.rs b/crates/ming/src/text_system.rs index decab5b..ba7424a 100644 --- a/crates/ming/src/text_system.rs +++ b/crates/ming/src/text_system.rs @@ -656,6 +656,8 @@ impl<'de> serde::Deserialize<'de> for FontWeight { .map_err(|()| E::custom("expected a font weight value (e.g. 'normal' or 'bold')")) } } + + deserializer.deserialize_any(Visitor) } } diff --git a/crates/ming/src/util.rs b/crates/ming/src/util.rs index 089ce01..6a074cd 100644 --- a/crates/ming/src/util.rs +++ b/crates/ming/src/util.rs @@ -15,6 +15,13 @@ pub trait FluentBuilder { { f(self) } + + /// Imperatively modify self with the given closure. + fn apply(mut self, f: impl FnOnce(&mut Self)) -> Self where Self: Sized { + f(&mut self); + + self + } /// Conditionally modify self with the given closure. fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self diff --git a/src/buffer.rs b/src/buffer.rs index 9acd986..01c1128 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,7 +1,10 @@ +use careless::prelude::*; +use inkjet::tree_sitter_highlight::{Error, HighlightEvent, Highlighter}; use std::{ops::Range, path::PathBuf}; -use tree_sitter_highlight::{HighlightConfiguration, Highlighter}; use widestring::Utf16String; +use crate::editor::Theme; + use super::*; pub struct Buffer { @@ -31,71 +34,43 @@ impl Buffer { } } - pub fn styled( - &self, - default_style: &TextStyle, - theme: &crate::editor::Theme, - syntax_set: &SyntaxSet, - ) -> Vec { + pub fn styled(&self, default_style: &TextStyle, theme: &Theme) -> Vec { let text = self.text.to_string(); - if let Some(syntax) = self + if let Some(language) = self .path .as_ref() .and_then(|path| path.extension()) .and_then(|extension| { - Some( - match extension.to_str().expect("non-utf8 file ext") { - "rs" => HighlightConfiguration::new( - tree_sitter_rust::language(), - "rust", - tree_sitter_rust::HIGHLIGHTS_QUERY, - tree_sitter_rust::INJECTIONS_QUERY, - "", - ), - _ => return None, - } - .unwrap(), - ) + inkjet::Language::from_token(extension.to_str()?).map(|lang| lang.config()) }) { - let mut parse = ParseState::new(syntax); - let hler = Highlighter::new(theme); - let mut hl = HighlightState::new(&hler, ScopeStack::new()); + let mut hl = Highlighter::new(); + let mut lines = vec![]; - text.lines() - .flat_map(|line| { - parse.parse_line(line, &syntax_set).map(|parsed| { - StyledText::new(Arc::from(line)).with_highlights( - default_style, - RangedHighlightIterator::new(&mut hl, &parsed, line, &hler).map( - move |(style, _text, range)| { - ( - range, - HighlightStyle { - color: Some(hsla_from_syntect(style.foreground)), - background_color: None, - font_style: style - .font_style - .contains(SFontStyle::ITALIC) - .then(|| FontStyle::Italic), - underline: style - .font_style - .contains(SFontStyle::UNDERLINE) - .then(|| UnderlineStyle::default()), - font_weight: style - .font_style - .contains(SFontStyle::BOLD) - .then(|| FontWeight::BOLD), - ..Default::default() - }, - ) - }, - ), - ) + for line in text.lines() { + // damnation + match hl + .highlight(language, line.as_bytes(), None, |lang| { + inkjet::Language::from_token(lang).map(|lang| lang.config()) }) - }) - .collect() + .and_then(|hls| { + Ok(StyledText::new(Arc::from(line)).with_runs( + MingHighlighter::new(hls, default_style, theme).try_to_collect()?, + )) + }) { + Ok(styled) => lines.push(styled), + Err(error) => { + log::error!("highlight error: {error}"); + + lines.push(StyledText::new(Arc::from(line))); + + continue; + } + } + } + + lines } else { text.lines() .map(|line| StyledText::new(Arc::from(line))) @@ -104,12 +79,57 @@ impl Buffer { } } -pub fn hsla_from_syntect(color: Color) -> Hsla { - Rgba { - r: color.r as f32 / 255.0, - g: color.g as f32 / 255.0, - b: color.b as f32 / 255.0, - a: color.a as f32 / 255.0, - } - .into() +pub struct MingHighlighter<'a, I> { + iter: I, + default_style: &'a TextStyle, + theme: &'a Theme, + current_hl_idx: Option, + current_span: Option>, +} + +impl<'a, I> MingHighlighter<'a, I> { + pub fn new(iter: I, default_style: &'a TextStyle, theme: &'a Theme) -> Self { + Self { + iter, + default_style, + theme, + current_hl_idx: None, + current_span: None, + } + } +} + +impl>> Iterator for MingHighlighter<'_, I> { + type Item = Result; + + fn next(&mut self) -> Option { + use careless::prelude::*; + + TryOption::into( + try { + match self.iter.try_next()? { + HighlightEvent::Source { start, end } => { + self.current_span = Some(start..end); + + self.default_style.to_run(end - start).apply(|run| { + if let Some(hl) = self + .current_hl_idx + .and_then(|idx| self.theme.highlight_indices.get(idx)) + { + hl.apply_to_run(run) + } + }) + } + HighlightEvent::HighlightStart(hl_idx) => { + self.current_hl_idx = Some(hl_idx.0); + self.try_next()? + } + HighlightEvent::HighlightEnd => { + self.current_hl_idx = None; + self.try_next()? + } + } + }, + ) + } } diff --git a/src/editor.rs b/src/editor.rs index 2cce0f5..6c4bf70 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -11,7 +11,7 @@ pub struct EditorSettings { style: EditorStyle, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Deserialize)] pub struct Highlight { pub color: Option, #[serde(default)] @@ -20,17 +20,41 @@ pub struct Highlight { pub weight: Option, } -#[derive(Clone, Debug, Serialize, Deserialize)] +impl Highlight { + pub fn apply_to_run(&self, run: &mut TextRun) { + if let Some(color) = self.color { + run.color = color.into(); + } + + if let Some(style) = self.style { + run.font.style = style; + } + + if let Some(weight) = self.weight { + run.font.weight = weight; + } + } +} + +#[derive(Clone, Debug, Deserialize)] pub struct Theme { pub name: String, pub background_color: Rgba, pub text_color: Rgba, pub highlights: HashMap, + #[serde(skip)] + pub highlight_indices: Vec } impl Theme { pub fn load(text: &str) -> Result { - kaydle::serde::from_str(text) + kaydle::serde::from_str(text).map(|mut me: Self| { + me.highlight_indices = inkjet::constants::HIGHLIGHT_NAMES.iter().flat_map(|hl| { + me.highlights.get(*hl).cloned() + }).collect(); + + me + }) } } @@ -61,6 +85,7 @@ pub struct Editor { buf: Model, settings: Model, cursor: Point, + scroll: Point, logger: Logger, } @@ -82,6 +107,7 @@ impl Editor { settings, logger, cursor: Point::default(), + scroll: Point::default() }) } } @@ -107,10 +133,10 @@ impl Render for Editor { me.cursor.y = me.cursor.y.saturating_sub(1); funnylog::trace!(me.logger, ?me.cursor, "up"); })) + .overflow_hidden() .child(element::EditorElement::new( cx.view(), self.settings.read(cx).style.clone(), - self.cursor, )) } } diff --git a/src/editor/element.rs b/src/editor/element.rs index 274a38f..decb5ce 100644 --- a/src/editor/element.rs +++ b/src/editor/element.rs @@ -1,20 +1,15 @@ -use crate::buffer::hsla_from_syntect; - use super::*; - pub struct EditorElement { editor: View, style: EditorStyle, - cursor: Point, } impl EditorElement { - pub fn new(viewref: &View, style: EditorStyle, cursor: Point) -> Self { + pub fn new(viewref: &View, style: EditorStyle) -> Self { Self { editor: viewref.clone(), style, - cursor, } } } @@ -66,14 +61,9 @@ impl Element for EditorElement { display: Display::Flex, flex_direction: FlexDirection::Column, align_content: Some(AlignContent::FlexStart), - overflow: Point::all(Overflow::Scroll), + overflow: Point::all(Overflow::Hidden), size: Size::full(), - background: self - .style - .theme - .settings - .background - .map(|bg| Fill::Color(hsla_from_syntect(bg))), + background: Some(Fill::Color(self.style.theme.background_color.into())), ..Default::default() }, children.iter().copied(), @@ -95,35 +85,12 @@ impl Element for EditorElement { request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> Self::PrepaintState { - if self.cursor.y > 0 { - let offset = Point::new( - px(0.), - request_layout.styled.iter_mut().take(self.cursor.y).fold( - px(0.), - |px, (id, _, bounds)| { - *bounds = cx.layout_bounds(*id); - px + bounds.size.height - }, - ), - ); - - cx.with_element_offset(offset, |cx| { - request_layout - .styled - .iter_mut() - .skip(self.cursor.y) - .for_each(|(_id, styled, bounds)| styled.prepaint(None, *bounds, &mut (), cx)); - }); - } else { + cx.with_element_offset(self.editor.read(cx).scroll, |cx| { request_layout .styled .iter_mut() - .skip(self.cursor.y) - .for_each(|(id, styled, bounds)| { - *bounds = cx.layout_bounds(*id); - styled.prepaint(None, *bounds, &mut (), cx) - }); - } + .for_each(|(_id, styled, bounds)| styled.prepaint(None, *bounds, &mut (), cx)); + }); EditorLayout {} } @@ -151,8 +118,7 @@ impl Element for EditorElement { log::trace!("{scroll:#?}"); view.update(cx, |editor, cx| match scroll.delta { ScrollDelta::Lines(lines) => { - let lines_y = lines.y.ceil() as i32; - editor.cursor.y = editor.cursor.y.saturating_add_signed(lines_y as isize) + } ScrollDelta::Pixels(pix) => { let px_y = (pix.y.ceil().0 / font_size.0) as i32; @@ -163,22 +129,18 @@ impl Element for EditorElement { cx.with_text_style( Some(TextStyleRefinement { - color: self - .style - .theme - .settings - .foreground - .map(hsla_from_syntect), + color: Some(self.style.theme.text_color.into()), ..Default::default() }), |cx| { - request_layout - .styled - .iter_mut() - .skip(self.cursor.y) - .for_each(|(id, styled, bounds)| { - styled.paint(None, *bounds, &mut (), &mut (), cx); - }); + cx.with_element_offset(self.editor.read(cx).scroll, |cx| { + request_layout + .styled + .iter_mut() + .for_each(|(_id, styled, bounds)| { + styled.paint(None, *bounds, &mut (), &mut (), cx) + }); + }); }, ); } diff --git a/src/main.rs b/src/main.rs index 680c6ad..957e844 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ +#![feature(try_blocks)] + use std::sync::Arc; use funnylog::{filter::LevelFilter, span, Drain, Level}; -use ming::*; +use ming::{*, prelude::*}; mod buffer; mod editor;