i treesittered

This commit is contained in:
Borodinov Ilya 2024-05-17 22:01:05 +03:00
parent 638a9a112b
commit 7424dc67b7
Signed by: noth
GPG key ID: 75503B2EF596D1BD
9 changed files with 219 additions and 148 deletions

97
Cargo.lock generated
View file

@ -500,6 +500,10 @@ dependencies = [
"wayland-client", "wayland-client",
] ]
[[package]]
name = "careless"
version = "0.1.0"
[[package]] [[package]]
name = "castaway" name = "castaway"
version = "0.1.2" version = "0.1.2"
@ -1651,6 +1655,22 @@ dependencies = [
"hashbrown", "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]] [[package]]
name = "inout" name = "inout"
version = "0.1.3" version = "0.1.3"
@ -2166,13 +2186,13 @@ name = "nite"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"careless",
"funnylog", "funnylog",
"inkjet",
"kaydle", "kaydle",
"log", "log",
"ming", "ming",
"serde", "serde",
"tree-sitter-highlight",
"tree-sitter-rust",
"widestring", "widestring",
] ]
@ -2689,7 +2709,7 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
dependencies = [ dependencies = [
"toml_edit", "toml_edit 0.21.1",
] ]
[[package]] [[package]]
@ -3140,6 +3160,15 @@ dependencies = [
"syn 2.0.63", "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]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@ -3592,11 +3621,26 @@ dependencies = [
"tokio", "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]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.5" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
@ -3606,7 +3650,20 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"toml_datetime", "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]] [[package]]
@ -3653,9 +3710,9 @@ dependencies = [
[[package]] [[package]]
name = "tree-sitter" name = "tree-sitter"
version = "0.22.6" version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df7cc499ceadd4dcdf7ec6d4cbc34ece92c3fa07821e287aedecd4416c516dca" checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
dependencies = [ dependencies = [
"cc", "cc",
"regex", "regex",
@ -3663,26 +3720,15 @@ dependencies = [
[[package]] [[package]]
name = "tree-sitter-highlight" name = "tree-sitter-highlight"
version = "0.22.6" version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaca0fe34fa96eec6aaa8e63308dbe1bafe65a6317487c287f93938959b21907" checksum = "042342584c5a7a0b833d9fc4e2bdab3f9868ddc6c4b339a1e01451c6720868bc"
dependencies = [ dependencies = [
"lazy_static",
"regex", "regex",
"thiserror", "thiserror",
"tree-sitter", "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]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.20.0" version = "0.20.0"
@ -4250,6 +4296,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winnow"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "wio" name = "wio"
version = "0.2.2" version = "0.2.2"
@ -4471,7 +4526,3 @@ dependencies = [
"quote", "quote",
"syn 1.0.109", "syn 1.0.109",
] ]
[[patch.unused]]
name = "careless"
version = "0.1.0"

View file

@ -9,10 +9,10 @@ ming.workspace = true
anyhow.workspace = true anyhow.workspace = true
widestring = "1.1.0" widestring = "1.1.0"
log.workspace = true log.workspace = true
tree-sitter-highlight = "0.22.6"
tree-sitter-rust = "0.21.2"
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
kaydle = "0.2.0" 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] [workspace]
members = [ members = [

View file

@ -109,6 +109,7 @@ pub trait IntoElement: Sized {
} }
} }
impl FluentBuilder for crate::TextRun {}
impl<T: IntoElement> FluentBuilder for T {} impl<T: IntoElement> FluentBuilder for T {}
/// An object that can be drawn to the screen. This is the trait that distinguishes `Views` from /// An object that can be drawn to the screen. This is the trait that distinguishes `Views` from

View file

@ -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')")) .map_err(|()| E::custom("expected a font weight value (e.g. 'normal' or 'bold')"))
} }
} }
deserializer.deserialize_any(Visitor)
} }
} }

View file

