esc
Externally Scriptable Editor
git clone git://mccd.space/esc
| Log | Files | Refs | README |
commit 509ef5287a4c6d6e8c618da05b0f8dcb73560b99 parent 11aba860b262389f612f61f8e6b04da294e3dad2 Author: Marc Coquand <marc@coquand.email> Date: Thu, 19 Feb 2026 20:54:07 +0100 Lots of improvements Diffstat:
| M | editor.c | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | editor.h | | | 2 | ++ |
| M | main.c | | | 85 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
| M | unix_utils.c | | | 53 | ++++++++++++++++++++++++++++++++++++++++++----------- |
| M | unix_utils.h | | | 3 | ++- |
5 files changed, 167 insertions(+), 32 deletions(-)
diff --git a/editor.c b/editor.c
@@ -1,6 +1,7 @@
#include "editor.h"
#include <stdlib.h>
#include <string.h>
+#include <ctype.h>
Editor *editor_create(int char_width, float line_height) {
Editor *ed = malloc(sizeof(Editor));
@@ -208,6 +209,61 @@ void editor_set_cursor_from_coords(Editor *ed, float mx, float my,
ed->cursor_idx = (int)(ptr - ed->buffer);
}
+static bool is_word_char(char c) {
+ if (c == '\0') return false;
+ // For plumbing, we select everything except standard whitespace
+ return !isspace((unsigned char)c);
+}
+
+void editor_goto_word_start(Editor *ed) {
+ if (ed->length == 0) return;
+
+ int start = ed->cursor_idx;
+ if (start > 0 && !is_word_char(ed->buffer[start])) {
+ if (is_word_char(ed->buffer[start - 1])) {
+ start--;
+ }
+ }
+ while (start > 0 && is_word_char(ed->buffer[start - 1])) {
+ start--;
+ }
+ ed->cursor_idx = start;
+}
+
+void editor_select_word(Editor *ed) {
+ if (ed->length == 0) return;
+
+ int start = ed->cursor_idx;
+ int end = ed->cursor_idx;
+
+ if (start > 0 && !is_word_char(ed->buffer[start])) {
+ if (is_word_char(ed->buffer[start - 1])) {
+ start--;
+ end--;
+ }
+ }
+
+ // Only proceed if we are actually on a word character
+ if (!is_word_char(ed->buffer[start])) return;
+
+ // Expand left
+ while (start > 0 && is_word_char(ed->buffer[start - 1])) {
+ start--;
+ }
+
+ // Expand right
+ while (end < (int)ed->length && is_word_char(ed->buffer[end])) {
+ end++;
+ }
+
+ ed->selection_anchor = start;
+ ed->cursor_idx = end;
+}
+
+bool editor_has_selection(Editor *ed) {
+ return ed->selection_anchor != ed->cursor_idx;
+}
+
char* editor_get_selection(Editor *ed) {
if (ed->cursor_idx == ed->selection_anchor) return NULL;
diff --git a/editor.h b/editor.h
@@ -43,5 +43,7 @@ void editor_select_all(Editor *ed);
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);
#endif
diff --git a/main.c b/main.c
@@ -3,7 +3,9 @@
#include <SDL3/SDL.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keycode.h>
+#include <SDL3/SDL_log.h>
#include <SDL3/SDL_main.h>
+#include <SDL3/SDL_mouse.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <stdbool.h>
#include <stdio.h>
@@ -29,7 +31,8 @@ int main(int argc, char *argv[]) {
TTF_Font *font = TTF_OpenFont("Go-Mono.ttf", 24.0f);
int char_width;
TTF_GetGlyphMetrics(font, 'A', NULL, NULL, NULL, NULL, &char_width);
- float line_height = (float)TTF_GetFontHeight(font);
+ 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;
@@ -79,29 +82,35 @@ int main(int argc, char *argv[]) {
editor_newline(ed);
editor_clear_selection(ed);
break;
- case SDLK_U:
+ case SDLK_1:
if (!(event.key.mod &
SDL_KMOD_ALT))
break;
- char *cmd =
- unix_select_command();
+ cmd = unix_select_command();
if (cmd) {
char *selection =
editor_get_selection(
ed);
- unix_run_notify(
- cmd, selection);
+ 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_E:
+ case SDLK_EXCLAIM:
if (!(event.key.mod &
SDL_KMOD_ALT))
break;
- cmd =
- unix_select_command();
+ cmd = unix_select_command();
if (cmd) {
char *selection =
editor_get_selection(
@@ -110,16 +119,12 @@ int main(int argc, char *argv[]) {
unix_run_command(
cmd, selection);
- if (output &&
- strlen(output) >
- 0) {
+ if (output) {
editor_insert_text(
ed, output,
- true);
- }
-
- if (output)
+ false);
free(output);
+ }
if (selection)
free(selection);
free(cmd);
@@ -194,6 +199,47 @@ int main(int argc, char *argv[]) {
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);
+ }
+ }
}
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
@@ -266,9 +312,8 @@ int main(int argc, char *argv[]) {
20.0f + (cur_row * line_height) -
scroll_y,
(float)(cols_for_char *
- char_width), // <-- dynamic
- // width!
- line_height};
+ char_width),
+ cursor_height};
SDL_RenderFillRect(renderer, &sel_rect);
}
@@ -370,7 +415,7 @@ int main(int argc, char *argv[]) {
SDL_FRect cursor_rect = {
20.0f + (cur_col * char_width) - scroll_x,
20.0f + (cur_row * line_height) - scroll_y, 2.0f,
- (float)line_height};
+ (float)cursor_height};
if (cursor_rect.y + line_height > 0 &&
cursor_rect.y < render_h) {
diff --git a/unix_utils.c b/unix_utils.c
@@ -1,4 +1,5 @@
#include "unix_utils.h"
+#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -96,17 +97,47 @@ char *unix_run_command(const char *command, const char *input) {
return out_buf;
}
-void unix_run_notify(const char *command, const char *input) {
- char *output = unix_run_command(command, input);
-
- if (!output) return;
-
- if (fork() == 0) {
- // Milisecond expire time
- execlp("notify-send", "notify-send", "--expire-time=10000", "--app-name=ei", command, output, NULL);
-
- exit(1);
+// Ignores output and completely detaches the process
+void unix_exec_command(const char *command, const char *input) {
+ int pipe_in[2];
+ if (input && pipe(pipe_in) < 0) return;
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ if (fork() == 0) {
+ setsid();
+
+ if (input) {
+ dup2(pipe_in[0], STDIN_FILENO);
+ close(pipe_in[0]);
+ close(pipe_in[1]);
+ }
+
+ int dev_null = open("/dev/null", O_RDWR);
+ if (dev_null != -1) {
+ if (!input) dup2(dev_null, STDIN_FILENO);
+ dup2(dev_null, STDOUT_FILENO);
+ dup2(dev_null, STDERR_FILENO);
+ if (dev_null > STDERR_FILENO) close(dev_null);
+ }
+
+ execl("/bin/sh", "sh", "-c", command, NULL);
+ exit(1);
+ }
+ exit(0);
}
- free(output);
+ if (input) {
+ close(pipe_in[0]);
+ if (strlen(input) > 0) {
+ write(pipe_in[1], input, strlen(input));
+ if (input[strlen(input) - 1] != '\n') {
+ write(pipe_in[1], "\n", 1);
+ }
+ }
+ close(pipe_in[1]);
+ }
+
+ waitpid(pid, NULL, 0);
}
+
diff --git a/unix_utils.h b/unix_utils.h
@@ -9,5 +9,6 @@ char* unix_select_command(void);
// Uses memstreams for easy string building.
char *unix_run_command(const char *command, const char *input);
-void unix_run_notify(const char *command, const char *input);
+// Ignores output
+void unix_exec_command(const char *command, const char *input);
#endif