From a532f4868f63b3197e4e28a4563ad022b1a8b497 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 20:03:02 +0000 Subject: [PATCH 1/2] Add -H flag to capture-pane to show hyperlinks. --- cmd-capture-pane.c | 81 ++++++++++++++++++++++++++++++++++++++++------ grid.c | 2 ++ tmux.1 | 12 +++++-- tmux.h | 1 + 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 0f37ad5d..8f537b98 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -29,18 +29,21 @@ static enum cmd_retval cmd_capture_pane_exec(struct cmd *, struct cmdq_item *); -static char *cmd_capture_pane_append(char *, size_t *, char *, size_t); +static char *cmd_capture_pane_append(char *, size_t *, const char *, + size_t); static char *cmd_capture_pane_pending(struct args *, struct window_pane *, size_t *); static char *cmd_capture_pane_history(struct args *, struct cmdq_item *, struct window_pane *, size_t *); +static char *cmd_capture_pane_hyperlinks(struct grid *, struct screen *, + u_int, u_int *, u_int *, size_t *); const struct cmd_entry cmd_capture_pane_entry = { .name = "capture-pane", .alias = "capturep", - .args = { "ab:CeE:FJLMNpPqS:Tt:", 0, 0, NULL }, - .usage = "[-aCeFJLMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " + .args = { "ab:CeE:FHJLMNpPqS:Tt:", 0, 0, NULL }, + .usage = "[-aCeFHJLMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " "[-S start-line] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -63,7 +66,8 @@ const struct cmd_entry cmd_clear_history_entry = { }; static char * -cmd_capture_pane_append(char *buf, size_t *len, char *line, size_t linelen) +cmd_capture_pane_append(char *buf, size_t *len, const char *line, + size_t linelen) { buf = xrealloc(buf, *len + linelen + 1); memcpy(buf + *len, line, linelen); @@ -103,6 +107,46 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp, return (buf); } +static char * +cmd_capture_pane_hyperlinks(struct grid *gd, struct screen *s, u_int py, + u_int *links, u_int *nlinks, size_t *len) +{ + const struct grid_line *gl = grid_peek_line(gd, py); + struct grid_cell gc; + const char *uri; + char *line = xstrdup(""); + u_int i, j; + + *len = 0; + + if (s->hyperlinks == NULL || (~gl->flags & GRID_LINE_HYPERLINK)) + return (line); + + for (i = 0; i < gl->cellused; i++) { + grid_get_cell(gd, i, py, &gc); + if (gc.link == 0) + continue; + for (j = 0; j < *nlinks; j++) { + if (links[j] == gc.link) + break; + } + if (j != *nlinks) + continue; + + if (!hyperlinks_get(s->hyperlinks, gc.link, &uri, NULL, NULL)) + continue; + + if (*nlinks == gd->sx) + break; + links[(*nlinks)++] = gc.link; + + if (*len != 0) + line = cmd_capture_pane_append(line, len, " ", 1); + line = cmd_capture_pane_append(line, len, uri, strlen(uri)); + } + return (line); +} + static char * cmd_capture_pane_history(struct args *args, struct cmdq_item *item, struct window_pane *wp, size_t *len) @@ -113,9 +157,10 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, struct grid_cell *gc = NULL; struct window_mode_entry *wme; int n, join_lines, number_lines, flags = 0; - int show_flags; + int show_flags, hyperlinks; + u_int *links = NULL, nlinks = 0; u_int i, sx, top, bottom, tmp; - char *cause, *buf, *line, b[64], *cp; + char *cause, *buf = NULL, *line, b[64], *cp; const char *Sflag, *Eflag; size_t linelen; @@ -195,11 +240,22 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, flags |= GRID_STRING_TRIM_SPACES; number_lines = args_has(args, 'L'); show_flags = args_has(args, 'F'); + hyperlinks = args_has(args, 'H'); + if (hyperlinks) + links = xreallocarray(NULL, gd->sx, sizeof *links); - buf = NULL; for (i = top; i <= bottom; i++) { - line = grid_string_cells(gd, 0, i, sx, &gc, flags, s); - linelen = strlen(line); + if (hyperlinks) { + line = cmd_capture_pane_hyperlinks(gd, s, i, links, + &nlinks, &linelen); + } else { + line = grid_string_cells(gd, 0, i, sx, &gc, flags, s); + linelen = strlen(line); + } + if (hyperlinks && linelen == 0) { + free(line); + continue; + } if (number_lines) { if (i >= gd->hsize) @@ -217,6 +273,8 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, gl = grid_peek_line(gd, i); if (gl->flags & GRID_LINE_DEAD) *cp++ = 'D'; + if (gl->flags & GRID_LINE_HYPERLINK) + *cp++ = 'H'; if (gl->flags & GRID_LINE_START_OUTPUT) *cp++ = 'O'; if (gl->flags & GRID_LINE_START_PROMPT) @@ -239,6 +297,9 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, free(line); } + free(links); + if (buf == NULL) + buf = xstrdup(""); return (buf); } @@ -261,7 +322,7 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) } len = 0; - if (args_has(args, 'P')) + if (args_has(args, 'P') && !args_has(args, 'H')) buf = cmd_capture_pane_pending(args, wp, &len); else buf = cmd_capture_pane_history(args, item, wp, &len); diff --git a/grid.c b/grid.c index 20deb936..36c99184 100644 --- a/grid.c +++ b/grid.c @@ -125,6 +125,8 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, else if (gce->offset >= gl->extdsize) fatalx("offset too big"); gl->flags |= GRID_LINE_EXTENDED; + if (gc->link != 0) + gl->flags |= GRID_LINE_HYPERLINK; if (gc->flags & GRID_FLAG_TAB) uc = gc->data.width; diff --git a/tmux.1 b/tmux.1 index 07f379f0..2b1d543f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2666,7 +2666,7 @@ but a different format may be specified with .Fl F . .Tg capturep .It Xo Ic capture\-pane -.Op Fl aeFLpPqCJMN +.Op Fl aeFHLpPqCJMN .Op Fl b Ar buffer\-name .Op Fl E Ar end\-line .Op Fl S Ar start\-line @@ -2717,9 +2717,15 @@ is an unused line, .Ql O is a line marked as output, .Ql P -is a line marked as a prompt and +is a line marked as a prompt, .Ql X -is a line containing extended cells). +is a line containing extended cells and +.Ql H +is a line with hyperlinks). +With +.Fl H , +only hyperlinks in the specified lines are captured. +Multiple hyperlinks on the same line are separated by spaces. .Pp .Fl S and diff --git a/tmux.h b/tmux.h index 7f0e438a..887d21bb 100644 --- a/tmux.h +++ b/tmux.h @@ -771,6 +771,7 @@ struct colour_palette { #define GRID_LINE_DEAD 0x4 #define GRID_LINE_START_PROMPT 0x8 #define GRID_LINE_START_OUTPUT 0x10 +#define GRID_LINE_HYPERLINK 0x20 /* Grid string flags. */ #define GRID_STRING_WITH_SEQUENCES 0x1 From 529afada822a72887a94f17146460809134697d0 Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 7 Jun 2026 20:05:16 +0000 Subject: [PATCH 2/2] Move checking of whether the cursor is visible inside the if so that it always hits the calculation of the oy offset when the status line is at the top. From Michael Grant. --- server-client.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server-client.c b/server-client.c index dd4b6e08..481d5a71 100644 --- a/server-client.c +++ b/server-client.c @@ -1819,12 +1819,13 @@ server_client_reset_state(struct client *c) cx = wp->xoff + (int)s->cx - (int)ox; cy = wp->yoff + (int)s->cy - (int)oy; + r = screen_redraw_get_visible_ranges(wp, cx, cy, 1, NULL); + if (!screen_redraw_is_visible(r, cx)) + cursor = 0; + if (status_at_line(c) == 0) cy += status_line_size(c); } - r = screen_redraw_get_visible_ranges(wp, cx, cy, 1, NULL); - if (!screen_redraw_is_visible(r, cx)) - cursor = 0; if (!cursor) mode &= ~MODE_CURSOR;