Show match count in copy mode, GitHub issue 5399.

This commit is contained in:
nicm
2026-10-07 12:59:50 +00:00
committed by tmux update bot
parent d041d56bb2
commit ddd0698e30
3 changed files with 113 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: options-table.c,v 1.250 2026/10/07 12:55:41 nicm Exp $ */ /* $OpenBSD: options-table.c,v 1.251 2026/10/07 12:59:50 nicm Exp $ */
/* /*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1415,8 +1415,12 @@ const struct options_table_entry options_table[] = {
"#{t/p:top_line_time}#{?#{e|>:#{top_line_time},0}, ,}" "#{t/p:top_line_time}#{?#{e|>:#{top_line_time},0}, ,}"
"[#{copy_position}/#{copy_position_limit}]" "[#{copy_position}/#{copy_position_limit}]"
"#{?search_timed_out, (timed out)," "#{?search_timed_out, (timed out),"
"#{?search_count, (#{search_count}" "#{?search_count_present, ("
"#{?search_count_partial,+,} results),}}", "#{?#{==:#{search_count},0},0 matches,"
"#{?search_count_partial,#{search_count}+ matches,"
"#{?#{==:#{search_count},1},1 of 1 match,"
"#{?search_count_current,#{search_count_current} of "
"#{search_count} matches,#{search_count} matches}}}}),}}",
.text = "Format of the position indicator in copy mode." .text = "Format of the position indicator in copy mode."
}, },

4
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1178 2026/10/07 12:55:41 nicm Exp $ .\" $OpenBSD: tmux.1,v 1.1179 2026/10/07 12:59:50 nicm Exp $
.\" .\"
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> .\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
.\" .\"
@@ -7488,7 +7488,9 @@ The following variables are available, where appropriate:
.It Li "scroll_region_lower" Ta "" Ta "Bottom of scroll region in pane" .It Li "scroll_region_lower" Ta "" Ta "Bottom of scroll region in pane"
.It Li "scroll_region_upper" Ta "" Ta "Top of scroll region in pane" .It Li "scroll_region_upper" Ta "" Ta "Top of scroll region in pane"
.It Li "search_count" Ta "" Ta "Count of search results" .It Li "search_count" Ta "" Ta "Count of search results"
.It Li "search_count_current" Ta "" Ta "Index of the current search result"
.It Li "search_count_partial" Ta "" Ta "1 if search count is partial count" .It Li "search_count_partial" Ta "" Ta "1 if search count is partial count"
.It Li "search_count_present" Ta "" Ta "1 if search count is available"
.It Li "search_match" Ta "" Ta "Search match if any" .It Li "search_match" Ta "" Ta "Search match if any"
.It Li "search_present" Ta "" Ta "1 if search started in copy mode" .It Li "search_present" Ta "" Ta "1 if search started in copy mode"
.It Li "selection_active" Ta "" Ta "1 if selection started and changes with the cursor in copy mode" .It Li "selection_active" Ta "" Ta "1 if selection started and changes with the cursor in copy mode"

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-copy.c,v 1.435 2026/10/06 08:26:23 nicm Exp $ */ /* $OpenBSD: window-copy.c,v 1.436 2026/10/07 12:59:50 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -87,6 +87,10 @@ static int window_copy_last_regex(struct grid *, u_int, u_int, u_int,
int); int);
static int window_copy_search_mark_at(struct window_copy_mode_data *, static int window_copy_search_mark_at(struct window_copy_mode_data *,
u_int, u_int, u_int *); u_int, u_int, u_int *);
static int window_copy_grow_searchpos(struct window_copy_mode_data *,
u_int, u_int, u_int);
static void window_copy_search_set_index(struct window_copy_mode_data *,
u_int, u_int);
static char *window_copy_stringify(struct grid *, u_int, u_int, u_int, static char *window_copy_stringify(struct grid *, u_int, u_int, u_int,
char *, u_int *); char *, u_int *);
static void window_copy_cstrtocellpos(struct grid *, u_int, u_int *, static void window_copy_cstrtocellpos(struct grid *, u_int, u_int *,
@@ -341,6 +345,10 @@ struct window_copy_mode_data {
char *searchstr; char *searchstr;
u_char *searchmark; u_char *searchmark;
int searchcount; int searchcount;
int searchindex;
u_int *searchpos;
u_int searchposcount;
u_int searchpossize;
int searchmore; int searchmore;
int searchall; int searchall;
int searchx; int searchx;
@@ -581,6 +589,8 @@ window_copy_common_init(struct window_mode_entry *wme)
data->searchstr = NULL; data->searchstr = NULL;
} }
data->searchx = data->searchy = data->searcho = -1; data->searchx = data->searchy = data->searcho = -1;
data->searchcount = -1;
data->searchindex = -1;
data->searchall = 1; data->searchall = 1;
data->jumptype = WINDOW_COPY_OFF; data->jumptype = WINDOW_COPY_OFF;
@@ -682,6 +692,7 @@ window_copy_free(struct window_mode_entry *wme)
evtimer_del(&data->refresh_timer); evtimer_del(&data->refresh_timer);
free(data->searchmark); free(data->searchmark);
free(data->searchpos);
free(data->searchstr); free(data->searchstr);
free(data->jumpchar); free(data->jumpchar);
@@ -1168,8 +1179,12 @@ window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft)
format_add(ft, "search_present", "%d", data->searchmark != NULL); format_add(ft, "search_present", "%d", data->searchmark != NULL);
format_add(ft, "search_timed_out", "%d", data->timeout); format_add(ft, "search_timed_out", "%d", data->timeout);
if (data->searchcount != -1) { if (data->searchcount != -1) {
format_add(ft, "search_count_present", "1");
format_add(ft, "search_count", "%d", data->searchcount); format_add(ft, "search_count", "%d", data->searchcount);
format_add(ft, "search_count_partial", "%d", data->searchmore); format_add(ft, "search_count_partial", "%d", data->searchmore);
if (data->searchindex != -1)
format_add(ft, "search_count_current", "%d",
data->searchindex);
} }
format_add_cb(ft, "search_match", window_copy_search_match_cb); format_add_cb(ft, "search_match", window_copy_search_match_cb);
@@ -1194,9 +1209,11 @@ window_copy_size_changed(struct window_mode_entry *wme)
struct screen *s = &data->screen; struct screen *s = &data->screen;
struct screen_write_ctx ctx; struct screen_write_ctx ctx;
int search = (data->searchmark != NULL); int search = (data->searchmark != NULL);
int searchindex = data->searchindex;
window_copy_clear_selection(wme); window_copy_clear_selection(wme);
window_copy_clear_marks(wme); window_copy_clear_marks(wme);
data->searchindex = searchindex;
screen_write_start(&ctx, s); screen_write_start(&ctx, s);
window_copy_write_lines(wme, &ctx, 0, screen_size_y(s)); window_copy_write_lines(wme, &ctx, 0, screen_size_y(s));
@@ -2983,20 +3000,16 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs)
data->searchregex = 0; data->searchregex = 0;
free(data->searchstr); free(data->searchstr);
data->searchstr = xstrdup(arg0); data->searchstr = xstrdup(arg0);
if (!window_copy_search_up(wme, 0)) { if (!window_copy_search_up(wme, 0))
window_copy_clear_marks(wme);
return (WINDOW_COPY_CMD_REDRAW); return (WINDOW_COPY_CMD_REDRAW);
}
break; break;
case '+': case '+':
data->searchtype = WINDOW_COPY_SEARCHDOWN; data->searchtype = WINDOW_COPY_SEARCHDOWN;
data->searchregex = 0; data->searchregex = 0;
free(data->searchstr); free(data->searchstr);
data->searchstr = xstrdup(arg0); data->searchstr = xstrdup(arg0);
if (!window_copy_search_down(wme, 0)) { if (!window_copy_search_down(wme, 0))
window_copy_clear_marks(wme);
return (WINDOW_COPY_CMD_REDRAW); return (WINDOW_COPY_CMD_REDRAW);
}
break; break;
} }
return (action); return (action);
@@ -3040,20 +3053,16 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs)
data->searchregex = 0; data->searchregex = 0;
free(data->searchstr); free(data->searchstr);
data->searchstr = xstrdup(arg0); data->searchstr = xstrdup(arg0);
if (!window_copy_search_down(wme, 0)) { if (!window_copy_search_down(wme, 0))
window_copy_clear_marks(wme);
return (WINDOW_COPY_CMD_REDRAW); return (WINDOW_COPY_CMD_REDRAW);
}
break; break;
case '-': case '-':
data->searchtype = WINDOW_COPY_SEARCHUP; data->searchtype = WINDOW_COPY_SEARCHUP;
data->searchregex = 0; data->searchregex = 0;
free(data->searchstr); free(data->searchstr);
data->searchstr = xstrdup(arg0); data->searchstr = xstrdup(arg0);
if (!window_copy_search_up(wme, 0)) { if (!window_copy_search_up(wme, 0))
window_copy_clear_marks(wme);
return (WINDOW_COPY_CMD_REDRAW); return (WINDOW_COPY_CMD_REDRAW);
}
} }
return (action); return (action);
} }
@@ -4698,6 +4707,7 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex)
return (0); return (0);
if (data->searchall || wp->searchstr == NULL || if (data->searchall || wp->searchstr == NULL ||
data->searchpos == NULL ||
wp->searchregex != regex) { wp->searchregex != regex) {
visible_only = 0; visible_only = 0;
data->searchall = 0; data->searchall = 0;
@@ -4775,8 +4785,8 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex)
fx = data->cx; fx = data->cx;
fy = screen_hsize(data->backing) - data->oy + data->cy; fy = screen_hsize(data->backing) - data->oy + data->cy;
} }
if (direction) { if (direction) {
window_copy_search_set_index(data, fx, fy);
/* /*
* When in Emacs mode, position the cursor just after * When in Emacs mode, position the cursor just after
* the mark. * the mark.
@@ -4810,8 +4820,12 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex)
window_copy_move_left(s, &fx, &fy, 0); window_copy_move_left(s, &fx, &fy, 0);
} }
} }
fx = data->cx;
fy = screen_hsize(data->backing) - data->oy + data->cy;
window_copy_search_set_index(data, fx, fy);
} }
} } else if (!visible_only)
window_copy_search_marks(wme, &ss, regex, 0);
window_copy_redraw_screen(wme); window_copy_redraw_screen(wme);
screen_free(&ss); screen_free(&ss);
@@ -4854,6 +4868,51 @@ window_copy_clip_width(u_int width, u_int b, u_int sx, u_int sy)
return ((b + width > sx * sy) ? (sx * sy) - b : width); return ((b + width > sx * sy) ? (sx * sy) - b : width);
} }
static int
window_copy_grow_searchpos(struct window_copy_mode_data *data, u_int px,
u_int py, u_int sx)
{
u_int pos;
if (py > (UINT_MAX - px) / sx) {
free(data->searchpos);
data->searchpos = NULL;
data->searchposcount = 0;
data->searchpossize = 0;
return (0);
}
if (data->searchposcount == data->searchpossize) {
data->searchpossize *= 2;
data->searchpos = xreallocarray(data->searchpos,
data->searchpossize, sizeof *data->searchpos);
}
pos = py * sx + px;
data->searchpos[data->searchposcount++] = pos;
return (1);
}
static void
window_copy_search_set_index(struct window_copy_mode_data *data, u_int px,
u_int py)
{
u_int position, left = 0, right = data->searchposcount, middle;
u_int sx = data->backing->grid->sx;
data->searchindex = -1;
if (py > (UINT_MAX - px) / sx)
return;
position = py * sx + px;
while (left < right) {
middle = left + ((right - left) / 2);
if (data->searchpos[middle] < position)
left = middle + 1;
else
right = middle;
}
if (left < data->searchposcount && data->searchpos[left] == position)
data->searchindex = left + 1;
}
static u_int static u_int
window_copy_search_mark_match(struct window_copy_mode_data *data, u_int px, window_copy_search_mark_match(struct window_copy_mode_data *data, u_int px,
u_int py, u_int width, int regex) u_int py, u_int width, int regex)
@@ -4894,7 +4953,8 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp,
struct screen_write_ctx ctx; struct screen_write_ctx ctx;
struct grid *gd = s->grid; struct grid *gd = s->grid;
struct grid_cell gc; struct grid_cell gc;
int found, cis, stopped = 0; int found, cis, stopped = 0, oldindex;
int trackpos = 0;
int cflags = REG_EXTENDED; int cflags = REG_EXTENDED;
u_int px, py, nfound = 0, width; u_int px, py, nfound = 0, width;
u_int ssize = 1, start, end, sx = gd->sx; u_int ssize = 1, start, end, sx = gd->sx;
@@ -4939,6 +4999,14 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp,
start = 0; start = 0;
end = gd->hsize + sy; end = gd->hsize + sy;
stop = get_timer() + WINDOW_COPY_SEARCH_ALL_TIMEOUT; stop = get_timer() + WINDOW_COPY_SEARCH_ALL_TIMEOUT;
oldindex = data->searchindex;
data->searchindex = -1;
free(data->searchpos);
data->searchpos = xreallocarray(NULL, 64,
sizeof *data->searchpos);
data->searchposcount = 0;
data->searchpossize = 64;
trackpos = 1;
} }
again: again:
@@ -4964,6 +5032,9 @@ again:
break; break;
} }
nfound++; nfound++;
if (!visible_only && trackpos)
trackpos = window_copy_grow_searchpos(data, px, py,
sx);
px += window_copy_search_mark_match(data, px, py, width, px += window_copy_search_mark_match(data, px, py, width,
regex); regex);
} }
@@ -4992,6 +5063,14 @@ again:
if (!visible_only) { if (!visible_only) {
if (stopped) { if (stopped) {
data->searchindex = -1;
if (trackpos) {
/* Keep this non-NULL to avoid another full search. */
data->searchpos = xreallocarray(data->searchpos, 1,
sizeof *data->searchpos);
data->searchpossize = 1;
}
data->searchposcount = 0;
if (nfound > 1000) if (nfound > 1000)
data->searchcount = 1000; data->searchcount = 1000;
else if (nfound > 100) else if (nfound > 100)
@@ -5004,6 +5083,9 @@ again:
} else { } else {
data->searchcount = nfound; data->searchcount = nfound;
data->searchmore = 0; data->searchmore = 0;
if (data->searchindex == -1 && oldindex > 0 &&
oldindex <= data->searchcount)
data->searchindex = oldindex;
} }
} }
@@ -5021,7 +5103,12 @@ window_copy_clear_marks(struct window_mode_entry *wme)
struct window_copy_mode_data *data = wme->data; struct window_copy_mode_data *data = wme->data;
data->searchcount = -1; data->searchcount = -1;
data->searchindex = -1;
data->searchmore = 0; data->searchmore = 0;
free(data->searchpos);
data->searchpos = NULL;
data->searchposcount = 0;
data->searchpossize = 0;
free(data->searchmark); free(data->searchmark);
data->searchmark = NULL; data->searchmark = NULL;