@ -15,6 +15,13 @@ pub trait FluentBuilder {
{ {
f(self) 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. /// Conditionally modify self with the given closure.
fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self

View file

@ -1,7 +1,10 @@
use careless::prelude::*;
use inkjet::tree_sitter_highlight::{Error, HighlightEvent, Highlighter};
use std::{ops::Range, path::PathBuf}; use std::{ops::Range, path::PathBuf};
use tree_sitter_highlight::{HighlightConfiguration, Highlighter};
use widestring::Utf16String; use widestring::Utf16String;
use crate::editor::Theme;
use super::*; use super::*;
pub struct Buffer { pub struct Buffer {
@ -31,71 +34,43 @@ impl Buffer {
} }
} }
pub fn styled( pub fn styled(&self, default_style: &TextStyle, theme: &Theme) -> Vec<StyledText> {
&self,
default_style: &TextStyle,
theme: &crate::editor::Theme,
syntax_set: &SyntaxSet,
) -> Vec<StyledText> {
let text = self.text.to_string(); let text = self.text.to_string();
if let Some(syntax) = self if let Some(language) = self
.path .path
.as_ref() .as_ref()
.and_then(|path| path.extension()) .and_then(|path| path.extension())
.and_then(|extension| { .and_then(|extension| {
Some( inkjet::Language::from_token(extension.to_str()?).map(|lang| lang.config())
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(),
)
}) })
{ {
let mut parse = ParseState::new(syntax); let mut hl = Highlighter::new();
let hler = Highlighter::new(theme); let mut lines = vec![];
let mut hl = HighlightState::new(&hler, ScopeStack::new());
text.lines() for line in text.lines() {
.flat_map(|line| { // damnation
parse.parse_line(line, &syntax_set).map(|parsed| { match hl
StyledText::new(Arc::from(line)).with_highlights( .highlight(language, line.as_bytes(), None, |lang| {
default_style, inkjet::Language::from_token(lang).map(|lang| lang.config())
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()
},
)
},
),
)
}) })
}) .and_then(|hls| {
.collect() 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 { } else {
text.lines() text.lines()
.map(|line| StyledText::new(Arc::from(line))) .map(|line| StyledText::new(Arc::from(line)))
@ -104,12 +79,57 @@ impl Buffer {
} }
} }
pub fn hsla_from_syntect(color: Color) -> Hsla { pub struct MingHighlighter<'a, I> {
Rgba { iter: I,
r: color.r as f32 / 255.0, default_style: &'a TextStyle,
g: color.g as f32 / 255.0, theme: &'a Theme,
b: color.b as f32 / 255.0, current_hl_idx: Option<usize>,
a: color.a as f32 / 255.0, current_span: Option<Range<usize>>,
} }
.into()
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<I: Iterator<Item = Result<HighlightEvent, Error>>> Iterator for MingHighlighter<'_, I> {
type Item = Result<TextRun, Error>;
fn next(&mut self) -> Option<Self::Item> {
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()?
}
}
},
)
}
} }

View file

@ -11,7 +11,7 @@ pub struct EditorSettings {
style: EditorStyle, style: EditorStyle,
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Deserialize)]
pub struct Highlight { pub struct Highlight {
pub color: Option<Rgba>, pub color: Option<Rgba>,
#[serde(default)] #[serde(default)]
@ -20,17 +20,41 @@ pub struct Highlight {
pub weight: Option<FontWeight>, pub weight: Option<FontWeight>,
} }
#[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 struct Theme {
pub name: String, pub name: String,
pub background_color: Rgba, pub background_color: Rgba,
pub text_color: Rgba, pub text_color: Rgba,
pub highlights: HashMap<String, Highlight>, pub highlights: HashMap<String, Highlight>,
#[serde(skip)]
pub highlight_indices: Vec<Highlight>
} }
impl Theme { impl Theme {
pub fn load(text: &str) -> Result<Self, kaydle::serde::de::Error> { pub fn load(text: &str) -> Result<Self, kaydle::serde::de::Error> {
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<crate::buffer::Buffer>, buf: Model<crate::buffer::Buffer>,
settings: Model<EditorSettings>, settings: Model<EditorSettings>,
cursor: Point<usize>, cursor: Point<usize>,
scroll: Point<Pixels>,
logger: Logger, logger: Logger,
} }
@ -82,6 +107,7 @@ impl Editor {
settings, settings,
logger, logger,
cursor: Point::default(), cursor: Point::default(),
scroll: Point::default()
}) })
} }
} }
@ -107,10 +133,10 @@ impl Render for Editor {
me.cursor.y = me.cursor.y.saturating_sub(1); me.cursor.y = me.cursor.y.saturating_sub(1);
funnylog::trace!(me.logger, ?me.cursor, "up"); funnylog::trace!(me.logger, ?me.cursor, "up");
})) }))
.overflow_hidden()
.child(element::EditorElement::new( .child(element::EditorElement::new(
cx.view(), cx.view(),
self.settings.read(cx).style.clone(), self.settings.read(cx).style.clone(),
self.cursor,
)) ))
} }
} }

