esc
Externally Scriptable Editor
git clone git://mccd.space/esc
| Log | Files | Refs | README |
commit 1eb764779f5ba844ea4f62079651c5c43b4a92aa parent fd4536628f126a57db6a451a12feb265047a1371 Author: Marc Coquand <marc@coquand.email> Date: Thu, 19 Feb 2026 15:48:05 +0100 Support highlighting properly Diffstat:
| M | editor.c | | | 281 | ++++++++++++++++++++++++++++++++++++++++++++++--------------------------------- |
| M | editor.h | | | 1 | + |
| M | main.c | | | 83 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
3 files changed, 230 insertions(+), 135 deletions(-)
diff --git a/editor.c b/editor.c
@@ -2,149 +2,194 @@
#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;
+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);
+ free(ed->buffer);
+ free(ed);
}
-void editor_insert_text(Editor *ed, const char *text,bool replace) {
- if (replace && ed->cursor_idx != ed->selection_anchor) {
- editor_delete_range(ed, ed->cursor_idx, ed->selection_anchor) ;
- }
- 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_insert_text(Editor *ed, const char *text, bool replace) {
+ if (replace && ed->cursor_idx != ed->selection_anchor) {
+ editor_delete_range(ed, ed->cursor_idx, ed->selection_anchor);
+ }
+ 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", true);
-}
+void editor_newline(Editor *ed) { editor_insert_text(ed, "\n", true); }
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;
+ 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);
- }
+ 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));
- }
+ 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;
+ 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;
+ 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;
+ 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) {
+ if (*p == '\t') {
+ visual_col += TAB_SIZE - (visual_col % TAB_SIZE);
+ p++;
+ } else {
+ 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;
+ 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) {
+ if (*p == '\t') {
+ visual_col += TAB_SIZE - (visual_col % TAB_SIZE);
+ } else {
+ 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);
+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 == '\t') {
+ cur_c += TAB_SIZE - (cur_c % TAB_SIZE);
+ } else 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
@@ -1,5 +1,6 @@
#ifndef EDITOR_H
#define EDITOR_H
+#define TAB_SIZE 8
#include <SDL3/SDL.h>
#include <stdbool.h>
diff --git a/main.c b/main.c
@@ -29,7 +29,9 @@ int get_idx_from_coords(const char *buffer, float mx, float my, float scroll_x,
last_ptr = ptr;
Uint32 codepoint = SDL_StepUTF8(&ptr, NULL);
- if (codepoint == '\n') {
+ if (codepoint == '\t') {
+ current_col += TAB_SIZE - (current_col % TAB_SIZE);
+ } else if (codepoint == '\n') {
if (current_row == target_row) {
ptr = last_ptr;
break;
@@ -75,7 +77,7 @@ int main(int argc, char *argv[]) {
SDL_StartTextInput(window);
bool is_selecting = false;
- Editor* ed = editor_create(char_width, line_height);
+ Editor *ed = editor_create(char_width, line_height);
ed->cursor_idx = 0;
while (running) {
@@ -105,6 +107,10 @@ int main(int argc, char *argv[]) {
case SDLK_DELETE:
editor_delete_forward(ed);
break;
+ case SDLK_TAB:
+ editor_insert_text(ed, "\t",
+ true);
+ break;
case SDLK_RETURN:
editor_newline(ed);
break;
@@ -132,7 +138,7 @@ int main(int argc, char *argv[]) {
ed, mx, my, scroll_x,
scroll_y);
ed->selection_anchor =
- ed->cursor_idx;
+ ed->cursor_idx;
is_dragging = true;
}
break;
@@ -159,7 +165,8 @@ int main(int argc, char *argv[]) {
break;
case SDL_EVENT_TEXT_INPUT:
- editor_insert_text(ed, event.text.text, true);
+ editor_insert_text(ed, event.text.text,
+ true);
break;
}
} while (SDL_PollEvent(&event));
@@ -188,6 +195,14 @@ int main(int argc, char *argv[]) {
while (p < ed->buffer + ed->length) {
int start_of_char_idx = (int)(p - ed->buffer);
+ Uint32 cp = SDL_StepUTF8(&p, NULL);
+
+ int cols_for_char = 1;
+ if (cp == '\t') {
+ cols_for_char =
+ TAB_SIZE - (cur_col % TAB_SIZE);
+ }
+
if (start_of_char_idx >= sel_min &&
start_of_char_idx < sel_max) {
SDL_FRect sel_rect = {
@@ -195,19 +210,21 @@ int main(int argc, char *argv[]) {
scroll_x,
20.0f + (cur_row * line_height) -
scroll_y,
- (float)char_width, line_height};
+ (float)(cols_for_char *
+ char_width), // <-- dynamic
+ // width!
+ line_height};
SDL_RenderFillRect(renderer, &sel_rect);
}
- if (*p == '\n') {
+ if (cp == '\n') {
cur_row++;
cur_col = 0;
- p++;
} else {
- SDL_StepUTF8(&p, NULL);
- cur_col++;
+ cur_col += cols_for_char;
}
- if ((int)(p - ed->buffer) > sel_max)
+
+ if ((int)(p - ed->buffer) >= sel_max)
break;
}
}
@@ -226,9 +243,42 @@ int main(int argc, char *argv[]) {
if (len > 0 && current_y + line_height > 0 &&
current_y < render_h) {
+
+ // Expand tabs into spaces for correct
+ // rendering alignment
+ char *temp_line =
+ malloc(len * TAB_SIZE + 1);
+ int t_idx = 0;
+ int vis_col = 0;
+
+ for (size_t i = 0; i < len; i++) {
+ if (line_start[i] == '\t') {
+ int spaces = TAB_SIZE -
+ (vis_col %
+ TAB_SIZE);
+ for (int s = 0;
+ s < spaces; s++) {
+ temp_line
+ [t_idx++] =
+ ' ';
+ }
+ vis_col += spaces;
+ } else {
+ if ((line_start[i] &
+ 0xC0) != 0x80)
+ vis_col++; // Track
+ // start
+ // bytes
+ temp_line[t_idx++] =
+ line_start[i];
+ }
+ }
+ temp_line[t_idx] = '\0';
SDL_Surface *surf =
TTF_RenderText_Blended(
- font, line_start, len, black);
+ font, temp_line, t_idx, black);
+ free(temp_line);
+
if (surf) {
SDL_Texture *tex =
SDL_CreateTextureFromSurface(
@@ -244,21 +294,20 @@ int main(int argc, char *argv[]) {
}
}
current_y += line_height;
- line_start = line_end
- ? line_end + 1
- : NULL; // Move to next line
+ line_start = line_end ? line_end + 1 : NULL;
}
}
int cur_row = 0, cur_col = 0;
const char *ptr = ed->buffer;
while (ptr < ed->buffer + ed->cursor_idx) {
- if (*ptr == '\n') {
+ Uint32 cp = SDL_StepUTF8(&ptr, NULL);
+ if (cp == '\t') {
+ cur_col += TAB_SIZE - (cur_col % TAB_SIZE);
+ } else if (cp == '\n') {
cur_row++;
cur_col = 0;
- ptr++;
} else {
- SDL_StepUTF8(&ptr, NULL);
cur_col++;
}
}