esc
Externally Scriptable Editor
git clone git://mccd.space/esc
| Log | Files | Refs | README |
commit 3bdbc2bce5fe2e46613b7b70c4d97e09964e5f72 parent 415fa9026d05389297d0af1f2bd155f94f93d96a Author: Marc Coquand <marc@coquand.email> Date: Sat, 21 Feb 2026 20:08:30 +0100 Save load Diffstat:
| M | editor.c | | | 145 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- |
| M | editor.h | | | 6 | +++++- |
| M | main.c | | | 743 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
| M | unix_utils.c | | | 2 | +- |
4 files changed, 528 insertions(+), 368 deletions(-)
diff --git a/editor.c b/editor.c
@@ -1,5 +1,6 @@
#include "editor.h"
#include <ctype.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -19,6 +20,7 @@ Editor *editor_create(int char_width, float line_height) {
ed->ranges = NULL;
ed->ranges_count = 0;
ed->ranges_capacity = 0;
+ ed->filename = NULL;
ed->char_width = char_width;
ed->line_height = line_height;
@@ -26,12 +28,64 @@ Editor *editor_create(int char_width, float line_height) {
}
void editor_destroy(Editor *ed) {
+ if (ed->filename)
+ free(ed->filename);
free(ed->buffer);
free(ed);
}
void editor_clear_ranges(Editor *ed) { ed->ranges_count = 0; }
+bool editor_load_file(Editor *ed, const char *filename) {
+ FILE *f = fopen(filename, "rb");
+ if (!f)
+ return false;
+
+ fseek(f, 0, SEEK_END);
+ size_t size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (size + 1 > ed->capacity) {
+ ed->capacity = size + 1024;
+ char *new_buf = realloc(ed->buffer, ed->capacity);
+ if (!new_buf) {
+ fclose(f);
+ return false;
+ }
+ ed->buffer = new_buf;
+ }
+
+ fread(ed->buffer, 1, size, f);
+ ed->buffer[size] = '\0';
+ ed->length = size;
+ fclose(f);
+
+ if (ed->filename)
+ free(ed->filename);
+ ed->filename = strdup(filename);
+ ed->cursor_idx = 0;
+ ed->selection_anchor = 0;
+ editor_clear_ranges(ed);
+ return true;
+}
+
+bool editor_write_file(Editor *ed, const char *filename) {
+ const char *to_write;
+ if (filename)
+ to_write = filename;
+ else if (ed->filename) {
+ filename = ed->filename;
+ } else {
+ return false;
+ }
+ FILE *f = fopen(ed->filename, "wb");
+ if (!f)
+ return false;
+ fwrite(ed->buffer, 1, ed->length, f);
+ fclose(f);
+ return true;
+}
+
void editor_add_range(Editor *ed, int start, int end, int visual_cols) {
if (ed->ranges_count >= ed->ranges_capacity) {
ed->ranges_capacity =
@@ -40,24 +94,53 @@ void editor_add_range(Editor *ed, int start, int end, int visual_cols) {
ed->ranges_capacity * sizeof(EditorRange));
}
EditorRange r = {start, end, visual_cols};
- ed->ranges[ed->ranges_count++] = r;
+ // Insert in sorted order to allow binary search
+ size_t i = ed->ranges_count;
+ while (i > 0 && ed->ranges[i - 1].start_byte > start) {
+ ed->ranges[i] = ed->ranges[i - 1];
+ i--;
+ }
+ ed->ranges[i] = r;
+ ed->ranges_count++;
}
EditorRange *editor_get_range_at(Editor *ed, int byte_idx) {
- // XXX Use binary search
- for (size_t i = 0; i < ed->ranges_count; i++) {
- if (byte_idx >= ed->ranges[i].start_byte &&
- byte_idx < ed->ranges[i].end_byte) {
- return &ed->ranges[i];
+ int left = 0;
+ int right = (int)ed->ranges_count - 1;
+
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ EditorRange *r = &ed->ranges[mid];
+
+ if (byte_idx >= r->start_byte && byte_idx < r->end_byte) {
+ return r;
+ }
+
+ if (byte_idx < r->start_byte) {
+ right = mid - 1;
+ } else {
+ left = mid + 1;
}
}
return NULL;
}
EditorRange *editor_get_range_ending_at(Editor *ed, int byte_idx) {
- for (size_t i = 0; i < ed->ranges_count; i++) {
- if (byte_idx == ed->ranges[i].end_byte) {
- return &ed->ranges[i];
+ int left = 0;
+ int right = (int)ed->ranges_count - 1;
+
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+ EditorRange *r = &ed->ranges[mid];
+
+ if (byte_idx == r->end_byte) {
+ return r;
+ }
+
+ if (byte_idx <= r->start_byte) {
+ right = mid - 1;
+ } else {
+ left = mid + 1;
}
}
return NULL;
@@ -95,6 +178,17 @@ void editor_insert_text(Editor *ed, const char *text, bool replace) {
ed->buffer = new_buf;
}
+ // Shift ranges affected by insertion
+ for (size_t i = 0; i < ed->ranges_count; i++) {
+ if (ed->ranges[i].start_byte >= ed->cursor_idx) {
+ ed->ranges[i].start_byte += input_len;
+ ed->ranges[i].end_byte += input_len;
+ } else if (ed->cursor_idx > ed->ranges[i].start_byte &&
+ ed->cursor_idx < ed->ranges[i].end_byte) {
+ ed->ranges[i].end_byte += input_len;
+ }
+ }
+
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);
@@ -114,6 +208,39 @@ void editor_delete_range(Editor *ed, int start, int end) {
start = end;
end = tmp;
}
+ // Shift/Delete ranges affected by deletion
+ int len = end - start;
+ size_t new_count = 0;
+ for (size_t i = 0; i < ed->ranges_count; i++) {
+ EditorRange r = ed->ranges[i];
+
+ // Drop range completely contained within the deleted region
+ if (r.start_byte >= start && r.end_byte <= end) {
+ continue;
+ }
+
+ // Shift ranges entirely after the deleted section
+ if (r.start_byte >= end) {
+ r.start_byte -= len;
+ r.end_byte -= len;
+ } else {
+ // Handle partial overlaps bounds
+ if (r.start_byte > start && r.start_byte < end)
+ r.start_byte = start;
+ if (r.end_byte > start && r.end_byte < end)
+ r.end_byte = start;
+
+ // If deletion happened entirely inside a specific range
+ if (start > r.start_byte && end < r.end_byte) {
+ r.end_byte -= len;
+ }
+ }
+
+ if (r.end_byte > r.start_byte) {
+ ed->ranges[new_count++] = r;
+ }
+ }
+ ed->ranges_count = new_count;
memmove(&ed->buffer[start], &ed->buffer[end], ed->length - end + 1);
ed->length -= (end - start);
diff --git a/editor.h b/editor.h
@@ -18,6 +18,7 @@ typedef struct {
int cursor_idx;
int selection_anchor; // Where the selection started
+ char *filename;
EditorRange *ranges;
size_t ranges_count;
size_t ranges_capacity;
@@ -38,6 +39,9 @@ void editor_delete_back(Editor *ed);
void editor_delete_forward(Editor *ed);
void editor_clear_selection(Editor *ed);
void editor_delete_range(Editor *ed, int start, int end);
+bool editor_load_file(Editor *ed, const char *filename);
+bool editor_write_file(Editor *ed, const char *filename);
+void editor_add_range(Editor *ed, int start, int end, int visual_cols);
// Cursor Movement
void editor_cursor_left(Editor *ed);
@@ -56,5 +60,5 @@ void editor_goto_pos(Editor *ed, int pos);
char *editor_get_selection(Editor *ed);
void editor_select_word(Editor *ed);
bool editor_has_selection(Editor *ed);
-
+EditorRange *editor_get_range_at(Editor *ed, int byte_idx);
#endif
diff --git a/main.c b/main.c
@@ -13,296 +13,230 @@
#include <string.h>
#include <unistd.h>
-int main(int argc, char *argv[]) {
- // Otherwise wayland just wouldn't load.
- SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "wayland",
- SDL_HINT_OVERRIDE);
-
- SDL_Init(SDL_INIT_VIDEO);
- TTF_Init();
-
- SDL_Window *window = SDL_CreateWindow(
- "esc", 800, 600,
- SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
- SDL_Renderer *renderer = SDL_CreateRenderer(window, NULL);
- SDL_SetRenderLogicalPresentation(renderer, 800, 600,
- SDL_LOGICAL_PRESENTATION_DISABLED);
-
- TTF_Font *font = TTF_OpenFont("IosevkaTermSS13-Extended.ttf", 24.0f);
- int char_width;
- TTF_GetGlyphMetrics(font, 'A', NULL, NULL, NULL, NULL, &char_width);
- float line_height = (float)TTF_GetFontHeight(font)*1.5;
- float cursor_height = (float)TTF_GetFontHeight(font);
-
- float scroll_x = 0.0f;
- float scroll_y = 0.0f;
- bool running = true;
- bool is_dragging = false;
-
+void handle_events(Editor *ed, SDL_Renderer *renderer, float *scroll_x,
+ float *scroll_y, float line_height, bool *running,
+ bool *is_dragging) {
+ SDL_Event event;
char *cmd;
- SDL_StartTextInput(window);
- Editor *ed = editor_create(char_width, line_height);
- ed->cursor_idx = 0;
-
- while (running) {
- SDL_Event event;
+ if (SDL_WaitEvent(&event)) {
+ do {
+ if (event.type == SDL_EVENT_QUIT)
+ *running = false;
- if (SDL_WaitEvent(&event)) {
- do {
- if (event.type == SDL_EVENT_QUIT)
- running = false;
+ else if (event.type == SDL_EVENT_MOUSE_WHEEL) {
+ *scroll_y -= event.wheel.y * line_height;
+ *scroll_x -= -event.wheel.x * 30.0f;
- else if (event.type == SDL_EVENT_MOUSE_WHEEL) {
- scroll_y -= event.wheel.y * line_height;
- scroll_x -= -event.wheel.x * 30.0f;
-
- if (scroll_x < 0)
- scroll_x = 0;
- if (scroll_y < 0)
- scroll_y = 0;
- }
+ if (*scroll_x < 0)
+ *scroll_x = 0;
+ if (*scroll_y < 0)
+ *scroll_y = 0;
+ }
- 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_TAB:
- editor_insert_text(ed, "\t",
- true);
- editor_clear_selection(ed);
- break;
- case SDLK_RETURN:
- editor_newline(ed);
- editor_clear_selection(ed);
+ 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_TAB:
+ editor_insert_text(ed, "\t", true);
+ editor_clear_selection(ed);
+ break;
+ case SDLK_RETURN:
+ editor_newline(ed);
+ editor_clear_selection(ed);
+ break;
+ case SDLK_1:
+ if (!(event.key.mod & SDL_KMOD_ALT))
break;
- case SDLK_1:
- if (!(event.key.mod &
- SDL_KMOD_ALT))
- break;
- cmd = unix_select_command();
- if (cmd) {
- char *selection =
- editor_get_selection(
- ed);
- char *output =
- unix_run_command(
- cmd, selection);
-
- if (output) {
- editor_insert_text(
- ed, output,
- true);
- free(output);
- }
- if (selection)
- free(selection);
- free(cmd);
+ cmd = unix_select_command();
+ if (cmd) {
+ char *selection =
+ editor_get_selection(ed);
+ char *output = unix_run_command(
+ cmd, selection);
+ if (output) {
+ editor_insert_text(
+ ed, output, true);
+ free(output);
}
+ if (selection)
+ free(selection);
+ free(cmd);
+ }
+ break;
+ case SDLK_EXCLAIM:
+ if (!(event.key.mod & SDL_KMOD_ALT))
break;
- case SDLK_EXCLAIM:
- if (!(event.key.mod &
- SDL_KMOD_ALT))
- break;
- cmd = unix_select_command();
- if (cmd) {
- char *selection =
- editor_get_selection(
- ed);
- char *output =
- unix_run_command(
- cmd, selection);
-
- if (output) {
- editor_insert_text(
- ed, output,
- false);
- free(output);
- }
- if (selection)
- free(selection);
- free(cmd);
+ cmd = unix_select_command();
+ if (cmd) {
+ char *selection =
+ editor_get_selection(ed);
+ char *output = unix_run_command(
+ cmd, selection);
+ if (output) {
+ editor_insert_text(
+ ed, output, false);
+ free(output);
}
+ if (selection)
+ free(selection);
+ free(cmd);
+ }
+ break;
+ case SDLK_A:
+ if (!(event.key.mod & SDL_KMOD_ALT))
break;
- case SDLK_A:
- if (!(event.key.mod &
- SDL_KMOD_ALT))
- break;
- editor_select_all(ed);
- break;
- case SDLK_B:
- if (!(event.key.mod &
- SDL_KMOD_ALT) &&
- !event.key.repeat)
- break;
- case SDLK_LEFT:
- editor_cursor_left(ed);
- if (!(event.key.mod &
- SDL_KMOD_SHIFT) ||
- event.key.repeat)
- editor_clear_selection(
- ed);
- break;
- case SDLK_F:
- if (!(event.key.mod &
- SDL_KMOD_ALT) &&
- !event.key.repeat)
- break;
- case SDLK_RIGHT:
- editor_cursor_right(ed);
- if (!(event.key.mod &
- SDL_KMOD_SHIFT))
- editor_clear_selection(
- ed);
+ editor_select_all(ed);
+ break;
+ case SDLK_B:
+ if (!(event.key.mod & SDL_KMOD_ALT) &&
+ !event.key.repeat)
break;
- case SDLK_P:
- if (!(event.key.mod &
- SDL_KMOD_ALT) &&
- !event.key.repeat)
- break;
- case SDLK_UP:
- editor_cursor_up(ed);
- if (!(event.key.mod &
- SDL_KMOD_SHIFT))
- editor_clear_selection(
- ed);
+ case SDLK_LEFT:
+ editor_cursor_left(ed);
+ if (!(event.key.mod & SDL_KMOD_SHIFT) ||
+ event.key.repeat)
+ editor_clear_selection(ed);
+ break;
+ case SDLK_F:
+ if (!(event.key.mod & SDL_KMOD_ALT) &&
+ !event.key.repeat)
break;
- case SDLK_N:
- if (!(event.key.mod &
- SDL_KMOD_ALT) &&
- !event.key.repeat)
- break;
- case SDLK_DOWN:
- editor_cursor_down(ed);
- if (!(event.key.mod &
- SDL_KMOD_SHIFT))
- editor_clear_selection(
- ed);
+ case SDLK_RIGHT:
+ editor_cursor_right(ed);
+ if (!(event.key.mod & SDL_KMOD_SHIFT))
+ editor_clear_selection(ed);
+ break;
+ case SDLK_P:
+ if (!(event.key.mod & SDL_KMOD_ALT) &&
+ !event.key.repeat)
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);
- editor_set_cursor_from_coords(
- ed, mx, my, scroll_x,
- scroll_y);
- ed->selection_anchor =
- ed->cursor_idx;
- is_dragging = true;
- } else if (event.button.button ==
- SDL_BUTTON_MIDDLE) {
- SDL_Keymod mod =
- SDL_GetModState();
- if (mod & SDL_KMOD_ALT) {
- if (!editor_has_selection(
- ed)) {
- float mx, my;
- SDL_RenderCoordinatesFromWindow(
- renderer,
- event.button
- .x,
- event.button
- .y,
- &mx, &my);
- editor_set_cursor_from_coords(
- ed, mx, my,
- scroll_x,
- scroll_y);
- ed->selection_anchor =
- ed->cursor_idx;
- editor_select_word(
- ed);
- }
- char *selection =
- editor_get_selection(
- ed);
- if (selection) {
- char cmd[1024];
- snprintf(
- cmd,
- sizeof(cmd),
- "xdg-open "
- "%s",
- selection);
- unix_exec_command(
- cmd, NULL);
-
- free(selection);
- }
- }
- }
+ case SDLK_UP:
+ editor_cursor_up(ed);
+ if (!(event.key.mod & SDL_KMOD_SHIFT))
+ editor_clear_selection(ed);
break;
- case SDL_EVENT_MOUSE_BUTTON_UP:
- if (event.button.button ==
- SDL_BUTTON_LEFT) {
- is_dragging = false;
- }
+ case SDLK_N:
+ if (!(event.key.mod & SDL_KMOD_ALT) &&
+ !event.key.repeat)
+ break;
+ case SDLK_DOWN:
+ editor_cursor_down(ed);
+ if (!(event.key.mod & SDL_KMOD_SHIFT))
+ editor_clear_selection(ed);
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);
+ case SDLK_W:
+ if (event.key.mod & SDL_KMOD_ALT) {
+ editor_write_file(ed, NULL);
}
break;
-
- case SDL_EVENT_TEXT_INPUT:
- if ((SDL_GetModState() &
- (SDL_KMOD_ALT | SDL_KMOD_CTRL))) {
- 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);
+ editor_set_cursor_from_coords(
+ ed, mx, my, *scroll_x, *scroll_y);
+ ed->selection_anchor = ed->cursor_idx;
+ *is_dragging = true;
+ } else if (event.button.button ==
+ SDL_BUTTON_MIDDLE) {
+ SDL_Keymod mod = SDL_GetModState();
+ if (mod & SDL_KMOD_ALT) {
+ if (!editor_has_selection(ed)) {
+ float mx, my;
+ SDL_RenderCoordinatesFromWindow(
+ renderer,
+ event.button.x,
+ event.button.y, &mx,
+ &my);
+ editor_set_cursor_from_coords(
+ ed, mx, my,
+ *scroll_x,
+ *scroll_y);
+ ed->selection_anchor =
+ ed->cursor_idx;
+ editor_select_word(ed);
+ }
+ char *selection =
+ editor_get_selection(ed);
+ if (selection) {
+ char sys_cmd[1024];
+ snprintf(
+ sys_cmd,
+ sizeof(sys_cmd),
+ "xdg-open %s",
+ selection);
+ unix_exec_command(
+ sys_cmd, NULL);
+ free(selection);
+ }
}
- editor_insert_text(ed, event.text.text,
- true);
+ }
+ break;
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ if (event.button.button == SDL_BUTTON_LEFT) {
+ *is_dragging = false;
+ }
+ 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);
+ }
+ break;
+ case SDL_EVENT_TEXT_INPUT:
+ if ((SDL_GetModState() &
+ (SDL_KMOD_ALT | SDL_KMOD_CTRL))) {
break;
}
- } while (SDL_PollEvent(&event));
- }
-
- // --- RENDER PHASE ---
-
- int render_w, render_h;
- SDL_GetRenderOutputSize(renderer, &render_w, &render_h);
+ editor_insert_text(ed, event.text.text, true);
+ break;
+ }
+ } while (SDL_PollEvent(&event));
+ }
+}
- SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
- SDL_RenderClear(renderer);
- 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;
+void render_editor(SDL_Renderer *renderer, Editor *ed, TTF_Font *font,
+ int char_width, float line_height, float cursor_height,
+ float scroll_x, float scroll_y) {
+ int render_w, render_h;
+ SDL_GetRenderOutputSize(renderer, &render_w, &render_h);
- SDL_SetRenderDrawColor(renderer, 200, 220, 255,
- 255); // Light Blue
+ SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
+ SDL_RenderClear(renderer);
- int cur_row = 0, cur_col = 0;
- const char *p = ed->buffer;
- while (p < ed->buffer + ed->length) {
- int start_of_char_idx = (int)(p - ed->buffer);
+ 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;
- Uint32 cp = SDL_StepUTF8(&p, NULL);
+ SDL_SetRenderDrawColor(renderer, 200, 220, 255,
+ 255); // Light Blue
- int cols_for_char = 1;
- if (cp == '\t') {
- cols_for_char =
- TAB_SIZE - (cur_col % TAB_SIZE);
- }
+ int cur_row = 0, cur_col = 0;
+ const char *p = ed->buffer;
+ while (p < ed->buffer + ed->length) {
+ int start_of_char_idx = (int)(p - ed->buffer);
+ EditorRange *r =
+ editor_get_range_at(ed, start_of_char_idx);
+ if (r) {
if (start_of_char_idx >= sel_min &&
start_of_char_idx < sel_max) {
SDL_FRect sel_rect = {
@@ -310,119 +244,214 @@ int main(int argc, char *argv[]) {
scroll_x,
20.0f + (cur_row * line_height) -
scroll_y,
- (float)(cols_for_char *
- char_width),
+ (float)(r->visual_cols *
+ char_width),
cursor_height};
SDL_RenderFillRect(renderer, &sel_rect);
}
-
- if (cp == '\n') {
- cur_row++;
- cur_col = 0;
- } else {
- cur_col += cols_for_char;
- }
-
+ cur_col += r->visual_cols;
+ p = ed->buffer + r->end_byte;
if ((int)(p - ed->buffer) >= sel_max)
break;
+ continue;
+ }
+
+ 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 = {
+ 20.0f + (cur_col * char_width) - scroll_x,
+ 20.0f + (cur_row * line_height) - scroll_y,
+ (float)(cols_for_char * char_width),
+ cursor_height};
+ SDL_RenderFillRect(renderer, &sel_rect);
+ }
+
+ if (cp == '\n') {
+ cur_row++;
+ cur_col = 0;
+ } else {
+ cur_col += cols_for_char;
}
+
+ if ((int)(p - ed->buffer) >= sel_max)
+ break;
}
+ }
- if (font && ed->length > 0) {
- SDL_Color black = {0, 0, 0, 255};
-
- float current_y = 20.0f - scroll_y;
-
- const char *line_start = ed->buffer;
- while (line_start != NULL && *line_start != '\0') {
- const char *line_end = strchr(line_start, '\n');
- size_t len =
- line_end ? (size_t)(line_end - line_start)
- : strlen(line_start);
-
- 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
+ if (font && ed->length > 0) {
+ SDL_Color black = {0, 0, 0, 255};
+ float current_y = 20.0f - scroll_y;
+
+ const char *line_start = ed->buffer;
+ while (line_start != NULL && *line_start != '\0') {
+ const char *line_end = strchr(line_start, '\n');
+ size_t len = line_end ? (size_t)(line_end - line_start)
+ : strlen(line_start);
+
+ if (len > 0 && current_y + line_height > 0 &&
+ current_y < render_h) {
+ char *temp_line = malloc(
+ len * TAB_SIZE +
+ 256); // Added padding for range visuals
+ int t_idx = 0;
+ int vis_col = 0;
+
+ for (size_t i = 0; i < len; i++) {
+ size_t abs_idx =
+ (line_start - ed->buffer) + i;
+ EditorRange *r = editor_get_range_at(
+ ed, (int)abs_idx);
+
+ if (r) {
+ for (int v = 0;
+ v < r->visual_cols; v++) {
temp_line[t_idx++] =
- line_start[i];
+ ' ';
+ }
+ vis_col += r->visual_cols;
+
+ // Safety handling if the fold
+ // bounds across the new line
+ if (r->end_byte >
+ (line_start - ed->buffer) +
+ len) {
+ const char
+ *next_line_start =
+ ed->buffer +
+ r->end_byte;
+ line_end =
+ next_line_start - 1;
+ break;
+ } else {
+ i = (size_t)(r->end_byte -
+ (line_start -
+ ed->buffer)) -
+ 1;
}
+ continue;
}
- temp_line[t_idx] = '\0';
- SDL_Surface *surf =
- TTF_RenderText_Blended(
- font, temp_line, t_idx, black);
- free(temp_line);
-
- if (surf) {
- SDL_Texture *tex =
- SDL_CreateTextureFromSurface(
- renderer, surf);
- SDL_FRect dst = {
- 20.0f - scroll_x, current_y,
- (float)surf->w,
- (float)surf->h};
- SDL_RenderTexture(renderer, tex,
- NULL, &dst);
- SDL_DestroyTexture(tex);
- SDL_DestroySurface(surf);
+
+ 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++;
+ temp_line[t_idx++] =
+ line_start[i];
}
}
- current_y += line_height;
- line_start = line_end ? line_end + 1 : NULL;
+ temp_line[t_idx] = '\0';
+ SDL_Surface *surf = TTF_RenderText_Blended(
+ font, temp_line, t_idx, black);
+ free(temp_line);
+
+ if (surf) {
+ SDL_Texture *tex =
+ SDL_CreateTextureFromSurface(
+ renderer, surf);
+ SDL_FRect dst = {
+ 20.0f - scroll_x, current_y,
+ (float)surf->w, (float)surf->h};
+ SDL_RenderTexture(renderer, tex, NULL,
+ &dst);
+ SDL_DestroyTexture(tex);
+ SDL_DestroySurface(surf);
+ }
}
+ current_y += line_height;
+ 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) {
- 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;
- } else {
- cur_col++;
- }
- }
+ int cur_row = 0, cur_col = 0;
+ const char *ptr = ed->buffer;
+ while (ptr < ed->buffer + ed->cursor_idx) {
+ int current_idx = (int)(ptr - ed->buffer);
+ EditorRange *r = editor_get_range_at(ed, current_idx);
- SDL_FRect cursor_rect = {
- 20.0f + (cur_col * char_width) - scroll_x,
- 20.0f + (cur_row * line_height) - scroll_y, 2.0f,
- (float)cursor_height};
+ if (r) {
+ cur_col += r->visual_cols;
+ ptr = ed->buffer + r->end_byte;
+ continue;
+ }
- if (cursor_rect.y + line_height > 0 &&
- cursor_rect.y < render_h) {
- SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
- SDL_RenderFillRect(renderer, &cursor_rect);
+ 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;
+ } else {
+ cur_col++;
}
+ }
+
+ SDL_FRect cursor_rect = {20.0f + (cur_col * char_width) - scroll_x,
+ 20.0f + (cur_row * line_height) - scroll_y,
+ 2.0f, (float)cursor_height};
+
+ if (cursor_rect.y + line_height > 0 && cursor_rect.y < render_h) {
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderFillRect(renderer, &cursor_rect);
+ }
+
+ SDL_RenderPresent(renderer);
+}
+
+int main(int argc, char *argv[]) {
+ // Otherwise wayland just wouldn't load.
+ SDL_SetHintWithPriority(SDL_HINT_VIDEO_DRIVER, "wayland",
+ SDL_HINT_OVERRIDE);
+
+ SDL_Init(SDL_INIT_VIDEO);
+ TTF_Init();
+
+ SDL_Window *window = SDL_CreateWindow(
+ "esc", 800, 600,
+ SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
+ SDL_Renderer *renderer = SDL_CreateRenderer(window, NULL);
+ SDL_SetRenderLogicalPresentation(renderer, 800, 600,
+ SDL_LOGICAL_PRESENTATION_DISABLED);
+
+ TTF_Font *font = TTF_OpenFont("IosevkaTermSS13-Extended.ttf", 24.0f);
+ int char_width;
+ TTF_GetGlyphMetrics(font, 'A', NULL, NULL, NULL, NULL, &char_width);
+ float line_height = (float)TTF_GetFontHeight(font) * 1.5;
+ float cursor_height = (float)TTF_GetFontHeight(font);
+ Editor *ed = editor_create(char_width, line_height);
+ if (argc > 1) {
+ setenv("ESC_BUFFILE", argv[1], true);
+ editor_load_file(ed, argv[1]);
+ }
- SDL_RenderPresent(renderer);
+ float scroll_x = 0.0f;
+ float scroll_y = 0.0f;
+ bool running = true;
+ bool is_dragging = false;
+
+ SDL_StartTextInput(window);
+
+
+ while (running) {
+ handle_events(ed, renderer, &scroll_x, &scroll_y, line_height,
+ &running, &is_dragging);
+ render_editor(renderer, ed, font, char_width, line_height,
+ cursor_height, scroll_x, scroll_y);
}
editor_destroy(ed);
diff --git a/unix_utils.c b/unix_utils.c
@@ -7,7 +7,7 @@
#include <unistd.h>
char *unix_select_command(void) {
- const char *menu_cmd = getenv("EI_MENU");
+ const char *menu_cmd = getenv("ESC_MENU");
if (!menu_cmd)
menu_cmd = "wmenu";