From d8ad7cd9c53b4e75fefaa49498952a368d2f3708 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 21 Jul 2026 18:48:16 +0100 Subject: [PATCH] Add exit status on right. --- grid.c | 2 ++ input.c | 10 ++++--- options-table.c | 8 ++++++ tmux.1 | 8 ++++++ tmux.h | 4 ++- window-copy.c | 70 +++++++++++++++++++++++++++++++++++++++++++------ 6 files changed, 90 insertions(+), 12 deletions(-) diff --git a/grid.c b/grid.c index 1187aea5f..7d9de4dc4 100644 --- a/grid.c +++ b/grid.c @@ -1728,6 +1728,8 @@ grid_line_flags_string(int flags) strlcat(s, "START_OUTPUT,", sizeof s); if (flags & GRID_LINE_END_OUTPUT) strlcat(s, "END_OUTPUT,", sizeof s); + if (flags & GRID_LINE_END_OUTPUT_STATUS) + strlcat(s, "END_OUTPUT_STATUS,", sizeof s); if (flags & GRID_LINE_HYPERLINK) strlcat(s, "HYPERLINK,", sizeof s); if (*s == '\0') diff --git a/input.c b/input.c index a1bf49372..dcf02eb95 100644 --- a/input.c +++ b/input.c @@ -3193,13 +3193,14 @@ input_osc_112(struct input_ctx *ictx, const char *p) /* Parse the OSC 133 D exit status. */ static int -input_osc_133_exit_status(const char *p) +input_osc_133_exit_status(const char *p, int *present) { const char *end; char *copy; const char *errstr; long long status; + *present = 0; if (p[1] != ';' || p[2] == '\0' || strchr(p + 2, '=') == p + 2) return (0); end = strchr(p + 2, ';'); @@ -3213,6 +3214,7 @@ input_osc_133_exit_status(const char *p) free(copy); return (0); } + *present = 1; status = strtonum(copy, 0, 255, &errstr); free(copy); if (errstr != NULL) @@ -3270,7 +3272,7 @@ input_osc_133(struct input_ctx *ictx, const char *p) u_int line = s->cy + gd->hsize; struct grid_line *gl = NULL; const char *cp; - int status; + int status, status_present; if (line < gd->hsize + gd->sy) gl = grid_get_line(gd, line); @@ -3327,7 +3329,7 @@ input_osc_133(struct input_ctx *ictx, const char *p) } break; case 'D': - status = input_osc_133_exit_status(p); + status = input_osc_133_exit_status(p, &status_present); if (wp != NULL) { wp->cmd_end_time = time(NULL); wp->flags &= ~PANE_CMDRUNNING; @@ -3336,6 +3338,8 @@ input_osc_133(struct input_ctx *ictx, const char *p) } if (gl != NULL) { gl->flags |= GRID_LINE_END_OUTPUT; + if (status_present) + gl->flags |= GRID_LINE_END_OUTPUT_STATUS; gl->osc133_data.out_end_col = s->cx; gl->osc133_data.exit_status = status; } diff --git a/options-table.c b/options-table.c index 3e67ae3cf..d6fe37b70 100644 --- a/options-table.c +++ b/options-table.c @@ -1384,6 +1384,14 @@ const struct options_table_entry options_table[] = { .text = "When OSC 133 output collapse controls are shown in copy mode." }, + { .name = "copy-mode-exit-status-format", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_str = "#[align=right]" + "#{?exit_status_present,#[bg=themeyellow](#{exit_status}),}", + .text = "Format of OSC 133 exit status in copy mode." + }, + { .name = "copy-mode-current-match-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, diff --git a/tmux.1 b/tmux.1 index 2e816c7be..771313038 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5780,6 +5780,14 @@ section. .It Ic copy\-mode\-position\-format Ar format Format of the position indicator in copy mode. .Pp +.It Ic copy\-mode\-exit\-status\-format Ar format +Format of the OSC 133 exit status shown beside commands in copy mode. +The format is expanded with +.Ql exit_status +and +.Ql exit_status_present , +which is set only when the OSC 133 end-of-output marker includes a status. +.Pp .It Ic copy\-mode\-position\-style Ar style Set the style of the position indicator in copy mode. For how to specify diff --git a/tmux.h b/tmux.h index dd2d492a0..8cd7410b0 100644 --- a/tmux.h +++ b/tmux.h @@ -814,6 +814,7 @@ struct colour_palette { #define GRID_LINE_START_OUTPUT 0x40 #define GRID_LINE_END_OUTPUT 0x80 #define GRID_LINE_HYPERLINK 0x100 +#define GRID_LINE_END_OUTPUT_STATUS 0x200 /* All OSC 133 flags. */ #define GRID_LINE_OSC133_FLAGS \ @@ -821,7 +822,8 @@ struct colour_palette { GRID_LINE_SECOND_PROMPT| \ GRID_LINE_START_COMMAND| \ GRID_LINE_START_OUTPUT| \ - GRID_LINE_END_OUTPUT) + GRID_LINE_END_OUTPUT| \ + GRID_LINE_END_OUTPUT_STATUS) /* Grid string flags. */ #define GRID_STRING_WITH_SEQUENCES 0x1 diff --git a/window-copy.c b/window-copy.c index 75c06a084..7ad7fa805 100644 --- a/window-copy.c +++ b/window-copy.c @@ -273,6 +273,8 @@ struct window_copy_line { u_int source_line; u_int output_line; u_char flags; + u_char exit_status; + u_char exit_status_present; }; /* @@ -516,6 +518,19 @@ window_copy_set_control(struct window_copy_mode_data *data, u_int y, line->flags |= WINDOW_COPY_LINE_CONTROL; } +static void +window_copy_set_exit_status(struct window_copy_mode_data *data, u_int y, + u_char exit_status) +{ + struct window_copy_line *line; + + line = window_copy_get_line_info(data, y); + if (line == NULL) + return; + line->exit_status = exit_status; + line->exit_status_present = 1; +} + static int window_copy_any_output_collapsed(struct window_copy_mode_data *data) { @@ -624,6 +639,7 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) struct grid_cell gc; struct osc133_data *osc133; u_int y, x, total, dy, prompt_dy = UINT_MAX; + u_int end_prompt_dy; u_int output_line = UINT_MAX; int collapsed = 0, command = 0, hiding = 0, tree = 0; int initial = 0, was_hiding; @@ -668,6 +684,7 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) osc133 = &sgl->osc133_data; for (x = 0; x <= sgl->cellused; x++) { dy = dst->grid->hsize + dst->cy; + end_prompt_dy = prompt_dy; was_hiding = hiding; if (!hiding) window_copy_set_line(data, dy, y, output_line, 0); @@ -708,6 +725,8 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) if ((sgl->flags & GRID_LINE_END_OUTPUT) && x == osc133->out_end_col) { dgl->flags |= GRID_LINE_END_OUTPUT; + if (sgl->flags & GRID_LINE_END_OUTPUT_STATUS) + dgl->flags |= GRID_LINE_END_OUTPUT_STATUS; dgl->osc133_data.out_end_col = dst->cx; dgl->osc133_data.exit_status = osc133->exit_status; if (output_line == UINT_MAX && command) { @@ -718,6 +737,10 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) window_copy_set_control(data, prompt_dy, output_line); } + if (end_prompt_dy != UINT_MAX && + sgl->flags & GRID_LINE_END_OUTPUT_STATUS) + window_copy_set_exit_status(data, end_prompt_dy, + osc133->exit_status); if (initial && prompt_dy == UINT_MAX) hiding = data->top_output; else @@ -6029,8 +6052,9 @@ window_copy_write_line(struct window_mode_entry *wme, u_int width, line_width, gutter_width, backing_y; u_int absolute, line_number, content_sx; const char *value; - char *expanded; + char *expanded, *exit_status, *position; struct format_tree *ft; + struct window_copy_line *status_line; int current, mode; line_width = window_copy_line_number_width(wme); @@ -6042,6 +6066,7 @@ window_copy_write_line(struct window_mode_entry *wme, content_sx = sx - width; else content_sx = sx; + backing_y = hsize - data->oy + py; screen_write_cursormove(ctx, 0, py, 0); @@ -6064,7 +6089,6 @@ window_copy_write_line(struct window_mode_entry *wme, "copy-mode-current-line-number-style", ft); cur_ln_gc.flags |= GRID_FLAG_NOPALETTE; current = (py == data->cy); - backing_y = hsize - data->oy + py; absolute = backing_y + 1; if (data->lines != NULL && backing_y < data->line_count) absolute = data->lines[backing_y].source_line + 1; @@ -6136,18 +6160,48 @@ window_copy_write_line(struct window_mode_entry *wme, window_copy_write_one(wme, ctx, width, py, hsize - data->oy + py, content_sx, &mgc, &cgc, &mkgc, &clgc); + exit_status = NULL; + status_line = window_copy_get_line_info(data, backing_y); + if (data->output_gutter && status_line != NULL && + status_line->exit_status_present) { + format_add(ft, "exit_status", "%hhu", status_line->exit_status); + format_add(ft, "exit_status_present", "1"); + value = options_get_string(oo, "copy-mode-exit-status-format"); + if (*value != '\0') { + exit_status = format_expand(ft, value); + if (*exit_status == '\0') { + free(exit_status); + exit_status = NULL; + } + } + } + + position = NULL; if (py == 0 && s->rupper < s->rlower && !data->hide_position) { value = options_get_string(oo, "copy-mode-position-format"); if (*value != '\0') { - expanded = format_expand(ft, value); - if (*expanded != '\0') { - screen_write_cursormove(ctx, width, 0, 0); - format_draw(ctx, &gc, content_sx, expanded, - NULL, 0); + position = format_expand(ft, value); + if (*position == '\0') { + free(position); + position = NULL; } - free(expanded); } } + if (exit_status != NULL && position != NULL) + xasprintf(&expanded, "%s %s", exit_status, position); + else if (exit_status != NULL) + expanded = xstrdup(exit_status); + else if (position != NULL) + expanded = xstrdup(position); + else + expanded = NULL; + if (expanded != NULL) { + screen_write_cursormove(ctx, width, py, 0); + format_draw(ctx, &gc, content_sx, expanded, NULL, 0); + free(expanded); + } + free(exit_status); + free(position); if (py == data->cy && data->cx >= content_sx) { screen_write_cursormove(ctx, window_copy_cursor_offset(wme,