diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 89db90d75..ea7dceeb0 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-join-pane.c,v 1.76 2026/10/02 12:48:52 nicm Exp $ */ +/* $OpenBSD: cmd-join-pane.c,v 1.77 2026/10/07 12:02:21 nicm Exp $ */ /* * Copyright (c) 2011 George Nachman @@ -503,7 +503,8 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) layout_close_pane(src_wp); server_client_remove_pane(src_wp); - window_lost_pane(src_w, src_wp); + if (src_w != dst_w) + window_lost_pane(src_w, src_wp); TAILQ_REMOVE(&src_w->panes, src_wp, entry); TAILQ_REMOVE(&src_w->z_index, src_wp, zentry); diff --git a/options-table.c b/options-table.c index 7cd0135ca..a65694973 100644 --- a/options-table.c +++ b/options-table.c @@ -1,4 +1,4 @@ -/* $OpenBSD: options-table.c,v 1.249 2026/10/05 09:03:11 nicm Exp $ */ +/* $OpenBSD: options-table.c,v 1.251 2026/10/07 12:59:50 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -1414,8 +1414,12 @@ const struct options_table_entry options_table[] = { "#{t/p:top_line_time}#{?#{e|>:#{top_line_time},0}, ,}" "[#{copy_position}/#{copy_position_limit}]" "#{?search_timed_out, (timed out)," - "#{?search_count, (#{search_count}" - "#{?search_count_partial,+,} results),}}", + "#{?search_count_present, (" + "#{?#{==:#{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." }, @@ -1777,6 +1781,14 @@ const struct options_table_entry options_table[] = { .text = "Default style of the active pane." }, + { .name = "window-default-command", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "", + .text = "Default command to run in new panes. If set, " + "overrides 'default-command'." + }, + { .name = "window-pane-current-status-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, diff --git a/spawn.c b/spawn.c index 5a6aeea20..0bd6bdc70 100644 --- a/spawn.c +++ b/spawn.c @@ -1,4 +1,4 @@ -/* $OpenBSD: spawn.c,v 1.54 2026/10/02 13:20:42 nicm Exp $ */ +/* $OpenBSD: spawn.c,v 1.55 2026/10/07 12:55:41 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -377,7 +377,9 @@ spawn_pane(struct spawn_context *sc, char **cause) * directory. */ if (sc->argc == 0 && (~sc->flags & SPAWN_RESPAWN)) { - cmd = options_get_string(s->options, "default-command"); + cmd = options_get_string(w->options, "window-default-command"); + if (cmd == NULL || *cmd == '\0') + cmd = options_get_string(s->options, "default-command"); if (cmd != NULL && *cmd != '\0') { argc = 1; argv = (char **)&cmd; diff --git a/tmux.1 b/tmux.1 index 21b462c7f..c7c42c28c 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1177 2026/10/06 17:45:26 schwarze Exp $ +.\" $OpenBSD: tmux.1,v 1.1179 2026/10/07 12:59:50 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -14,7 +14,7 @@ .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: October 6 2026 $ +.Dd $Mdocdate: October 7 2026 $ .Dt TMUX 1 .Os .Sh NAME @@ -3621,6 +3621,8 @@ is the command to execute. If .Ar shell\-command is not specified, the value of the +.Ic window\-default\-command +or .Ic default\-command option is used. .Fl c @@ -6181,6 +6183,11 @@ see the .Sx STYLES section. .Pp +.It Ic window\-default\-command Ar shell\-command +Per-window version of the +.Ic default\-command +option. +.Pp .It Ic window\-status\-activity\-style Ar style Set status line style for windows with an activity alert. For how to specify @@ -7483,7 +7490,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_upper" Ta "" Ta "Top of scroll region in pane" .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_present" Ta "" Ta "1 if search count is available" .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 "selection_active" Ta "" Ta "1 if selection started and changes with the cursor in copy mode" diff --git a/window-copy.c b/window-copy.c index bac43ef20..48413ce87 100644 --- a/window-copy.c +++ b/window-copy.c @@ -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 @@ -87,6 +87,10 @@ static int window_copy_last_regex(struct grid *, u_int, u_int, u_int, int); static int window_copy_search_mark_at(struct window_copy_mode_data *, 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, char *, u_int *); static void window_copy_cstrtocellpos(struct grid *, u_int, u_int *, @@ -341,6 +345,10 @@ struct window_copy_mode_data { char *searchstr; u_char *searchmark; int searchcount; + int searchindex; + u_int *searchpos; + u_int searchposcount; + u_int searchpossize; int searchmore; int searchall; int searchx; @@ -581,6 +589,8 @@ window_copy_common_init(struct window_mode_entry *wme) data->searchstr = NULL; } data->searchx = data->searchy = data->searcho = -1; + data->searchcount = -1; + data->searchindex = -1; data->searchall = 1; data->jumptype = WINDOW_COPY_OFF; @@ -682,6 +692,7 @@ window_copy_free(struct window_mode_entry *wme) evtimer_del(&data->refresh_timer); free(data->searchmark); + free(data->searchpos); free(data->searchstr); 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_timed_out", "%d", data->timeout); if (data->searchcount != -1) { + format_add(ft, "search_count_present", "1"); format_add(ft, "search_count", "%d", data->searchcount); 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); @@ -1194,9 +1209,11 @@ window_copy_size_changed(struct window_mode_entry *wme) struct screen *s = &data->screen; struct screen_write_ctx ctx; int search = (data->searchmark != NULL); + int searchindex = data->searchindex; window_copy_clear_selection(wme); window_copy_clear_marks(wme); + data->searchindex = searchindex; screen_write_start(&ctx, 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; free(data->searchstr); data->searchstr = xstrdup(arg0); - if (!window_copy_search_up(wme, 0)) { - window_copy_clear_marks(wme); + if (!window_copy_search_up(wme, 0)) return (WINDOW_COPY_CMD_REDRAW); - } break; case '+': data->searchtype = WINDOW_COPY_SEARCHDOWN; data->searchregex = 0; free(data->searchstr); data->searchstr = xstrdup(arg0); - if (!window_copy_search_down(wme, 0)) { - window_copy_clear_marks(wme); + if (!window_copy_search_down(wme, 0)) return (WINDOW_COPY_CMD_REDRAW); - } break; } return (action); @@ -3040,20 +3053,16 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) data->searchregex = 0; free(data->searchstr); data->searchstr = xstrdup(arg0); - if (!window_copy_search_down(wme, 0)) { - window_copy_clear_marks(wme); + if (!window_copy_search_down(wme, 0)) return (WINDOW_COPY_CMD_REDRAW); - } break; case '-': data->searchtype = WINDOW_COPY_SEARCHUP; data->searchregex = 0; free(data->searchstr); data->searchstr = xstrdup(arg0); - if (!window_copy_search_up(wme, 0)) { - window_copy_clear_marks(wme); + if (!window_copy_search_up(wme, 0)) return (WINDOW_COPY_CMD_REDRAW); - } } return (action); } @@ -4698,6 +4707,7 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) return (0); if (data->searchall || wp->searchstr == NULL || + data->searchpos == NULL || wp->searchregex != regex) { visible_only = 0; data->searchall = 0; @@ -4775,8 +4785,8 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) fx = data->cx; fy = screen_hsize(data->backing) - data->oy + data->cy; } - if (direction) { + window_copy_search_set_index(data, fx, fy); /* * When in Emacs mode, position the cursor just after * 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); } } + 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); 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); } +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 window_copy_search_mark_match(struct window_copy_mode_data *data, u_int px, 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 grid *gd = s->grid; struct grid_cell gc; - int found, cis, stopped = 0; + int found, cis, stopped = 0, oldindex; + int trackpos = 0; int cflags = REG_EXTENDED; u_int px, py, nfound = 0, width; 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; end = gd->hsize + sy; 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: @@ -4964,6 +5032,9 @@ again: break; } nfound++; + if (!visible_only && trackpos) + trackpos = window_copy_grow_searchpos(data, px, py, + sx); px += window_copy_search_mark_match(data, px, py, width, regex); } @@ -4992,6 +5063,14 @@ again: if (!visible_only) { 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) data->searchcount = 1000; else if (nfound > 100) @@ -5004,6 +5083,9 @@ again: } else { data->searchcount = nfound; 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; data->searchcount = -1; + data->searchindex = -1; data->searchmore = 0; + free(data->searchpos); + data->searchpos = NULL; + data->searchposcount = 0; + data->searchpossize = 0; free(data->searchmark); data->searchmark = NULL;