From cebb280fe546190e55e071b6adbf37cd47950f34 Mon Sep 17 00:00:00 2001 From: Collin Williams <96917990+bluedragon1221@users.noreply.github.com> Date: Mon, 11 May 2026 15:07:55 -0500 Subject: initial commit --- src/main.c | 429 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 src/main.c (limited to 'src/main.c') diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..fd7590c --- /dev/null +++ b/src/main.c @@ -0,0 +1,429 @@ +#include + +#include + +#include +#include +#include +#include +#include + +#include "core/buffer.h" +#include "core/editor.h" +#include "core/lua_bridge.h" + +#ifndef LEMACS_RUNTIME_INIT +#define LEMACS_RUNTIME_INIT "runtime/init.lua" +#endif + +static const int WINDOW_WIDTH = 1100; +static const int WINDOW_HEIGHT = 760; +static const int DEFAULT_FONT_SIZE = 20; + +typedef struct { + Font text; + bool custom; + int text_size; + float cell_width; +} LoadedFonts; + +static Color color_from_editor(EditorColor c) { + return (Color){c.r, c.g, c.b, c.a}; +} + +static bool resolve_font(const char *font_query, char *out_path, size_t out_size, int *out_size_px) { + if (!FcInit()) { + return false; + } + + FcPattern *pattern = FcNameParse((const FcChar8 *)font_query); + if (!pattern) { + return false; + } + + FcConfigSubstitute(NULL, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcResult result; + FcPattern *match = FcFontMatch(NULL, pattern, &result); + FcPatternDestroy(pattern); + if (!match) { + return false; + } + + FcChar8 *file = NULL; + if (FcPatternGetString(match, FC_FILE, 0, &file) != FcResultMatch || !file) { + FcPatternDestroy(match); + return false; + } + + snprintf(out_path, out_size, "%s", (const char *)file); + + double pixel_size = 0.0; + if (FcPatternGetDouble(match, FC_PIXEL_SIZE, 0, &pixel_size) == FcResultMatch && pixel_size > 0.0) { + *out_size_px = (int)lround(pixel_size); + } else { + double point_size = 0.0; + if (FcPatternGetDouble(match, FC_SIZE, 0, &point_size) == FcResultMatch && point_size > 0.0) { + *out_size_px = (int)lround(point_size); + } + } + + FcPatternDestroy(match); + return true; +} + +static bool load_single_font(const char *path, int size, Font *out_font) { + if (!path || path[0] == '\0' || size <= 0) { + return false; + } + + Font loaded = LoadFontEx(path, size, NULL, 0); + if (loaded.texture.id == 0) { + return false; + } + + GenTextureMipmaps(&loaded.texture); + SetTextureFilter(loaded.texture, TEXTURE_FILTER_TRILINEAR); + *out_font = loaded; + return true; +} + +static LoadedFonts load_fonts_from_query(const char *font_query) { + LoadedFonts fonts = {0}; + fonts.text = GetFontDefault(); + fonts.custom = false; + fonts.text_size = DEFAULT_FONT_SIZE; + fonts.cell_width = (float)DEFAULT_FONT_SIZE * 0.5f; + + if (!font_query || font_query[0] == '\0') { + font_query = "monospace:size=14"; + } + + int query_size = DEFAULT_FONT_SIZE; + char font_path[1024] = {0}; + if (resolve_font(font_query, font_path, sizeof(font_path), &query_size)) { + if (query_size < 10) { + query_size = 10; + } + + Font text_font = {0}; + if (load_single_font(font_path, query_size, &text_font)) { + fonts.text = text_font; + fonts.custom = true; + fonts.text_size = query_size; + fprintf(stderr, "lemacs: loaded font '%s' from %s\n", font_query, font_path); + } else { + fprintf(stderr, "lemacs: failed to load font file %s, using default\n", font_path); + } + } else { + fprintf(stderr, "lemacs: fontconfig could not resolve '%s', using default\n", font_query); + } + + Vector2 measure = MeasureTextEx(fonts.text, "M", (float)fonts.text_size, 0.0f); + fonts.cell_width = measure.x > 1.0f ? measure.x : (float)fonts.text_size * 0.5f; + return fonts; +} + +static void unload_loaded_fonts(LoadedFonts *fonts) { + if (fonts->custom) { + UnloadFont(fonts->text); + } + fonts->text = GetFontDefault(); + fonts->custom = false; + fonts->text_size = DEFAULT_FONT_SIZE; + fonts->cell_width = (float)DEFAULT_FONT_SIZE * 0.5f; +} + +static void reload_font_if_needed(Editor *editor, LoadedFonts *fonts) { + if (!editor_consume_font_dirty(editor)) { + return; + } + + unload_loaded_fonts(fonts); + *fonts = load_fonts_from_query(editor_font_query(editor)); +} + +static const char *key_name_from_input(int key, bool ctrl, bool shift) { + if (ctrl) { + if (key == KEY_Q) return "C-q"; + if (key == KEY_S) return "C-s"; + if (key == KEY_O) return "C-o"; + if (key == KEY_H) return "C-h"; + if (key == KEY_T) return "C-t"; + if (key == KEY_A) return "C-a"; + if (key == KEY_E) return "C-e"; + if (key == KEY_F) return "C-f"; + if (key == KEY_B) return "C-b"; + return NULL; + } + + if (shift) { + if (key == KEY_FOUR) return "$"; + if (key == KEY_SIX) return "^"; + if (key == KEY_SEMICOLON) return ":"; + } + + switch (key) { + case KEY_H: return "h"; + case KEY_J: return "j"; + case KEY_K: return "k"; + case KEY_L: return "l"; + case KEY_I: return "i"; + case KEY_A: return "a"; + case KEY_O: return "o"; + case KEY_D: return "d"; + case KEY_F: return "f"; + case KEY_BACKSLASH: return "\\"; + case KEY_SPACE: return "space"; + case KEY_W: return "w"; + case KEY_B: return "b"; + case KEY_LEFT: return "left"; + case KEY_RIGHT: return "right"; + case KEY_UP: return "up"; + case KEY_DOWN: return "down"; + case KEY_BACKSPACE: return "backspace"; + case KEY_DELETE: return "delete"; + case KEY_ENTER: return "enter"; + case KEY_TAB: return "tab"; + case KEY_ESCAPE: return "escape"; + default: return NULL; + } +} + +static void draw_cursor_shape(int x, int y, int cell_width, int font_size, Color cursor_color, int shape, int thickness) { + if (shape == LEMACS_CURSOR_BLOCK) { + DrawRectangle(x, y, cell_width > 1 ? cell_width : 1, font_size + 2, cursor_color); + return; + } + + if (shape == LEMACS_CURSOR_UNDERLINE) { + DrawRectangle(x, y + font_size + 1, cell_width > 1 ? cell_width : 1, thickness, cursor_color); + return; + } + + DrawRectangle(x, y, thickness, font_size + 2, cursor_color); +} + +static void draw_buffer_in_view(Buffer *buffer, size_t point, int scroll_line, Rectangle rect, Font font, int font_size, float cell_width, Color fg, bool show_cursor, Color cursor_color, int cursor_shape, int cursor_thickness) { + int line_height = font_size + 4; + int max_lines = (int)(rect.height / (float)line_height); + if (max_lines <= 0) { + return; + } + + BeginScissorMode((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); + + size_t len = buffer_length(buffer); + size_t line_start = 0; + int current_line = 0; + while (line_start < len && current_line < scroll_line) { + if (buffer_char_at(buffer, line_start) == '\n') { + current_line++; + } + line_start++; + } + + for (int draw_line = 0; draw_line < max_lines && line_start <= len; ++draw_line) { + int col = 0; + size_t i = line_start; + while (i < len) { + char ch = buffer_char_at(buffer, i); + if (ch == '\n') { + i++; + break; + } + + char text[2] = {ch, '\0'}; + int x = (int)rect.x + (int)((float)col * cell_width); + int y = (int)rect.y + draw_line * line_height; + DrawTextEx(font, text, (Vector2){(float)x, (float)y}, (float)font_size, 0.0f, fg); + col++; + i++; + } + + if (i == line_start) { + break; + } + line_start = i; + } + + if (show_cursor) { + int cursor_line = 0; + int cursor_col = 0; + buffer_index_to_line_col(buffer, point, &cursor_line, &cursor_col); + int draw_line = cursor_line - scroll_line; + if (draw_line >= 0 && draw_line < max_lines) { + int x = (int)rect.x + (int)((float)cursor_col * cell_width); + int y = (int)rect.y + draw_line * line_height; + draw_cursor_shape(x, y, (int)cell_width, font_size, cursor_color, cursor_shape, cursor_thickness); + } + } + + EndScissorMode(); +} + +static Buffer *resolve_view_buffer(Editor *editor, const EditorView *view) { + if (view->source_type == 0) { + return editor_current_buffer(editor); + } + + if (view->source_type == 1) { + return editor_aux_buffer_get(editor, view->source_index); + } + + return NULL; +} + +static void ensure_primary_cursor_visible(Editor *editor, const EditorView *main_view, int font_size) { + if (!main_view || main_view->source_type != 0) { + return; + } + + int line_height = font_size + 4; + int max_lines = main_view->height / line_height; + if (max_lines <= 0) { + return; + } + + int line = 0; + int col = 0; + Buffer *buffer = editor_current_buffer(editor); + if (!buffer) { + return; + } + + buffer_index_to_line_col(buffer, buffer_point(buffer), &line, &col); + (void)col; + + if (line < editor->scroll_line) { + editor->scroll_line = line; + } else if (line >= editor->scroll_line + max_lines) { + editor->scroll_line = line - max_lines + 1; + } + + if (editor->scroll_line < 0) { + editor->scroll_line = 0; + } +} + +int main(int argc, char **argv) { + SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT); + InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "lemacs"); + SetExitKey(KEY_NULL); + SetTargetFPS(60); + + Editor editor; + if (!editor_init(&editor)) { + fprintf(stderr, "failed to initialize editor\n"); + CloseWindow(); + return 1; + } + + LuaBridge bridge; + if (!lua_bridge_init(&bridge, &editor)) { + fprintf(stderr, "failed to initialize lua\n"); + editor_free(&editor); + CloseWindow(); + return 1; + } + + if (!lua_bridge_load_runtime(&bridge, LEMACS_RUNTIME_INIT)) { + lua_bridge_free(&bridge); + editor_free(&editor); + CloseWindow(); + return 1; + } + + lua_bridge_load_optional_init(&bridge, "init.lua"); + + LoadedFonts fonts = load_fonts_from_query(editor_font_query(&editor)); + + if (argc > 1) { + lua_bridge_call_open_file(&bridge, argv[1]); + } + + while (!WindowShouldClose() && !editor.should_quit) { + reload_font_if_needed(&editor, &fonts); + + bool ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL); + bool shift = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); + + int key = GetKeyPressed(); + while (key != 0) { + const char *name = key_name_from_input(key, ctrl, shift); + if (name) { + lua_bridge_call_key(&bridge, name); + } + key = GetKeyPressed(); + } + + if (!ctrl) { + int ch = GetCharPressed(); + while (ch > 0) { + if (ch >= 32 && ch != 127) { + char utf8[5] = {0}; + if (ch < 0x80) { + utf8[0] = (char)ch; + } else { + utf8[0] = '?'; + } + lua_bridge_call_text(&bridge, utf8); + } + ch = GetCharPressed(); + } + } + + lua_bridge_call_tick(&bridge); + + const EditorView *main_view = NULL; + for (int i = 0; i < editor.view_count; ++i) { + if (editor.views[i].source_type == 0 && editor.views[i].show_cursor) { + main_view = &editor.views[i]; + break; + } + } + ensure_primary_cursor_visible(&editor, main_view, fonts.text_size); + + BeginDrawing(); + Color bg_top = color_from_editor(editor.bg_top); + Color bg_bottom = color_from_editor(editor.bg_bottom); + DrawRectangleGradientV(0, 0, GetScreenWidth(), GetScreenHeight(), bg_top, bg_bottom); + + for (int i = 0; i < editor.view_count; ++i) { + EditorView *view = &editor.views[i]; + Buffer *buffer = resolve_view_buffer(&editor, view); + if (!buffer) { + continue; + } + + Rectangle rect = {(float)view->x, (float)view->y, (float)view->width, (float)view->height}; + DrawRectangle(view->x, view->y, view->width, view->height, color_from_editor(view->bg)); + + size_t point = view->source_type == 0 ? buffer_point(buffer) : 0; + int scroll_line = view->source_type == 0 ? editor.scroll_line : 0; + draw_buffer_in_view( + buffer, + point, + scroll_line, + rect, + fonts.text, + fonts.text_size, + fonts.cell_width, + color_from_editor(view->fg), + view->show_cursor && view->source_type == 0, + color_from_editor(editor.cursor), + editor_cursor_shape(&editor), + editor_cursor_thickness(&editor) + ); + } + + EndDrawing(); + } + + lua_bridge_free(&bridge); + editor_free(&editor); + unload_loaded_fonts(&fonts); + CloseWindow(); + return 0; +} -- cgit v1.3.1