A simple CPU rendered GUI IDE experience.
search
bendn 5 months ago
parent 010e37e · commit 5854767
-rw-r--r--Cargo.toml3
-rw-r--r--src/bar.rs9
-rw-r--r--src/main.rs104
-rw-r--r--src/text.rs25
4 files changed, 127 insertions, 14 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 4be6bca..6fb7ede 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,11 +28,12 @@ rust-fsm = { version = "0.8.0", path = "../rust-fsm/rust-fsm", features = [
clipp = "0.1.0"
parking_lot = "0.12.4"
diff-match-patch-rs = "0.5.1"
+regex = "1.11.3"
[build-dependencies]
cc = "*"
[profile.release]
debug = 2
-overflow-checks = true
+# overflow-checks = true
debug-assertions = true
diff --git a/src/bar.rs b/src/bar.rs
index 4e9a250..beaee4b 100644
--- a/src/bar.rs
+++ b/src/bar.rs
@@ -7,7 +7,6 @@ use winit::keyboard::{Key, ModifiersState, NamedKey};
use crate::text::TextArea;
pub struct Bar {
- pub text: crate::text::TextArea,
pub last_action: String,
}
@@ -74,6 +73,14 @@ impl Bar {
.zip(row.iter_mut().rev())
.for_each(|(x, y)| y.letter = Some(x));
}
+ State::Search((x, y, z)) => {
+ format!("{} ({} of {z})", x.as_str(), y + 1)
+ .chars()
+ .zip(row)
+ .for_each(|(c, x)| {
+ x.letter = Some(c);
+ });
+ }
State::Save => unreachable!(),
_ => {}
}
diff --git a/src/main.rs b/src/main.rs
index 53e27a4..13ce961 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,6 +21,7 @@ use diff_match_patch_rs::PatchInput;
use dsb::cell::Style;
use dsb::{Cell, F};
use fimg::Image;
+use regex::Regex;
use rust_fsm::StateMachineImpl;
use swash::{FontRef, Instance};
use winit::event::{
@@ -131,8 +132,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
let mut cursor_position = (0, 0);
let mut state = State::Default;
- let mut bar =
- Bar { text: TextArea::default(), last_action: String::default() };
+ let mut bar = Bar { last_action: String::default() };
let mut i = Image::build(1, 1).fill(BG);
let mut cells = vec![];
std::env::args().nth(1).map(|x| {
@@ -247,16 +247,42 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
(1, 0),
);
let t_ox = text.line_number_offset() + 1;
+ text.c = c - t_ox;
+ text.r = r - 1;
text.write_to(
- (c - t_ox, r - 1),
FG,
BG,
(&mut cells, (c, r)),
(t_ox, 0),
x,
+ |(c, r), text, x| {
+ if let State::Search((re, j, _)) = &state {
+ re.find_iter(&text.rope.to_string())
+ .enumerate()
+ .for_each(|(i, m)| {
+ for x in text.slice(
+ (c, r),
+ x,
+ text.rope.byte_to_char(
+ m.start(),
+ )
+ ..text
+ .rope
+ .byte_to_char(
+ m.end(),
+ ),
+ ) {
+ x.style.bg = if i == *j {
+ [105, 83, 128]
+ } else {
+ [65, 62, 83]
+ }
+ }
+ });
+ }
+ },
);
- text.c = c - t_ox;
- text.r = r - 1;
+
bar.write_to(
BG,
FG,
@@ -298,7 +324,12 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
let (x, y) = text.cursor();
let x = x + t_ox;
- if (text.vo..text.vo + r).contains(&y) {
+ if (text.vo..text.vo + r).contains(&y)
+ && matches!(
+ state,
+ State::Default | State::Selection(_)
+ )
+ {
i.as_mut().overlay_at(
&cursor,
(x as f32 * fw).floor() as u32,
@@ -449,10 +480,20 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
) {
return;
}
- let o = state
+ let mut o = state
.consume(Action::K(event.logical_key.clone()))
.unwrap();
match o {
+ Some(Do::Reinsert) =>
+ o = state
+ .consume(Action::K(
+ event.logical_key.clone(),
+ ))
+ .unwrap(),
+ _ => {}
+ }
+ match o {
+ Some(Do::Reinsert) => panic!(),
Some(Do::Save) => match &origin {
Some(_) => {
state.consume(Action::Saved).unwrap();
@@ -553,6 +594,33 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
) => {
unreachable!()
}
+ Some(Do::StartSearch(x)) => {
+ let s = Regex::new(&x).unwrap();
+ let n = s
+ .find_iter(&text.rope.to_string())
+ .enumerate()
+ .count();
+ s.clone()
+ .find_iter(&text.rope.to_string())
+ .enumerate()
+ .find(|(_, x)| x.start() > text.cursor)
+ .map(|(x, m)| {
+ state = State::Search((s, x, n));
+ text.cursor =
+ text.rope.byte_to_char(m.end());
+ text.scroll_to_cursor_centering();
+ })
+ .unwrap_or_else(|| {
+ bar.last_action = "no matches".into()
+ });
+ }
+ Some(Do::SearchChanged) => {
+ let (re, index, _) = state.search();
+ let s = text.rope.to_string();
+ let m = re.find_iter(&s).nth(*index).unwrap();
+ text.cursor = text.rope.byte_to_char(m.end());
+ text.scroll_to_cursor_centering();
+ }
None => {}
}
window.request_redraw();
@@ -635,6 +703,10 @@ impl State {
let State::Selection(x) = self else { panic!() };
x
}
+ fn search(&mut self) -> &mut (Regex, usize, usize) {
+ let State::Search(x) = self else { panic!() };
+ x
+ }
}
use std::ops::Range;
@@ -650,7 +722,9 @@ Default => {
K(Key::Character(x) if x == "v" && ctrl()) => Default [Paste],
K(Key::Character(x) if x == "z" && ctrl()) => Default [Undo],
K(Key::Character(x) if x == "y" && ctrl()) => Default [Redo],
+ K(Key::Character(x) if x == "f" && ctrl()) => Procure((default(), InputRequest::Search)),
K(Key::Character(x) if x == "o" && ctrl()) => Procure((default(), InputRequest::OpenFile)),
+ K(Key::Character(x) if x == "c" && ctrl()) => Default,
K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End) if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
M(MouseButton => MouseButton::Left if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
M(MouseButton => MouseButton::Left) => Default [MoveCursor],
@@ -662,9 +736,9 @@ Default => {
Selection(x if shift()) => {
K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End)) => Selection(x) [UpdateSelection],
M(MouseButton => MouseButton::Left) => Selection(x) [ExtendSelectionToMouse],
-},
+}, // note: it does in fact fall through. this syntax is not an arm, merely shorthand.
Selection(x) => {
- C(y if unsafe { CLICKING }) => Selection(x) [ExtendSelectionToMouse],
+ C(_ if unsafe { CLICKING }) => Selection(x) [ExtendSelectionToMouse],
C(_) => Selection(x),
M(MouseButton => MouseButton::Left) => Default [MoveCursor],
K(Key::Named(Backspace)) => Default [Delete(Range<usize> => x)],
@@ -677,15 +751,24 @@ Save => {
RequireFilename => Procure((TextArea, InputRequest) => (default(), InputRequest::SaveFile)),
Saved => Default,
},
-Procure((t, _)) => K(Key::Named(Escape)) => Default,
+Procure((_, _)) => K(Key::Named(Escape)) => Default,
+Procure((t, InputRequest::Search)) => K(Key::Named(Enter)) => Default [StartSearch(String => t.rope.to_string())],
Procure((t, InputRequest::SaveFile)) => K(Key::Named(Enter)) => Default [SaveTo(String => t.rope.to_string())],
Procure((t, InputRequest::OpenFile)) => K(Key::Named(Enter)) => Default [OpenFile(String => t.rope.to_string())],
Procure((t, a)) => K(k) => Procure((handle(k, t), a)),
+Search((x, y, m)) => {
+ M(MouseButton => MouseButton::Left) => Default [MoveCursor],
+ C(_) => Search((x, y, m)),
+ K(Key::Named(Enter) if shift()) => Search((x, y.checked_sub(1).unwrap_or(m-1), m)) [SearchChanged],
+ K(Key::Named(Enter)) => Search((Regex, usize, usize) => (x, (y+ 1) % m, m)) [SearchChanged],
+ K(_) => Default [Reinsert],
+}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum InputRequest {
SaveFile,
OpenFile,
+ Search,
}
impl InputRequest {
@@ -693,6 +776,7 @@ impl InputRequest {
match self {
InputRequest::SaveFile => "write to file: ",
InputRequest::OpenFile => "open file: ",
+ InputRequest::Search => "search: ",
}
}
}
diff --git a/src/text.rs b/src/text.rs
index ff619b8..b5535e0 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -126,7 +126,7 @@ impl TextArea {
let y2 = self.rope.char_to_line(end);
let x1 = start - self.rope.line_to_char(y1);
let x2 = end - self.rope.line_to_char(y2);
- [(x1, y1), (x2, y2)]
+ [(min(x1, self.c), y1), (min(x2, self.c), y2)]
}
/// number of lines
@@ -366,16 +366,36 @@ impl TextArea {
}
}
+ #[lower::apply(saturating)]
+ pub fn scroll_to_cursor_centering(&mut self) {
+ let (_, y) = self.cursor();
+
+ if !(self.vo..self.vo + self.r).contains(&y) {
+ self.vo = y - (self.r / 2);
+ }
+ }
+
+ pub fn slice<'c>(
+ &self,
+ (c, r): (usize, usize),
+ cell: &'c mut [Cell],
+ range: Range<usize>,
+ ) -> &'c mut [Cell] {
+ let [(x1, y1), (x2, y2)] = dbg!(self.position(range));
+ &mut cell[y1 * c + x1..y2 * c + x2]
+ }
+
#[implicit_fn]
pub fn write_to(
&mut self,
- (c, r): (usize, usize),
color: [u8; 3],
bg: [u8; 3],
(into, (w, _)): (&mut [Cell], (usize, usize)),
(ox, oy): (usize, usize),
selection: Option<Range<usize>>,
+ apply: impl FnOnce((usize, usize), &mut Self, &mut [Cell]),
) {
+ let (c, r) = (self.c, self.r);
static HL: LazyLock<HighlightConfiguration> =
LazyLock::new(|| {
let mut x = HighlightConfiguration::new(
@@ -504,6 +524,7 @@ impl TextArea {
});
});
});
+ apply((c, r), self, &mut cells);
let cells = &cells[self.vo * c..self.vo * c + r * c];
assert_eq!(cells.len(), c * r);