#include "buffers.h" #include "common.h" #include "util.h" #include #include #include #include #define BUFFER_INITIAL_CAP 64 static int buffer_is_word_char(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } static int buffer_require_editable(buffer_t *buffer) { if (!buffer || buffer->read_only) return ECEX_ERR; return ECEX_OK; } static void buffer_undo_entry_free(ecex_undo_entry_t *entry) { if (!entry) return; free(entry->data); entry->data = NULL; entry->len = 0; entry->point = 0; entry->mark = 0; entry->mark_active = 0; } static void buffer_undo_stack_clear(ecex_undo_entry_t *stack, size_t count) { if (!stack) return; for (size_t i = 0; i < count; i++) buffer_undo_entry_free(&stack[i]); } static int buffer_push_snapshot_to(ecex_undo_entry_t **stack, size_t *count, size_t *cap, buffer_t *buffer) { if (!stack || !count || !cap || !buffer) return ECEX_ERR; if (ECEX_GROW_ARRAY(*stack, *count, *cap, 64) != ECEX_OK) return ECEX_ERR; ecex_undo_entry_t *entry = &(*stack)[(*count)++]; memset(entry, 0, sizeof(*entry)); entry->data = malloc(buffer->len + 1); if (!entry->data) { (*count)--; return ECEX_ERR; } memcpy(entry->data, buffer->data, buffer->len + 1); entry->len = buffer->len; entry->point = buffer->point; entry->mark = buffer->mark; entry->mark_active = buffer->mark_active; return ECEX_OK; } static void buffer_clear_redo(buffer_t *buffer) { if (!buffer) return; buffer_undo_stack_clear(buffer->redo_stack, buffer->redo_count); buffer->redo_count = 0; } static int buffer_record_undo(buffer_t *buffer) { if (!buffer || buffer->undo_disabled) return ECEX_OK; if (buffer_push_snapshot_to(&buffer->undo_stack, &buffer->undo_count, &buffer->undo_cap, buffer) != ECEX_OK) { return ECEX_ERR; } buffer_clear_redo(buffer); return ECEX_OK; } static int buffer_restore_snapshot(buffer_t *buffer, ecex_undo_entry_t *entry) { if (!buffer || !entry || !entry->data) return ECEX_ERR; char *copy = malloc(entry->len + 1); if (!copy) return ECEX_ERR; memcpy(copy, entry->data, entry->len + 1); free(buffer->data); buffer->data = copy; buffer->len = entry->len; buffer->cap = entry->len + 1; buffer->point = ECEX_MIN(entry->point, buffer->len); buffer->mark = ECEX_MIN(entry->mark, buffer->len); buffer->mark_active = entry->mark_active; buffer->modified = 1; return ECEX_OK; } buffer_t *buffer_new(const char *name, const char *path, int read_only) { assert(name && "buffer name cannot be NULL"); buffer_t *buffer = calloc(1, sizeof(*buffer)); if (!buffer) return NULL; buffer->name = ecex_strdup(name); buffer->path = ecex_strdup(path); buffer->data = malloc(1); if (!buffer->name || (path && !buffer->path) || !buffer->data) { buffer_free(buffer); return NULL; } buffer->data[0] = '\0'; buffer->len = 0; buffer->cap = 1; buffer->read_only = read_only; return buffer; } void buffer_free(buffer_t *buffer) { if (!buffer) return; buffer_clear_interactive_actions(buffer); buffer_undo_stack_clear(buffer->undo_stack, buffer->undo_count); buffer_undo_stack_clear(buffer->redo_stack, buffer->redo_count); free(buffer->undo_stack); free(buffer->redo_stack); free(buffer->interactive_actions); free(buffer->name); free(buffer->path); free(buffer->data); free(buffer); } int buffer_reserve(buffer_t *buffer, size_t needed) { ECEX_RETURN_ERR_IF_NULL(buffer); if (needed <= buffer->cap) return ECEX_OK; size_t new_cap = buffer->cap ? buffer->cap : BUFFER_INITIAL_CAP; while (new_cap < needed) { new_cap *= 2; } char *new_data = realloc(buffer->data, new_cap); if (!new_data) return ECEX_ERR; buffer->data = new_data; buffer->cap = new_cap; return ECEX_OK; } int buffer_clear(buffer_t *buffer) { if (buffer_require_editable(buffer) != ECEX_OK) return ECEX_ERR; if (buffer_record_undo(buffer) != ECEX_OK) return ECEX_ERR; if (buffer_reserve(buffer, 1) != ECEX_OK) return ECEX_ERR; buffer->data[0] = '\0'; buffer->len = 0; buffer->point = 0; buffer->mark = 0; buffer->mark_active = 0; buffer->scroll_line = 0; buffer->scroll_col = 0; buffer_clear_interactive_actions(buffer); buffer->modified = 1; return ECEX_OK; } int buffer_set_text(buffer_t *buffer, const char *text) { if (buffer_require_editable(buffer) != ECEX_OK || !text) return ECEX_ERR; if (buffer_record_undo(buffer) != ECEX_OK) return ECEX_ERR; size_t len = strlen(text); if (buffer_reserve(buffer, len + 1) != ECEX_OK) return ECEX_ERR; memcpy(buffer->data, text, len + 1); buffer->len = len; buffer->point = len; buffer->mark = 0; buffer->mark_active = 0; buffer->scroll_line = 0; buffer->scroll_col = 0; buffer_clear_interactive_actions(buffer); buffer->modified = 1; return ECEX_OK; } int buffer_insert_at(buffer_t *buffer, size_t pos, const char *text) { if (buffer_require_editable(buffer) != ECEX_OK || !text) return ECEX_ERR; if (pos > buffer->len) return ECEX_ERR; size_t text_len = strlen(text); if (text_len == 0) return ECEX_OK; if (buffer_record_undo(buffer) != ECEX_OK) return ECEX_ERR; size_t needed = buffer->len + text_len + 1; if (buffer_reserve(buffer, needed) != ECEX_OK) return ECEX_ERR; memmove(buffer->data + pos + text_len, buffer->data + pos, buffer->len - pos + 1); memcpy(buffer->data + pos, text, text_len); buffer->len += text_len; buffer->point = pos + text_len; buffer->modified = 1; return ECEX_OK; } int buffer_insert(buffer_t *buffer, const char *text) { ECEX_RETURN_ERR_IF_NULL(buffer); return buffer_insert_at(buffer, buffer->point, text); } int buffer_append(buffer_t *buffer, const char *text) { ECEX_RETURN_ERR_IF_NULL(buffer); return buffer_insert_at(buffer, buffer->len, text); } int buffer_prepend(buffer_t *buffer, const char *text) { return buffer_insert_at(buffer, 0, text); } int buffer_insert_char_at(buffer_t *buffer, size_t pos, char c) { if (buffer_require_editable(buffer) != ECEX_OK) return ECEX_ERR; if (pos > buffer->len) return ECEX_ERR; if (buffer_record_undo(buffer) != ECEX_OK) return ECEX_ERR; if (buffer_reserve(buffer, buffer->len + 2) != ECEX_OK) return ECEX_ERR; memmove(buffer->data + pos + 1, buffer->data + pos, buffer->len - pos + 1); buffer->data[pos] = c; buffer->len++; buffer->point = pos + 1; buffer->modified = 1; return ECEX_OK; } int buffer_insert_char(buffer_t *buffer, char c) { ECEX_RETURN_ERR_IF_NULL(buffer); return buffer_insert_char_at(buffer, buffer->point, c); } int buffer_delete_range(buffer_t *buffer, size_t start, size_t end) { if (buffer_require_editable(buffer) != ECEX_OK) return ECEX_ERR; if (start > end || end > buffer->len) return ECEX_ERR; if (start == end) return ECEX_OK; if (buffer_record_undo(buffer) != ECEX_OK) return ECEX_ERR; size_t deleted = end - start; memmove(buffer->data + start, buffer->data + end, buffer->len - end + 1); buffer->len -= deleted; if (buffer->point > end) { buffer->point -= deleted; } else if (buffer->point > start) { buffer->point = start; } if (buffer->mark > end) { buffer->mark -= deleted; } else if (buffer->mark > start) { buffer->mark = start; } if (buffer->mark_active && buffer->mark == buffer->point) { buffer->mark_active = 0; } buffer->modified = 1; return ECEX_OK; } int buffer_delete_selection(buffer_t *buffer) { if (!buffer_has_selection(buffer)) return ECEX_OK; size_t start = 0; size_t end = 0; buffer_selection_range(buffer, &start, &end); int result = buffer_delete_range(buffer, start, end); if (result == ECEX_OK) buffer_clear_mark(buffer); return result; } int buffer_replace_selection(buffer_t *buffer, const char *text) { if (!buffer || !text) return ECEX_ERR; if (!buffer_has_selection(buffer)) return buffer_insert(buffer, text); size_t start = 0; size_t end = 0; buffer_selection_range(buffer, &start, &end); if (buffer_delete_range(buffer, start, end) != ECEX_OK) return ECEX_ERR; buffer->point = start; buffer_clear_mark(buffer); return buffer_insert(buffer, text); } int buffer_backspace(buffer_t *buffer) { if (!buffer) return ECEX_ERR; if (buffer_has_selection(buffer)) return buffer_delete_selection(buffer); if (buffer->point == 0) return ECEX_OK; return buffer_delete_range(buffer, buffer->point - 1, buffer->point); } int buffer_delete_forward(buffer_t *buffer) { if (!buffer) return ECEX_ERR; if (buffer_has_selection(buffer)) return buffer_delete_selection(buffer); if (buffer->point >= buffer->len) return ECEX_OK; return buffer_delete_range(buffer, buffer->point, buffer->point + 1); } int buffer_kill_line(buffer_t *buffer) { if (!buffer) return ECEX_ERR; size_t end = buffer_line_end_at(buffer, buffer->point); if (end == buffer->point && end < buffer->len && buffer->data[end] == '\n') { end++; } return buffer_delete_range(buffer, buffer->point, end); } void buffer_set_point(buffer_t *buffer, size_t point) { if (!buffer) return; buffer->point = ECEX_MIN(point, buffer->len); } void buffer_move_left(buffer_t *buffer) { if (!buffer || buffer->point == 0) return; buffer->point--; } void buffer_move_right(buffer_t *buffer) { if (!buffer || buffer->point >= buffer->len) return; buffer->point++; } void buffer_move_up(buffer_t *buffer) { if (!buffer) return; size_t current_start = buffer_line_start_at(buffer, buffer->point); if (current_start == 0) { buffer->point = 0; return; } size_t wanted_col = buffer->point - current_start; size_t prev_end = current_start - 1; size_t prev_start = buffer_line_start_at(buffer, prev_end); size_t prev_len = prev_end - prev_start; buffer->point = prev_start + ECEX_MIN(wanted_col, prev_len); } void buffer_move_down(buffer_t *buffer) { if (!buffer) return; size_t current_start = buffer_line_start_at(buffer, buffer->point); size_t current_end = buffer_line_end_at(buffer, buffer->point); if (current_end >= buffer->len) { buffer->point = buffer->len; return; } size_t wanted_col = buffer->point - current_start; size_t next_start = current_end + 1; size_t next_end = buffer_line_end_at(buffer, next_start); size_t next_len = next_end - next_start; buffer->point = next_start + ECEX_MIN(wanted_col, next_len); } void buffer_move_word_left(buffer_t *buffer) { if (!buffer) return; size_t pos = buffer->point; while (pos > 0 && !buffer_is_word_char(buffer->data[pos - 1])) pos--; while (pos > 0 && buffer_is_word_char(buffer->data[pos - 1])) pos--; buffer->point = pos; } void buffer_move_word_right(buffer_t *buffer) { if (!buffer) return; size_t pos = buffer->point; while (pos < buffer->len && !buffer_is_word_char(buffer->data[pos])) pos++; while (pos < buffer->len && buffer_is_word_char(buffer->data[pos])) pos++; buffer->point = pos; } void buffer_move_beginning_of_line(buffer_t *buffer) { if (!buffer) return; buffer->point = buffer_line_start_at(buffer, buffer->point); } void buffer_move_end_of_line(buffer_t *buffer) { if (!buffer) return; buffer->point = buffer_line_end_at(buffer, buffer->point); } void buffer_move_beginning_of_buffer(buffer_t *buffer) { if (!buffer) return; buffer->point = 0; } void buffer_move_end_of_buffer(buffer_t *buffer) { if (!buffer) return; buffer->point = buffer->len; } void buffer_set_mark(buffer_t *buffer, size_t mark) { if (!buffer) return; buffer->mark = ECEX_MIN(mark, buffer->len); buffer->mark_active = 1; } void buffer_clear_mark(buffer_t *buffer) { if (!buffer) return; buffer->mark_active = 0; } int buffer_has_selection(buffer_t *buffer) { return buffer && buffer->mark_active && buffer->mark != buffer->point; } void buffer_selection_range(buffer_t *buffer, size_t *out_start, size_t *out_end) { size_t start = 0; size_t end = 0; if (buffer && buffer_has_selection(buffer)) { start = ECEX_MIN(buffer->point, buffer->mark); end = ECEX_MAX(buffer->point, buffer->mark); } if (out_start) *out_start = start; if (out_end) *out_end = end; } size_t buffer_line_start_at(buffer_t *buffer, size_t pos) { if (!buffer) return 0; pos = ECEX_MIN(pos, buffer->len); while (pos > 0 && buffer->data[pos - 1] != '\n') { pos--; } return pos; } size_t buffer_line_end_at(buffer_t *buffer, size_t pos) { if (!buffer) return 0; pos = ECEX_MIN(pos, buffer->len); while (pos < buffer->len && buffer->data[pos] != '\n') { pos++; } return pos; } size_t buffer_current_line_start(buffer_t *buffer) { if (!buffer) return 0; return buffer_line_start_at(buffer, buffer->point); } size_t buffer_current_line_end(buffer_t *buffer) { if (!buffer) return 0; return buffer_line_end_at(buffer, buffer->point); } size_t buffer_current_column(buffer_t *buffer) { if (!buffer) return 0; return buffer->point - buffer_line_start_at(buffer, buffer->point); } size_t buffer_current_line_number(buffer_t *buffer) { if (!buffer) return 0; size_t line = 1; for (size_t i = 0; i < buffer->point && i < buffer->len; i++) { if (buffer->data[i] == '\n') line++; } return line; } size_t buffer_line_count(buffer_t *buffer) { if (!buffer || buffer->len == 0) return 1; size_t lines = 1; for (size_t i = 0; i < buffer->len; i++) { if (buffer->data[i] == '\n') lines++; } return lines; } char *buffer_substring(buffer_t *buffer, size_t start, size_t end) { if (!buffer || start > end || end > buffer->len) return NULL; size_t len = end - start; char *copy = malloc(len + 1); if (!copy) return NULL; memcpy(copy, buffer->data + start, len); copy[len] = '\0'; return copy; } char *buffer_current_line_copy(buffer_t *buffer) { if (!buffer) return NULL; return buffer_substring(buffer, buffer_current_line_start(buffer), buffer_current_line_end(buffer)); } int buffer_load_file(buffer_t *buffer, const char *path) { if (buffer_require_editable(buffer) != ECEX_OK || !path) return ECEX_ERR; size_t size = 0; char *new_data = ecex_read_entire_file(path, &size); if (!new_data) return ECEX_ERR; char *new_path = ecex_strdup(path); if (!new_path) { free(new_data); return ECEX_ERR; } free(buffer->data); free(buffer->path); buffer->data = new_data; buffer->path = new_path; buffer->len = size; buffer->cap = size + 1; buffer->point = 0; buffer->mark = 0; buffer->mark_active = 0; buffer->scroll_line = 0; buffer->scroll_col = 0; buffer->modified = 0; buffer_clear_undo(buffer); return ECEX_OK; } int buffer_save(buffer_t *buffer) { if (!buffer || buffer->read_only || !buffer->path) return ECEX_ERR; FILE *file = fopen(buffer->path, "wb"); if (!file) return ECEX_ERR; size_t written = fwrite(buffer->data, 1, buffer->len, file); int close_result = fclose(file); if (close_result != 0 || written != buffer->len) return ECEX_ERR; buffer->modified = 0; return ECEX_OK; } int buffer_save_as(buffer_t *buffer, const char *path) { if (buffer_require_editable(buffer) != ECEX_OK || !path) return ECEX_ERR; char *new_path = ecex_strdup(path); if (!new_path) return ECEX_ERR; free(buffer->path); buffer->path = new_path; return buffer_save(buffer); } int buffer_undo(buffer_t *buffer) { if (buffer_require_editable(buffer) != ECEX_OK) return ECEX_ERR; if (buffer->undo_count == 0) return ECEX_OK; if (buffer_push_snapshot_to(&buffer->redo_stack, &buffer->redo_count, &buffer->redo_cap, buffer) != ECEX_OK) return ECEX_ERR; ecex_undo_entry_t entry = buffer->undo_stack[--buffer->undo_count]; int result = buffer_restore_snapshot(buffer, &entry); buffer_undo_entry_free(&entry); return result; } int buffer_redo(buffer_t *buffer) { if (buffer_require_editable(buffer) != ECEX_OK) return ECEX_ERR; if (buffer->redo_count == 0) return ECEX_OK; if (buffer_push_snapshot_to(&buffer->undo_stack, &buffer->undo_count, &buffer->undo_cap, buffer) != ECEX_OK) return ECEX_ERR; ecex_undo_entry_t entry = buffer->redo_stack[--buffer->redo_count]; int result = buffer_restore_snapshot(buffer, &entry); buffer_undo_entry_free(&entry); return result; } void buffer_clear_undo(buffer_t *buffer) { if (!buffer) return; buffer_undo_stack_clear(buffer->undo_stack, buffer->undo_count); buffer_undo_stack_clear(buffer->redo_stack, buffer->redo_count); buffer->undo_count = 0; buffer->redo_count = 0; } int buffer_search_forward(buffer_t *buffer, const char *query, size_t start, size_t *out_pos) { if (!buffer || !query || !query[0]) return ECEX_ERR; if (start > buffer->len) start = buffer->len; char *hit = strstr(buffer->data + start, query); if (!hit && start > 0) hit = strstr(buffer->data, query); if (!hit) return ECEX_ERR; if (out_pos) *out_pos = (size_t)(hit - buffer->data); return ECEX_OK; } int buffer_search_backward(buffer_t *buffer, const char *query, size_t start, size_t *out_pos) { if (!buffer || !query || !query[0]) return ECEX_ERR; size_t qlen = strlen(query); if (qlen > buffer->len) return ECEX_ERR; if (start > buffer->len) start = buffer->len; size_t best = (size_t)-1; for (size_t i = 0; i + qlen <= start; i++) { if (memcmp(buffer->data + i, query, qlen) == 0) best = i; } if (best == (size_t)-1) { for (size_t i = start; i + qlen <= buffer->len; i++) { if (memcmp(buffer->data + i, query, qlen) == 0) best = i; } } if (best == (size_t)-1) return ECEX_ERR; if (out_pos) *out_pos = best; return ECEX_OK; } void buffer_set_interactive(buffer_t *buffer, int interactive) { if (!buffer) return; buffer->interactive = interactive ? 1 : 0; } int buffer_is_interactive(buffer_t *buffer) { return buffer && buffer->interactive; } int buffer_clear_interactive_actions(buffer_t *buffer) { if (!buffer) return ECEX_ERR; for (size_t i = 0; i < buffer->interactive_action_count; i++) { free(buffer->interactive_actions[i].payload); buffer->interactive_actions[i].payload = NULL; buffer->interactive_actions[i].fn = NULL; buffer->interactive_actions[i].userdata = NULL; buffer->interactive_actions[i].line = 0; } buffer->interactive_action_count = 0; return ECEX_OK; } int buffer_add_interactive_action(buffer_t *buffer, size_t line, ecex_interactive_line_fn fn, const char *payload, void *userdata) { if (!buffer || !fn) return ECEX_ERR; if (ECEX_GROW_ARRAY(buffer->interactive_actions, buffer->interactive_action_count, buffer->interactive_action_cap, 16) != ECEX_OK) { return ECEX_ERR; } char *payload_copy = ecex_strdup(payload); if (payload && !payload_copy) return ECEX_ERR; ecex_interactive_line_action_t *action = &buffer->interactive_actions[buffer->interactive_action_count++]; action->line = line; action->fn = fn; action->payload = payload_copy; action->userdata = userdata; buffer->interactive = 1; return ECEX_OK; } ecex_interactive_line_action_t *buffer_interactive_action_at_line(buffer_t *buffer, size_t line) { if (!buffer || !buffer->interactive) return NULL; for (size_t i = 0; i < buffer->interactive_action_count; i++) { if (buffer->interactive_actions[i].line == line) { return &buffer->interactive_actions[i]; } } return NULL; }