diff --git a/src/main.odin b/src/main.odin index 2de8138..c5496d6 100644 --- a/src/main.odin +++ b/src/main.odin @@ -1,15 +1,84 @@ package main import "core:os" +import "core:math" +import "core:strings" import "core:runtime" import "core:fmt" import "core:mem" import "core:slice" import "vendor:raylib" -source_font_width :: 8; -source_font_height :: 16; -line_number_padding :: 4 * source_font_width; +source_font_width :: 8*2; +source_font_height :: 16*2; +line_number_padding :: 5 * source_font_width; + +PaletteColor :: enum { + Background, + Foreground, + + Background1, + Background2, + Background3, + Background4, + + Foreground1, + Foreground2, + Foreground3, + Foreground4, + + Red, + Green, + Yellow, + Blue, + Purple, + Aqua, + Gray, + + BrightRed, + BrightGreen, + BrightYellow, + BrightBlue, + BrightPurple, + BrightAqua, + BrightGray, +} + +// Its the gruvbox dark theme +palette := []u32 { + 0x282828ff, + 0xebdbb2ff, + + 0x3c3836ff, + 0x504945ff, + 0x665c54ff, + 0x7c6f64ff, + + 0xfbf1c7ff, + 0xebdbb2ff, + 0xd5c4a1ff, + 0xbdae93ff, + + 0xcc241dff, + 0x98981aff, + 0xd79921ff, + 0x458588ff, + 0xb16286ff, + 0x689d6aff, + 0xa89984ff, + + 0xfb4934ff, + 0xb8bb26ff, + 0xfabd2fff, + 0x83a598ff, + 0xd3869bff, + 0x8ec07cff, + 0x928374ff, +}; + +get_palette_raylib_color :: proc(palette_color: PaletteColor) -> raylib.Color { + return raylib.GetColor(palette[palette_color]); +} ErrorType :: enum { None, @@ -66,7 +135,7 @@ Cursor :: struct { Glyph :: struct #packed { codepoint: u8, - color: u16, + color: PaletteColor, } FileBuffer :: struct { @@ -116,6 +185,15 @@ iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBuf cond = true; character = it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index]; + if it.cursor.index.content_index < len(it.buffer.content_slices[it.cursor.index.slice_index])-1 { + it.cursor.index.content_index += 1; + } else if it.cursor.index.slice_index < len(it.buffer.content_slices)-1 { + it.cursor.index.content_index = 0; + it.cursor.index.slice_index += 1; + } else { + return character, it.cursor.index, false; + } + if character == '\n' { it.cursor.col = 0; it.cursor.line += 1; @@ -123,14 +201,10 @@ iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBuf it.cursor.col += 1; } - it.cursor.index.content_index += 1; - if it.cursor.index.content_index >= len(it.buffer.content_slices[it.cursor.index.slice_index]) { - it.cursor.index.content_index = 0; - it.cursor.index.slice_index += 1; - } - - return; + return character, it.cursor.index, true; } +// NOTE: This give the character for the NEXT position, unlike the non-reverse version +// which gives the character for the CURRENT position. iterate_file_buffer_reverse_mangle_cursor :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool) { if it.cursor.index.content_index == 0 { if it.cursor.index.slice_index > 0 { @@ -165,6 +239,94 @@ iterate_file_buffer_reverse :: proc(it: ^FileBufferIter) -> (character: u8, idx: return character, it.cursor.index, cond; } +get_character_at_iter :: proc(it: FileBufferIter) -> u8 { + return it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index]; +} + +IterProc :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool); +UntilProc :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool; + +iterate_file_buffer_until :: proc(it: ^FileBufferIter, until_proc: UntilProc) { + for until_proc(it, iterate_file_buffer) {} +} +iterate_file_buffer_until_reverse :: proc(it: ^FileBufferIter, until_proc: UntilProc) { + for until_proc(it, iterate_file_buffer_reverse) {} +} + +until_non_whitespace :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool { + if character, _, cond := iter_proc(it); cond && strings.is_space(rune(character)) { + return false; + } + + return true; +} + +until_non_alpha_num :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool { + // TODO: make this global + set, _ := strings.ascii_set_make("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); + + peek_it := it^; + if character, _, cond := iter_proc(&peek_it); cond && strings.ascii_set_contains(set, character) { + it^ = peek_it; + return true; + } + + return false; +} + +until_end_of_word :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool { + set, _ := strings.ascii_set_make("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); + + if character, _, cond := iter_proc(it); cond && !strings.ascii_set_contains(set, character) && !strings.is_space(rune(character)) { + for until_non_whitespace(it, iter_proc) {} + return false; + } + + for until_non_alpha_num(it, iter_proc) {} + return false; +} + +until_double_quote :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool { + before_it := it^; + character, _, cond := iter_proc(it); + if !cond { return cond; } + + // skip over escaped characters + if character == '\\' { + _, _, cond = iter_proc(it); + } else if character == '"' { + it^ = before_it; + return false; + } + + return cond; +} + +until_single_quote :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool { + before_it := it^; + character, _, cond := iter_proc(it); + if !cond { return cond; } + + // skip over escaped characters + if character == '\\' { + _, _, cond = iter_proc(it); + } else if character == '\'' { + it^ = before_it; + return false; + } + + return cond; +} + +until_line_break :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> (cond: bool) { + if it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index] == '\n' { + return false; + } + + _, _, cond = iter_proc(it); + return cond; +} + update_file_buffer_index_from_cursor :: proc(buffer: ^FileBuffer) { it := new_file_buffer_iter(buffer); before_it := new_file_buffer_iter(buffer); @@ -299,7 +461,7 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string) -> (FileBuf if original_content, success := os.read_entire_file_from_handle(fd); success { width := 256; - height := 50; + height := 256; buffer := FileBuffer { allocator = allocator, @@ -324,6 +486,109 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string) -> (FileBuf } } +color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette_index: PaletteColor) { + start, end := start, end; + + if end.line < buffer.top_line { return; } + if start.line < buffer.top_line { + start.line = 0; + } else { + start.line -= buffer.top_line; + } + + if end.line >= buffer.top_line + buffer.glyph_buffer_height { + end.line = buffer.glyph_buffer_height - 1; + end.col = buffer.glyph_buffer_width - 1; + } else { + end.line -= buffer.top_line; + } + + for j in start.line..=end.line { + start_col := start.col; + end_col := end.col; + if j > start.line && j < end.line { + start_col = 0; + end_col = buffer.glyph_buffer_width; + } else if j < end.line { + end_col = buffer.glyph_buffer_width; + } else if j > start.line && j == end.line { + start_col = 0; + } + + for i in start_col.. it.buffer.glyph_buffer_height && (it.cursor.line - it.buffer.top_line) > it.buffer.glyph_buffer_height { + break; + } + + if character == '/' { + start_it = it; + // need to go back one character because `it` is on the next character + iterate_file_buffer_reverse(&start_it); + + character, _, succ := iterate_file_buffer(&it); + if !succ { break; } + + if character == '/' { + iterate_file_buffer_until(&it, until_line_break); + color_character(buffer, start_it.cursor, it.cursor, .Foreground4); + } else if character == '*' { + // TODO: block comments + } + } else if character == '\'' { + start_it = it; + // need to go back one character because `it` is on the next character + iterate_file_buffer_reverse(&start_it); + + // jump into the quoted text + iterate_file_buffer_until(&it, until_single_quote); + color_character(buffer, start_it.cursor, it.cursor, .Yellow); + + iterate_file_buffer(&it); + } else if character == '"' { + start_it = it; + // need to go back one character because `it` is on the next character + iterate_file_buffer_reverse(&start_it); + + // jump into the quoted text + iterate_file_buffer_until(&it, until_double_quote); + color_character(buffer, start_it.cursor, it.cursor, .Yellow); + + iterate_file_buffer(&it); + } else if (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || character == '_' { + start_it = it; + // need to go back one character because `it` is on the next character + iterate_file_buffer_reverse(&start_it); + it = start_it; + + iterate_file_buffer_until(&it, until_end_of_word); + + end_of_word_it := it; + _, _, succ := iterate_file_buffer_reverse(&end_of_word_it); + if !succ { break; } + + // TODO: color keywords + + if character, _, cond := iterate_file_buffer(&it); cond { + if character == '(' { + color_character(buffer, start_it.cursor, end_of_word_it.cursor, .Green); + } + } else { + break; + } + } + } +} + update_glyph_buffer :: proc(buffer: ^FileBuffer) { for &glyph in buffer.glyph_buffer { glyph = Glyph{}; @@ -353,7 +618,7 @@ update_glyph_buffer :: proc(buffer: ^FileBuffer) { } if rendered_line >= begin && rendered_col < buffer.glyph_buffer_width { - buffer.glyph_buffer[rendered_col + screen_line * buffer.glyph_buffer_width].color = 0xFFFF; + buffer.glyph_buffer[rendered_col + screen_line * buffer.glyph_buffer_width].color = .Foreground; buffer.glyph_buffer[rendered_col + screen_line * buffer.glyph_buffer_width].codepoint = buffer.input_buffer[k]; rendered_col += 1; @@ -370,7 +635,7 @@ update_glyph_buffer :: proc(buffer: ^FileBuffer) { } if rendered_line >= begin && rendered_col < buffer.glyph_buffer_width { - buffer.glyph_buffer[rendered_col + screen_line * buffer.glyph_buffer_width] = Glyph { codepoint = character, color = 0 }; + buffer.glyph_buffer[rendered_col + screen_line * buffer.glyph_buffer_width] = Glyph { codepoint = character, color = .Foreground }; } rendered_col += 1; @@ -379,17 +644,39 @@ update_glyph_buffer :: proc(buffer: ^FileBuffer) { draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, font: raylib.Font) { update_glyph_buffer(buffer); + color_buffer(buffer); begin := buffer.top_line; cursor_x := x + line_number_padding + buffer.cursor.col * source_font_width; cursor_y := y + buffer.cursor.line * source_font_height; cursor_y -= begin * source_font_height; + + // draw cursor if state.mode == .Normal { raylib.DrawRectangle(i32(cursor_x), i32(cursor_y), source_font_width, source_font_height, raylib.BLUE); } else if state.mode == .Insert { raylib.DrawRectangle(i32(cursor_x), i32(cursor_y), source_font_width, source_font_height, raylib.GREEN); - raylib.DrawRectangle(i32(cursor_x + len(buffer.input_buffer) * source_font_width), i32(cursor_y), source_font_width, source_font_height, raylib.BLUE); + + num_line_break := 0; + line_length := 0; + for c in buffer.input_buffer { + if c == '\n' { + num_line_break += 1; + line_length = 0; + } else { + line_length += 1; + } + } + + if num_line_break > 0 { + cursor_x = x + line_number_padding + line_length * source_font_width; + cursor_y = cursor_y + num_line_break * source_font_height; + } else { + cursor_x += line_length * source_font_width; + } + + raylib.DrawRectangle(i32(cursor_x), i32(cursor_y), source_font_width, source_font_height, raylib.BLUE); } for j in 0..= buffer.top_line + buffer.glyph_buffer_height - 4 { buffer.cursor.line = buffer.top_line + buffer.glyph_buffer_height - 1 - 4; } + } case .Down: { @@ -435,6 +724,8 @@ scroll_file_buffer :: proc(buffer: ^FileBuffer, dir: ScrollDir) { } } } + + update_file_buffer_index_from_cursor(buffer); } // TODO: use buffer list in state @@ -581,21 +872,44 @@ main :: proc() { font := raylib.LoadFont("../c_editor/Mx437_ToshibaSat_8x16.ttf"); state: State; - - buffer, err := new_file_buffer(context.allocator, "./src/main.odin"); + buffer, err := new_file_buffer(context.allocator, os.args[1]); if err.type != .None { fmt.println("Failed to create file buffer:", err); os.exit(1); } for !raylib.WindowShouldClose() { + screen_width := raylib.GetScreenWidth(); + screen_height := raylib.GetScreenHeight(); + buffer.glyph_buffer_height = math.min(256, int((screen_height - 32 - source_font_height) / source_font_height)); + { raylib.BeginDrawing(); defer raylib.EndDrawing(); - raylib.ClearBackground(raylib.GetColor(0x232136ff)); - draw_file_buffer(&state, &buffer, 0, 32, font); - raylib.DrawTextEx(font, raylib.TextFormat("Line: %d, Col: %d --- Slice Index: %d, Content Index: %d", buffer.cursor.line + 1, buffer.cursor.col + 1, buffer.cursor.index.slice_index, buffer.cursor.index.content_index), raylib.Vector2 { 0, 0 }, source_font_height, 0, raylib.DARKGRAY); + raylib.ClearBackground(get_palette_raylib_color(.Background)); + draw_file_buffer(&state, &buffer, 32, 32, font); + + raylib.DrawRectangle(0, screen_height - source_font_height, screen_width, source_font_height, get_palette_raylib_color(.Background2)); + + line_info_text := raylib.TextFormat("Line: %d, Col: %d --- Slice Index: %d, Content Index: %d", buffer.cursor.line + 1, buffer.cursor.col + 1, buffer.cursor.index.slice_index, buffer.cursor.index.content_index); + line_info_width := raylib.MeasureTextEx(font, line_info_text, source_font_height, 0).x; + + switch state.mode { + case .Normal: + raylib.DrawRectangle(0, screen_height - source_font_height, 8 + len("NORMAL")*source_font_width, source_font_height, get_palette_raylib_color(.Foreground4)); + raylib.DrawRectangleV(raylib.Vector2 { f32(screen_width) - line_info_width - 8 , f32(screen_height - source_font_height) }, raylib.Vector2 { 8 + line_info_width, f32(source_font_height) }, get_palette_raylib_color(.Foreground4)); + + raylib.DrawTextEx(font, "NORMAL", raylib.Vector2 { 4, f32(screen_height - source_font_height) }, source_font_height, 0, get_palette_raylib_color(.Background1)); + case .Insert: + raylib.DrawRectangle(0, screen_height - source_font_height, 8 + len("INSERT")*source_font_width, source_font_height, raylib.SKYBLUE); + raylib.DrawRectangleV(raylib.Vector2 { f32(screen_width) - line_info_width - 8 , f32(screen_height - source_font_height) }, raylib.Vector2 { 8 + line_info_width, f32(source_font_height) }, raylib.SKYBLUE); + + raylib.DrawTextEx(font, "INSERT", raylib.Vector2 { 4, f32(screen_height - source_font_height) }, source_font_height, 0, raylib.DARKBLUE); + } + + + raylib.DrawTextEx(font, line_info_text, raylib.Vector2 { f32(screen_width) - line_info_width - 4, f32(screen_height - source_font_height) }, source_font_height, 0, get_palette_raylib_color(.Background1)); } switch state.mode {