From 8f7fa920bd910beadd9634de1b979b2c3b25f190 Mon Sep 17 00:00:00 2001 From: Grant Horner Date: Thu, 11 Dec 2025 12:29:24 -0500 Subject: [PATCH] refactor to using "Editor" struct --- src/main.odin | 235 ++++++++++++++++++++++++++++---------------------- 1 file changed, 130 insertions(+), 105 deletions(-) diff --git a/src/main.odin b/src/main.odin index edb0150..c7c8f4c 100644 --- a/src/main.odin +++ b/src/main.odin @@ -51,34 +51,49 @@ Range :: struct { end: int, } -window_width := 800 -window_height := 600 -font_size: f32 = 20.0 -text_height: f32 -cursor: Cursor -lines: [dynamic][dynamic]u8 -viewport_size := window_height / int(font_size) -viewport := Viewport { - start = 0, - end = viewport_size, +Editor :: struct { + window_width: i32, + window_height: i32, + font_size: f32, + text_height: f32, + cursor: Cursor, + lines: [dynamic][dynamic]u8, + viewport: Viewport, + viewport_size: int, + selection: Selection, } -selection := Selection{} + +DEFAULT_WINDOW_WIDTH :: 800 +DEFAULT_WINDOW_HEIGHT :: 600 +DEFAULT_FONT_SIZE :: 20 +DEFAULT_VIEWPORT_SIZE :: DEFAULT_WINDOW_HEIGHT / DEFAULT_FONT_SIZE main :: proc() { - r.InitWindow(i32(window_width), i32(window_height), "odit") + editor := Editor { + window_width = DEFAULT_WINDOW_WIDTH, + window_height = DEFAULT_WINDOW_HEIGHT, + font_size = DEFAULT_FONT_SIZE, + viewport_size = DEFAULT_WINDOW_HEIGHT / DEFAULT_FONT_SIZE, + viewport = Viewport{start = 0, end = DEFAULT_VIEWPORT_SIZE}, + selection = Selection{}, + text_height = {}, + cursor = {}, + lines = {}, + } + r.InitWindow(editor.window_width, editor.window_height, "odit") r.SetTargetFPS(60) default_font := r.GetFontDefault() font := r.LoadFont("/Users/grant/Library/Fonts/BerkeleyMono-Regular.otf") r.GuiSetFont(font) - two_lines_size := r.MeasureTextEx(font, "foo\nbar", font_size, 0.0) - text_height = two_lines_size[1] / 2 + two_lines_size := r.MeasureTextEx(font, "foo\nbar", editor.font_size, 0.0) + editor.text_height = two_lines_size[1] / 2 content, _success := read_file("src/main.odin") for line in strings.split_lines_iterator(&content) { bs := make([dynamic]u8, 0, len(line) == 0 ? 1 : len(line) * 2) append(&bs, line) - append(&lines, bs) + append(&editor.lines, bs) } text: string @@ -88,41 +103,41 @@ main :: proc() { chars, _ := utf8.encode_rune(c) char := chars[0] // TODO: selection - if selection.active { - delete_selection() - selection.active = false + if editor.selection.active { + delete_selection(&editor) + editor.selection.active = false } - inject_at(&lines[cursor.line], cursor.char, char) - cursor.char += 1 + inject_at(&editor.lines[editor.cursor.line], editor.cursor.char, char) + editor.cursor.char += 1 } - move_cursor() - handle_backspace() + move_cursor(&editor) + handle_backspace(&editor) if repeatable_key_pressed(.ENTER) { // TODO: selection - if selection.active { - delete_selection() - selection.active = false + if editor.selection.active { + delete_selection(&editor) + editor.selection.active = false } - new_line_cap := len(current_line()) - cursor.char - bs := make([dynamic]u8, 0, len(current_line()) == 0 ? 1 : new_line_cap * 2) - if cursor.char < len(current_line()) { - for c, i in current_line()[cursor.char:] { + new_line_cap := len(current_line(&editor)) - editor.cursor.char + bs := make([dynamic]u8, 0, len(current_line(&editor)) == 0 ? 1 : new_line_cap * 2) + if editor.cursor.char < len(current_line(&editor)) { + for c, i in current_line(&editor)[editor.cursor.char:] { append(&bs, c) } } - inject_at(&lines, cursor.line + 1, bs) - resize(current_line(), cursor.char) - cursor.line += 1 - cursor.char = 0 + inject_at(&editor.lines, editor.cursor.line + 1, bs) + resize(current_line(&editor), editor.cursor.char) + editor.cursor.line += 1 + editor.cursor.char = 0 } if r.IsKeyDown(.LEFT_SUPER) && r.IsKeyPressed(.S) { builder := strings.builder_make() defer strings.builder_destroy(&builder) - for line in lines { + for line in editor.lines { strings.write_bytes(&builder, line[:]) strings.write_byte(&builder, '\n') } @@ -131,39 +146,39 @@ main :: proc() { } if r.IsKeyDown(.LEFT_SUPER) && r.IsKeyDown(.LEFT_SHIFT) && r.IsKeyPressed(.D) { - if selection.active { - earliest := selection_earliest() - latest := selection_latest() - #reverse for line, index in lines[earliest.line:latest.line + 1] { + if editor.selection.active { + earliest := selection_earliest(&editor) + latest := selection_latest(&editor) + #reverse for line, index in editor.lines[earliest.line:latest.line + 1] { new_line, _ := slice.clone_to_dynamic(line[:]) - inject_at(&lines, latest.line + 1, new_line) + inject_at(&editor.lines, latest.line + 1, new_line) } // NOTE: should we move the selection as well? } else { - new_line, _ := slice.clone_to_dynamic(current_line()[:]) - inject_at(&lines, cursor.line + 1, new_line) - cursor.line += 1 + new_line, _ := slice.clone_to_dynamic(current_line(&editor)[:]) + inject_at(&editor.lines, editor.cursor.line + 1, new_line) + editor.cursor.line += 1 } } cursor_padding := 3 // move viewport up - if cursor.line < viewport.start + cursor_padding { - diff := (cursor.line - viewport.start) - cursor_padding - viewport.start = min(max(0, viewport.start + diff), len(lines) - viewport_size) - viewport.end = min(max(viewport_size, viewport.end + diff), len(lines)) + if editor.cursor.line < editor.viewport.start + cursor_padding { + diff := (editor.cursor.line - editor.viewport.start) - cursor_padding + editor.viewport.start = min(max(0, editor.viewport.start + diff), len(editor.lines) - editor.viewport_size) + editor.viewport.end = min(max(editor.viewport_size, editor.viewport.end + diff), len(editor.lines)) } // move viewport down - if viewport.end - cursor_padding < cursor.line { - diff := cursor.line - (viewport.end - cursor_padding) - viewport.start = min(max(0, viewport.start + diff), len(lines) - viewport_size) - viewport.end = min(max(viewport_size, viewport.end + diff), len(lines)) + if editor.viewport.end - cursor_padding < editor.cursor.line { + diff := editor.cursor.line - (editor.viewport.end - cursor_padding) + editor.viewport.start = min(max(0, editor.viewport.start + diff), len(editor.lines) - editor.viewport_size) + editor.viewport.end = min(max(editor.viewport_size, editor.viewport.end + diff), len(editor.lines)) } r.BeginDrawing() r.ClearBackground(r.BLACK) - for &line, line_index in lines[viewport.start:viewport.end] { + for &line, line_index in editor.lines[editor.viewport.start:editor.viewport.end] { if cap(line) == 0 { reserve(&line, 1) } @@ -172,23 +187,23 @@ main :: proc() { } raw_data(line)[len(line)] = 0 - if selection.active && - selection_earliest().line <= line_index && - line_index <= selection_latest().line { - render_line_with_selection(line[:], line_index, font) + if editor.selection.active && + selection_earliest(&editor).line <= line_index && + line_index <= selection_latest(&editor).line { + render_line_with_selection(&editor, line[:], line_index, font) } else { // No selection active cstr := strings.unsafe_string_to_cstring(string(line[:])) - pos := r.Vector2{0, font_size * f32(line_index)} - r.DrawTextEx(font, cstr, pos, font_size, 0, r.WHITE) + pos := r.Vector2{0, editor.font_size * f32(line_index)} + r.DrawTextEx(font, cstr, pos, editor.font_size, 0, r.WHITE) } } - render_cursor(&cursor) + render_cursor(&editor) fps_width := r.MeasureText("60", default_font.baseSize) - r.DrawFPS(i32(window_width) - (fps_width + 20), 10) + r.DrawFPS(editor.window_width - (fps_width + 20), 10) r.EndDrawing() @@ -197,15 +212,16 @@ main :: proc() { } } -render_cursor :: proc(cursor: ^Cursor) { +render_cursor :: proc(editor: ^Editor) { + using editor x := cursor.char * int(font_size / 2) y := i32(font_size) * i32(cursor.line - viewport.start) r.DrawLine(i32(x), y, i32(x), y + i32(font_size), r.WHITE) r.DrawLine(i32(x) + 1, y, i32(x) + 1, y + i32(font_size), r.WHITE) } -current_line :: proc() -> ^[dynamic]u8 { - return &lines[cursor.line] +current_line :: proc(editor: ^Editor) -> ^[dynamic]u8 { + return &editor.lines[editor.cursor.line] } is_whitespace :: proc(c: u8) -> bool { @@ -238,7 +254,8 @@ find_previous_space :: proc(current_position: int, line: []u8) -> (result: int, return result, found } -handle_selection_start :: proc() { +handle_selection_start :: proc(editor: ^Editor) { + using editor was_active := selection.active if r.IsKeyDown(.LEFT_SHIFT) { selection.active = true @@ -251,17 +268,19 @@ handle_selection_start :: proc() { selection.start.char = cursor.char } -handle_selection_end :: proc() { +handle_selection_end :: proc(editor: ^Editor) { + using editor if !selection.active do return selection.end.char = cursor.char selection.end.line = cursor.line } -render_line_with_selection :: proc(line: []u8, line_index: int, font: r.Font) { - earliest := selection_earliest() - latest := selection_latest() +render_line_with_selection :: proc(editor: ^Editor, line: []u8, line_index: int, font: r.Font) { + using editor + earliest := selection_earliest(editor) + latest := selection_latest(editor) - selection_range := get_selection_for_line(line, line_index) + selection_range := get_selection_for_line(editor, line, line_index) prefix := line[:selection_range.start] suffix := line[selection_range.end:] if selection_range.end < len(line) else []u8{} @@ -296,18 +315,19 @@ render_line_with_selection :: proc(line: []u8, line_index: int, font: r.Font) { } } -move_cursor :: proc() { +move_cursor :: proc(editor: ^Editor) { + using editor if repeatable_key_pressed(.RIGHT) { - handle_selection_start() - defer handle_selection_end() + handle_selection_start(editor) + defer handle_selection_end(editor) preferred_position := cursor.char + 1 - if cursor.char == len(current_line()) && cursor.line != len(lines) - 1 { + if cursor.char == len(current_line(editor)) && cursor.line != len(lines) - 1 { preferred_position = 0 cursor.line += 1 } - if r.IsKeyDown(.LEFT_ALT) && cursor.char + 1 < len(current_line()) { + if r.IsKeyDown(.LEFT_ALT) && cursor.char + 1 < len(current_line(editor)) { seen_space := false - for c, c_index in current_line()[cursor.char + 1:] { + for c, c_index in current_line(editor)[cursor.char + 1:] { if seen_space && !is_whitespace(c) { preferred_position = cursor.char + c_index break @@ -317,30 +337,30 @@ move_cursor :: proc() { } } if !seen_space { - preferred_position = len(current_line()) + preferred_position = len(current_line(editor)) } } - cursor.char = min(preferred_position, len(current_line())) + cursor.char = min(preferred_position, len(current_line(editor))) } if repeatable_key_pressed(.LEFT) { - handle_selection_start() - defer handle_selection_end() + handle_selection_start(editor) + defer handle_selection_end(editor) preferred_position := cursor.char - 1 if cursor.char == 0 && cursor.line != 0 { cursor.line -= 1 - preferred_position = len(current_line()) + preferred_position = len(current_line(editor)) } if r.IsKeyDown(.LEFT_ALT) && 0 < cursor.char { found := false - preferred_position, found = find_previous_space(cursor.char, current_line()[:]) + preferred_position, found = find_previous_space(cursor.char, current_line(editor)[:]) } cursor.char = max(preferred_position, 0) } if repeatable_key_pressed(.UP) { - handle_selection_start() - defer handle_selection_end() + handle_selection_start(editor) + defer handle_selection_end(editor) preferred_line := cursor.line - 1 if r.IsKeyDown(.LEFT_ALT) && 0 < cursor.line { @@ -353,14 +373,14 @@ move_cursor :: proc() { } cursor.line = max(0, preferred_line) - if len(current_line()) < cursor.char { - cursor.char = len(current_line()) + if len(current_line(editor)) < cursor.char { + cursor.char = len(current_line(editor)) } } if repeatable_key_pressed(.DOWN) { - handle_selection_start() - defer handle_selection_end() + handle_selection_start(editor) + defer handle_selection_end(editor) preferred_line := cursor.line + 1 if r.IsKeyDown(.LEFT_ALT) && cursor.line + 1 < len(lines) { @@ -373,13 +393,14 @@ move_cursor :: proc() { } cursor.line = min(preferred_line, len(lines) - 1) - if len(current_line()) < cursor.char { - cursor.char = len(current_line()) + if len(current_line(editor)) < cursor.char { + cursor.char = len(current_line(editor)) } } } -selection_earliest :: proc() -> Position { +selection_earliest :: proc(editor: ^Editor) -> Position { + using editor earliest: Position latest: Position if selection.start.line < selection.end.line { @@ -403,7 +424,8 @@ selection_earliest :: proc() -> Position { return earliest } -selection_latest :: proc() -> Position { +selection_latest :: proc(editor: ^Editor) -> Position { + using editor earliest: Position latest: Position if selection.start.line < selection.end.line { @@ -427,9 +449,10 @@ selection_latest :: proc() -> Position { return latest } -get_selection_for_line :: proc(line: []u8, line_index: int) -> Range { - earliest := selection_earliest() - latest := selection_latest() +get_selection_for_line :: proc(editor: ^Editor, line: []u8, line_index: int) -> Range { + using editor + earliest := selection_earliest(editor) + latest := selection_latest(editor) selection_begin: int selection_end: int if line_index == earliest.line && line_index == latest.line { @@ -448,30 +471,31 @@ get_selection_for_line :: proc(line: []u8, line_index: int) -> Range { return Range{start = selection_begin, end = selection_end} } -handle_backspace :: proc() { +handle_backspace :: proc(editor: ^Editor) { + using editor skip_backspace: if repeatable_key_pressed(.BACKSPACE) { if cursor.line == 0 && cursor.char == 0 do break skip_backspace // TODO: selection if selection.active { - delete_selection() + delete_selection(editor) selection.active = false } else if cursor.char == 0 { // join lines old_len := len(lines[cursor.line - 1]) - append(&lines[cursor.line - 1], string(current_line()[:])) - line_to_remove := current_line()^ + append(&lines[cursor.line - 1], string(current_line(editor)[:])) + line_to_remove := current_line(editor)^ defer delete(line_to_remove) ordered_remove(&lines, cursor.line) cursor.line -= 1 cursor.char = old_len } else if r.IsKeyDown(.LEFT_ALT) { // delete by word - delete_to, found := find_previous_space(cursor.char, current_line()[:]) + delete_to, found := find_previous_space(cursor.char, current_line(editor)[:]) if found { - remove_range(current_line(), delete_to, cursor.char) + remove_range(current_line(editor), delete_to, cursor.char) cursor.char = delete_to } else { - remove_range(current_line(), 0, cursor.char) + remove_range(current_line(editor), 0, cursor.char) cursor.char = 0 } } else { @@ -482,15 +506,16 @@ handle_backspace :: proc() { } } -delete_selection :: proc() { - earliest := selection_earliest() - latest := selection_latest() +delete_selection :: proc(editor: ^Editor) { + using editor + earliest := selection_earliest(editor) + latest := selection_latest(editor) earliest_line := &lines[earliest.line] - earliest_selection := get_selection_for_line(earliest_line[:], earliest.line) + earliest_selection := get_selection_for_line(editor, earliest_line[:], earliest.line) remove_range(earliest_line, earliest_selection.start, earliest_selection.end) if latest.line != earliest.line { latest_line := lines[latest.line] - latest_selection := get_selection_for_line(latest_line[:], latest.line) + latest_selection := get_selection_for_line(editor, latest_line[:], latest.line) append(earliest_line, string(latest_line[latest_selection.end:len(latest_line)])) ordered_remove(&lines, latest.line) delete(latest_line)