A simple CPU rendered GUI IDE experience.
use helix for syntax highlighting
bendn 5 months ago
parent 0d9796b · commit 0574121
-rw-r--r--Cargo.toml7
-rw-r--r--sample.rs19
-rw-r--r--src/bar.rs6
-rw-r--r--src/main.rs16
-rw-r--r--src/text.rs358
5 files changed, 270 insertions, 136 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 6fb7ede..2ed4b48 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,12 +14,10 @@ swash = "0.2.5"
winit = "0.30.12"
tree-sitter = "0.25.0"
-tree-sitter-rust = "0.24"
-tree-sitter-highlight = "0.25.0"
car = "0.1.2"
memchr = "2.7.5"
lower = "0.2.0"
-amap = "0.1.2"
+amap = "0.1.3"
run_times = "0.1.0"
array_chunks = "1.0.0"
rust-fsm = { version = "0.8.0", path = "../rust-fsm/rust-fsm", features = [
@@ -29,6 +27,9 @@ clipp = "0.1.0"
parking_lot = "0.12.4"
diff-match-patch-rs = "0.5.1"
regex = "1.11.3"
+tree-house = { version = "0.3.0", features = ["fixtures"] }
+helix-loader = { path = "../helix/helix-loader/" }
+helix-core = { path = "../helix/helix-core/" }
[build-dependencies]
cc = "*"
diff --git a/sample.rs b/sample.rs
deleted file mode 100644
index fe9bfd5..0000000
--- a/sample.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use atools::Chunked;
-use dsb::Cell;
-use dsb::cell::Style;
-use ropey::Rope;
-use tree_sitter::{InputEdit, Language, Parser, Point, Tree};
-use tree_sitter_highlight::{
- HighlightConfiguration, HighlightEvent, Highlighter,
-};
-#[rustfmt::skip]
-const NAMES: [&str; 13] = ["attribute", "comment", "constant", "function", "keyword", "number", "operator", "punctuation",
- "string", "tag", "type", "variable", "variable.parameter"];
-#[rustfmt::skip]
-const COLORS: [[u8; 3]; 13] = car::map!(
- [
- b"ffd173", b"5c6773", b"DFBFFF", b"FFD173", b"FFAD66", b"dfbfff", b"F29E74", b"cccac2",
- b"D5FF80", b"DFBFFF", b"73D0FF", b"5ccfe6", b"5ccfe6"
- ],
- |x| color(x)
-);
diff --git a/src/bar.rs b/src/bar.rs
index a624aa8..e453651 100644
--- a/src/bar.rs
+++ b/src/bar.rs
@@ -33,9 +33,13 @@ impl Bar {
match state {
State::Default if super::ctrl() => {
let x = "C + { S, Q, V, Z, Y }".chars();
- x.zip(row).for_each(|(x, y)| {
+ x.zip(&mut *row).for_each(|(x, y)| {
y.letter = Some(x);
});
+ row.iter_mut()
+ .rev()
+ .zip(self.last_action.chars().rev())
+ .for_each(|(x, y)| x.letter = Some(y));
}
State::Default => {
row[1.."gracilaria".len() + 1]
diff --git a/src/main.rs b/src/main.rs
index 23f3334..df2555f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,8 @@
// this looks pretty good though
#![feature(tuple_trait, unboxed_closures, fn_traits)]
#![feature(
+ const_cmp,
+ const_default,
import_trait_associated_functions,
if_let_guard,
deref_patterns,
@@ -38,6 +40,7 @@ mod bar;
mod text;
mod winit_app;
fn main() {
+ // text::man();
entry(EventLoop::new().unwrap())
}
#[derive(Debug)]
@@ -297,6 +300,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
});
}
},
+ origin.as_deref(),
);
bar.write_to(
@@ -312,8 +316,8 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
&text,
);
- // println!("cell=");
- // dbg!(now.elapsed());
+ println!("cell=");
+ dbg!(now.elapsed());
let now = Instant::now();
unsafe {
dsb::render(
@@ -365,7 +369,13 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
// }
let buffer = surface.buffer_mut().unwrap();
-
+ let x = unsafe {
+ std::slice::from_raw_parts_mut(
+ buffer.as_ptr() as *mut u8,
+ buffer.len() * 4,
+ )
+ .as_chunks_unchecked_mut::<4>()
+ };
fimg::overlay::copy_rgb_bgr_(
i.flatten(),
unsafe {
diff --git a/src/text.rs b/src/text.rs
index 3f187dd..08c1cf2 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -1,38 +1,59 @@
use std::cmp::min;
use std::fmt::{Debug, Display};
use std::ops::{Not as _, Range};
+use std::path::Path;
use std::sync::LazyLock;
-use atools::Chunked;
+use atools::prelude::*;
use diff_match_patch_rs::{DiffMatchPatch, Patches};
use dsb::Cell;
use dsb::cell::Style;
+use helix_core::Syntax;
+use helix_core::syntax::{HighlightEvent, Loader};
use implicit_fn::implicit_fn;
use ropey::{Rope, RopeSlice};
-use tree_sitter_highlight::{
- HighlightConfiguration, HighlightEvent, Highlighter,
-};
use winit::keyboard::{NamedKey, SmolStr};
-#[rustfmt::skip]
-const NAMES: [&str; 13] = ["attribute", "comment", "constant", "function", "keyword", "number", "operator", "punctuation",
- "string", "tag", "type", "variable", "variable.parameter"];
-#[rustfmt::skip]
-const COLORS: [[u8; 3]; 13] = car::map!(
- [
- b"ffd173", b"5c6773", b"DFBFFF", b"FFD173", b"FFAD66", b"dfbfff", b"F29E74", b"cccac2",
- b"D5FF80", b"DFBFFF", b"73D0FF", b"5ccfe6", b"5ccfe6"
- ],
- |x| color(x)
-);
-
-const STYLES: [Option<u8>; 13] = amap::amap! {
- 4 | 9 => Style::ITALIC| Style::BOLD,
- 1 | 3 => Style::ITALIC,
- 12 => 0,
-};
-
+macro_rules! theme {
+ ($($x:literal $color:literal),+ $(,)?) => {
+ #[rustfmt::skip]
+ const NAMES: [&str; 15] = [$($x),+];
+ #[rustfmt::skip]
+ const COLORS: [[u8; 3]; NAMES.len()] = car::map!([$($color),+], |x| color(x.tail())
+ );};
+}
+theme! {
+ "attribute" b"#ffd173",
+ "comment" b"#5c6773",
+ "constant" b"#DFBFFF",
+ "function" b"#FFD173",
+ "variable.builtin" b"#FFAD66",
+ "keyword" b"#FFAD66",
+ "number" b"#dfbfff",
+ "operator" b"#F29E74",
+ "punctuation" b"#cccac2",
+ "string" b"#D5FF80",
+ "tag" b"#DFBFFF",
+ "type" b"#73D0FF",
+ "variable" b"#cccac2",
+ "variable.parameter" b"#DFBFFF",
+ "namespace" b"#73d0ff",
+}
+const fn of(x: &'static str) -> usize {
+ let mut i = 0;
+ while i < NAMES.len() {
+ if NAMES[i] == x {
+ return i;
+ }
+ i += 1;
+ }
+ panic!()
+}
+const STYLES: [u8; NAMES.len()] = amap::amap_d! {
+ const { of("type") } | const { of("keyword") } | const { of("tag") } => Style::ITALIC | Style::BOLD,
+ const { of("comment") } | const { of("function") }=> Style::ITALIC,
+};
const fn color(x: &[u8; 6]) -> [u8; 3] {
car::map!(
@@ -76,7 +97,6 @@ impl Diff {
pub struct TextArea {
pub rope: Rope,
pub cursor: usize,
- highlighter: Highlighter,
pub column: usize,
/// ┌─────────────────┐
/// │#invisible text │
@@ -111,7 +131,6 @@ impl Clone for TextArea {
Self {
rope: self.rope.clone(),
cursor: self.cursor,
- highlighter: Highlighter::default(),
column: self.column,
vo: self.vo,
r: self.r,
@@ -298,13 +317,13 @@ impl TextArea {
self.right();
} else {
self.left();
+ self.right();
while is_word(self.at()) {
if self.cursor == 0 {
return;
}
self.left();
}
- self.right();
}
}
@@ -413,23 +432,9 @@ impl TextArea {
(ox, oy): (usize, usize),
selection: Option<Range<usize>>,
apply: impl FnOnce((usize, usize), &mut Self, &mut [Cell]),
+ path: Option<&Path>,
) {
let (c, r) = (self.c, self.r);
- static HL: LazyLock<HighlightConfiguration> =
- LazyLock::new(|| {
- let mut x = HighlightConfiguration::new(
- tree_sitter_rust::LANGUAGE.into(),
- "rust",
- include_str!("queries.scm"),
- tree_sitter_rust::INJECTIONS_QUERY,
- "",
- )
- .unwrap();
-
- x.configure(&NAMES);
- x
- });
-
let mut cells = vec![
Cell {
style: Style { color, bg, flags: 0 },
@@ -438,78 +443,71 @@ impl TextArea {
(self.l().max(r) + r - 1) * c
];
- // dbg!(unsafe {
- // String::from_utf8_unchecked(
- // self.rope.bytes().collect::<Vec<_>>(),
+ let language = path
+ .and_then(|x| LOADER.language_for_filename(x))
+ .unwrap_or_else(|| LOADER.language_for_name("rust").unwrap());
+ let syntax =
+ Syntax::new(self.rope.slice(..), language, &LOADER).unwrap();
+
+ // println!(
+ // "{}",
+ // tree_house::fixtures::highlighter_fixture(
+ // "hmm",
+ // &*LOADER,
+ // |y| NAMES[y.idx()].to_string(),
+ // &syntax.inner,
+ // self.rope.slice(..),
+ // ..,
// )
- // });
- let mut s = 0;
- for hl in self
- .highlighter
- .highlight(
- &HL,
- &self.rope.bytes().collect::<Vec<_>>(),
- None,
- |_| None,
- )
- .unwrap()
- .map(Result::unwrap)
- {
- match hl {
- HighlightEvent::Source { start, end } => {
- // for elem in start..end {
- // styles[elem] = s;
- // }
- let y1 = self.rope.byte_to_line(start);
- let y2 = self.rope.byte_to_line(end);
- let x1 = min(
- self.rope.byte_to_char(start)
- - self.rope.line_to_char(y1),
- c,
- );
- let x2 = min(
- self.rope.byte_to_char(end)
- - self.rope.line_to_char(y2),
- c,
- );
-
- cells.get_mut(y1 * c + x1..y2 * c + x2).map(|x| {
- x.iter_mut().for_each(|x| {
- x.style.flags = STYLES[s].unwrap_or_default();
- x.style.color = COLORS[s];
- })
- });
- // println!(
- // "highlight {} {s} {}: {:?}",
- // self.rope.byte_slice(start..end),
- // NAMES[s],
- // COLORS[s],
- // )
- }
- HighlightEvent::HighlightStart(s_) => s = s_.0,
- HighlightEvent::HighlightEnd => s = 0,
+ // );
+ let s = self.rope.line_to_char(self.vo);
+ let e = self
+ .rope
+ .try_line_to_char(self.vo + self.r + 20)
+ .unwrap_or(self.rope.len_chars());
+ let mut h = syntax.highlighter(
+ self.rope.slice(..),
+ &LOADER,
+ s as u32..e as u32,
+ );
+ let mut at = 0;
+ let mut highlight_stack = Vec::with_capacity(8);
+
+ loop {
+ let (e, new_highlights) = h.advance();
+ if e == HighlightEvent::Refresh {
+ highlight_stack.clear();
}
- }
+ highlight_stack.extend(new_highlights);
- // let (a, b) = code.into_iter().collect::<(Vec<_>, Vec<_>)>();
-
- // let mut i = 0;
- // let mut y = 0;
- // let mut x = 0;
- // while i < code.len() {
- // if code[i] == b'\n' {
- // x = 0;
- // y += 1;
- // if y == r {
- // break;
- // }
- // } else {
- // cells[y * c + x].letter = Some(code[i] as char);
- // cells[y * c + x].style.color = COLORS[styles[i]];
- // x += 1
- // }
- // i += 1;
- // }
+ let end = h.next_event_offset() as _;
+ if end == 4294967295 {
+ break;
+ }
+ for &h in &highlight_stack {
+ let y1 = self.rope.byte_to_line(at);
+ let y2 = self.rope.byte_to_line(end);
+ let x1 = min(
+ self.rope.byte_to_char(at)
+ - self.rope.line_to_char(y1),
+ c,
+ );
+ let x2 = min(
+ self.rope.byte_to_char(end)
+ - self.rope.line_to_char(y2),
+ c,
+ );
+
+ cells.get_mut(y1 * c + x1..y2 * c + x2).map(|x| {
+ x.iter_mut().for_each(|x| {
+ x.style.flags = STYLES[h.idx()];
+ x.style.color = COLORS[h.idx()];
+ })
+ });
+ }
+ at = end;
+ }
+ drop(h);
for (l, y) in self.rope.lines().zip(0..) {
for (e, x) in l.chars().take(c).zip(0..) {
@@ -535,7 +533,7 @@ impl TextArea {
)
.map(|x| {
if x.letter == Some(' ') {
- x.letter = Some('·'); // tabs? what are those
+ x.letter = Some('·'); // tabs? what are those
x.style.color = [0x4e, 0x62, 0x79];
}
x.style.bg = [0x27, 0x43, 0x64];
@@ -684,3 +682,143 @@ impl TextArea {
fn is_word(r: char) -> bool {
matches!(r, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')
}
+static LOADER: LazyLock<Loader> = LazyLock::new(|| {
+ let x = helix_core::config::default_lang_loader();
+ x.set_scopes(NAMES.map(|x| x.to_string()).to_vec());
+ x
+});
+#[test]
+pub fn man() {
+ let query_str = r#"
+ (line_comment)+ @quantified_nodes
+ ((line_comment)+) @quantified_nodes_grouped
+ ((line_comment) (line_comment)) @multiple_nodes_grouped
+ "#;
+ let source = Rope::from_str(
+ r#"
+
+ pub fn extend_selection_to(
+ &mut self,
+ to: usize,
+ r: std::ops::Range<usize>,
+ ) -> std::ops::Range<usize> {
+ if [r.start, r.end].contains(&to) {
+ return r;
+ }
+ let r = if self.cursor == r.start {
+ if to < r.start {
+ to..r.end
+ } else if to > r.end {
+ r.end..to
+ } else {
+ to..r.end
+ }
+ } else if self.cursor == r.end {
+ if to > r.end {
+ r.start..to
+ } else if to < r.start {
+ to..r.start
+ } else {
+ r.start..to
+ }
+ } else {
+ panic!()
+ };
+ assert!(r.start < r.end);
+ self.cursor = to;
+ self.setc();
+ r
+ }
+"#,
+ );
+ let language = LOADER.language_for_name("rust").unwrap();
+ let mut n = 0;
+ let mut set = std::collections::HashMap::new();
+ LOADER.languages().for_each(|(_, x)| {
+ x.syntax_config(&LOADER).map(|x| {
+ x.configure(|x| {
+ let x = set.entry(x.to_string()).or_insert_with(|| {
+ n += 1;
+ n
+ });
+ // dbg!(x);
+ // NAMES
+ // .iter()
+ // .position(|&y| y == x)
+ // .map(|x| x as u32)
+ // .map(helix_core::syntax::Highlight::new)
+ Some(helix_core::syntax::Highlight::new(*x))
+ })
+ });
+ });
+ // let c = LOADER.languages().next().unwrap().1;
+ // let grammar = LOADER.get_config(language).unwrap().grammar;
+ // let query = Query::new(grammar, query_str, |_, _| Ok(())).unwrap();
+ // let textobject = TextObjectQuery::new(query);
+ // reconfigure_highlights(
+ // LOADER.get_config(language).unwrap(),
+ // &NAMES.map(|x| x.to_string()),
+ // );
+
+ let syntax = Syntax::new(source.slice(..), language, &LOADER).unwrap();
+ let mut h = syntax.highlighter(
+ source.slice(..),
+ &LOADER,
+ 0..source.len_chars() as u32,
+ );
+ println!(
+ "{}",
+ tree_house::fixtures::highlighter_fixture(
+ "hmm",
+ &*LOADER,
+ |y| set
+ .iter()
+ .find(|x| x.1 == &y.get())
+ .unwrap()
+ .0
+ .to_string(),
+ // |y| NAMES[y.idx()].to_string(),
+ &syntax.inner,
+ source.slice(..),
+ ..,
+ )
+ );
+ for n in 0..40 {
+ dbg!(h.next_event_offset());
+ let (e, hl) = h.advance();
+ dbg!(e);
+ // dbg!(hl.map(|x| NAMES[x.idx()]).collect::<Vec<_>>(), e);
+ dbg!(
+ h.active_highlights()
+ .map(|y| set
+ .iter()
+ .find(|x| x.1 == &y.get())
+ .unwrap()
+ .0
+ .to_string())
+ // .map(|x| NAMES[x.idx()])
+ .collect::<Vec<_>>()
+ );
+ // panic!()
+ }
+
+ panic!();
+
+ // let root = syntax.tree().root_node();
+ // let test = |capture, range| {
+ // let matches: Vec<_> = textobject
+ // .capture_nodes(capture, &root, source.slice(..))
+ // .unwrap()
+ // .collect();
+
+ // assert_eq!(
+ // matches[0].byte_range(),
+ // range,
+ // "@{} expected {:?}",
+ // capture,
+ // range
+ // )
+ // };
+ // test("quantified_nodes", 1..37);
+ panic!()
+}