1
0
mirror of https://github.com/tmux/tmux.git synced 2025-03-24 14:58:47 +00:00

Add support for popups to control mode

This commit is contained in:
George Nachman 2025-02-07 13:24:57 -08:00
parent ef68debc8d
commit 5fcda70d0c
5 changed files with 160 additions and 26 deletions

View File

@ -432,7 +432,15 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
w = tty->sx;
if (h > tty->sy)
h = tty->sy;
if (!cmd_display_menu_get_position(tc, item, args, &px, &py, w, h))
if (tc->flags & CLIENT_CONTROL) {
/* Control clients may not have a window size, so provide a reasonable default so popups can still work. */
if (w == 0)
w = 80;
if (h == 0)
h = 25;
px = 0;
py = 0;
} else if (!cmd_display_menu_get_position(tc, item, args, &px, &py, w, h))
return (CMD_RETURN_NORMAL);
value = args_get(args, 'b');
@ -485,7 +493,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
else if (args_has(args, 'E'))
flags |= POPUP_CLOSEEXIT;
if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0) {
argv, cwd, title, tc, s, style, border_style, NULL, NULL, target->wp) != 0) {
cmd_free_argv(argc, argv);
if (env != NULL)
environ_free(env);
@ -498,5 +506,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
free(cwd);
free(title);
cmd_free_argv(argc, argv);
if (tc->flags & CLIENT_CONTROL)
return (CMD_RETURN_NORMAL);
return (CMD_RETURN_WAIT);
}

View File

@ -260,3 +260,19 @@ control_notify_paste_buffer_deleted(const char *name)
control_write(c, "%%paste-buffer-deleted %s", name);
}
}
void
control_notify_popup(struct client *c, int status, char *buf, size_t len, int wp)
{
struct evbuffer *message = evbuffer_new();
if (message == NULL)
fatalx("out of memory");
evbuffer_add_printf(message, "%%popup %d", status);
if (wp != -1)
evbuffer_add_printf(message, " %u", wp);
evbuffer_add_printf(message, " : ");
control_escape(message, buf, len);
control_write_buffer(c, message);
evbuffer_free(message);
}

View File

