esc

Externally Scriptable Editor

git clone git://mccd.space/esc

commit 509ef5287a4c6d6e8c618da05b0f8dcb73560b99
parent 11aba860b262389f612f61f8e6b04da294e3dad2
Author: Marc Coquand <marc@coquand.email>
Date:   Thu, 19 Feb 2026 20:54:07 +0100

Lots of improvements

Diffstat:
Meditor.c | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Meditor.h | 2++
Mmain.c | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Munix_utils.c | 53++++++++++++++++++++++++++++++++++++++++++-----------
Munix_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