esc
Externally Scriptable Editor
git clone git://mccd.space/esc
| Log | Files | Refs | README |
commit 510afa756f3e66e75d3a4423a4b20addf1071a5a parent adbfcdaeca908232daad90536e393e7e4695db67 Author: Marc Coquand <marc@coquand.email> Date: Thu, 19 Feb 2026 15:01:01 +0100 Migrate to editor code Diffstat:
| A | editor.c | | | 147 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | editor.h | | | 41 | +++++++++++++++++++++++++++++++++++++++++ |
| M | main.c | | | 309 | +++++++++++++++++++------------------------------------------------------------ |
3 files changed, 263 insertions(+), 234 deletions(-)
diff --git a/editor.c b/editor.c
@@ -0,0 +1,147 @@
+#include "editor.h"
+#include <stdlib.h>
+#include <string.h>
+
+Editor* editor_create(int char_width, float line_height) {
+ Editor *ed = malloc(sizeof(Editor));
+ ed->capacity = 1024;
+ ed->buffer = malloc(ed->capacity);
+ ed->buffer[0] = '\0';
+ ed->length = 0;
+ ed->cursor_idx = 0;
+ ed->selection_anchor = 0;
+ ed->char_width = char_width;
+ ed->line_height = line_height;
+ return ed;
+}
+
+void editor_destroy(Editor *ed) {
+ free(ed->buffer);
+ free(ed);
+}
+
+void editor_insert_text(Editor *ed, const char *text) {
+ size_t input_len = strlen(text);
+ if (ed->length + input_len + 1 > ed->capacity) {
+ ed->capacity *= 2;
+ ed->buffer = realloc(ed->buffer, ed->capacity);
+ }
+ memmove(&ed->buffer[ed->cursor_idx + input_len], &ed->buffer[ed->cursor_idx], ed->length - ed->cursor_idx + 1);
+ memcpy(&ed->buffer[ed->cursor_idx], text, input_len);
+ ed->length += input_len;
+ ed->cursor_idx += (int)input_len;
+ ed->selection_anchor = ed->cursor_idx;
+}
+
+void editor_newline(Editor *ed) {
+ editor_insert_text(ed, "\n");
+}
+
+void editor_delete_range(Editor *ed, int start, int end) {
+ if (start == end) return;
+ if (start > end) { int tmp = start; start = end; end = tmp; }
+
+ memmove(&ed->buffer[start], &ed->buffer[end], ed->length - end + 1);
+ ed->length -= (end - start);
+ ed->cursor_idx = start;
+ ed->selection_anchor = start;
+}
+
+void editor_delete_back(Editor *ed) {
+ if (ed->cursor_idx != ed->selection_anchor) {
+ editor_delete_range(ed, ed->selection_anchor, ed->cursor_idx);
+ } else if (ed->cursor_idx > 0) {
+ int prev = ed->cursor_idx;
+ do { prev--; } while (prev > 0 && (ed->buffer[prev] & 0xC0) == 0x80);
+ editor_delete_range(ed, prev, ed->cursor_idx);
+ }
+}
+
+void editor_delete_forward(Editor *ed) {
+ if (ed->cursor_idx != ed->selection_anchor) {
+ editor_delete_range(ed, ed->selection_anchor, ed->cursor_idx);
+ } else if (ed->cursor_idx < (int)ed->length) {
+ const char *ptr = ed->buffer + ed->cursor_idx;
+ const char *next = ptr;
+ SDL_StepUTF8(&next, NULL);
+ editor_delete_range(ed, ed->cursor_idx, (int)(next - ed->buffer));
+ }
+}
+
+void editor_cursor_left(Editor *ed) {
+ if (ed->cursor_idx > 0) {
+ ed->cursor_idx--;
+ while (ed->cursor_idx > 0 && (ed->buffer[ed->cursor_idx] & 0xC0) == 0x80) ed->cursor_idx--;
+ }
+ ed->selection_anchor = ed->cursor_idx;
+}
+
+void editor_cursor_right(Editor *ed) {
+ if (ed->cursor_idx < (int)ed->length) {
+ const char *next = ed->buffer + ed->cursor_idx;
+ SDL_StepUTF8(&next, NULL);
+ ed->cursor_idx = (int)(next - ed->buffer);
+ }
+ ed->selection_anchor = ed->cursor_idx;
+}
+
+void editor_cursor_up(Editor *ed) {
+ int line_start = ed->cursor_idx;
+ while (line_start > 0 && ed->buffer[line_start - 1] != '\n') line_start--;
+
+ int visual_col = 0;
+ const char *p = ed->buffer + line_start;
+ while (p < ed->buffer + ed->cursor_idx) { SDL_StepUTF8(&p, NULL); visual_col++; }
+
+ if (line_start > 0) {
+ int prev_line_end = line_start - 1;
+ int prev_line_start = prev_line_end;
+ while (prev_line_start > 0 && ed->buffer[prev_line_start - 1] != '\n') prev_line_start--;
+
+ const char *ptr = ed->buffer + prev_line_start;
+ for (int i = 0; i < visual_col && *ptr != '\n' && *ptr != '\0'; i++) SDL_StepUTF8(&ptr, NULL);
+ ed->cursor_idx = (int)(ptr - ed->buffer);
+ }
+ ed->selection_anchor = ed->cursor_idx;
+}
+void editor_cursor_down(Editor *ed) {
+ int line_start = ed->cursor_idx;
+ while (line_start > 0 && ed->buffer[line_start - 1] != '\n') line_start--;
+
+ int visual_col = 0;
+ const char *p = ed->buffer + line_start;
+ while (p < ed->buffer + ed->cursor_idx) { SDL_StepUTF8(&p, NULL); visual_col++; }
+
+ const char *next_line = strchr(ed->buffer + ed->cursor_idx, '\n');
+ if (next_line) {
+ int target_idx = (int)(next_line - ed->buffer) + 1;
+ const char *ptr = ed->buffer + target_idx;
+ for(int i=0; i < visual_col; i++) {
+ if (*ptr == '\n' || *ptr == '\0') break;
+ SDL_StepUTF8(&ptr, NULL);
+ }
+ ed->cursor_idx = (int)(ptr - ed->buffer);
+ }
+
+ ed->selection_anchor = ed->cursor_idx;
+}
+void editor_set_cursor_from_coords(Editor *ed, float mx, float my, float scroll_x, float scroll_y) {
+ int target_col = (int)((mx - 20.0f + scroll_x + (ed->char_width / 2.0f)) / ed->char_width);
+ int target_row = (int)((my - 20.0f + scroll_y) / ed->line_height);
+
+ int cur_r = 0, cur_c = 0;
+ const char *ptr = ed->buffer;
+ const char *last_ptr = ed->buffer;
+
+ while (*ptr != '\0') {
+ if (cur_r == target_row && cur_c == target_col) break;
+ last_ptr = ptr;
+ Uint32 cp = SDL_StepUTF8(&ptr, NULL);
+ if (cp == '\n') {
+ if (cur_r == target_row) { ptr = last_ptr; break; }
+ cur_r++; cur_c = 0;
+ } else cur_c++;
+ if (cur_r > target_row) { ptr = last_ptr; break; }
+ }
+ ed->cursor_idx = (int)(ptr - ed->buffer);
+}
diff --git a/editor.h b/editor.h
@@ -0,0 +1,41 @@
+#ifndef EDITOR_H
+#define EDITOR_H
+
+#include <SDL3/SDL.h>
+#include <stdbool.h>
+
+typedef struct {
+ char *buffer;
+ size_t capacity;
+ size_t length;
+ int cursor_idx;
+ int selection_anchor; // Where the selection started
+
+ // Metrics for coordinate calculations
+ int char_width;
+ float line_height;
+} Editor;
+
+// Lifecycle
+Editor* editor_create(int char_width, float line_height);
+void editor_destroy(Editor *ed);
+
+// Core Actions
+void editor_insert_text(Editor *ed, const char *text);
+void editor_newline(Editor *ed);
+void editor_delete_back(Editor *ed);
+void editor_delete_forward(Editor *ed);
+void editor_delete_range(Editor *ed, int start, int end);
+
+// Cursor Movement
+void editor_cursor_left(Editor *ed);
+void editor_cursor_right(Editor *ed);
+void editor_cursor_up(Editor *ed);
+void editor_cursor_down(Editor *ed);
+
+// Selection & Coordination
+void editor_set_cursor_from_coords(Editor *ed, float mx, float my, float scroll_x, float scroll_y);
+void editor_set_selection(Editor *ed, int anchor, int cursor);
+void editor_clear_selection(Editor *ed);
+
+#endif
diff --git a/main.c b/main.c
@@ -1,4 +1,6 @@
+#include "editor.h"
#include <SDL3/SDL.h>
+#include <SDL3/SDL_events.h>
#include <SDL3/SDL_main.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <stdbool.h>
@@ -66,19 +68,15 @@ int main(int argc, char *argv[]) {
TTF_GetGlyphMetrics(font, 'A', NULL, NULL, NULL, NULL, &char_width);
float line_height = (float)TTF_GetFontHeight(font);
- size_t text_capacity = 1024; // Start with 1KB
- char *text_buffer = malloc(text_capacity);
- text_buffer[0] = '\0'; // Initialize as empty string
- size_t text_length = 0;
-
float scroll_x = 0.0f;
float scroll_y = 0.0f;
bool running = true;
- int cursor_idx = 0;
+ bool is_dragging = false;
SDL_StartTextInput(window);
- int selection_idx = 0;
bool is_selecting = false;
+ Editor* ed = editor_create(char_width, line_height);
+ ed->cursor_idx = 0;
while (running) {
SDL_Event event;
@@ -96,231 +94,73 @@ int main(int argc, char *argv[]) {
scroll_x = 0;
if (scroll_y < 0)
scroll_y = 0;
- } else if (event.type ==
- SDL_EVENT_MOUSE_BUTTON_DOWN) {
+ }
+
+ switch (event.type) {
+ case SDL_EVENT_KEY_DOWN:
+ switch (event.key.key) {
+ case SDLK_BACKSPACE:
+ editor_delete_back(ed);
+ break;
+ case SDLK_DELETE:
+ editor_delete_forward(ed);
+ break;
+ case SDLK_RETURN:
+ editor_newline(ed);
+ break;
+ case SDLK_LEFT:
+ editor_cursor_left(ed);
+ break;
+ case SDLK_RIGHT:
+ editor_cursor_right(ed);
+ break;
+ case SDLK_UP:
+ editor_cursor_up(ed);
+ break;
+ case SDLK_DOWN:
+ editor_cursor_down(ed);
+ break;
+ }
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
if (event.button.button ==
SDL_BUTTON_LEFT) {
float mx, my;
SDL_RenderCoordinatesFromWindow(
renderer, event.button.x,
event.button.y, &mx, &my);
- cursor_idx =
- get_idx_from_coords(
- text_buffer, mx, my,
- scroll_x, scroll_y,
- char_width,
- line_height);
- selection_idx = cursor_idx;
-
- is_selecting = true;
- }
- } else if (event.type ==
- SDL_EVENT_MOUSE_MOTION) {
- if (is_selecting) {
- float mx, my;
- SDL_RenderCoordinatesFromWindow(
- renderer, event.motion.x,
- event.motion.y, &mx, &my);
- cursor_idx =
- get_idx_from_coords(
- text_buffer, mx, my,
- scroll_x, scroll_y,
- char_width,
- line_height);
+ editor_set_cursor_from_coords(
+ ed, mx, my, scroll_x,
+ scroll_y);
+ ed->selection_anchor =
+ ed->cursor_idx;
+ is_dragging = true;
}
- } else if (event.type ==
- SDL_EVENT_MOUSE_BUTTON_UP) {
+ break;
+ case SDL_EVENT_MOUSE_BUTTON_UP:
if (event.button.button ==
SDL_BUTTON_LEFT) {
- is_selecting = false;
+ is_dragging = false;
}
- }
-
- else if (event.type == SDL_EVENT_KEY_DOWN) {
- is_selecting = false;
- SDL_Keycode key = event.key.key;
-
- if (key == SDLK_BACKSPACE &&
- cursor_idx > 0) {
- int prev_idx = cursor_idx;
- do {
- prev_idx--;
- } while (
- prev_idx > 0 &&
- (text_buffer[prev_idx] &
- 0xC0) == 0x80);
-
- int char_size =
- cursor_idx - prev_idx;
- memmove(
- &text_buffer[prev_idx],
- &text_buffer[cursor_idx],
- text_length - cursor_idx +
- 1);
- text_length -= char_size;
- cursor_idx = prev_idx;
- } else if (key == SDLK_RETURN) {
- memmove(
- &text_buffer[cursor_idx +
- 1],
- &text_buffer[cursor_idx],
- text_length - cursor_idx +
- 1);
- text_buffer[cursor_idx] = '\n';
- text_length++;
- cursor_idx++;
- selection_idx = cursor_idx;
-
- } else if (key == SDLK_LEFT &&
- cursor_idx > 0) {
- cursor_idx--;
-
- while (
- cursor_idx > 0 &&
- (text_buffer[cursor_idx] &
- 0xC0) == 0x80) {
- cursor_idx--;
- }
- selection_idx = cursor_idx;
- } else if (key == SDLK_DELETE &&
- cursor_idx < text_length) {
- const char *ptr =
- text_buffer + cursor_idx;
- const char *next = ptr;
- SDL_StepUTF8(&next, NULL);
- int char_size =
- (int)(next - ptr);
- memmove(
- &text_buffer[cursor_idx],
- &text_buffer[cursor_idx +
- char_size],
- text_length -
- (cursor_idx +
- char_size) +
- 1);
- text_length -= char_size;
- }
-
- else if (key == SDLK_UP ||
- key == SDLK_DOWN) {
- // 1. Find start of current line
- // and current visual column
- int line_start_idx = cursor_idx;
- while (
- line_start_idx > 0 &&
- text_buffer[line_start_idx -
- 1] != '\n') {
- line_start_idx--;
- }
-
- int visual_col = 0;
- const char *p = text_buffer +
- line_start_idx;
- while (p < text_buffer +
- cursor_idx) {
- SDL_StepUTF8(&p, NULL);
- visual_col++;
- }
-
- int target_idx = -1;
- if (key == SDLK_UP) {
- if (line_start_idx >
- 0) {
- // Find start of
- // PREVIOUS line
- int prev_line_end =
- line_start_idx -
- 1;
- int prev_line_start =
- prev_line_end;
- while (
- prev_line_start >
- 0 &&
- text_buffer
- [prev_line_start -
- 1] !=
- '\n') {
- prev_line_start--;
- }
- target_idx =
- prev_line_start;
- }
- selection_idx =
- cursor_idx;
-
- } else { // SDLK_DOWN
- const char *next_line =
- strchr(
- text_buffer +
- cursor_idx,
- '\n');
- if (next_line) {
- target_idx =
- (int)(next_line -
- text_buffer) +
- 1;
- }
- selection_idx =
- cursor_idx;
- }
-
- if (target_idx != -1) {
- const char *ptr =
- text_buffer +
- target_idx;
- for (int i = 0;
- i < visual_col;
- i++) {
- if (*ptr ==
- '\n' ||
- *ptr ==
- '\0')
- break;
- SDL_StepUTF8(
- &ptr, NULL);
- }
- cursor_idx =
- (int)(ptr -
- text_buffer);
- }
- } else if (key == SDLK_RIGHT &&
- cursor_idx < text_length) {
- const char *next =
- text_buffer + cursor_idx;
- SDL_StepUTF8(&next, NULL);
-
- cursor_idx =
- (int)(next - text_buffer);
- selection_idx = cursor_idx;
- }
- }
+ break;
- else if (event.type == SDL_EVENT_TEXT_INPUT) {
- size_t input_len =
- strlen(event.text.text);
-
- if (text_length + input_len + 1 >
- text_capacity) {
- text_capacity *= 2;
- char *new_buffer = realloc(
- text_buffer, text_capacity);
- if (new_buffer) {
- text_buffer =
- new_buffer;
- } else {
- break;
- }
+ case SDL_EVENT_MOUSE_MOTION:
+ if (is_dragging) {
+ float mx, my;
+ SDL_RenderCoordinatesFromWindow(
+ renderer, event.motion.x,
+ event.motion.y, &mx, &my);
+ editor_set_cursor_from_coords(
+ ed, mx, my, scroll_x,
+ scroll_y);
+ // We don't update
+ // selection_anchor here, so the
+ // range expands
}
+ break;
- memmove(&text_buffer[cursor_idx +
- input_len],
- &text_buffer[cursor_idx],
- text_length - cursor_idx + 1);
- memcpy(&text_buffer[cursor_idx],
- event.text.text, input_len);
- text_length += input_len;
- cursor_idx += input_len;
- selection_idx = cursor_idx;
+ case SDL_EVENT_TEXT_INPUT:
+ editor_insert_text(ed, event.text.text);
+ break;
}
} while (SDL_PollEvent(&event));
}
@@ -332,21 +172,21 @@ int main(int argc, char *argv[]) {
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
- if (cursor_idx != selection_idx) {
- int sel_min = (cursor_idx < selection_idx)
- ? cursor_idx
- : selection_idx;
- int sel_max = (cursor_idx > selection_idx)
- ? cursor_idx
- : selection_idx;
+ if (ed->cursor_idx != ed->selection_anchor) {
+ int sel_min = (ed->cursor_idx < ed->selection_anchor)
+ ? ed->cursor_idx
+ : ed->selection_anchor;
+ int sel_max = (ed->cursor_idx > ed->selection_anchor)
+ ? ed->cursor_idx
+ : ed->selection_anchor;
SDL_SetRenderDrawColor(renderer, 200, 220, 255,
255); // Light Blue
int cur_row = 0, cur_col = 0;
- const char *p = text_buffer;
- while (p < text_buffer + text_length) {
- int start_of_char_idx = (int)(p - text_buffer);
+ const char *p = ed->buffer;
+ while (p < ed->buffer + ed->length) {
+ int start_of_char_idx = (int)(p - ed->buffer);
if (start_of_char_idx >= sel_min &&
start_of_char_idx < sel_max) {
@@ -367,17 +207,17 @@ int main(int argc, char *argv[]) {
SDL_StepUTF8(&p, NULL);
cur_col++;
}
- if ((int)(p - text_buffer) > sel_max)
+ if ((int)(p - ed->buffer) > sel_max)
break;
}
}
- if (font && text_length > 0) {
+ if (font && ed->length > 0) {
SDL_Color black = {0, 0, 0, 255};
float current_y = 20.0f - scroll_y;
- const char *line_start = text_buffer;
+ const char *line_start = ed->buffer;
while (line_start != NULL && *line_start != '\0') {
const char *line_end = strchr(line_start, '\n');
size_t len =
@@ -411,8 +251,8 @@ int main(int argc, char *argv[]) {
}
int cur_row = 0, cur_col = 0;
- const char *ptr = text_buffer;
- while (ptr < text_buffer + cursor_idx) {
+ const char *ptr = ed->buffer;
+ while (ptr < ed->buffer + ed->cursor_idx) {
if (*ptr == '\n') {
cur_row++;
cur_col = 0;
@@ -437,6 +277,7 @@ int main(int argc, char *argv[]) {
SDL_RenderPresent(renderer);
}
+ editor_destroy(ed);
TTF_CloseFont(font);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);