From 8b04b275f3e110648f65677e3c3f2a7ee82b70e7 Mon Sep 17 00:00:00 2001
From: koniifer <koniifer@proton.me>
Date: Sat, 9 Nov 2024 01:54:33 +0000
Subject: [PATCH] text editor is actually a text editor

---
 Cargo.lock                                    |  10 +-
 .../render_example/src/examples/text.hb       | 201 +++++++++++++-----
 2 files changed, 149 insertions(+), 62 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index d2ffccc..f408d39 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13,9 +13,9 @@ dependencies = [
 
 [[package]]
 name = "allocator-api2"
-version = "0.2.18"
+version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
+checksum = "611cc2ae7d2e242c457e4be7f97036b8ad9ca152b499f53faf99b1ed8fc2553f"
 
 [[package]]
 name = "anyhow"
@@ -228,12 +228,12 @@ dependencies = [
 [[package]]
 name = "hbbytecode"
 version = "0.1.0"
-source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#29367d8f8bdfc23d8662a74edcecf859285ce265"
+source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#65e9f272a85cc9d46c31072af9d0f2bc43ef1217"
 
 [[package]]
 name = "hblang"
 version = "0.1.0"
-source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#29367d8f8bdfc23d8662a74edcecf859285ce265"
+source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#65e9f272a85cc9d46c31072af9d0f2bc43ef1217"
 dependencies = [
  "hashbrown 0.15.1",
  "hbbytecode",
@@ -245,7 +245,7 @@ dependencies = [
 [[package]]
 name = "hbvm"
 version = "0.1.0"
-source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#29367d8f8bdfc23d8662a74edcecf859285ce265"
+source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#65e9f272a85cc9d46c31072af9d0f2bc43ef1217"
 dependencies = [
  "hbbytecode",
 ]
diff --git a/sysdata/programs/render_example/src/examples/text.hb b/sysdata/programs/render_example/src/examples/text.hb
index 2f2c299..b9301c7 100644
--- a/sysdata/programs/render_example/src/examples/text.hb
+++ b/sysdata/programs/render_example/src/examples/text.hb
@@ -1,13 +1,15 @@
-.{memory, log, string} := @use("../../../../libraries/stn/src/lib.hb")
+.{memory, log, string, math} := @use("../../../../libraries/stn/src/lib.hb")
 render := @use("../../../../libraries/render/src/lib.hb")
 
-/* expected result: almost-not-trash notepad app
-   very jank
+/* expected result: pretty decent notepad app
+   slightly jank
    -----------------
    features:
    - basic keys
    - holding support with DAS
    - visible cursor
+   - l+r arrow key support
+   - proper insertion and deletion
    - shift key support
 */
 
@@ -15,12 +17,17 @@ psf := @embed("../../../../consolefonts/tamsyn/10x20r.psf")
 
 is_shift_pressed := false
 is_ctrl_pressed := false
+is_extended := false
 $initial_delay := 50
 $repeat_delay := 7
+$left_arrow := 0x4B
+$right_arrow := 0x4D
+$up_arrow := 0x48
+$down_arrow := 0x50
 
 example := fn(): void {
 	screen := render.init(true)
-	window := render.new_surface(480, 340)
+	window := render.new_surface(600, 300)
 	font := render.text.font_from_psf2(@bitcast(&psf), false)
 
 	if font == null {
@@ -35,12 +42,10 @@ example := fn(): void {
 	bottom := buf + msg_len
 
 	@inline(memory.copy, u8, msg, buf, msg_len)
-	cursor := buf + msg_len;
+	cursor := bottom
 
-	*cursor = 95
-	draw_window(window, font, buf)
-	draw_screen(screen, window);
-	*cursor = 32
+	draw_window(window, font, buf, cursor)
+	draw_screen(screen, window)
 
 	memory.outb(96, 238)
 	memory.outb(96, 238)
@@ -53,24 +58,35 @@ example := fn(): void {
 	loop {
 		input := memory.inb(96)
 		if input != prev_input {
-			if (input & 0x80) != 0 {
-				if (input & 0x7F) == current_key {
-					current_key = 0
-					holding_timer = 0
-				} else if input == 0xAA | input == 0xB6 {
-					is_shift_pressed = false
-				} else if input == 0x9D {
-					is_ctrl_pressed = false
-				}
+			if input == 0xE0 {
+				is_extended = true
 			} else {
-				if input == 0x2A | input == 0x36 {
-					is_shift_pressed = true
-				} else if input == 0x1D {
-					is_ctrl_pressed = true
+				if (input & 0x80) != 0 {
+					if (input & 0x7F) == current_key {
+						current_key = 0
+						holding_timer = 0
+					} else if input == 0xAA | input == 0xB6 {
+						is_shift_pressed = false
+					} else if input == 0x9D {
+						is_ctrl_pressed = false
+					}
+					is_extended = false
 				} else {
-					current_key = input
-					holding_timer = 1
-					cursor = handle_char(map_keys(current_key), cursor, bottom)
+					if is_extended {
+						current_key = input
+						holding_timer = 1
+						cursor = handle_extended_key(input, cursor, bottom, font)
+					} else {
+						if input == 0x2A | input == 0x36 {
+							is_shift_pressed = true
+						} else if input == 0x1D {
+							is_ctrl_pressed = true
+						} else {
+							current_key = input
+							holding_timer = 1
+							cursor = handle_char(map_keys(current_key), cursor, bottom)
+						}
+					}
 				}
 			}
 			prev_input = input
@@ -80,14 +96,16 @@ example := fn(): void {
 			holding_timer += 1
 
 			if holding_timer >= initial_delay {
-				cursor = handle_char(map_keys(current_key), cursor, bottom)
+				if is_extended {
+					cursor = handle_extended_key(current_key, cursor, bottom, font)
+				} else {
+					cursor = handle_char(map_keys(current_key), cursor, bottom)
+				}
 				holding_timer = initial_delay - repeat_delay
 			}
-		};
-		*cursor = 95
-		draw_window(window, font, buf)
-		draw_screen(screen, window);
-		*cursor = 32
+		}
+		draw_window(window, font, buf, cursor)
+		draw_screen(screen, window)
 
 		if holding_timer > 0 & current_key != 0 {
 			if (memory.inb(96) & 0x80) != 0 {
@@ -96,17 +114,87 @@ example := fn(): void {
 			}
 		}
 	}
+}
 
-	return
+handle_extended_key := fn(scancode: u8, cursor: ^u8, bottom: ^u8, font: render.text.Font): ^u8 {
+	if scancode == left_arrow {
+		if cursor > bottom {
+			return cursor - 1
+		}
+	} else if scancode == right_arrow {
+		if *cursor != 0 {
+			return cursor + 1
+		}
+	}
+	return cursor
+}
+
+padding := 3 * @sizeof(render.Color)
+
+draw_window := fn(window: render.Surface, font: render.text.Font, buf: ^u8, cursor: ^u8): void {
+	render.clear(window, render.light_yellow)
+	line := font.height + padding - 1
+	render.put_rect(window, .(0, 0), .(window.width - 1, window.height - 1), render.black)
+
+	loop if line >= window.height break else {
+		render.put_hline(window, line, padding, window.width - padding, render.yellow)
+		line += font.height
+	}
+
+	render.put_text(window, font, .(padding, padding), render.black, buf)
+
+	cursor_offset := cursor - buf
+
+	y_pos := padding
+	x_pos := padding
+	i := 0
+
+	loop if i >= cursor_offset break else {
+		if *(buf + i) == 10 {
+			y_pos += font.height + font.line_gap
+			x_pos = padding
+		} else {
+			if x_pos + font.width >= window.width - padding {
+				y_pos += font.height + font.line_gap
+				x_pos = padding - font.width
+			}
+			x_pos += font.width
+		}
+		i += 1
+	}
+
+	render.put_rect(window, .(x_pos, y_pos), .(1, font.height - 1), render.black)
+}
+
+draw_screen := fn(screen: render.Surface, window: render.Surface): void {
+	render.clear(screen, render.light_blue)
+	render.put_surface(screen, window, .(100, 100), false)
+	render.sync(screen)
 }
 
 handle_char := fn(char: u8, cursor: ^u8, bottom: ^u8): ^u8 {
 	if char == 0 {
 		return cursor
 	}
+
 	if is_ctrl_pressed & char == 48 {
 		cursor = bottom
 	} else if char != 0x8 {
+		end := cursor
+		loop if *end == 0 break else {
+			end += 1
+		}
+
+		if cursor < end {
+			src := end
+			dst := end + 1
+			loop if src < cursor break else {
+				*dst = *src
+				dst -= 1
+				src -= 1
+			}
+		};
+
 		*cursor = char
 		return cursor + 1
 	} else if char == 0xA {
@@ -115,37 +203,36 @@ handle_char := fn(char: u8, cursor: ^u8, bottom: ^u8): ^u8 {
 		*cursor = 92
 		return cursor + 1
 	} else if cursor > bottom {
-		cursor -= 1;
-		*cursor = 32
-		return cursor
+		if cursor == bottom {
+			return cursor
+		}
+
+		end := cursor
+		loop if *end == 0 break else {
+			end += 1
+		}
+
+		if cursor < end {
+			src := cursor
+			dst := cursor - 1
+			loop if src > end break else {
+				*dst = *src
+				dst += 1
+				src += 1
+			}
+			return cursor - 1
+		} else {
+			cursor -= 1;
+			*cursor = 32
+			return cursor
+		}
 	}
 	return cursor
 }
-
 map_keys := fn(scancode: u8): u8 {
 	if is_shift_pressed {
 		return ps2_table[scancode + 0x40]
 	}
 	return ps2_table[scancode]
 }
-
-ps2_table := [u8].(0x0, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x8, 0x9, 0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0xA, 0x0, 0x61, 0x73, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x60, 0x0, 0x5C, 0x7A, 0x78, 0x63, 0x76, 0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F, 0x0, 0x2A, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x8, 0x9, 0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x7B, 0x7D, 0xA, 0x0, 0x41, 0x53, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22, 0x7E, 0x0, 0x7C, 0x5A, 0x58, 0x43, 0x56, 0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F, 0x0, 0x0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
-$padding := 7
-
-draw_window := fn(window: render.Surface, font: render.text.Font, buf: ^u8): void {
-	render.clear(window, render.light_yellow)
-	line := font.height + font.line_gap + padding - 1
-	render.put_rect(window, .(0, 0), .(window.width - 1, window.height - 1), render.black)
-	loop if line >= window.height break else {
-		render.put_hline(window, line, padding, window.width - padding, render.yellow)
-		line += font.height + font.line_gap
-	}
-
-	render.put_text(window, font, .(padding, padding), render.black, buf)
-}
-
-draw_screen := fn(screen: render.Surface, window: render.Surface): void {
-	render.clear(screen, render.light_blue)
-	render.put_surface(screen, window, .(100, 100), false)
-	render.sync(screen)
-}
+ps2_table := [u8].(0x0, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x8, 0x9, 0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0xA, 0x0, 0x61, 0x73, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x60, 0x0, 0x5C, 0x7A, 0x78, 0x63, 0x76, 0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F, 0x0, 0x2A, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x8, 0x9, 0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x7B, 0x7D, 0xA, 0x0, 0x41, 0x53, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22, 0x7E, 0x0, 0x7C, 0x5A, 0x58, 0x43, 0x56, 0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F, 0x0, 0x0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
\ No newline at end of file