#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; }