esc

Externally Scriptable Editor

git clone git://mccd.space/esc

commit c3721b0d69b193b892ff2f78dfe20905adfd3693
parent ea79a68bf6f26d5ae61c101d53e5a93b5e2d8eca
Author: Marc Coquand <marc@coquand.email>
Date:   Mon, 23 Feb 2026 21:52:09 +0100

*

Diffstat:
MREADME.md | 2+-
Meditor.c | 34++++++++++++++++++++++++++++++++++
Meditor.h | 1+
Mfuse_ipc.c | 26++++++++++++++++++--------
4 files changed, 54 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
@@ -18,7 +18,7 @@ Esc (**E**xternally **Sc**riptable Editor) is an extensible text editor written 
 ## Compiling
 
 ```sh
-cc main.c unix_utils.* editor.* strbuf.* fuse_ipc.* -o esc \
+cc main.c unix_utils.* editor.* strbuf.* fuse_ipc.* renderer.* -o esc \
    $(pkg-config --cflags --libs sdl3 sdl3-ttf fuse)
 ```
 
diff --git a/editor.c b/editor.c
@@ -627,6 +627,40 @@ VisualPos editor_byte_to_visual_pos(const Editor *ed, int byte_idx) {
 	return pos;
 }
 
+int editor_visual_pos_to_byte(const Editor *ed, VisualPos target) {
+	int row = 0, col = 0;
+	const char *ptr = ed->text.data;
+	const char *end = ed->text.data + ed->text.len;
+
+	while (ptr < end) {
+		if (row == target.row && col >= target.col)
+			return (int)(ptr - ed->text.data);
+
+		int current_idx = (int)(ptr - ed->text.data);
+		EditorRange *r =
+		    editor_get_replacement_at((Editor *)ed, current_idx);
+		if (r) {
+			col += r->data.replacement.visual_cols;
+			ptr = ed->text.data + r->end_byte;
+			continue;
+		}
+
+		const char *prev = ptr;
+		Uint32 cp = SDL_StepUTF8(&ptr, NULL);
+		if (cp == '\n') {
+			if (row == target.row)
+				return (int)(prev - ed->text.data);
+			row++;
+			col = 0;
+		} else if (cp == '\t') {
+			col += TAB_SIZE - (col % TAB_SIZE);
+		} else {
+			col++;
+		}
+	}
+	return (int)(end - ed->text.data);
+}
+
 char *editor_get_selection(Editor *ed) {
 	if (ed->cursor_idx == ed->selection_anchor)
 		return NULL;
diff --git a/editor.h b/editor.h
@@ -91,4 +91,5 @@ typedef struct {
 	int col;
 } VisualPos;
 VisualPos editor_byte_to_visual_pos(const Editor *ed, int byte_idx);
+int editor_visual_pos_to_byte(const Editor *ed, VisualPos target);
 #endif
diff --git a/fuse_ipc.c b/fuse_ipc.c
@@ -178,9 +178,13 @@ static int op_getattr(const char *path, struct stat *st) {
 
 		if (strcmp(path, "/cursor") == 0) {
 			char tmp[64];
+			VisualPos cp =
+			    editor_byte_to_visual_pos(ed, ed->cursor_idx);
+			VisualPos sp = editor_byte_to_visual_pos(
+			    ed, ed->selection_anchor);
 			st->st_size =
-			    snprintf(tmp, sizeof(tmp), "%d %d\n",
-				     ed->cursor_idx, ed->selection_anchor);
+			    snprintf(tmp, sizeof(tmp), "%d %d %d %d\n",
+				     cp.row, cp.col, sp.row, sp.col);
 		} else if (strcmp(path, "/buffer/0/body") == 0) {
 			st->st_size = (off_t)ed->text.len;
 		} else if (strcmp(path, "/buffer/0/path") == 0) {
@@ -318,8 +322,12 @@ static int op_read(const char *path, char *buf, size_t size, off_t offset,
 	if (strcmp(path, "/cursor") == 0) {
 		editor_lock(ed);
 		char tmp[64];
-		int n = snprintf(tmp, sizeof(tmp), "%d %d\n", ed->cursor_idx,
-				 ed->selection_anchor);
+		VisualPos cp =
+		    editor_byte_to_visual_pos(ed, ed->cursor_idx);
+		VisualPos sp =
+		    editor_byte_to_visual_pos(ed, ed->selection_anchor);
+		int n = snprintf(tmp, sizeof(tmp), "%d %d %d %d\n",
+				 cp.row, cp.col, sp.row, sp.col);
 		editor_unlock(ed);
 
 		if (offset >= (off_t)n)
@@ -473,16 +481,18 @@ static int op_release(const char *path, struct fuse_file_info *fi) {
 
 	} else if (strcmp(path, "/cursor") == 0 && wb->data && wb->len > 0) {
 		char *tmp = malloc(wb->len + 1);
-		int anchor = 0, cursor_pos = 0;
+		int crow = 0, ccol = 0, srow = 0, scol = 0;
 		if (tmp) {
 			memcpy(tmp, wb->data, wb->len);
 			tmp[wb->len] = '\0';
-			sscanf(tmp, "%d %d", &anchor, &cursor_pos);
+			sscanf(tmp, "%d %d %d %d", &crow, &ccol, &srow, &scol);
 			free(tmp);
 		}
 		editor_lock(ed);
-		ed->selection_anchor = anchor;
-		ed->cursor_idx = cursor_pos;
+		ed->cursor_idx = editor_visual_pos_to_byte(
+		    ed, (VisualPos){crow, ccol});
+		ed->selection_anchor = editor_visual_pos_to_byte(
+		    ed, (VisualPos){srow, scol});
 		editor_unlock(ed);
 		wake_render();
 	}