summaryrefslogtreecommitdiff
path: root/src/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c429
1 files changed, 429 insertions, 0 deletions
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 <raylib.h>
+
+#include <fontconfig/fontconfig.h>
+
+#include <stdbool.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;
+}