make things look a little prettier

plugins
Patrick Cleavelin 2023-12-25 00:34:17 -06:00
parent 16b8b4084d
commit 2ba63ea655
1 changed files with 335 additions and 21 deletions

View File

@ -1,15 +1,84 @@
package main package main
import "core:os" import "core:os"
import "core:math"
import "core:strings"
import "core:runtime" import "core:runtime"
import "core:fmt" import "core:fmt"
import "core:mem" import "core:mem"
import "core:slice" import "core:slice"
import "vendor:raylib" import "vendor:raylib"
source_font_width :: 8; source_font_width :: 8*2;
source_font_height :: 16; source_font_height :: 16*2;
line_number_padding :: 4 * source_font_width; 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 <https://github.com/morhetz/gruvbox>
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 { ErrorType :: enum {
None, None,
@ -66,7 +135,7 @@ Cursor :: struct {
Glyph :: struct #packed { Glyph :: struct #packed {
codepoint: u8, codepoint: u8,
color: u16, color: PaletteColor,
} }
FileBuffer :: struct { FileBuffer :: struct {
@ -116,6 +185,15 @@ iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBuf
cond = true; cond = true;
character = it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index]; 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' { if character == '\n' {
it.cursor.col = 0; it.cursor.col = 0;
it.cursor.line += 1; it.cursor.line += 1;
@ -123,14 +201,10 @@ iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBuf
it.cursor.col += 1; it.cursor.col += 1;
} }
it.cursor.index.content_index += 1; return character, it.cursor.index, true;
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;
} }
// 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) { 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.content_index == 0 {
if it.cursor.index.slice_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; 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) { update_file_buffer_index_from_cursor :: proc(buffer: ^FileBuffer) {
it := new_file_buffer_iter(buffer); it := new_file_buffer_iter(buffer);
before_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 { if original_content, success := os.read_entire_file_from_handle(fd); success {
width := 256; width := 256;
height := 50; height := 256;
buffer := FileBuffer { buffer := FileBuffer {
allocator = allocator, 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..<math.min(end_col+1, buffer.glyph_buffer_width) {
buffer.glyph_buffer[i + j * buffer.glyph_buffer_width].color = palette_index;
}
}
}
color_buffer :: proc(buffer: ^FileBuffer) {
start_it := new_file_buffer_iter(buffer);
it := new_file_buffer_iter(buffer);
for character in iterate_file_buffer(&it) {
if it.cursor.line > 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) { update_glyph_buffer :: proc(buffer: ^FileBuffer) {
for &glyph in buffer.glyph_buffer { for &glyph in buffer.glyph_buffer {
glyph = Glyph{}; glyph = Glyph{};
@ -353,7 +618,7 @@ update_glyph_buffer :: proc(buffer: ^FileBuffer) {
} }
if rendered_line >= begin && rendered_col < buffer.glyph_buffer_width { 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]; buffer.glyph_buffer[rendered_col + screen_line * buffer.glyph_buffer_width].codepoint = buffer.input_buffer[k];
rendered_col += 1; rendered_col += 1;
@ -370,7 +635,7 @@ update_glyph_buffer :: proc(buffer: ^FileBuffer) {
} }
if rendered_line >= begin && rendered_col < buffer.glyph_buffer_width { 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; 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) { draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, font: raylib.Font) {
update_glyph_buffer(buffer); update_glyph_buffer(buffer);
color_buffer(buffer);
begin := buffer.top_line; begin := buffer.top_line;
cursor_x := x + line_number_padding + buffer.cursor.col * source_font_width; cursor_x := x + line_number_padding + buffer.cursor.col * source_font_width;
cursor_y := y + buffer.cursor.line * source_font_height; cursor_y := y + buffer.cursor.line * source_font_height;
cursor_y -= begin * source_font_height; cursor_y -= begin * source_font_height;
// draw cursor
if state.mode == .Normal { if state.mode == .Normal {
raylib.DrawRectangle(i32(cursor_x), i32(cursor_y), source_font_width, source_font_height, raylib.BLUE); raylib.DrawRectangle(i32(cursor_x), i32(cursor_y), source_font_width, source_font_height, raylib.BLUE);
} else if state.mode == .Insert { } 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), 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.glyph_buffer_height { for j in 0..<buffer.glyph_buffer_height {
@ -404,11 +691,12 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, fon
if glyph.codepoint == 0 { break; } if glyph.codepoint == 0 { break; }
raylib.DrawTextCodepoint(font, rune(glyph.codepoint), raylib.Vector2 { f32(text_x), f32(text_y) }, source_font_height, raylib.LIGHTGRAY); raylib.DrawTextCodepoint(font, rune(glyph.codepoint), raylib.Vector2 { f32(text_x), f32(text_y) }, source_font_height, raylib.GetColor(palette[glyph.color]));
} }
} }
} }
// TODO: don't mangle cursor
scroll_file_buffer :: proc(buffer: ^FileBuffer, dir: ScrollDir) { scroll_file_buffer :: proc(buffer: ^FileBuffer, dir: ScrollDir) {
switch dir { switch dir {
case .Up: case .Up:
@ -424,6 +712,7 @@ scroll_file_buffer :: proc(buffer: ^FileBuffer, dir: ScrollDir) {
if buffer.cursor.line >= buffer.top_line + buffer.glyph_buffer_height - 4 { if buffer.cursor.line >= buffer.top_line + buffer.glyph_buffer_height - 4 {
buffer.cursor.line = buffer.top_line + buffer.glyph_buffer_height - 1 - 4; buffer.cursor.line = buffer.top_line + buffer.glyph_buffer_height - 1 - 4;
} }
} }
case .Down: 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 // TODO: use buffer list in state
@ -581,21 +872,44 @@ main :: proc() {
font := raylib.LoadFont("../c_editor/Mx437_ToshibaSat_8x16.ttf"); font := raylib.LoadFont("../c_editor/Mx437_ToshibaSat_8x16.ttf");
state: State; state: State;
buffer, err := new_file_buffer(context.allocator, os.args[1]);
buffer, err := new_file_buffer(context.allocator, "./src/main.odin");
if err.type != .None { if err.type != .None {
fmt.println("Failed to create file buffer:", err); fmt.println("Failed to create file buffer:", err);
os.exit(1); os.exit(1);
} }
for !raylib.WindowShouldClose() { 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(); raylib.BeginDrawing();
defer raylib.EndDrawing(); defer raylib.EndDrawing();
raylib.ClearBackground(raylib.GetColor(0x232136ff)); raylib.ClearBackground(get_palette_raylib_color(.Background));
draw_file_buffer(&state, &buffer, 0, 32, font); draw_file_buffer(&state, &buffer, 32, 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.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 { switch state.mode {