From d04b1ffca55c41ec8bc3b7a31ade57cdea4995dc Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 19 Jun 2026 18:37:10 +0000 Subject: [PATCH] Use a floating pane for the buffer mode editor instead of a popup. --- server.c | 1 + spawn.c | 195 +++++++++++++++++++++++++++++++++++++++++++++--- window-buffer.c | 87 ++++++++++++++++++++- window.c | 3 + 4 files changed, 275 insertions(+), 11 deletions(-) diff --git a/server.c b/server.c index e83b3230..e3f923d8 100644 --- a/server.c +++ b/server.c @@ -492,6 +492,7 @@ server_child_exited(pid_t pid, int status) wp->flags |= PANE_EXITED; window_pane_wait_finish(wp); + spawn_editor_finish(wp); if (window_pane_destroy_ready(wp)) server_destroy_pane(wp, 1); diff --git a/spawn.c b/spawn.c index e6bfed00..0c6e49d1 100644 --- a/spawn.c +++ b/spawn.c @@ -17,10 +17,12 @@ */ #include +#include #include #include #include +#include #include #include #include @@ -56,11 +58,10 @@ spawn_log(const char *from, struct spawn_context *sc) struct session *s = sc->s; struct winlink *wl = sc->wl; struct window_pane *wp0 = sc->wp0; - const char *name = cmdq_get_name(sc->item); + const char *name = (sc->name == NULL ? "none" : sc->name); char tmp[128]; - log_debug("%s: %s, flags=%#x", from, name, sc->flags); - + log_debug("%s: name=%s, flags=%#x", from, name, sc->flags); if (wl != NULL && wp0 != NULL) xsnprintf(tmp, sizeof tmp, "wl=%d wp0=%%%u", wl->idx, wp0->id); else if (wl != NULL) @@ -70,7 +71,6 @@ spawn_log(const char *from, struct spawn_context *sc) else xsnprintf(tmp, sizeof tmp, "wl=none wp0=none"); log_debug("%s: s=$%u %s idx=%d", from, s->id, tmp, sc->idx); - log_debug("%s: name=%s", from, sc->name == NULL ? "none" : sc->name); } struct winlink * @@ -203,9 +203,9 @@ struct window_pane * spawn_pane(struct spawn_context *sc, char **cause) { struct cmdq_item *item = sc->item; - struct cmd_find_state *target = cmdq_get_target(item); - struct client *c = cmdq_get_client(item); + struct client *c; struct session *s = sc->s; + struct session *ts; struct window *w = sc->wl->window; struct window_pane *new_wp; struct environ *child; @@ -222,6 +222,14 @@ spawn_pane(struct spawn_context *sc, char **cause) sigset_t set, oldset; key_code key; + if (item != NULL) { + ts = cmdq_get_target(item)->s; + c = cmdq_get_client(item); + } else { + ts = s; + c = sc->tc; + } + spawn_log(__func__, sc); /* @@ -229,16 +237,19 @@ spawn_pane(struct spawn_context *sc, char **cause) * the pane's stored one unless specified. */ if (sc->cwd != NULL) { - cwd = format_single(item, sc->cwd, c, target->s, NULL, NULL); + if (item != NULL) + cwd = format_single(item, sc->cwd, c, ts, NULL, NULL); + else + cwd = xstrdup(sc->cwd); if (*cwd != '/') { xasprintf(&new_cwd, "%s%s%s", - server_client_get_cwd(c, target->s), + server_client_get_cwd(c, ts), *cwd != '\0' ? "/" : "", cwd); free(cwd); cwd = new_cwd; } } else if (~sc->flags & SPAWN_RESPAWN) - cwd = xstrdup(server_client_get_cwd(c, target->s)); + cwd = xstrdup(server_client_get_cwd(c, ts)); else cwd = NULL; @@ -496,3 +507,169 @@ complete: notify_window("window-layout-changed", w); return (new_wp); } + +struct spawn_editor_state { + char *path; + pid_t pid; + spawn_finish_edit_cb cb; + void *arg; +}; + +static void +spawn_editor_free(struct spawn_editor_state *es) +{ + unlink(es->path); + free(es->path); + free(es); +} + +void +spawn_cancel_editor(struct spawn_editor_state *es) +{ + if (es == NULL) + return; + es->cb = NULL; + es->arg = NULL; +} + +pid_t +spawn_get_editor_pid(struct spawn_editor_state *es) +{ + if (es == NULL) + return (-1); + return (es->pid); +} + +void +spawn_editor_finish(struct window_pane *wp) +{ + struct spawn_editor_state *es = wp->editor; + FILE *f; + char *buf = NULL; + off_t len = 0; + int status = 128 + SIGHUP; + + if (es == NULL) + return; + wp->editor = NULL; + + if (wp->flags & PANE_STATUSREADY) { + if (WIFEXITED(wp->status)) + status = WEXITSTATUS(wp->status); + else if (WIFSIGNALED(wp->status)) + status = WTERMSIG(wp->status) + 128; + } + + if (es->cb == NULL) { + spawn_editor_free(es); + return; + } + if (status != 0) { + es->cb(NULL, 0, es->arg); + spawn_editor_free(es); + return; + } + + f = fopen(es->path, "r"); + if (f != NULL) { + if (fseeko(f, 0, SEEK_END) == 0) { + len = ftello(f); + if (len > 0 && (uintmax_t)len <= (uintmax_t)SIZE_MAX) { + if (fseeko(f, 0, SEEK_SET) == 0) { + buf = malloc(len); + if (buf != NULL && + fread(buf, len, 1, f) != 1) { + free(buf); + buf = NULL; + len = 0; + } + } + } else + len = 0; + } + fclose(f); + } + + es->cb(buf, len, es->arg); + spawn_editor_free(es); +} + +struct spawn_editor_state * +spawn_editor(struct client *c, const char *buf, size_t len, + spawn_finish_edit_cb cb, void *arg) +{ + struct spawn_editor_state *es; + struct spawn_context sc = { 0 }; + struct session *s = c->session; + struct winlink *wl = s->curw; + struct window *w = wl->window; + struct window_pane *wp; + struct layout_cell *lc; + struct environ *env; + FILE *f; + char *cmd, *cause = NULL; + char path[] = _PATH_TMP "tmux.XXXXXXXX"; + const char *editor; + int fd; + u_int px, py, sx, sy; + + editor = options_get_string(global_options, "editor"); + fd = mkstemp(path); + if (fd == -1) + return (NULL); + f = fdopen(fd, "w"); + if (f == NULL) { + close(fd); + unlink(path); + return (NULL); + } + if (fwrite(buf, len, 1, f) != 1) { + fclose(f); + unlink(path); + return (NULL); + } + fclose(f); + + es = xcalloc(1, sizeof *es); + es->path = xstrdup(path); + es->cb = cb; + es->arg = arg; + + sx = w->sx * 9 / 10; + sy = w->sy * 9 / 10; + px = w->sx / 2 - sx / 2; + py = w->sy / 2 - sy / 2; + window_push_zoom(w, 1, 0); + lc = layout_floating_pane(w, sx, sy, px, py); + if (lc == NULL) { + spawn_editor_free(es); + return (NULL); + } + + xasprintf(&cmd, "%s %s", editor, path); + env = environ_create(); + sc.s = s; + sc.wl = wl; + sc.tc = c; + sc.wp0 = w->active; + sc.lc = lc; + sc.argc = 1; + sc.argv = &cmd; + sc.environ = env; + sc.idx = -1; + sc.cwd = _PATH_TMP; + sc.flags = SPAWN_FLOATING; + + wp = spawn_pane(&sc, &cause); + free(cmd); + environ_free(env); + if (wp == NULL) { + free(cause); + spawn_editor_free(es); + return (NULL); + } + options_set_number(wp->options, "remain-on-exit", 0); + es->pid = wp->pid; + wp->editor = es; + return (es); +} diff --git a/window-buffer.c b/window-buffer.c index b24e6eb1..5c0424c2 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -87,6 +87,8 @@ struct window_buffer_modedata { struct cmd_find_state fs; struct mode_tree_data *data; + struct spawn_editor_state *editor; + struct window_buffer_editdata *edit; char *command; char *format; char *key_format; @@ -99,8 +101,12 @@ struct window_buffer_editdata { u_int wp_id; char *name; struct paste_buffer *pb; + struct spawn_editor_state *editor; }; +static void window_buffer_finish_edit(struct window_buffer_editdata *); +static void window_buffer_draw_waiting(struct window_buffer_modedata *); + static enum sort_order window_buffer_order_seq[] = { SORT_CREATION, SORT_NAME, @@ -394,6 +400,11 @@ window_buffer_free(struct window_mode_entry *wme) if (data == NULL) return; + if (data->editor != NULL) { + spawn_cancel_editor(data->editor); + window_buffer_finish_edit(data->edit); + } + mode_tree_free(data->data); for (i = 0; i < data->item_size; i++) @@ -422,6 +433,7 @@ window_buffer_update(struct window_mode_entry *wme) mode_tree_build(data->data); mode_tree_draw(data->data); + window_buffer_draw_waiting(data); data->wp->flags |= PANE_REDRAW; } @@ -466,6 +478,49 @@ window_buffer_finish_edit(struct window_buffer_editdata *ed) free(ed); } +static void +window_buffer_draw_waiting(struct window_buffer_modedata *data) +{ + struct screen_write_ctx ctx; + struct screen *s = data->wp->screen; + struct grid_cell gc; + char text[128]; + u_int sx, sy, box_w, box_h, x, y, text_x; + size_t textlen; + pid_t pid; + + if (data->editor == NULL) + return; + sx = screen_size_x(s); + sy = screen_size_y(s); + if (sx == 0 || sy == 0) + return; + + pid = spawn_get_editor_pid(data->editor); + if (pid == -1) + xsnprintf(text, sizeof text, "WAITING FOR EDITOR"); + else + xsnprintf(text, sizeof text, "WAITING FOR EDITOR (PID %ld)", + (long)pid); + + textlen = strlen(text); + box_w = textlen + 4; + box_h = 3; + if (sx < box_w || sy < box_h) + return; + x = (sx - box_w) / 2; + y = (sy - box_h) / 2; + text_x = x + (box_w - textlen) / 2; + + memcpy(&gc, &grid_default_cell, sizeof gc); + screen_write_start(&ctx, s); + screen_write_cursormove(&ctx, x, y, 0); + screen_write_box(&ctx, box_w, box_h, BOX_LINES_DEFAULT, &gc, NULL); + screen_write_cursormove(&ctx, text_x, y + 1, 0); + screen_write_nputs(&ctx, box_w - 2, &gc, "%s", text); + screen_write_stop(&ctx); +} + static void window_buffer_edit_close_cb(char *buf, size_t len, void *arg) { @@ -477,6 +532,18 @@ window_buffer_edit_close_cb(char *buf, size_t len, void *arg) struct window_buffer_modedata *data; struct window_mode_entry *wme; + wp = window_pane_find_by_id(ed->wp_id); + if (wp != NULL) { + wme = TAILQ_FIRST(&wp->modes); + if (wme != NULL && wme->mode == &window_buffer_mode) { + data = wme->data; + if (data->editor == ed->editor) { + data->editor = NULL; + data->edit = NULL; + } + } + } + if (buf == NULL || len == 0) { window_buffer_finish_edit(ed); return; @@ -499,10 +566,11 @@ window_buffer_edit_close_cb(char *buf, size_t len, void *arg) wp = window_pane_find_by_id(ed->wp_id); if (wp != NULL) { wme = TAILQ_FIRST(&wp->modes); - if (wme->mode == &window_buffer_mode) { + if (wme != NULL && wme->mode == &window_buffer_mode) { data = wme->data; mode_tree_build(data->data); mode_tree_draw(data->data); + window_buffer_draw_waiting(data); } wp->flags |= PANE_REDRAW; } @@ -518,6 +586,8 @@ window_buffer_start_edit(struct window_buffer_modedata *data, size_t len; struct window_buffer_editdata *ed; + if (data->editor != NULL) + return; if ((pb = paste_get_name(item->name)) == NULL) return; buf = paste_buffer_data(pb, &len); @@ -527,8 +597,13 @@ window_buffer_start_edit(struct window_buffer_modedata *data, ed->name = xstrdup(paste_buffer_name(pb)); ed->pb = pb; - if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0) + ed->editor = spawn_editor(c, buf, len, window_buffer_edit_close_cb, ed); + if (ed->editor == NULL) window_buffer_finish_edit(ed); + else { + data->editor = ed->editor; + data->edit = ed; + } } static void @@ -546,6 +621,13 @@ window_buffer_key(struct window_mode_entry *wme, struct client *c, finished = 1; goto out; } + if (data->editor != NULL) { + if (key == 'q' || key == '\033' || key == '\003') + finished = 1; + else + finished = 0; + goto out; + } finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); switch (key) { @@ -579,6 +661,7 @@ out: window_pane_reset_mode(wp); else { mode_tree_draw(mtd); + window_buffer_draw_waiting(data); wp->flags |= PANE_REDRAW; } } diff --git a/window.c b/window.c index 5361884d..61deb721 100644 --- a/window.c +++ b/window.c @@ -390,6 +390,8 @@ window_pane_destroy_ready(struct window_pane *wp) */ if (wp->wait_item != NULL && (~wp->flags & PANE_STATUSREADY)) return (0); + if (wp->editor != NULL && (~wp->flags & PANE_STATUSREADY)) + return (0); return (1); } @@ -1124,6 +1126,7 @@ static void window_pane_destroy(struct window_pane *wp) { window_pane_wait_finish(wp); + spawn_editor_finish(wp); window_pane_reset_mode_all(wp); free(wp->searchstr);