esc
Externally Scriptable Editor
git clone git://mccd.space/esc
| Log | Files | Refs | README |
commit a88cbab0d1f560f8336cee6e055e003285381acf parent f9ff11d6c910dc2551fb92ab0e66656b7a782a00 Author: Marc Coquand <marc@coquand.email> Date: Mon, 23 Feb 2026 17:12:43 +0100 * Diffstat:
| M | fuse_ipc.c | | | 250 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- |
1 file changed, 122 insertions(+), 128 deletions(-)
diff --git a/fuse_ipc.c b/fuse_ipc.c
@@ -15,7 +15,7 @@
/* Per-open write accumulator (stored in fi->fh cast to/from uintptr_t) */
typedef struct {
- char *data;
+ char *data;
size_t len;
size_t cap;
} WriteBuffer;
@@ -27,34 +27,32 @@ typedef struct {
*/
#define MAX_TEMP_FILES 8
typedef struct {
- char path[256];
- char *data;
+ char path[256];
+ char *data;
size_t len;
} TempFile;
/* FUSE private data passed as private_data to fuse_new */
typedef struct {
- Editor *ed;
- TempFile temps[MAX_TEMP_FILES];
+ Editor *ed;
+ TempFile temps[MAX_TEMP_FILES];
} FuseCtx;
struct FuseIPC {
- struct fuse *fuse;
- struct fuse_chan *chan;
- SDL_Thread *thread;
- char mountpoint[256];
- FuseCtx *ctx;
+ struct fuse *fuse;
+ struct fuse_chan *chan;
+ SDL_Thread *thread;
+ char mountpoint[256];
+ FuseCtx *ctx;
};
-static FuseCtx *get_ctx(void)
-{
+static FuseCtx *get_ctx(void) {
return (FuseCtx *)fuse_get_context()->private_data;
}
/* ---- temp-file helpers ------------------------------------------------ */
-static TempFile *find_temp(FuseCtx *ctx, const char *path)
-{
+static TempFile *find_temp(FuseCtx *ctx, const char *path) {
for (int i = 0; i < MAX_TEMP_FILES; i++) {
if (ctx->temps[i].path[0] &&
strcmp(ctx->temps[i].path, path) == 0)
@@ -63,31 +61,28 @@ static TempFile *find_temp(FuseCtx *ctx, const char *path)
return NULL;
}
-static TempFile *alloc_temp(FuseCtx *ctx, const char *path)
-{
+static TempFile *alloc_temp(FuseCtx *ctx, const char *path) {
for (int i = 0; i < MAX_TEMP_FILES; i++) {
if (!ctx->temps[i].path[0]) {
strncpy(ctx->temps[i].path, path,
sizeof(ctx->temps[i].path) - 1);
ctx->temps[i].data = NULL;
- ctx->temps[i].len = 0;
+ ctx->temps[i].len = 0;
return &ctx->temps[i];
}
}
return NULL;
}
-static void free_temp(TempFile *tf)
-{
+static void free_temp(TempFile *tf) {
free(tf->data);
- tf->data = NULL;
- tf->len = 0;
+ tf->data = NULL;
+ tf->len = 0;
tf->path[0] = '\0';
}
/* Push a wakeup event so SDL_WaitEvent unblocks and the frame is redrawn. */
-static void wake_render(void)
-{
+static void wake_render(void) {
SDL_Event ev;
memset(&ev, 0, sizeof(ev));
ev.type = SDL_EVENT_USER;
@@ -96,23 +91,23 @@ static void wake_render(void)
/* ---- path helpers ---------------------------------------------------- */
-static int is_dir(const char *path)
-{
- return strcmp(path, "/") == 0 ||
- strcmp(path, "/buffer") == 0 ||
+static int is_dir(const char *path) {
+ return strcmp(path, "/") == 0 || strcmp(path, "/buffer") == 0 ||
strcmp(path, "/buffer/0") == 0;
}
-static int is_file(const char *path)
-{
+static int is_symlink(const char *path) {
+ return strcmp(path, "/cwd") == 0;
+}
+
+static int is_file(const char *path) {
return strcmp(path, "/cursor") == 0 ||
strcmp(path, "/buffer/0/body") == 0 ||
strcmp(path, "/buffer/0/path") == 0 ||
strcmp(path, "/buffer/0/ranges") == 0;
}
-static int is_writable(const char *path)
-{
+static int is_writable(const char *path) {
return strcmp(path, "/cursor") == 0 ||
strcmp(path, "/buffer/0/body") == 0;
}
@@ -121,24 +116,23 @@ static int is_writable(const char *path)
/* Build a malloc'd text representation of ed->ranges.
* Caller must hold editor lock. */
-static char *build_ranges(Editor *ed, size_t *out_len)
-{
+static char *build_ranges(Editor *ed, size_t *out_len) {
size_t total = 0;
- char tmp[128];
+ char tmp[128];
for (size_t i = 0; i < ed->ranges_count; i++) {
EditorRange *r = &ed->ranges[i];
if (r->type == RANGE_FORMAT)
- total += snprintf(tmp, sizeof(tmp),
- "format %d %d %d %d\n",
- r->start_byte, r->end_byte,
- r->data.format.bold ? 1 : 0,
- r->data.format.italic ? 1 : 0);
+ total +=
+ snprintf(tmp, sizeof(tmp), "format %d %d %d %d\n",
+ r->start_byte, r->end_byte,
+ r->data.format.bold ? 1 : 0,
+ r->data.format.italic ? 1 : 0);
else
- total += snprintf(tmp, sizeof(tmp),
- "replacement %d %d %d\n",
- r->start_byte, r->end_byte,
- r->data.replacement.visual_cols);
+ total +=
+ snprintf(tmp, sizeof(tmp), "replacement %d %d %d\n",
+ r->start_byte, r->end_byte,
+ r->data.replacement.visual_cols);
}
char *buf = malloc(total + 1);
@@ -150,51 +144,51 @@ static char *build_ranges(Editor *ed, size_t *out_len)
EditorRange *r = &ed->ranges[i];
if (r->type == RANGE_FORMAT)
pos += sprintf(buf + pos, "format %d %d %d %d\n",
- r->start_byte, r->end_byte,
- r->data.format.bold ? 1 : 0,
- r->data.format.italic ? 1 : 0);
+ r->start_byte, r->end_byte,
+ r->data.format.bold ? 1 : 0,
+ r->data.format.italic ? 1 : 0);
else
pos += sprintf(buf + pos, "replacement %d %d %d\n",
- r->start_byte, r->end_byte,
- r->data.replacement.visual_cols);
+ r->start_byte, r->end_byte,
+ r->data.replacement.visual_cols);
}
buf[pos] = '\0';
- *out_len = pos;
+ *out_len = pos;
return buf;
}
/* ---- FUSE callbacks -------------------------------------------------- */
-static int op_getattr(const char *path, struct stat *st)
-{
+static int op_getattr(const char *path, struct stat *st) {
memset(st, 0, sizeof(*st));
FuseCtx *ctx = get_ctx();
- Editor *ed = ctx->ed;
+ Editor *ed = ctx->ed;
if (is_dir(path)) {
- st->st_mode = S_IFDIR | 0755;
+ st->st_mode = S_IFDIR | 0755;
st->st_nlink = 2;
return 0;
}
if (is_file(path)) {
- st->st_mode = S_IFREG | 0644;
+ st->st_mode = S_IFREG | 0644;
st->st_nlink = 1;
editor_lock(ed);
if (strcmp(path, "/cursor") == 0) {
char tmp[64];
- st->st_size = snprintf(tmp, sizeof(tmp), "%d %d\n",
- ed->cursor_idx, ed->selection_anchor);
+ st->st_size =
+ snprintf(tmp, sizeof(tmp), "%d %d\n",
+ ed->cursor_idx, ed->selection_anchor);
} else if (strcmp(path, "/buffer/0/body") == 0) {
st->st_size = (off_t)ed->text.len;
} else if (strcmp(path, "/buffer/0/path") == 0) {
- st->st_size = ed->filename
- ? (off_t)strlen(ed->filename) : 0;
+ st->st_size =
+ ed->filename ? (off_t)strlen(ed->filename) : 0;
} else if (strcmp(path, "/buffer/0/ranges") == 0) {
size_t sz;
- char *tmp = build_ranges(ed, &sz);
+ char *tmp = build_ranges(ed, &sz);
st->st_size = (off_t)sz;
free(tmp);
}
@@ -203,12 +197,21 @@ static int op_getattr(const char *path, struct stat *st)
return 0;
}
+ if (is_symlink(path)) {
+ char tmp[64];
+ st->st_mode = S_IFLNK | 0777;
+ st->st_nlink = 1;
+ st->st_size =
+ snprintf(tmp, sizeof(tmp), "/proc/%d/cwd", (int)getpid());
+ return 0;
+ }
+
/* In-flight or committed temp files */
TempFile *tf = find_temp(ctx, path);
if (tf) {
- st->st_mode = S_IFREG | 0644;
+ st->st_mode = S_IFREG | 0644;
st->st_nlink = 1;
- st->st_size = (off_t)tf->len;
+ st->st_size = (off_t)tf->len;
return 0;
}
@@ -216,8 +219,7 @@ static int op_getattr(const char *path, struct stat *st)
}
static int op_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
- off_t offset, struct fuse_file_info *fi)
-{
+ off_t offset, struct fuse_file_info *fi) {
(void)offset;
(void)fi;
@@ -226,6 +228,7 @@ static int op_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
if (strcmp(path, "/") == 0) {
filler(buf, "cursor", NULL, 0);
+ filler(buf, "cwd", NULL, 0);
filler(buf, "buffer", NULL, 0);
return 0;
}
@@ -242,8 +245,14 @@ static int op_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
return -ENOENT;
}
-static int op_open(const char *path, struct fuse_file_info *fi)
-{
+static int op_readlink(const char *path, char *buf, size_t size) {
+ if (!is_symlink(path))
+ return -ENOENT;
+ snprintf(buf, size, "/proc/%d/cwd", (int)getpid());
+ return 0;
+}
+
+static int op_open(const char *path, struct fuse_file_info *fi) {
fi->fh = 0;
if (!is_file(path)) {
@@ -275,9 +284,7 @@ static int op_open(const char *path, struct fuse_file_info *fi)
* op_create — called when a new file is created (e.g. sed -i's temp file).
* We only allow creation inside /buffer/0/ and reject known permanent names.
*/
-static int op_create(const char *path, mode_t mode,
- struct fuse_file_info *fi)
-{
+static int op_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
(void)mode;
FuseCtx *ctx = get_ctx();
@@ -303,17 +310,16 @@ static int op_create(const char *path, mode_t mode,
}
static int op_read(const char *path, char *buf, size_t size, off_t offset,
- struct fuse_file_info *fi)
-{
+ struct fuse_file_info *fi) {
(void)fi;
FuseCtx *ctx = get_ctx();
- Editor *ed = ctx->ed;
+ Editor *ed = ctx->ed;
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);
+ int n = snprintf(tmp, sizeof(tmp), "%d %d\n", ed->cursor_idx,
+ ed->selection_anchor);
editor_unlock(ed);
if (offset >= (off_t)n)
@@ -357,7 +363,7 @@ static int op_read(const char *path, char *buf, size_t size, off_t offset,
if (strcmp(path, "/buffer/0/ranges") == 0) {
editor_lock(ed);
size_t rlen;
- char *rbuf = build_ranges(ed, &rlen);
+ char *rbuf = build_ranges(ed, &rlen);
editor_unlock(ed);
if (!rbuf)
@@ -378,22 +384,21 @@ static int op_read(const char *path, char *buf, size_t size, off_t offset,
}
static int op_write(const char *path, const char *buf, size_t size,
- off_t offset, struct fuse_file_info *fi)
-{
+ off_t offset, struct fuse_file_info *fi) {
(void)path;
if (!fi->fh)
return -EACCES;
- WriteBuffer *wb = (WriteBuffer *)(uintptr_t)fi->fh;
- size_t end = (size_t)offset + size;
+ WriteBuffer *wb = (WriteBuffer *)(uintptr_t)fi->fh;
+ size_t end = (size_t)offset + size;
if (end > wb->cap) {
size_t new_cap = end < 128 ? 128 : end * 2;
- char *nd = realloc(wb->data, new_cap);
+ char *nd = realloc(wb->data, new_cap);
if (!nd)
return -ENOMEM;
wb->data = nd;
- wb->cap = new_cap;
+ wb->cap = new_cap;
}
memcpy(wb->data + offset, buf, size);
@@ -402,8 +407,7 @@ static int op_write(const char *path, const char *buf, size_t size,
return (int)size;
}
-static int op_truncate(const char *path, off_t size)
-{
+static int op_truncate(const char *path, off_t size) {
(void)size;
/* Accept truncate on writable files; we reset on open anyway */
if (is_writable(path))
@@ -416,8 +420,7 @@ static int op_truncate(const char *path, off_t size)
}
static int op_ftruncate(const char *path, off_t size,
- struct fuse_file_info *fi)
-{
+ struct fuse_file_info *fi) {
(void)path;
if (!fi->fh)
return 0;
@@ -427,24 +430,23 @@ static int op_ftruncate(const char *path, off_t size,
return 0;
}
-/* Apply content to the editor and wake the render loop. Caller holds no lock. */
-static void apply_body(Editor *ed, const char *data, size_t len)
-{
+/* Apply content to the editor and wake the render loop. Caller holds no lock.
+ */
+static void apply_body(Editor *ed, const char *data, size_t len) {
editor_lock(ed);
- ed->ranges_count = 0;
+ ed->ranges_count = 0;
strbuf_delete(&ed->text, 0, ed->text.len);
strbuf_insert(&ed->text, 0, data ? data : "", len);
- ed->cursor_idx = 0;
+ ed->cursor_idx = 0;
ed->selection_anchor = 0;
editor_unlock(ed);
editor_parse_ansi_codes(ed);
wake_render();
}
-static int op_release(const char *path, struct fuse_file_info *fi)
-{
+static int op_release(const char *path, struct fuse_file_info *fi) {
FuseCtx *ctx = get_ctx();
- Editor *ed = ctx->ed;
+ Editor *ed = ctx->ed;
if (!fi->fh)
return 0;
@@ -460,7 +462,7 @@ static int op_release(const char *path, struct fuse_file_info *fi)
if (tf) {
free(tf->data);
tf->data = wb->data; /* steal */
- tf->len = wb->len;
+ tf->len = wb->len;
wb->data = NULL;
free(wb);
return 0;
@@ -469,10 +471,9 @@ static int op_release(const char *path, struct fuse_file_info *fi)
if (strcmp(path, "/buffer/0/body") == 0) {
apply_body(ed, wb->data, wb->len);
- } else if (strcmp(path, "/cursor") == 0 &&
- wb->data && wb->len > 0) {
+ } else if (strcmp(path, "/cursor") == 0 && wb->data && wb->len > 0) {
char *tmp = malloc(wb->len + 1);
- int anchor = 0, cursor_pos = 0;
+ int anchor = 0, cursor_pos = 0;
if (tmp) {
memcpy(tmp, wb->data, wb->len);
tmp[wb->len] = '\0';
@@ -481,7 +482,7 @@ static int op_release(const char *path, struct fuse_file_info *fi)
}
editor_lock(ed);
ed->selection_anchor = anchor;
- ed->cursor_idx = cursor_pos;
+ ed->cursor_idx = cursor_pos;
editor_unlock(ed);
wake_render();
}
@@ -491,13 +492,9 @@ static int op_release(const char *path, struct fuse_file_info *fi)
return 0;
}
-/*
- * op_rename — the final step of `sed -i`: rename the temp file over body.
- */
-static int op_rename(const char *from, const char *to)
-{
+static int op_rename(const char *from, const char *to) {
FuseCtx *ctx = get_ctx();
- Editor *ed = ctx->ed;
+ Editor *ed = ctx->ed;
TempFile *tf = find_temp(ctx, from);
if (!tf)
@@ -510,8 +507,7 @@ static int op_rename(const char *from, const char *to)
return 0;
}
-static int op_unlink(const char *path)
-{
+static int op_unlink(const char *path) {
FuseCtx *ctx = get_ctx();
TempFile *tf = find_temp(ctx, path);
if (!tf)
@@ -522,8 +518,7 @@ static int op_unlink(const char *path)
/* ---- FUSE thread ----------------------------------------------------- */
-static int fuse_thread_fn(void *arg)
-{
+static int fuse_thread_fn(void *arg) {
fuse_loop((struct fuse *)arg);
return 0;
}
@@ -531,23 +526,23 @@ static int fuse_thread_fn(void *arg)
/* ---- operations table ------------------------------------------------ */
static const struct fuse_operations ops = {
- .getattr = op_getattr,
- .readdir = op_readdir,
- .open = op_open,
- .create = op_create,
- .read = op_read,
- .write = op_write,
- .truncate = op_truncate,
- .ftruncate = op_ftruncate,
- .release = op_release,
- .rename = op_rename,
- .unlink = op_unlink,
+ .getattr = op_getattr,
+ .readdir = op_readdir,
+ .readlink = op_readlink,
+ .open = op_open,
+ .create = op_create,
+ .read = op_read,
+ .write = op_write,
+ .truncate = op_truncate,
+ .ftruncate = op_ftruncate,
+ .release = op_release,
+ .rename = op_rename,
+ .unlink = op_unlink,
};
/* ---- Public API ------------------------------------------------------ */
-FuseIPC *fuse_ipc_start(Editor *ed)
-{
+FuseIPC *fuse_ipc_start(Editor *ed) {
FuseIPC *ipc = calloc(1, sizeof(FuseIPC));
if (!ipc)
return NULL;
@@ -557,18 +552,18 @@ FuseIPC *fuse_ipc_start(Editor *ed)
free(ipc);
return NULL;
}
- ctx->ed = ed;
+ ctx->ed = ed;
ipc->ctx = ctx;
- const char *xdg = getenv("XDG_RUNTIME_DIR");
+ const char *xdg = getenv("XDG_RUNTIME_DIR");
const char *base = xdg ? xdg : "/tmp";
char esc_dir[240];
snprintf(esc_dir, sizeof(esc_dir), "%s/esc", base);
mkdir(esc_dir, 0700); /* ignore EEXIST */
- snprintf(ipc->mountpoint, sizeof(ipc->mountpoint),
- "%s/esc/%d", base, (int)getpid());
+ snprintf(ipc->mountpoint, sizeof(ipc->mountpoint), "%s/esc/%d", base,
+ (int)getpid());
if (mkdir(ipc->mountpoint, 0700) != 0) {
if (errno == EEXIST) {
@@ -578,8 +573,8 @@ FuseIPC *fuse_ipc_start(Editor *ed)
*/
char cmd[512];
snprintf(cmd, sizeof(cmd),
- "fusermount -u -- %s 2>/dev/null",
- ipc->mountpoint);
+ "fusermount -u -- %s 2>/dev/null",
+ ipc->mountpoint);
system(cmd); /* ignore errors */
} else {
SDL_Log("fuse_ipc: mkdir %s failed: %s",
@@ -626,8 +621,7 @@ FuseIPC *fuse_ipc_start(Editor *ed)
return ipc;
}
-void fuse_ipc_stop(FuseIPC *ipc)
-{
+void fuse_ipc_stop(FuseIPC *ipc) {
if (!ipc)
return;
/* Unmount first: tears down the kernel-side channel, causing