A simple CPU rendered GUI IDE experience.
add cursor
| -rw-r--r-- | Cargo.toml | 8 | ||||
| -rw-r--r-- | sample.rs | 19 | ||||
| -rw-r--r-- | src/main.rs | 50 | ||||
| -rw-r--r-- | src/queries.scm | 172 | ||||
| -rw-r--r-- | src/text.rs | 138 |
5 files changed, 331 insertions, 56 deletions
@@ -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]>; |