View file

@ -1,20 +1,15 @@
use crate::buffer::hsla_from_syntect;
use super::*; use super::*;
pub struct EditorElement { pub struct EditorElement {
editor: View<Editor>, editor: View<Editor>,
style: EditorStyle, style: EditorStyle,
cursor: Point<usize>,
} }
impl EditorElement { impl EditorElement {
pub fn new(viewref: &View<Editor>, style: EditorStyle, cursor: Point<usize>) -> Self { pub fn new(viewref: &View<Editor>, style: EditorStyle) -> Self {
Self { Self {
editor: viewref.clone(), editor: viewref.clone(),
style, style,
cursor,
} }
} }
} }
@ -66,14 +61,9 @@ impl Element for EditorElement {
display: Display::Flex, display: Display::Flex,
flex_direction: FlexDirection::Column, flex_direction: FlexDirection::Column,
align_content: Some(AlignContent::FlexStart), align_content: Some(AlignContent::FlexStart),
overflow: Point::all(Overflow::Scroll), overflow: Point::all(Overflow::Hidden),
size: Size::full(), size: Size::full(),
background: self background: Some(Fill::Color(self.style.theme.background_color.into())),
.style
.theme
.settings
.background
.map(|bg| Fill::Color(hsla_from_syntect(bg))),
..Default::default() ..Default::default()
}, },
children.iter().copied(), children.iter().copied(),
@ -95,35 +85,12 @@ impl Element for EditorElement {
request_layout: &mut Self::RequestLayoutState, request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
if self.cursor.y > 0 { cx.with_element_offset(self.editor.read(cx).scroll, |cx| {
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 {
request_layout request_layout
.styled .styled
.iter_mut() .iter_mut()
.skip(self.cursor.y) .for_each(|(_id, styled, bounds)| styled.prepaint(None, *bounds, &mut (), cx));
.for_each(|(id, styled, bounds)| { });
*bounds = cx.layout_bounds(*id);
styled.prepaint(None, *bounds, &mut (), cx)
});
}
EditorLayout {} EditorLayout {}
} }
@ -151,8 +118,7 @@ impl Element for EditorElement {
log::trace!("{scroll:#?}"); log::trace!("{scroll:#?}");
view.update(cx, |editor, cx| match scroll.delta { view.update(cx, |editor, cx| match scroll.delta {
ScrollDelta::Lines(lines) => { 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) => { ScrollDelta::Pixels(pix) => {
let px_y = (pix.y.ceil().0 / font_size.0) as i32; let px_y = (pix.y.ceil().0 / font_size.0) as i32;
@ -163,22 +129,18 @@ impl Element for EditorElement {
cx.with_text_style( cx.with_text_style(
Some(TextStyleRefinement { Some(TextStyleRefinement {
color: self color: Some(self.style.theme.text_color.into()),
.style
.theme
.settings
.foreground
.map(hsla_from_syntect),
..Default::default() ..Default::default()
}), }),
|cx| { |cx| {
request_layout cx.with_element_offset(self.editor.read(cx).scroll, |cx| {
.styled request_layout
.iter_mut() .styled
.skip(self.cursor.y) .iter_mut()
.for_each(|(id, styled, bounds)| { .for_each(|(_id, styled, bounds)| {
styled.paint(None, *bounds, &mut (), &mut (), cx); styled.paint(None, *bounds, &mut (), &mut (), cx)
}); });
});
}, },
); );
} }

View file

@ -1,7 +1,9 @@
#![feature(try_blocks)]
use std::sync::Arc; use std::sync::Arc;
use funnylog::{filter::LevelFilter, span, Drain, Level}; use funnylog::{filter::LevelFilter, span, Drain, Level};
use ming::*; use ming::{*, prelude::*};
mod buffer; mod buffer;
mod editor; mod editor;