@ -43,7 +43,9 @@
*/
struct control_block {
size_t size;
/* exactly one of `line` and `buffer` will be nonnull */
char *line;
struct evbuffer *buffer;
uint64_t t;
TAILQ_ENTRY(control_block) entry;
@ -225,7 +227,10 @@ control_free_sub(struct control_state *cs, struct control_sub *csub)
static void
control_free_block(struct control_state *cs, struct control_block *cb)
{
free(cb->line);
if (cb->line != NULL)
free(cb->line);
if (cb->buffer != NULL)
evbuffer_free(cb->buffer);
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
free(cb);
}
@ -401,13 +406,63 @@ control_vwrite(struct client *c, const char *fmt, va_list ap)
free(s);
}
static void
control_vwrite_buffer(struct client *c, struct evbuffer *buffer)
{
struct control_state *cs = c->control_state;
log_debug("%s: %s: writing buffer", __func__, c->name);
bufferevent_write_buffer(cs->write_event, buffer);
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
}
/* Frees line and buffer after using them asynchronously. */
static void
control_enqueue(struct client *c, struct control_state *cs, char *line, struct evbuffer *buffer)
{
struct control_block *cb = xcalloc(1, sizeof *cb);
if (line != NULL) {
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
cb->line = line;
} else {
log_debug("%s: %s: storing buffer", __func__, c->name);
cb->buffer = buffer;
}
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
cb->t = get_timer();
bufferevent_enable(cs->write_event, EV_WRITE);
}
void
control_write_buffer(struct client *c, struct evbuffer *buffer)
{
struct control_state *cs = c->control_state;
struct control_block *cb;
va_list ap;
if (TAILQ_EMPTY(&cs->all_blocks)) {
control_vwrite_buffer(c, buffer);
return;
}
control_enqueue(c, cs, NULL, buffer);
va_end(ap);
}
/* Write a line. */
void
control_write(struct client *c, const char *fmt, ...)
{
struct control_state *cs = c->control_state;
struct control_block *cb;
va_list ap;
char *line;
va_start(ap, fmt);
@ -417,13 +472,8 @@ control_write(struct client *c, const char *fmt, ...)
return;
}
cb = xcalloc(1, sizeof *cb);
xvasprintf(&cb->line, fmt, ap);
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
cb->t = get_timer();
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
bufferevent_enable(cs->write_event, EV_WRITE);
xvasprintf(&line, fmt, ap);
control_enqueue(c, cs, line, NULL);
va_end(ap);
}
@ -604,6 +654,17 @@ control_flush_all_blocks(struct client *c)
}
}
void
control_escape(struct evbuffer *message, char *s, size_t size)
{
for (size_t i = 0; i < size; i++) {
if (s[i] < ' ' || s[i] == '\\')
evbuffer_add_printf(message, "\\%03o", s[i]);
else
evbuffer_add_printf(message, "%c", s[i]);
}
}
/* Append data to buffer. */
static struct evbuffer *
control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
@ -611,7 +672,6 @@ control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
{
u_char *new_data;
size_t new_size;
u_int i;
if (message == NULL) {
message = evbuffer_new();
@ -628,12 +688,7 @@ control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
new_data = window_pane_get_new_data(wp, &cp->offset, &new_size);
if (new_size < size)
fatalx("not enough data: %zu < %zu", new_size, size);
for (i = 0; i < size; i++) {
if (new_data[i] < ' ' || new_data[i] == '\\')
evbuffer_add_printf(message, "\\%03o", new_data[i]);
else
evbuffer_add_printf(message, "%c", new_data[i]);
}
control_escape(message, new_data, size);
window_pane_update_used_data(wp, &cp->offset, size);
return (message);
}

64
popup.c
View File

@ -28,6 +28,7 @@
struct popup_data {
struct client *c;
int wp;
struct cmdq_item *item;
int flags;
char *title;
@ -614,6 +615,47 @@ popup_job_update_cb(struct job *job)
evbuffer_drain(evb, size);
}
// NOTE TO REVIEWER: This is a copy of cmd_capture_pane_append. I think we'd want a shared implementation but I don't know where it should go.
static char *
popup_append(char *buf, size_t *len, char *line, size_t linelen)
{
buf = xrealloc(buf, *len + linelen + 1);
memcpy(buf + *len, line, linelen);
*len += linelen;
return (buf);
}
static void
popup_notify_control(struct client *c, int status, struct screen *s, int wp)
{
char *buf = NULL;
struct grid_cell *gc = NULL;
int sx = screen_size_x(s);
int sy = screen_size_y(s);
char *line;
size_t linelen;
size_t len = 0;
int i;
struct grid *gd = s->grid;
const struct grid_line *gl;
for (i = 0; i < sy; i++) {
line = grid_string_cells(gd, 0, i, sx, &gc, GRID_STRING_WITH_SEQUENCES, s);
linelen = strlen(line);
buf = popup_append(buf, &len, line, linelen);
gl = grid_peek_line(gd, i);
if (!(gl->flags & GRID_LINE_WRAPPED))
buf[len++] = '\n';
free(line);
}
control_notify_popup(c, status, buf, len, wp);
free(buf);
}
static void
popup_job_complete_cb(struct job *job)
{
@ -629,8 +671,10 @@ popup_job_complete_cb(struct job *job)
pd->status = 0;
pd->job = NULL;
if ((pd->flags & POPUP_CLOSEEXIT) ||
((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
if (pd->c->flags & CLIENT_CONTROL)
popup_notify_control(pd->c, pd->status, &pd->s, pd->wp);
else if ((pd->flags & POPUP_CLOSEEXIT) ||
((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
server_client_clear_overlay(pd->c);
}
@ -639,7 +683,7 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd,
int argc, char **argv, const char *cwd, const char *title, struct client *c,
struct session *s, const char *style, const char *border_style,
popup_close_cb cb, void *arg)
popup_close_cb cb, void *arg, struct window_pane *wp)
{
struct popup_data *pd;
u_int jx, jy;
@ -664,10 +708,14 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
jx = sx - 2;
jy = sy - 2;
}
if (c->tty.sx < sx || c->tty.sy < sy)
if (!(c->flags & CLIENT_CONTROL) && (c->tty.sx < sx || c->tty.sy < sy))
return (-1);
pd = xcalloc(1, sizeof *pd);
if (wp != NULL)
pd->wp = wp->id;
else
pd->wp = -1;
pd->item = item;
pd->flags = flags;
if (title != NULL)
@ -723,8 +771,10 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette);
server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
if (!(c->flags & CLIENT_CONTROL)) {
server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
}
return (0);
}
@ -811,7 +861,7 @@ popup_editor(struct client *c, const char *buf, size_t len,
xasprintf(&cmd, "%s %s", editor, path);
if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT,
NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL,
NULL, NULL, popup_editor_close_cb, pe) != 0) {
NULL, NULL, popup_editor_close_cb, pe, NULL) != 0) {
popup_editor_free(pe);
free(cmd);
return (-1);

5
tmux.h
View File

@ -3379,11 +3379,13 @@ struct window_pane_offset *control_pane_offset(struct client *,
struct window_pane *, int *);
void control_reset_offsets(struct client *);
void printflike(2, 3) control_write(struct client *, const char *, ...);
void control_write_buffer(struct client *c, struct evbuffer *buffer);
void control_write_output(struct client *, struct window_pane *);
int control_all_done(struct client *);
void control_add_sub(struct client *, const char *, enum control_sub_type,
int, const char *);
void control_remove_sub(struct client *, const char *);
void control_escape(struct evbuffer *, char *, size_t);
/* control-notify.c */
void control_notify_pane_mode_changed(int);
@ -3400,6 +3402,7 @@ void control_notify_session_closed(struct session *);
void control_notify_session_window_changed(struct session *);
void control_notify_paste_buffer_changed(const char *);
void control_notify_paste_buffer_deleted(const char *);
void control_notify_popup(struct client *c, int status, char *buf, size_t len, int wp);
/* session.c */
extern struct sessions sessions;
@ -3531,7 +3534,7 @@ int popup_display(int, enum box_lines, struct cmdq_item *, u_int,
u_int, u_int, u_int, struct environ *, const char *, int,
char **, const char *, const char *, struct client *,
struct session *, const char *, const char *,
popup_close_cb, void *);
popup_close_cb, void *, struct window_pane *);
int popup_editor(struct client *, const char *, size_t,
popup_finish_edit_cb, void *);