diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/buffer.c | 296 | ||||
| -rw-r--r-- | src/core/buffer.h | 38 | ||||
| -rw-r--r-- | src/core/editor.c | 466 | ||||
| -rw-r--r-- | src/core/editor.h | 96 | ||||
| -rw-r--r-- | src/core/lua_bridge.c | 611 | ||||
| -rw-r--r-- | src/core/lua_bridge.h | 26 | ||||
| -rw-r--r-- | src/main.c | 429 |
7 files changed, 1962 insertions, 0 deletions
diff --git a/src/core/buffer.c b/src/core/buffer.c new file mode 100644 index 0000000..16a9dfe --- /dev/null +++ b/src/core/buffer.c @@ -0,0 +1,296 @@ +#include "core/buffer.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define INITIAL_CAPACITY 1024 + +static size_t gap_size(const Buffer *buffer) { + return buffer->gap_end - buffer->gap_start; +} + +static size_t physical_index(const Buffer *buffer, size_t logical) { + if (logical < buffer->gap_start) { + return logical; + } + return logical + gap_size(buffer); +} + +static bool ensure_gap(Buffer *buffer, size_t needed) { + if (gap_size(buffer) >= needed) { + return true; + } + + size_t old_gap = gap_size(buffer); + size_t old_capacity = buffer->capacity; + size_t new_capacity = old_capacity; + while ((new_capacity - buffer->gap_start - (old_capacity - buffer->gap_end)) < needed) { + new_capacity *= 2; + } + + char *new_data = (char *)malloc(new_capacity); + if (!new_data) { + return false; + } + + memcpy(new_data, buffer->data, buffer->gap_start); + size_t tail_size = old_capacity - buffer->gap_end; + size_t new_gap_end = new_capacity - tail_size; + memcpy(new_data + new_gap_end, buffer->data + buffer->gap_end, tail_size); + + free(buffer->data); + buffer->data = new_data; + buffer->capacity = new_capacity; + buffer->gap_end = new_gap_end; + + (void)old_gap; + return true; +} + +static void move_gap(Buffer *buffer, size_t target) { + if (target == buffer->gap_start) { + return; + } + + if (target < buffer->gap_start) { + size_t move = buffer->gap_start - target; + memmove(buffer->data + buffer->gap_end - move, buffer->data + target, move); + buffer->gap_start = target; + buffer->gap_end -= move; + return; + } + + size_t move = target - buffer->gap_start; + memmove(buffer->data + buffer->gap_start, buffer->data + buffer->gap_end, move); + buffer->gap_start += move; + buffer->gap_end += move; +} + +bool buffer_init(Buffer *buffer) { + buffer->data = (char *)malloc(INITIAL_CAPACITY); + if (!buffer->data) { + return false; + } + buffer->capacity = INITIAL_CAPACITY; + buffer->gap_start = 0; + buffer->gap_end = INITIAL_CAPACITY; + buffer->path[0] = '\0'; + buffer->modified = false; + return true; +} + +void buffer_free(Buffer *buffer) { + free(buffer->data); + buffer->data = NULL; + buffer->capacity = 0; + buffer->gap_start = 0; + buffer->gap_end = 0; +} + +size_t buffer_length(const Buffer *buffer) { + return buffer->capacity - gap_size(buffer); +} + +size_t buffer_point(const Buffer *buffer) { + return buffer->gap_start; +} + +void buffer_set_point(Buffer *buffer, size_t point) { + size_t len = buffer_length(buffer); + if (point > len) { + point = len; + } + move_gap(buffer, point); +} + +bool buffer_insert_char(Buffer *buffer, char c) { + if (!ensure_gap(buffer, 1)) { + return false; + } + buffer->data[buffer->gap_start++] = c; + buffer->modified = true; + return true; +} + +bool buffer_insert_cstr(Buffer *buffer, const char *text) { + size_t len = strlen(text); + if (!ensure_gap(buffer, len)) { + return false; + } + memcpy(buffer->data + buffer->gap_start, text, len); + buffer->gap_start += len; + if (len > 0) { + buffer->modified = true; + } + return true; +} + +bool buffer_backspace(Buffer *buffer) { + if (buffer->gap_start == 0) { + return false; + } + buffer->gap_start--; + buffer->modified = true; + return true; +} + +bool buffer_delete_forward(Buffer *buffer) { + if (buffer->gap_end >= buffer->capacity) { + return false; + } + buffer->gap_end++; + buffer->modified = true; + return true; +} + +char buffer_char_at(const Buffer *buffer, size_t index) { + size_t len = buffer_length(buffer); + if (index >= len) { + return '\0'; + } + return buffer->data[physical_index(buffer, index)]; +} + +bool buffer_load_file(Buffer *buffer, const char *path) { + FILE *fp = fopen(path, "rb"); + if (!fp) { + return false; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + + long file_size = ftell(fp); + if (file_size < 0) { + fclose(fp); + return false; + } + rewind(fp); + + buffer->gap_start = 0; + buffer->gap_end = buffer->capacity; + + if (!ensure_gap(buffer, (size_t)file_size + 1)) { + fclose(fp); + return false; + } + + size_t read = fread(buffer->data, 1, (size_t)file_size, fp); + fclose(fp); + if (read != (size_t)file_size) { + return false; + } + + buffer->gap_start = (size_t)file_size; + buffer->gap_end = buffer->capacity; + + strncpy(buffer->path, path, sizeof(buffer->path) - 1); + buffer->path[sizeof(buffer->path) - 1] = '\0'; + buffer->modified = false; + return true; +} + +bool buffer_save_file(Buffer *buffer, const char *path) { + const char *out_path = path; + if (!out_path || out_path[0] == '\0') { + out_path = buffer->path; + } + + if (!out_path || out_path[0] == '\0') { + return false; + } + + FILE *fp = fopen(out_path, "wb"); + if (!fp) { + return false; + } + + size_t before = buffer->gap_start; + size_t after = buffer->capacity - buffer->gap_end; + + if (before > 0 && fwrite(buffer->data, 1, before, fp) != before) { + fclose(fp); + return false; + } + + if (after > 0 && fwrite(buffer->data + buffer->gap_end, 1, after, fp) != after) { + fclose(fp); + return false; + } + + fclose(fp); + + if (path && path[0] != '\0') { + strncpy(buffer->path, path, sizeof(buffer->path) - 1); + buffer->path[sizeof(buffer->path) - 1] = '\0'; + } + + buffer->modified = false; + + return true; +} + +const char *buffer_path(const Buffer *buffer) { + return buffer->path; +} + +bool buffer_modified(const Buffer *buffer) { + return buffer->modified; +} + +void buffer_index_to_line_col(const Buffer *buffer, size_t index, int *line, int *col) { + size_t len = buffer_length(buffer); + if (index > len) { + index = len; + } + + int l = 0; + int c = 0; + for (size_t i = 0; i < index; ++i) { + char ch = buffer_char_at(buffer, i); + if (ch == '\n') { + l++; + c = 0; + } else { + c++; + } + } + + *line = l; + *col = c; +} + +size_t buffer_line_col_to_index(const Buffer *buffer, int target_line, int target_col) { + if (target_line < 0) { + target_line = 0; + } + if (target_col < 0) { + target_col = 0; + } + + size_t len = buffer_length(buffer); + int line = 0; + int col = 0; + + for (size_t i = 0; i < len; ++i) { + if (line == target_line && col == target_col) { + return i; + } + + char ch = buffer_char_at(buffer, i); + if (ch == '\n') { + if (line == target_line) { + return i; + } + line++; + col = 0; + } else { + col++; + } + } + + return len; +} diff --git a/src/core/buffer.h b/src/core/buffer.h new file mode 100644 index 0000000..8fac8ab --- /dev/null +++ b/src/core/buffer.h @@ -0,0 +1,38 @@ +#ifndef LEMACS_BUFFER_H +#define LEMACS_BUFFER_H + +#include <stdbool.h> +#include <stddef.h> + +typedef struct { + char *data; + size_t capacity; + size_t gap_start; + size_t gap_end; + char path[1024]; + bool modified; +} Buffer; + +bool buffer_init(Buffer *buffer); +void buffer_free(Buffer *buffer); + +size_t buffer_length(const Buffer *buffer); +size_t buffer_point(const Buffer *buffer); +void buffer_set_point(Buffer *buffer, size_t point); + +bool buffer_insert_char(Buffer *buffer, char c); +bool buffer_insert_cstr(Buffer *buffer, const char *text); +bool buffer_backspace(Buffer *buffer); +bool buffer_delete_forward(Buffer *buffer); + +char buffer_char_at(const Buffer *buffer, size_t index); + +bool buffer_load_file(Buffer *buffer, const char *path); +bool buffer_save_file(Buffer *buffer, const char *path); +const char *buffer_path(const Buffer *buffer); +bool buffer_modified(const Buffer *buffer); + +void buffer_index_to_line_col(const Buffer *buffer, size_t index, int *line, int *col); +size_t buffer_line_col_to_index(const Buffer *buffer, int target_line, int target_col); + +#endif diff --git a/src/core/editor.c b/src/core/editor.c new file mode 100644 index 0000000..43c28c7 --- /dev/null +++ b/src/core/editor.c @@ -0,0 +1,466 @@ +#include "core/editor.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +static EditorColor make_color(int r, int g, int b, int a) { + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + if (a < 0) a = 0; + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + if (a > 255) a = 255; + + EditorColor c; + c.r = (uint8_t)r; + c.g = (uint8_t)g; + c.b = (uint8_t)b; + c.a = (uint8_t)a; + return c; +} + +static Buffer *active_buffer(Editor *editor) { + if (editor->current_buffer < 0 || editor->current_buffer >= editor->buffer_count) { + return NULL; + } + return &editor->buffers[editor->current_buffer]; +} + +static const Buffer *active_buffer_const(const Editor *editor) { + if (editor->current_buffer < 0 || editor->current_buffer >= editor->buffer_count) { + return NULL; + } + return &editor->buffers[editor->current_buffer]; +} + +static void clamp_point(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + size_t len = buffer_length(buffer); + if (point > len) { + buffer_set_point(buffer, len); + } +} + +static bool valid_aux_index(const Editor *editor, int aux_index) { + return aux_index >= 0 && aux_index < editor->aux_buffer_count; +} + +static bool valid_buffer_index(const Editor *editor, int index) { + return index >= 0 && index < editor->buffer_count; +} + +bool editor_init(Editor *editor) { + editor->buffer_count = 0; + editor->current_buffer = 0; + + if (!buffer_init(&editor->buffers[0])) { + return false; + } + + editor->buffer_count = 1; + editor->aux_buffer_count = 0; + editor->desired_col = -1; + editor->scroll_line = 0; + snprintf(editor->font_query, sizeof(editor->font_query), "%s", "monospace:size=14"); + editor->font_dirty = true; + editor->bg_top = make_color(248, 248, 248, 255); + editor->bg_bottom = make_color(238, 238, 238, 255); + editor->text = make_color(56, 60, 67, 255); + editor->cursor = make_color(176, 65, 11, 255); + editor->cursor_shape = LEMACS_CURSOR_BAR; + editor->cursor_thickness = 2; + editor->view_count = 0; + editor->should_quit = false; + return true; +} + +void editor_free(Editor *editor) { + for (int i = 0; i < editor->buffer_count; ++i) { + buffer_free(&editor->buffers[i]); + } + + for (int i = 0; i < editor->aux_buffer_count; ++i) { + buffer_free(&editor->aux_buffers[i]); + } + + editor->buffer_count = 0; + editor->current_buffer = 0; + editor->aux_buffer_count = 0; + editor->view_count = 0; +} + +void editor_set_font_query(Editor *editor, const char *query) { + snprintf(editor->font_query, sizeof(editor->font_query), "%s", (query && query[0] != '\0') ? query : "monospace:size=14"); + editor->font_dirty = true; +} + +const char *editor_font_query(const Editor *editor) { + return editor->font_query; +} + +bool editor_consume_font_dirty(Editor *editor) { + bool dirty = editor->font_dirty; + editor->font_dirty = false; + return dirty; +} + +bool editor_set_theme_color(Editor *editor, const char *name, int r, int g, int b, int a) { + EditorColor c = make_color(r, g, b, a); + + if (strcmp(name, "bg_top") == 0) { + editor->bg_top = c; + return true; + } + if (strcmp(name, "bg_bottom") == 0) { + editor->bg_bottom = c; + return true; + } + if (strcmp(name, "text") == 0) { + editor->text = c; + return true; + } + if (strcmp(name, "cursor") == 0) { + editor->cursor = c; + return true; + } + if (strcmp(name, "accent") == 0) { + editor->cursor = c; + return true; + } + + return false; +} + +bool editor_set_cursor_shape(Editor *editor, int shape) { + if (shape < LEMACS_CURSOR_BAR || shape > LEMACS_CURSOR_UNDERLINE) { + return false; + } + editor->cursor_shape = shape; + return true; +} + +void editor_set_cursor_thickness(Editor *editor, int thickness) { + if (thickness < 1) { + thickness = 1; + } + if (thickness > 24) { + thickness = 24; + } + editor->cursor_thickness = thickness; +} + +int editor_cursor_shape(const Editor *editor) { + return editor->cursor_shape; +} + +int editor_cursor_thickness(const Editor *editor) { + return editor->cursor_thickness; +} + +int editor_aux_buffer_new(Editor *editor) { + if (editor->aux_buffer_count >= LEMACS_MAX_AUX_BUFFERS) { + return -1; + } + + int index = editor->aux_buffer_count; + if (!buffer_init(&editor->aux_buffers[index])) { + return -1; + } + + editor->aux_buffer_count++; + return index; +} + +bool editor_aux_buffer_set_text(Editor *editor, int aux_index, const char *text) { + if (!valid_aux_index(editor, aux_index)) { + return false; + } + + Buffer *buffer = &editor->aux_buffers[aux_index]; + buffer->gap_start = 0; + buffer->gap_end = buffer->capacity; + buffer->path[0] = '\0'; + buffer->modified = false; + + if (!text || text[0] == '\0') { + return true; + } + + return buffer_insert_cstr(buffer, text); +} + +Buffer *editor_aux_buffer_get(Editor *editor, int aux_index) { + if (!valid_aux_index(editor, aux_index)) { + return NULL; + } + return &editor->aux_buffers[aux_index]; +} + +void editor_layout_reset(Editor *editor) { + editor->view_count = 0; +} + +bool editor_layout_add_view(Editor *editor, int source_type, int source_index, int x, int y, int width, int height, bool show_cursor, EditorColor bg, EditorColor fg) { + if (editor->view_count >= LEMACS_MAX_VIEWS) { + return false; + } + + if (width <= 0 || height <= 0) { + return false; + } + + if (source_type == 1 && !valid_aux_index(editor, source_index)) { + return false; + } + + if (source_type != 0 && source_type != 1) { + return false; + } + + EditorView *view = &editor->views[editor->view_count++]; + view->x = x; + view->y = y; + view->width = width; + view->height = height; + view->source_type = source_type; + view->source_index = source_index; + view->show_cursor = show_cursor; + view->bg = bg; + view->fg = fg; + return true; +} + +void editor_move_left(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + if (point > 0) { + buffer_set_point(buffer, point - 1); + } + editor->desired_col = -1; +} + +void editor_move_right(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + size_t len = buffer_length(buffer); + if (point < len) { + buffer_set_point(buffer, point + 1); + } + editor->desired_col = -1; +} + +void editor_move_up(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + int line = 0; + int col = 0; + buffer_index_to_line_col(buffer, point, &line, &col); + if (editor->desired_col < 0) { + editor->desired_col = col; + } + if (line > 0) { + size_t new_point = buffer_line_col_to_index(buffer, line - 1, editor->desired_col); + buffer_set_point(buffer, new_point); + } + clamp_point(editor); +} + +void editor_move_down(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + int line = 0; + int col = 0; + buffer_index_to_line_col(buffer, point, &line, &col); + if (editor->desired_col < 0) { + editor->desired_col = col; + } + + size_t len = buffer_length(buffer); + int last_line = 0; + int last_col = 0; + buffer_index_to_line_col(buffer, len, &last_line, &last_col); + if (line < last_line) { + size_t new_point = buffer_line_col_to_index(buffer, line + 1, editor->desired_col); + buffer_set_point(buffer, new_point); + } + clamp_point(editor); +} + +void editor_move_beginning_of_line(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + while (point > 0) { + char ch = buffer_char_at(buffer, point - 1); + if (ch == '\n') { + break; + } + point--; + } + buffer_set_point(buffer, point); + editor->desired_col = -1; +} + +void editor_move_end_of_line(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + size_t len = buffer_length(buffer); + while (point < len) { + char ch = buffer_char_at(buffer, point); + if (ch == '\n') { + break; + } + point++; + } + buffer_set_point(buffer, point); + editor->desired_col = -1; +} + +void editor_set_point(Editor *editor, size_t point) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + buffer_set_point(buffer, point); + editor->desired_col = -1; +} + +char editor_char_at(const Editor *editor, size_t index) { + const Buffer *buffer = active_buffer_const(editor); + if (!buffer) { + return '\0'; + } + return buffer_char_at(buffer, index); +} + +bool editor_open_file(Editor *editor, const char *path) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return false; + } + + if (!path || path[0] == '\0') { + return false; + } + + if (!buffer_load_file(buffer, path)) { + if (errno != ENOENT) { + return false; + } + + buffer->gap_start = 0; + buffer->gap_end = buffer->capacity; + buffer->modified = false; + strncpy(buffer->path, path, sizeof(buffer->path) - 1); + buffer->path[sizeof(buffer->path) - 1] = '\0'; + } + + buffer_set_point(buffer, 0); + editor->desired_col = -1; + editor->scroll_line = 0; + + return true; +} + +bool editor_save_file(Editor *editor, const char *path) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return false; + } + return buffer_save_file(buffer, path); +} + +Buffer *editor_current_buffer(Editor *editor) { + return active_buffer(editor); +} + +const Buffer *editor_current_buffer_const(const Editor *editor) { + return active_buffer_const(editor); +} + +int editor_buffer_count(const Editor *editor) { + return editor->buffer_count; +} + +int editor_current_buffer_index(const Editor *editor) { + return editor->current_buffer; +} + +bool editor_switch_buffer(Editor *editor, int index) { + if (!valid_buffer_index(editor, index)) { + return false; + } + + editor->current_buffer = index; + editor->desired_col = -1; + editor->scroll_line = 0; + return true; +} + +int editor_new_buffer(Editor *editor) { + if (editor->buffer_count >= LEMACS_MAX_BUFFERS) { + return -1; + } + + int index = editor->buffer_count; + if (!buffer_init(&editor->buffers[index])) { + return -1; + } + + editor->buffer_count++; + editor->current_buffer = index; + editor->desired_col = -1; + editor->scroll_line = 0; + return index; +} + +const char *editor_buffer_path_at(const Editor *editor, int index) { + if (!valid_buffer_index(editor, index)) { + return NULL; + } + + const char *path = buffer_path(&editor->buffers[index]); + if (!path || path[0] == '\0') { + return NULL; + } + return path; +} + +bool editor_buffer_modified_at(const Editor *editor, int index) { + if (!valid_buffer_index(editor, index)) { + return false; + } + return buffer_modified(&editor->buffers[index]); +} diff --git a/src/core/editor.h b/src/core/editor.h new file mode 100644 index 0000000..5388197 --- /dev/null +++ b/src/core/editor.h @@ -0,0 +1,96 @@ +#ifndef LEMACS_EDITOR_H +#define LEMACS_EDITOR_H + +#include <stdbool.h> +#include <stdint.h> + +#include "core/buffer.h" + +#define LEMACS_MAX_AUX_BUFFERS 8 +#define LEMACS_MAX_VIEWS 16 +#define LEMACS_MAX_BUFFERS 32 + +#define LEMACS_CURSOR_BAR 0 +#define LEMACS_CURSOR_BLOCK 1 +#define LEMACS_CURSOR_UNDERLINE 2 + +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} EditorColor; + +typedef struct { + int x; + int y; + int width; + int height; + int source_type; + int source_index; + bool show_cursor; + EditorColor bg; + EditorColor fg; +} EditorView; + +typedef struct { + Buffer buffers[LEMACS_MAX_BUFFERS]; + int buffer_count; + int current_buffer; + Buffer aux_buffers[LEMACS_MAX_AUX_BUFFERS]; + int aux_buffer_count; + int desired_col; + int scroll_line; + char font_query[256]; + bool font_dirty; + EditorView views[LEMACS_MAX_VIEWS]; + int view_count; + EditorColor bg_top; + EditorColor bg_bottom; + EditorColor text; + EditorColor cursor; + int cursor_shape; + int cursor_thickness; + bool should_quit; +} Editor; + +bool editor_init(Editor *editor); +void editor_free(Editor *editor); + +void editor_set_font_query(Editor *editor, const char *query); +const char *editor_font_query(const Editor *editor); +bool editor_consume_font_dirty(Editor *editor); +bool editor_set_theme_color(Editor *editor, const char *name, int r, int g, int b, int a); +bool editor_set_cursor_shape(Editor *editor, int shape); +void editor_set_cursor_thickness(Editor *editor, int thickness); +int editor_cursor_shape(const Editor *editor); +int editor_cursor_thickness(const Editor *editor); + +int editor_aux_buffer_new(Editor *editor); +bool editor_aux_buffer_set_text(Editor *editor, int aux_index, const char *text); +Buffer *editor_aux_buffer_get(Editor *editor, int aux_index); + +void editor_layout_reset(Editor *editor); +bool editor_layout_add_view(Editor *editor, int source_type, int source_index, int x, int y, int width, int height, bool show_cursor, EditorColor bg, EditorColor fg); + +void editor_move_left(Editor *editor); +void editor_move_right(Editor *editor); +void editor_move_up(Editor *editor); +void editor_move_down(Editor *editor); +void editor_move_beginning_of_line(Editor *editor); +void editor_move_end_of_line(Editor *editor); +void editor_set_point(Editor *editor, size_t point); +char editor_char_at(const Editor *editor, size_t index); + +bool editor_open_file(Editor *editor, const char *path); +bool editor_save_file(Editor *editor, const char *path); +Buffer *editor_current_buffer(Editor *editor); +const Buffer *editor_current_buffer_const(const Editor *editor); +int editor_buffer_count(const Editor *editor); +int editor_current_buffer_index(const Editor *editor); +bool editor_switch_buffer(Editor *editor, int index); +int editor_new_buffer(Editor *editor); +const char *editor_buffer_path_at(const Editor *editor, int index); +bool editor_buffer_modified_at(const Editor *editor, int index); + +#endif diff --git a/src/core/lua_bridge.c b/src/core/lua_bridge.c new file mode 100644 index 0000000..f1bf16c --- /dev/null +++ b/src/core/lua_bridge.c @@ -0,0 +1,611 @@ +#include "core/lua_bridge.h" + +#include <lauxlib.h> +#include <lualib.h> + +#include <raylib.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static const char *EDITOR_REG_KEY = "lemacs.editor"; + +static Editor *get_editor(lua_State *L) { + lua_getfield(L, LUA_REGISTRYINDEX, EDITOR_REG_KEY); + Editor *editor = (Editor *)lua_touserdata(L, -1); + lua_pop(L, 1); + return editor; +} + +static int l_insert(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + if (!buffer) { + lua_pushboolean(L, 0); + return 1; + } + + size_t len = 0; + const char *text = luaL_checklstring(L, 1, &len); + for (size_t i = 0; i < len; ++i) { + if (!buffer_insert_char(buffer, text[i])) { + lua_pushboolean(L, 0); + return 1; + } + } + lua_pushboolean(L, 1); + return 1; +} + +static int l_backspace(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushboolean(L, buffer ? buffer_backspace(buffer) : false); + return 1; +} + +static int l_delete_forward(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushboolean(L, buffer ? buffer_delete_forward(buffer) : false); + return 1; +} + +static int l_move_left(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_left(editor); + return 0; +} + +static int l_move_right(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_right(editor); + return 0; +} + +static int l_move_up(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_up(editor); + return 0; +} + +static int l_move_down(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_down(editor); + return 0; +} + +static int l_move_beginning_of_line(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_beginning_of_line(editor); + return 0; +} + +static int l_move_end_of_line(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_end_of_line(editor); + return 0; +} + +static int l_open_file(lua_State *L) { + Editor *editor = get_editor(L); + const char *path = luaL_checkstring(L, 1); + lua_pushboolean(L, editor_open_file(editor, path)); + return 1; +} + +static int l_save_file(lua_State *L) { + Editor *editor = get_editor(L); + const char *path = NULL; + if (!lua_isnoneornil(L, 1)) { + path = luaL_checkstring(L, 1); + } + lua_pushboolean(L, editor_save_file(editor, path)); + return 1; +} + +static int l_get_point(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushinteger(L, (lua_Integer)(buffer ? buffer_point(buffer) : 0)); + return 1; +} + +static int l_set_point(lua_State *L) { + Editor *editor = get_editor(L); + lua_Integer point = luaL_checkinteger(L, 1); + if (point < 0) { + point = 0; + } + editor_set_point(editor, (size_t)point); + return 0; +} + +static int l_char_at(lua_State *L) { + Editor *editor = get_editor(L); + lua_Integer idx = luaL_checkinteger(L, 1); + if (idx < 0) { + lua_pushstring(L, ""); + return 1; + } + + char ch = editor_char_at(editor, (size_t)idx); + if (ch == '\0') { + lua_pushstring(L, ""); + return 1; + } + + char text[2] = {ch, '\0'}; + lua_pushstring(L, text); + return 1; +} + +static int l_set_font(lua_State *L) { + Editor *editor = get_editor(L); + const char *query = luaL_checkstring(L, 1); + editor_set_font_query(editor, query); + return 0; +} + +static int l_set_theme_color(lua_State *L) { + Editor *editor = get_editor(L); + const char *name = luaL_checkstring(L, 1); + int r = (int)luaL_checkinteger(L, 2); + int g = (int)luaL_checkinteger(L, 3); + int b = (int)luaL_checkinteger(L, 4); + int a = 255; + if (!lua_isnoneornil(L, 5)) { + a = (int)luaL_checkinteger(L, 5); + } + + lua_pushboolean(L, editor_set_theme_color(editor, name, r, g, b, a)); + return 1; +} + +static int l_set_cursor_shape(lua_State *L) { + Editor *editor = get_editor(L); + const char *shape = luaL_checkstring(L, 1); + + int cursor_shape = -1; + if (strcmp(shape, "bar") == 0) { + cursor_shape = LEMACS_CURSOR_BAR; + } else if (strcmp(shape, "block") == 0) { + cursor_shape = LEMACS_CURSOR_BLOCK; + } else if (strcmp(shape, "underline") == 0) { + cursor_shape = LEMACS_CURSOR_UNDERLINE; + } else { + lua_pushboolean(L, 0); + return 1; + } + + lua_pushboolean(L, editor_set_cursor_shape(editor, cursor_shape)); + return 1; +} + +static int l_set_cursor_thickness(lua_State *L) { + Editor *editor = get_editor(L); + int thickness = (int)luaL_checkinteger(L, 1); + editor_set_cursor_thickness(editor, thickness); + return 0; +} + +static int l_aux_buffer_new(lua_State *L) { + Editor *editor = get_editor(L); + int index = editor_aux_buffer_new(editor); + if (index < 0) { + lua_pushnil(L); + return 1; + } + lua_pushinteger(L, index); + return 1; +} + +static int l_aux_buffer_set_text(lua_State *L) { + Editor *editor = get_editor(L); + int aux_index = (int)luaL_checkinteger(L, 1); + const char *text = luaL_checkstring(L, 2); + lua_pushboolean(L, editor_aux_buffer_set_text(editor, aux_index, text)); + return 1; +} + +static int l_layout_reset(lua_State *L) { + Editor *editor = get_editor(L); + editor_layout_reset(editor); + return 0; +} + +static int l_layout_add_view(lua_State *L) { + Editor *editor = get_editor(L); + int source_type = (int)luaL_checkinteger(L, 1); + int source_index = (int)luaL_checkinteger(L, 2); + int x = (int)luaL_checkinteger(L, 3); + int y = (int)luaL_checkinteger(L, 4); + int width = (int)luaL_checkinteger(L, 5); + int height = (int)luaL_checkinteger(L, 6); + bool show_cursor = lua_toboolean(L, 7) != 0; + int bg_r = (int)luaL_checkinteger(L, 8); + int bg_g = (int)luaL_checkinteger(L, 9); + int bg_b = (int)luaL_checkinteger(L, 10); + int bg_a = (int)luaL_optinteger(L, 11, 255); + int fg_r = (int)luaL_checkinteger(L, 12); + int fg_g = (int)luaL_checkinteger(L, 13); + int fg_b = (int)luaL_checkinteger(L, 14); + int fg_a = (int)luaL_optinteger(L, 15, 255); + + EditorColor bg = {(uint8_t)bg_r, (uint8_t)bg_g, (uint8_t)bg_b, (uint8_t)bg_a}; + EditorColor fg = {(uint8_t)fg_r, (uint8_t)fg_g, (uint8_t)fg_b, (uint8_t)fg_a}; + + lua_pushboolean(L, editor_layout_add_view(editor, source_type, source_index, x, y, width, height, show_cursor, bg, fg)); + return 1; +} + +static int l_screen_width(lua_State *L) { + lua_pushinteger(L, GetScreenWidth()); + return 1; +} + +static int l_screen_height(lua_State *L) { + lua_pushinteger(L, GetScreenHeight()); + return 1; +} + +static int l_buffer_length(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushinteger(L, (lua_Integer)(buffer ? buffer_length(buffer) : 0)); + return 1; +} + +static int l_request_quit(lua_State *L) { + Editor *editor = get_editor(L); + editor->should_quit = true; + return 0; +} + +static int l_buffer_path(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + const char *path = buffer ? buffer_path(buffer) : NULL; + if (path && path[0] != '\0') { + lua_pushstring(L, path); + } else { + lua_pushnil(L); + } + return 1; +} + +static int l_buffer_modified(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushboolean(L, buffer ? buffer_modified(buffer) : false); + return 1; +} + +static int l_buffer_count(lua_State *L) { + Editor *editor = get_editor(L); + lua_pushinteger(L, (lua_Integer)editor_buffer_count(editor)); + return 1; +} + +static int l_current_buffer_index(lua_State *L) { + Editor *editor = get_editor(L); + lua_pushinteger(L, (lua_Integer)editor_current_buffer_index(editor)); + return 1; +} + +static int l_switch_buffer(lua_State *L) { + Editor *editor = get_editor(L); + int index = (int)luaL_checkinteger(L, 1); + lua_pushboolean(L, editor_switch_buffer(editor, index)); + return 1; +} + +static int l_new_buffer(lua_State *L) { + Editor *editor = get_editor(L); + int index = editor_new_buffer(editor); + if (index < 0) { + lua_pushnil(L); + } else { + lua_pushinteger(L, index); + } + return 1; +} + +static int l_buffer_path_at(lua_State *L) { + Editor *editor = get_editor(L); + int index = (int)luaL_checkinteger(L, 1); + const char *path = editor_buffer_path_at(editor, index); + if (!path) { + lua_pushnil(L); + } else { + lua_pushstring(L, path); + } + return 1; +} + +static int l_buffer_modified_at(lua_State *L) { + Editor *editor = get_editor(L); + int index = (int)luaL_checkinteger(L, 1); + lua_pushboolean(L, editor_buffer_modified_at(editor, index)); + return 1; +} + +static void register_editor_api(lua_State *L) { + lua_newtable(L); + + lua_pushcfunction(L, l_insert); + lua_setfield(L, -2, "insert"); + + lua_pushcfunction(L, l_backspace); + lua_setfield(L, -2, "backspace"); + + lua_pushcfunction(L, l_delete_forward); + lua_setfield(L, -2, "delete_forward"); + + lua_pushcfunction(L, l_move_left); + lua_setfield(L, -2, "move_left"); + + lua_pushcfunction(L, l_move_right); + lua_setfield(L, -2, "move_right"); + + lua_pushcfunction(L, l_move_up); + lua_setfield(L, -2, "move_up"); + + lua_pushcfunction(L, l_move_down); + lua_setfield(L, -2, "move_down"); + + lua_pushcfunction(L, l_move_beginning_of_line); + lua_setfield(L, -2, "move_beginning_of_line"); + + lua_pushcfunction(L, l_move_end_of_line); + lua_setfield(L, -2, "move_end_of_line"); + + lua_pushcfunction(L, l_open_file); + lua_setfield(L, -2, "open_file"); + + lua_pushcfunction(L, l_save_file); + lua_setfield(L, -2, "save_file"); + + lua_pushcfunction(L, l_aux_buffer_new); + lua_setfield(L, -2, "aux_buffer_new"); + + lua_pushcfunction(L, l_aux_buffer_set_text); + lua_setfield(L, -2, "aux_buffer_set_text"); + + lua_pushcfunction(L, l_layout_reset); + lua_setfield(L, -2, "layout_reset"); + + lua_pushcfunction(L, l_layout_add_view); + lua_setfield(L, -2, "layout_add_view"); + + lua_pushcfunction(L, l_screen_width); + lua_setfield(L, -2, "screen_width"); + + lua_pushcfunction(L, l_screen_height); + lua_setfield(L, -2, "screen_height"); + + lua_pushcfunction(L, l_get_point); + lua_setfield(L, -2, "point"); + + lua_pushcfunction(L, l_set_point); + lua_setfield(L, -2, "set_point"); + + lua_pushcfunction(L, l_char_at); + lua_setfield(L, -2, "char_at"); + + lua_pushcfunction(L, l_set_font); + lua_setfield(L, -2, "set_font"); + + lua_pushcfunction(L, l_set_theme_color); + lua_setfield(L, -2, "set_theme_color"); + + lua_pushcfunction(L, l_set_cursor_shape); + lua_setfield(L, -2, "set_cursor_shape"); + + lua_pushcfunction(L, l_set_cursor_thickness); + lua_setfield(L, -2, "set_cursor_thickness"); + + lua_pushcfunction(L, l_buffer_length); + lua_setfield(L, -2, "buffer_length"); + + lua_pushcfunction(L, l_buffer_path); + lua_setfield(L, -2, "buffer_path"); + + lua_pushcfunction(L, l_buffer_modified); + lua_setfield(L, -2, "buffer_modified"); + + lua_pushcfunction(L, l_buffer_count); + lua_setfield(L, -2, "buffer_count"); + + lua_pushcfunction(L, l_current_buffer_index); + lua_setfield(L, -2, "current_buffer_index"); + + lua_pushcfunction(L, l_switch_buffer); + lua_setfield(L, -2, "switch_buffer"); + + lua_pushcfunction(L, l_new_buffer); + lua_setfield(L, -2, "new_buffer"); + + lua_pushcfunction(L, l_buffer_path_at); + lua_setfield(L, -2, "buffer_path_at"); + + lua_pushcfunction(L, l_buffer_modified_at); + lua_setfield(L, -2, "buffer_modified_at"); + + lua_pushcfunction(L, l_request_quit); + lua_setfield(L, -2, "request_quit"); + + lua_setglobal(L, "editor"); +} + +static bool call_global_with_string(lua_State *L, const char *name, const char *arg) { + lua_getglobal(L, name); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return false; + } + + lua_pushstring(L, arg); + if (lua_pcall(L, 1, 1, 0) != LUA_OK) { + fprintf(stderr, "lua error in %s: %s\n", name, lua_tostring(L, -1)); + lua_pop(L, 1); + return false; + } + + bool handled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + return handled; +} + +static bool call_global_noargs(lua_State *L, const char *name) { + lua_getglobal(L, name); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return false; + } + + if (lua_pcall(L, 0, 0, 0) != LUA_OK) { + fprintf(stderr, "lua error in %s: %s\n", name, lua_tostring(L, -1)); + lua_pop(L, 1); + return false; + } + + return true; +} + +static void configure_lua_package_path(lua_State *L, const char *init_script_path) { + char runtime_lua_path[2048] = {0}; + snprintf(runtime_lua_path, sizeof(runtime_lua_path), "%s", init_script_path ? init_script_path : "runtime/init.lua"); + + char *last_slash = strrchr(runtime_lua_path, '/'); + if (!last_slash) { + return; + } + *last_slash = '\0'; + + char add_path[2300] = {0}; + snprintf(add_path, sizeof(add_path), "%s/lua/?.lua;%s/lua/?/init.lua", runtime_lua_path, runtime_lua_path); + + lua_getglobal(L, "package"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return; + } + + lua_getfield(L, -1, "path"); + const char *current = lua_tostring(L, -1); + if (!current) { + current = ""; + } + lua_pop(L, 1); + + char merged[4096] = {0}; + if (current[0] != '\0') { + snprintf(merged, sizeof(merged), "%s;%s", add_path, current); + } else { + snprintf(merged, sizeof(merged), "%s", add_path); + } + + lua_pushstring(L, merged); + lua_setfield(L, -2, "path"); + lua_pop(L, 1); +} + +bool lua_bridge_init(LuaBridge *bridge, Editor *editor) { + bridge->editor = editor; + bridge->runtime_init_path[0] = '\0'; + bridge->L = luaL_newstate(); + if (!bridge->L) { + return false; + } + + luaL_openlibs(bridge->L); + + lua_pushlightuserdata(bridge->L, editor); + lua_setfield(bridge->L, LUA_REGISTRYINDEX, EDITOR_REG_KEY); + + register_editor_api(bridge->L); + return true; +} + +void lua_bridge_free(LuaBridge *bridge) { + if (bridge->L) { + lua_close(bridge->L); + bridge->L = NULL; + } +} + +bool lua_bridge_load_runtime(LuaBridge *bridge, const char *init_script_path) { + configure_lua_package_path(bridge->L, init_script_path); + snprintf(bridge->runtime_init_path, sizeof(bridge->runtime_init_path), "%s", init_script_path ? init_script_path : ""); + + if (luaL_dofile(bridge->L, init_script_path) != LUA_OK) { + const char *err = lua_tostring(bridge->L, -1); + fprintf(stderr, "failed to load %s: %s\n", init_script_path, err ? err : "unknown"); + lua_pop(bridge->L, 1); + return false; + } + return true; +} + +bool lua_bridge_load_optional_init(LuaBridge *bridge, const char *script_path) { + if (!script_path || script_path[0] == '\0') { + return false; + } + + lua_getglobal(bridge->L, "dofile"); + if (!lua_isfunction(bridge->L, -1)) { + lua_pop(bridge->L, 1); + return false; + } + + lua_pushstring(bridge->L, script_path); + if (lua_pcall(bridge->L, 1, 0, 0) != LUA_OK) { + fprintf(stderr, "optional init load error (%s): %s\n", script_path, lua_tostring(bridge->L, -1)); + lua_pop(bridge->L, 1); + return false; + } + + return true; +} + +static bool call_global_with_optional_string(lua_State *L, const char *name, const char *arg) { + lua_getglobal(L, name); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return false; + } + + if (arg && arg[0] != '\0') { + lua_pushstring(L, arg); + } else { + lua_pushnil(L); + } + + if (lua_pcall(L, 1, 1, 0) != LUA_OK) { + fprintf(stderr, "lua error in %s: %s\n", name, lua_tostring(L, -1)); + lua_pop(L, 1); + return false; + } + + bool ok = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + return ok; +} + +bool lua_bridge_call_open_file(LuaBridge *bridge, const char *path) { + return call_global_with_optional_string(bridge->L, "on_open_file", path); +} + +bool lua_bridge_call_text(LuaBridge *bridge, const char *text) { + return call_global_with_string(bridge->L, "on_text", text ? text : ""); +} + +bool lua_bridge_call_key(LuaBridge *bridge, const char *key_name) { + return call_global_with_string(bridge->L, "on_key", key_name); +} + +bool lua_bridge_call_tick(LuaBridge *bridge) { + return call_global_noargs(bridge->L, "on_tick"); +} diff --git a/src/core/lua_bridge.h b/src/core/lua_bridge.h new file mode 100644 index 0000000..9ff9123 --- /dev/null +++ b/src/core/lua_bridge.h @@ -0,0 +1,26 @@ +#ifndef LEMACS_LUA_BRIDGE_H +#define LEMACS_LUA_BRIDGE_H + +#include <stdbool.h> + +#include <lua.h> + +#include "core/editor.h" + +typedef struct { + lua_State *L; + Editor *editor; + char runtime_init_path[1024]; +} LuaBridge; + +bool lua_bridge_init(LuaBridge *bridge, Editor *editor); +void lua_bridge_free(LuaBridge *bridge); + +bool lua_bridge_load_runtime(LuaBridge *bridge, const char *init_script_path); +bool lua_bridge_load_optional_init(LuaBridge *bridge, const char *script_path); +bool lua_bridge_call_key(LuaBridge *bridge, const char *key_name); +bool lua_bridge_call_tick(LuaBridge *bridge); +bool lua_bridge_call_open_file(LuaBridge *bridge, const char *path); +bool lua_bridge_call_text(LuaBridge *bridge, const char *text); + +#endif 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; +} |
