A simple CPU rendered GUI IDE experience.
add cursor
bendn 7 months ago
parent b1b6b71 · commit bc0736a
-rw-r--r--Cargo.toml8
-rw-r--r--sample.rs19
-rw-r--r--src/main.rs50
-rw-r--r--src/queries.scm172
-rw-r--r--src/text.rs138
5 files changed, 331 insertions, 56 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 300797d..85df31b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2024"
[dependencies]
atools = "0.1.7"
dsb = { version = "0.1.0", path = "../dsb" }
-fimg = "0.4.45"
+fimg = { path = "../fimg" }
implicit-fn = "0.1.0"
ropey = "1.6.1"
softbuffer = "0.4.6"
@@ -18,6 +18,12 @@ tree-sitter-rust = "0.24"
tree-sitter-highlight = "0.25.0"
car = "0.1.2"
memchr = "2.7.5"
+lower = "0.2.0"
[build-dependencies]
cc = "*"
+
+[profile.release]
+debug = 2
+overflow-checks = true
+debug-assertions = true
diff --git a/sample.rs b/sample.rs
new file mode 100644
index 0000000..fe9bfd5
--- /dev/null
+++ b/sample.rs
@@ -0,0 +1,19 @@
+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/main.rs b/src/main.rs
index 4d370fc..378cdcf 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,10 @@
-#![feature(generic_const_exprs, const_trait_impl)]
+#![feature(generic_const_exprs, const_trait_impl,try_blocks)]
+#![allow(incomplete_features, redundant_semicolons)]
use std::num::NonZeroU32;
use std::sync::LazyLock;
use dsb::cell::Style;
+use fimg::Image;
use swash::FontRef;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
@@ -18,7 +20,7 @@ fn main() {
}
#[implicit_fn::implicit_fn]
pub(crate) fn entry(event_loop: EventLoop<()>) {
- let ppem = 15.0;
+ let ppem = 20.0;
let ls = 20.0;
let mut text = TextArea::default();
std::env::args().nth(1).map(|x| {
@@ -72,9 +74,32 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
{
let (c, r) = dsb::fit(&FONT,ppem,ls, (size.width as _,size.height as _));
let cells =text.cells((c, r),[204, 202, 194], [36, 41, 54],);
- let r = unsafe {
+ let mut r = unsafe {
dsb::render(&cells, (c, r), ppem, [36, 41, 54],dsb::Fonts::new(*FONT, *FONT, *FONT, *FONT),
- ls, false)};
+ ls, true)};
+
+
+ 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() {
+ let cell =
+ Image::<_, 4>::build(3, (ppem * 1.25).ceil() as u32).fill([0xFF, 0xCC, 0x66, 255]);
+ use fimg::OverlayAt;
+ let (fw, fh) = dsb::dims(&FONT, ppem);
+ unsafe {
+ let (x, y) = text.cursor();
+
+ r.as_mut().overlay_at(
+ &cell,
+ (x as f32 * fw).floor() as u32,
+ (y as f32 * (fh + ls * fac)).floor()
+ as u32,
+ // 4 + ((x - 1) as f32 * sz) as u32,
+ // (x as f32 * (ppem * 1.25)) as u32 - 20,
+ )
+ };
+ // }
let mut buffer = surface.buffer_mut().unwrap();
for y in 0..height.get() {
@@ -101,14 +126,21 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
event:WindowEvent::KeyboardInput { device_id, event, is_synthetic }, window_id
} if event.state == ElementState::Pressed => {
- match dbg!(event.logical_key ){
- Key::Named(NamedKey::Space)=>
+ use NamedKey::*;
+ use Key::*;
+ match (event.logical_key ){
+ Named(Space)=>
text.insert(" "),
- Key::Named(NamedKey::Backspace) => text.backspace(),
+ Named(Backspace) => text.backspace(),
+ Named(ArrowLeft) =>
+ text.left(),
+ Named(ArrowRight)=> text.right(),
+ Named(ArrowUp) => text.up(),
+ Named(ArrowDown) => text.down(),
- Key::Named(NamedKey::Enter)=>
+ Named(Enter)=>
text.insert("\n"),
- Key::Character(x) => {
+ Character(x) => {
text.insert(&*x);
}
_ =>{}
diff --git a/src/queries.scm b/src/queries.scm
new file mode 100644
index 0000000..7dba476
--- /dev/null
+++ b/src/queries.scm
@@ -0,0 +1,172 @@
+; Identifiers
+
+(type_identifier) @type
+(primitive_type) @type.builtin
+(field_identifier) @property
+
+; Identifier conventions
+
+; Assume all-caps names are constants
+((identifier) @constant
+ (#match? @constant "^[A-Z][A-Z\\d_]+$'"))
+
+; Assume uppercase names are enum constructors
+((identifier) @constructor
+ (#match? @constructor "^[A-Z]"))
+
+; Assume that uppercase names in paths are types
+((scoped_identifier
+ path: (identifier) @type)
+ (#match? @type "^[A-Z]"))
+((scoped_identifier
+ path: (scoped_identifier
+ name: (identifier) @type))
+ (#match? @type "^[A-Z]"))
+((scoped_type_identifier
+ path: (identifier) @type)
+ (#match? @type "^[A-Z]"))
+((scoped_type_identifier
+ path: (scoped_identifier
+ name: (identifier) @type))
+ (#match? @type "^[A-Z]"))
+
+; Assume all qualified names in struct patterns are enum constructors. (They're
+; either that, or struct names; highlighting both as constructors seems to be
+; the less glaring choice of error, visually.)
+(struct_pattern
+ type: (scoped_type_identifier
+ name: (type_identifier) @constructor))
+
+; Function calls
+
+(call_expression
+ function: (identifier) @function)
+(call_expression
+ function: (field_expression
+ field: (field_identifier) @function.method))
+(call_expression
+ function: (scoped_identifier
+ "::"
+ name: (identifier) @function))
+
+(generic_function
+ function: (identifier) @function)
+(generic_function
+ function: (scoped_identifier
+ name: (identifier) @function))
+(generic_function
+ function: (field_expression
+ field: (field_identifier) @function.method))
+
+(macro_invocation
+ macro: (identifier) @function.macro
+ "!" @function.macro)
+
+; Function definitions
+
+(function_item (identifier) @function)
+(function_signature_item (identifier) @function)
+
+(line_comment) @comment
+(block_comment) @comment
+
+(line_comment (doc_comment)) @comment.documentation
+(block_comment (doc_comment)) @comment.documentation
+
+"(" @punctuation.bracket
+")" @punctuation.bracket
+"[" @punctuation.bracket
+"]" @punctuation.bracket
+"{" @punctuation.bracket
+"}" @punctuation.bracket
+
+(type_arguments
+ "<" @punctuation.bracket
+ ">" @punctuation.bracket)
+(type_parameters
+ "<" @punctuation.bracket
+ ">" @punctuation.bracket)
+
+"::" @punctuation.delimiter
+":" @punctuation.delimiter
+"." @punctuation.delimiter
+"," @punctuation.delimiter
+";" @punctuation.delimiter
+
+(parameter (identifier) @variable.parameter)
+
+(lifetime (identifier) @label)
+
+"as" @keyword
+"async" @keyword
+"await" @keyword
+"break" @keyword
+"const" @keyword
+"continue" @keyword
+"default" @keyword
+"dyn" @keyword
+"else" @keyword
+"enum" @keyword
+"extern" @keyword
+"fn" @keyword
+"for" @keyword
+"gen" @keyword
+"if" @keyword
+"impl" @keyword
+"in" @keyword
+"let" @keyword
+"loop" @keyword
+"macro_rules!" @keyword
+"match" @keyword
+"mod" @keyword
+"move" @keyword
+"pub" @keyword
+"raw" @keyword
+"ref" @keyword
+"return" @keyword
+"static" @keyword
+"struct" @keyword
+"trait" @keyword
+"type" @keyword
+"union" @keyword
+"unsafe" @keyword
+"use" @keyword
+"where" @keyword
+"while" @keyword
+"yield" @keyword
+(crate) @keyword
+(mutable_specifier) @keyword
+(use_list (self) @keyword)
+(scoped_use_list (self) @keyword)
+(scoped_identifier (self) @keyword)
+(super) @keyword
+
+(self) @variable.builtin
+
+(char_literal) @string
+(string_literal) @string
+(raw_string_literal) @string
+
+(boolean_literal) @constant.builtin
+(integer_literal) @constant.builtin
+(float_literal) @constant.builtin
+
+(escape_sequence) @escape
+
+(attribute_item) @attribute
+(inner_attribute_item) @attribute
+
+["*"
+"&"
+"'"
+"+"
+"="
+">>"
+"-"
+"/"
+"^"
+"<<"
+"|"
+"=="
+"!="
+] @operator \ No newline at end of file
diff --git a/src/text.rs b/src/text.rs
index 3245f82..35b87a3 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -1,6 +1,3 @@
-use std::iter::{repeat, repeat_n};
-use std::os::fd::AsRawFd;
-
use atools::Chunked;
use dsb::Cell;
use dsb::cell::Style;
@@ -27,24 +24,83 @@ const fn color(x: &[u8; 6]) -> [u8; 3] {
|[a, b]| a * 16 + b
)
}
-#[test]
-fn x() {
- dbg!(color(b"ffd173"));
-}
+#[derive(Default)]
pub struct TextArea {
rope: Rope,
cursor: usize,
- // parser: Parser,
highlighter: Highlighter,
- tree: Option<Tree>,
+ column: usize,
}
impl TextArea {
pub fn insert(&mut self, c: &str) {
- dbg!(c);
self.rope.insert(self.cursor, c);
self.cursor += c.chars().count();
+ self.setc();
+ }
+
+ pub fn cursor(&self) -> (usize, usize) {
+ let y = self.rope.char_to_line(self.cursor);
+ let x = self.cursor - self.rope.line_to_char(y);
+ (x, y)
+ }
+
+ fn setc(&mut self) {
+ self.column = self.cursor
+ - self.rope.line_to_char(self.rope.char_to_line(self.cursor));
+ }
+
+ #[lower::apply(saturating)]
+ pub fn left(&mut self) {
+ self.cursor -= 1;
+ self.setc();
+ }
+
+ #[lower::apply(saturating)]
+ pub fn right(&mut self) {
+ self.cursor += 1;
+ self.cursor = self.cursor.min(self.rope.len_chars());
+ self.setc();
+ }
+
+ pub fn down(&mut self) {
+ let l = self.rope.try_char_to_line(self.cursor).unwrap_or(0);
+
+ // next line size
+ let Some(s) = self.rope.get_line(l + 1) else {
+ return;
+ };
+ if s.len_chars() == 0 {
+ return self.cursor += 1;
+ }
+ // position of start of next line
+ let b = self.rope.line_to_char(l.wrapping_add(1));
+ self.cursor = b + if s.len_chars() > self.column {
+ // if next line is long enough to position the cursor at column, do so
+ self.column
+ } else {
+ // otherwise, put it at the end of the next line, as it is too short.
+ s.len_chars()
+ - self
+ .rope
+ .get_line(l.wrapping_add(2))
+ .map(|_| 1)
+ .unwrap_or(0)
+ };
+ }
+
+ pub fn up(&mut self) {
+ let l = self.rope.try_char_to_line(self.cursor).unwrap_or(0);
+ let Some(s) = self.rope.get_line(l.wrapping_sub(1)) else {
+ return;
+ };
+ let b = self.rope.line_to_char(l - 1);
+ self.cursor = b + if s.len_chars() > self.column {
+ self.column
+ } else {
+ s.len_chars() - 1
+ };
}
pub fn backspace(&mut self) {
@@ -61,7 +117,7 @@ impl TextArea {
let mut x = HighlightConfiguration::new(
tree_sitter_rust::LANGUAGE.into(),
"rust",
- tree_sitter_rust::HIGHLIGHTS_QUERY,
+ include_str!("queries.scm"),
tree_sitter_rust::INJECTIONS_QUERY,
"",
)
@@ -99,26 +155,31 @@ impl TextArea {
.map(Result::unwrap)
{
match hl {
- HighlightEvent::Source { start, end } => {
- // for elem in start..end {
- // styles[elem] = s;
- // }
- let y1 = self.rope.char_to_line(start);
- let y2 = self.rope.char_to_line(start);
- let x1 = start - self.rope.line_to_char(y1);
- let x2 = end - self.rope.line_to_char(y2);
- // dbg!((x1, y1), (x2, y2));
- cells.get_mut(y1 * c + x1..y2 * c + x2).map(|x| {
- x.iter_mut()
- .for_each(|x| x.style.color = COLORS[s])
- });
- // println!(
- // "highlight {} {s} {}: {:?}",
- // self.rope.byte_slice(start..end),
- // NAMES[s],
- // COLORS[s],
- // )
- }
+ HighlightEvent::Source { start, end } => drop::<
+ ropey::Result<()>,
+ >(
+ try {
+ // for elem in start..end {
+ // styles[elem] = s;
+ // }
+ let y1 = self.rope.try_char_to_line(start)?;
+ let y2 = self.rope.try_char_to_line(start)?;
+ let x1 = start - self.rope.try_line_to_char(y1)?;
+ let x2 = end - self.rope.try_line_to_char(y2)?;
+ // dbg!((x1, y1), (x2, y2));
+ cells.get_mut(y1 * c + x1..y2 * c + x2).map(|x| {
+ x.iter_mut()
+ .for_each(|x| 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,
}
@@ -155,21 +216,6 @@ impl TextArea {
}
}
-impl Default for TextArea {
- fn default() -> Self {
- // let mut parser = Highlighter::new();
-
- let mut x = Self {
- rope: Default::default(),
- cursor: 0,
- tree: None,
- highlighter: Highlighter::new(),
- };
-
- x
- }
-}
-
pub trait TakeLine<'b> {
fn take_line<'a>(&'a mut self) -> Option<&'b [u8]>;
fn take_backline<'a>(&'a mut self) -> Option<&'b [u8]>;