esc
Externally Scriptable Editor
git clone git://mccd.space/esc
| Log | Files | Refs | README |
commit c3721b0d69b193b892ff2f78dfe20905adfd3693 parent ea79a68bf6f26d5ae61c101d53e5a93b5e2d8eca Author: Marc Coquand <marc@coquand.email> Date: Mon, 23 Feb 2026 21:52:09 +0100 * Diffstat:
| M | README.md | | | 2 | +- |
| M | editor.c | | | 34 | ++++++++++++++++++++++++++++++++++ |
| M | editor.h | | | 1 | + |
| M | fuse_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();
}