Merge branch 'master' into floating_panes

This commit is contained in:
Dane Jensen
2026-06-19 11:31:46 -07:00
14 changed files with 483 additions and 171 deletions

View File

@@ -168,6 +168,7 @@ cmd_refresh_report(struct tty *tty, const char *value)
{ {
struct window_pane *wp; struct window_pane *wp;
u_int pane; u_int pane;
int fg, bg;
size_t size = 0; size_t size = 0;
char *copy, *split; char *copy, *split;
@@ -184,8 +185,14 @@ cmd_refresh_report(struct tty *tty, const char *value)
if (wp == NULL) if (wp == NULL)
goto out; goto out;
tty_keys_colours(tty, split, strlen(split), &size, &wp->control_fg, fg = wp->control_fg;
&wp->control_bg); bg = wp->control_bg;
if (tty_keys_colours(tty, split, strlen(split), &size, &fg, &bg) == 0) {
if (bg != wp->control_bg)
wp->flags |= PANE_THEMECHANGED;
wp->control_fg = fg;
wp->control_bg = bg;
}
out: out:
free(copy); free(copy);

View File

@@ -224,6 +224,14 @@ control_notify_session_window_changed(struct session *s)
{ {
struct client *c; struct client *c;
/*
* A deferred session-window-changed notification can fire after the
* session has been destroyed (which sets curw to NULL) but is kept
* alive by the notification's reference. Skip the notification.
*/
if (s->curw == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;

View File

@@ -1856,15 +1856,6 @@ format_cb_keypad_flag(struct format_tree *ft)
return (NULL); return (NULL);
} }
/* Callback for loop_last_flag. */
static void *
format_cb_loop_last_flag(struct format_tree *ft)
{
if (ft->flags & FORMAT_LAST)
return (xstrdup("1"));
return (xstrdup("0"));
}
/* Callback for mouse_all_flag. */ /* Callback for mouse_all_flag. */
static void * static void *
format_cb_mouse_all_flag(struct format_tree *ft) format_cb_mouse_all_flag(struct format_tree *ft)
@@ -3363,9 +3354,6 @@ static const struct format_table_entry format_table[] = {
{ "last_window_index", FORMAT_TABLE_STRING, { "last_window_index", FORMAT_TABLE_STRING,
format_cb_last_window_index format_cb_last_window_index
}, },
{ "loop_last_flag", FORMAT_TABLE_STRING,
format_cb_loop_last_flag
},
{ "mouse_all_flag", FORMAT_TABLE_STRING, { "mouse_all_flag", FORMAT_TABLE_STRING,
format_cb_mouse_all_flag format_cb_mouse_all_flag
}, },
@@ -4703,7 +4691,7 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
struct evbuffer *buffer; struct evbuffer *buffer;
size_t size; size_t size;
struct session *s, **l; struct session *s, **l;
int i, n, last = 0; int i, n;
if (format_choose(es, fmt, &all, &active, 0) != 0) { if (format_choose(es, fmt, &all, &active, 0) != 0) {
all = xstrdup(fmt); all = xstrdup(fmt);
@@ -4724,9 +4712,9 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
use = active; use = active;
else else
use = all; use = all;
if (i == n - 1) nft = format_create(c, item, FORMAT_NONE, ft->flags);
last = FORMAT_LAST; format_add(nft, "loop_index", "%d", i);
nft = format_create(c, item, FORMAT_NONE, ft->flags|last); format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, ft->c, s, NULL, NULL); format_defaults(nft, ft->c, s, NULL, NULL);
format_copy_state(&next, es, 0); format_copy_state(&next, es, 0);
next.ft = nft; next.ft = nft;
@@ -4818,7 +4806,7 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
size_t size; size_t size;
struct winlink *wl, **l; struct winlink *wl, **l;
struct window *w; struct window *w;
int i, n, last = 0; int i, n;
if (ft->s == NULL) { if (ft->s == NULL) {
format_log(es, "window loop but no session"); format_log(es, "window loop but no session");
@@ -4843,10 +4831,10 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
use = active; use = active;
else else
use = all; use = all;
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, FORMAT_WINDOW|w->id, nft = format_create(c, item, FORMAT_WINDOW|w->id,
ft->flags|last); ft->flags);
format_add(nft, "loop_index", "%d", i);
format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, ft->c, ft->s, wl, NULL); format_defaults(nft, ft->c, ft->s, wl, NULL);
/* Add neighbor window data to the format tree. */ /* Add neighbor window data to the format tree. */
@@ -4893,7 +4881,7 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
struct evbuffer *buffer; struct evbuffer *buffer;
size_t size; size_t size;
struct window_pane *wp, **l; struct window_pane *wp, **l;
int i, n, last = 0; int i, n;
if (ft->w == NULL) { if (ft->w == NULL) {
format_log(es, "pane loop but no window"); format_log(es, "pane loop but no window");
@@ -4917,10 +4905,10 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
use = active; use = active;
else else
use = all; use = all;
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, FORMAT_PANE|wp->id, nft = format_create(c, item, FORMAT_PANE|wp->id,
ft->flags|last); ft->flags);
format_add(nft, "loop_index", "%d", i);
format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, ft->c, ft->s, ft->wl, wp); format_defaults(nft, ft->c, ft->s, ft->wl, wp);
format_copy_state(&next, es, 0); format_copy_state(&next, es, 0);
next.ft = nft; next.ft = nft;
@@ -4955,7 +4943,7 @@ format_loop_clients(struct format_expand_state *es, const char *fmt)
char *expanded, *value; char *expanded, *value;
struct evbuffer *buffer; struct evbuffer *buffer;
size_t size; size_t size;
int i, n, last = 0; int i, n;
buffer = evbuffer_new(); buffer = evbuffer_new();
if (buffer == NULL) if (buffer == NULL)
@@ -4965,9 +4953,9 @@ format_loop_clients(struct format_expand_state *es, const char *fmt)
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
c = l[i]; c = l[i];
format_log(es, "client loop: %s", c->name); format_log(es, "client loop: %s", c->name);
if (i == n - 1) nft = format_create(c, item, 0, ft->flags);
last = FORMAT_LAST; format_add(nft, "loop_index", "%d", i);
nft = format_create(c, item, 0, ft->flags|last); format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, c, ft->s, ft->wl, ft->wp); format_defaults(nft, c, ft->s, ft->wl, ft->wp);
format_copy_state(&next, es, 0); format_copy_state(&next, es, 0);
next.ft = nft; next.ft = nft;

View File

@@ -450,11 +450,11 @@ sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
new->set_ra = si->set_ra; new->set_ra = si->set_ra;
/* subtract offset */ /* subtract offset */
new->ra_x = new->ra_x > pox ? new->ra_x - pox : 0; new->ra_x = si->ra_x > pox ? si->ra_x - pox : 0;
new->ra_y = new->ra_y > poy ? new->ra_y - poy : 0; new->ra_y = si->ra_y > poy ? si->ra_y - poy : 0;
/* clamp to size */ /* clamp to size */
new->ra_x = si->ra_x < psx ? si->ra_x : psx; new->ra_x = new->ra_x < psx ? new->ra_x : psx;
new->ra_y = si->ra_y < psy ? si->ra_y : psy; new->ra_y = new->ra_y < psy ? new->ra_y : psy;
/* resize */ /* resize */
new->ra_x = new->ra_x * xpixel / si->xpixel; new->ra_x = new->ra_x * xpixel / si->xpixel;
new->ra_y = new->ra_y * ypixel / si->ypixel; new->ra_y = new->ra_y * ypixel / si->ypixel;

View File

@@ -25,11 +25,10 @@
#include "tmux.h" #include "tmux.h"
#define DEFAULT_SESSION_MENU \ #define DEFAULT_SESSION_MENU \
" 'Next' 'n' {switch-client -n}" \ " #{S/t:#{?#{&&:#{<:#{loop_index},6},#{!:#{session_active}}},'Switch To #[underscore]#{session_name}' '' {switch-client -t=#{session_id}#} ,}}" \
" 'Previous' 'p' {switch-client -p}" \
" ''" \ " ''" \
" 'Renumber' 'N' {move-window -r}" \ " 'Renumber' 'N' {move-window -r}" \
" 'Rename' 'r' {command-prompt -I \"#S\" {rename-session -- '%%'}}" \ " 'Rename' 'r' {command-prompt -I '#S' {rename-session -- '%%'}}" \
" 'Detach' 'd' {detach-client}" \ " 'Detach' 'd' {detach-client}" \
" ''" \ " ''" \
" 'New Session' 's' {new-session}" \ " 'New Session' 's' {new-session}" \
@@ -489,8 +488,8 @@ key_bindings_init(void)
"bind -n WheelUpStatus { previous-window }", "bind -n WheelUpStatus { previous-window }",
/* Mouse button 3 down on status left. */ /* Mouse button 3 down on status left. */
"bind -n MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }", "bind -n MouseDown3StatusLeft { run -C \"display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU "\" }",
"bind -n M-MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }", "bind -n M-MouseDown3StatusLeft { run -C \"display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU "\" }",
/* Mouse button 3 down on status line. */ /* Mouse button 3 down on status line. */
"bind -n MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}", "bind -n MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}",
@@ -581,6 +580,8 @@ key_bindings_init(void)
"bind -Tcopy-mode M-Down { send -X halfpage-down }", "bind -Tcopy-mode M-Down { send -X halfpage-down }",
"bind -Tcopy-mode C-Up { send -X scroll-up }", "bind -Tcopy-mode C-Up { send -X scroll-up }",
"bind -Tcopy-mode C-Down { send -X scroll-down }", "bind -Tcopy-mode C-Down { send -X scroll-down }",
"bind -Tcopy-mode M-C-Up { send -X previous-prompt }",
"bind -Tcopy-mode M-C-Down { send -X next-prompt }",
/* Copy mode (vi) keys. */ /* Copy mode (vi) keys. */
"bind -Tcopy-mode-vi '#' { send -FX search-backward -- '#{copy_cursor_word}' }", "bind -Tcopy-mode-vi '#' { send -FX search-backward -- '#{copy_cursor_word}' }",

View File

@@ -84,6 +84,7 @@ struct mode_tree_data {
int no_matches; int no_matches;
enum mode_tree_search_dir search_dir; enum mode_tree_search_dir search_dir;
int search_icase; int search_icase;
int help;
}; };
struct mode_tree_item { struct mode_tree_item {
@@ -124,6 +125,8 @@ struct mode_tree_menu {
}; };
static void mode_tree_free_items(struct mode_tree_list *); static void mode_tree_free_items(struct mode_tree_list *);
static void mode_tree_draw_help(struct mode_tree_data *,
struct screen_write_ctx *);
static const struct menu_item mode_tree_menu_items[] = { static const struct menu_item mode_tree_menu_items[] = {
{ "Scroll Left", '<', NULL }, { "Scroll Left", '<', NULL },
@@ -135,31 +138,31 @@ static const struct menu_item mode_tree_menu_items[] = {
}; };
static const char* mode_tree_help_start[] = { static const char* mode_tree_help_start[] = {
"\r\033[1m Up, k \033[0m\016x\017 \033[0mMove cursor up\n", "#[bold] Up, k #[default]#[acs]x#[default] Move cursor up",
"\r\033[1m Down, j \033[0m\016x\017 \033[0mMove cursor down\n", "#[bold] Down, j #[default]#[acs]x#[default] Move cursor down",
"\r\033[1m g \033[0m\016x\017 \033[0mGo to top\n", "#[bold] g #[default]#[acs]x#[default] Go to top",
"\r\033[1m G \033[0m\016x\017 \033[0mGo to bottom\n", "#[bold] G #[default]#[acs]x#[default] Go to bottom",
"\r\033[1m PPage, C-b \033[0m\016x\017 \033[0mPage up\n", "#[bold] PPage, C-b #[default]#[acs]x#[default] Page up",
"\r\033[1m NPage, C-f \033[0m\016x\017 \033[0mPage down\n", "#[bold] NPage, C-f #[default]#[acs]x#[default] Page down",
"\r\033[1m Left, h \033[0m\016x\017 \033[0mCollapse %1\n", "#[bold] Left, h #[default]#[acs]x#[default] Collapse %1",
"\r\033[1m Right, l \033[0m\016x\017 \033[0mExpand %1\n", "#[bold] Right, l #[default]#[acs]x#[default] Expand %1",
"\r\033[1m M-- \033[0m\016x\017 \033[0mCollapse all %1s\n", "#[bold] M-- #[default]#[acs]x#[default] Collapse all %1s",
"\r\033[1m M-+ \033[0m\016x\017 \033[0mExpand all %1s\n", "#[bold] M-+ #[default]#[acs]x#[default] Expand all %1s",
"\r\033[1m t \033[0m\016x\017 \033[0mToggle %1 tag\n", "#[bold] t #[default]#[acs]x#[default] Toggle %1 tag",
"\r\033[1m T \033[0m\016x\017 \033[0mUntag all %1s\n", "#[bold] T #[default]#[acs]x#[default] Untag all %1s",
"\r\033[1m C-t \033[0m\016x\017 \033[0mTag all %1s\n", "#[bold] C-t #[default]#[acs]x#[default] Tag all %1s",
"\r\033[1m C-s \033[0m\016x\017 \033[0mSearch forward\n", "#[bold] C-s #[default]#[acs]x#[default] Search forward",
"\r\033[1m C-r \033[0m\016x\017 \033[0mSearch backward\n", "#[bold] C-r #[default]#[acs]x#[default] Search backward",
"\r\033[1m n \033[0m\016x\017 \033[0mRepeat search forward\n", "#[bold] n #[default]#[acs]x#[default] Repeat search forward",
"\r\033[1m N \033[0m\016x\017 \033[0mRepeat search backward\n", "#[bold] N #[default]#[acs]x#[default] Repeat search backward",
"\r\033[1m f \033[0m\016x\017 \033[0mFilter %1s\n", "#[bold] f #[default]#[acs]x#[default] Filter %1s",
"\r\033[1m O \033[0m\016x\017 \033[0mChange sort order\n", "#[bold] O #[default]#[acs]x#[default] Change sort order",
"\r\033[1m r \033[0m\016x\017 \033[0mReverse sort order\n", "#[bold] r #[default]#[acs]x#[default] Reverse sort order",
"\r\033[1m v \033[0m\016x\017 \033[0mToggle preview\n", "#[bold] v #[default]#[acs]x#[default] Toggle preview",
NULL NULL
}; };
static const char* mode_tree_help_end[] = { static const char* mode_tree_help_end[] = {
"\r\033[1m q, Escape \033[0m\016x\017 \033[0mExit mode\033[H", "#[bold] q, Escape #[default]#[acs]x#[default] Exit mode",
NULL NULL
}; };
#define MODE_TREE_HELP_DEFAULT_WIDTH 39 #define MODE_TREE_HELP_DEFAULT_WIDTH 39
@@ -928,6 +931,8 @@ mode_tree_draw(struct mode_tree_data *mtd)
} }
done: done:
if (mtd->help)
mode_tree_draw_help(mtd, &ctx);
screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0); screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
screen_write_stop(&ctx); screen_write_stop(&ctx);
} }
@@ -1175,12 +1180,28 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
} }
static void static void
mode_tree_display_help(__unused struct mode_tree_data *mtd, struct client *c) mode_tree_draw_help_line(struct screen_write_ctx *ctx,
const struct grid_cell *gc, const char *line, const char *item, u_int x,
u_int y, u_int w)
{ {
struct session *s = c->session; char *expanded;
u_int px, py, w, h = 0;
expanded = cmd_template_replace(line, item, 1);
screen_write_cursormove(ctx, x, y, 0);
screen_write_clearcharacter(ctx, w, gc->bg);
screen_write_cursormove(ctx, x, y, 0);
format_draw(ctx, gc, w, expanded, NULL, 0);
free(expanded);
}
static void
mode_tree_draw_help(struct mode_tree_data *mtd, struct screen_write_ctx *ctx)
{
struct screen *s = &mtd->screen;
struct grid_cell gc;
const char **line, **lines = NULL, *item = "item"; const char **line, **lines = NULL, *item = "item";
char *new_line; u_int sx = screen_size_x(s), sy = screen_size_y(s);
u_int x, y, w, h = 0, box_w, box_h;
if (mtd->helpcb == NULL) if (mtd->helpcb == NULL)
w = MODE_TREE_HELP_DEFAULT_WIDTH; w = MODE_TREE_HELP_DEFAULT_WIDTH;
@@ -1196,33 +1217,32 @@ mode_tree_display_help(__unused struct mode_tree_data *mtd, struct client *c)
for (line = mode_tree_help_end; *line != NULL; line++) for (line = mode_tree_help_end; *line != NULL; line++)
h++; h++;
if (c->tty.sx < w || c->tty.sy < h) box_w = w + 2;
box_h = h + 2;
if (sx < box_w || sy < box_h)
return; return;
px = (c->tty.sx - w) / 2; x = (sx - box_w) / 2;
py = (c->tty.sy - h) / 2; y = (sy - box_h) / 2;
if (popup_display(POPUP_CLOSEANYKEY|POPUP_NOJOB, BOX_LINES_DEFAULT, memcpy(&gc, &grid_default_cell, sizeof gc);
NULL, px, py, w, h, NULL, NULL, 0, NULL, NULL, NULL, c, s, NULL, screen_write_cursormove(ctx, x, y, 0);
NULL, NULL, NULL) != 0) screen_write_box(ctx, box_w, box_h, BOX_LINES_DEFAULT, &gc, NULL);
return;
popup_write(c, "\033[H\033[?25l\033[?7l\033)0", 17); y++;
for (line = mode_tree_help_start; *line != NULL; line++) { x++;
new_line = cmd_template_replace(*line, item, 1); for (line = mode_tree_help_start; *line != NULL; line++, y++)
popup_write(c, new_line, strlen(new_line)); mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w);
free(new_line); for (line = lines; line != NULL && *line != NULL; line++, y++)
} mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w);
for (line = lines; line != NULL && *line != NULL; line++) { for (line = mode_tree_help_end; *line != NULL; line++, y++)
new_line = cmd_template_replace(*line, item, 1); mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w);
popup_write(c, new_line, strlen(new_line)); }
free(new_line);
} static void
for (line = mode_tree_help_end; *line != NULL; line++) { mode_tree_display_help(struct mode_tree_data *mtd)
new_line = cmd_template_replace(*line, item, 1); {
popup_write(c, new_line, strlen(new_line)); mtd->help = 1;
free(new_line); mode_tree_draw(mtd);
}
popup_write(c, "\033[H", 3);
} }
int int
@@ -1239,6 +1259,13 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
return (1); return (1);
} }
if (mtd->help) {
mtd->help = 0;
mode_tree_draw(mtd);
*key = KEYC_NONE;
return (0);
}
if (KEYC_IS_MOUSE(*key) && m != NULL) { if (KEYC_IS_MOUSE(*key) && m != NULL) {
if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) { if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
*key = KEYC_NONE; *key = KEYC_NONE;
@@ -1304,7 +1331,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
return (1); return (1);
case KEYC_F1: case KEYC_F1:
case 'h'|KEYC_CTRL: case 'h'|KEYC_CTRL:
mode_tree_display_help(mtd, c); mode_tree_display_help(mtd);
break; break;
case KEYC_UP: case KEYC_UP:
case 'k': case 'k':

20
regress/command-alias.sh Normal file
View File

@@ -0,0 +1,20 @@
#!/bin/sh
# command-alias expansion
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -sfoo || exit 1
$TMUX split-window -d -tfoo:0.0 || exit 1
$TMUX set -s command-alias[100] zoom='resize-pane -Z' || exit 1
$TMUX zoom -tfoo:0.0 || exit 1
[ "$($TMUX display-message -p -tfoo:0.0 '#{window_zoomed_flag}')" = 1 ] || exit 1
$TMUX kill-server 2>/dev/null
exit 0

248
regress/tty-draw-line.sh Normal file
View File

@@ -0,0 +1,248 @@
#!/bin/sh
# Exercise tty_draw_line through a real client. An inner tmux is attached
# inside an outer tmux pane; the outer pane is then captured to inspect what
# the inner client actually drew.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
fail() {
echo "$*" >&2
exit 1
}
capture() {
$TMUX capturep -pS0 -E- >$TMP || exit 1
}
capturee() {
$TMUX capturep -peS0 -E- >$TMP || exit 1
}
capturen() {
$TMUX capturep -pNS0 -E- >$TMP || exit 1
}
captureen() {
$TMUX capturep -peNS0 -E- >$TMP || exit 1
}
check_line() {
line=$1
want=$2
got=$(sed -n "$line"p $TMP)
[ "$got" = "$want" ] || fail "line $line: expected '$want', got '$got'"
}
check_grep() {
pattern=$1
grep -q "$pattern" $TMP || fail "missing pattern: $pattern"
}
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
TMP=$(mktemp)
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" 0 1 15
$TMUX2 -f/dev/null new -d -x20 -y6 -s test \
"printf 'abcdefghijklmnopqrst'; exec sleep 100" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g mode-style "fg=white,bg=red" || exit 1
$TMUX2 setw -g mode-keys vi || exit 1
$TMUX2 neww -d \
"printf '\033[31;44;1mRED\033[0m\tTAIL\nu:e\314\201:\347\225\214:\360\237\207\272\360\237\207\270:Z\nAAA BBB'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf '12345678\347\225\214Z'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf 'wrap-ABCDEFGHIJKLMNOZ'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf 'AA BB'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf 'XYZ'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf '\347\225\214\347\225\214\347\225\214\347\225\214\347\225\214'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf '123456789Z'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf 'abcdef\347\225\214GHIJKLMNOPQRSTUVWXYZ'; printf '\033[H'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf 'abcdefghijklmnopqrst\r\033[KXYZ\nnext'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf 'ab\tcdefghijklmnopqrstuvwxyz'; printf '\033[H'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"printf '\033(0x\033(B'; exec sleep 100" || exit 1
$TMUX2 neww -d \
"awk 'BEGIN { for (i = 0; i < 1100; i++) printf \"a\" }'; exec sleep 100" || exit 1
$TMUX2 selectw -t:0 || exit 1
$TMUX -f/dev/null new -d -x20 -y6 || exit 1
$TMUX set -g status off || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
CLIENT=$($TMUX2 list-clients -F '#{client_name}' | head -1)
[ -n "$CLIENT" ] || fail "no inner client"
# Long line, then short line: default cells after cellsize must clear stale text.
capture
check_line 1 "abcdefghijklmnopqrst"
$TMUX2 selectw -t:5 || exit 1
sleep 1
capture
check_line 1 "XYZ"
grep -q '^XYZdefghijklmnopqrst$' $TMP && fail "short redraw left stale tail"
# Styles, tabs, same runs, Unicode combining/wide/flag cells.
$TMUX2 selectw -t:1 || exit 1
sleep 1
capture
check_grep '^RED[ ]*TAIL$'
check_grep '^u:e.*:.*:.*:Z$'
check_line 3 "AAA BBB"
capturee
esc=$(printf '\033')
grep -q "$esc" $TMP || fail "styled redraw did not preserve attributes"
# Wide character clipping and padding after resize.
$TMUX2 selectw -t:2 || exit 1
$TMUX resizew -x10 -y6 || exit 1
sleep 1
$TMUX2 respawnp -k "printf '12345678\347\225\214Z'; exec sleep 100" || exit 1
sleep 1
capture
check_line 1 "12345678界"
$TMUX resizew -x9 -y6 || exit 1
sleep 1
capture
check_line 1 "12345678"
grep -q '^12345678Z$' $TMP && fail "wide clipping left stale cell"
# Repeated wide characters at the right edge should not leave orphan padding.
$TMUX resizew -x9 -y6 || exit 1
$TMUX2 selectw -t:6 || exit 1
sleep 1
$TMUX2 respawnp -k "printf '\347\225\214\347\225\214\347\225\214\347\225\214\347\225\214'; exec sleep 100" || exit 1
sleep 1
capture
check_line 1 "界界界界"
# Tabs should clear stale cells as an empty run.
$TMUX resizew -x10 -y6 || exit 1
$TMUX2 selectw -t:7 || exit 1
sleep 1
capture
check_line 1 "123456789Z"
$TMUX2 respawnp -k "printf '123456789\t'; exec sleep 100" || exit 1
sleep 1
capture
check_line 1 "123456789"
grep -q '^123456789.*Z$' $TMP && fail "tab clipping left stale cell"
# Tabs clipped at both ends, at the right, and at the left. This is like
# drawing spans over the middle of a tab when an overlay or viewport clips the
# pane line.
$TMUX resizew -x4 -y6 || exit 1
$TMUX2 selectw -t:10 || exit 1
$TMUX2 resizew -t:10 -x26 -y6 || exit 1
$TMUX2 respawnp -k "printf 'ab\tcdefghijklmnopqrstuvwxyz'; printf '\033[H'; exec sleep 100" || exit 1
$TMUX2 refresh -t"$CLIENT" -c || exit 1
sleep 1
capturen
check_line 1 "ab "
$TMUX2 refresh -t"$CLIENT" -R 2 || exit 1
sleep 1
capturen
check_line 1 " "
$TMUX2 refresh -t"$CLIENT" -R 4 || exit 1
sleep 1
capturen
check_line 1 " cd"
$TMUX2 refresh -t"$CLIENT" -R 2 || exit 1
sleep 1
capturen
check_line 1 "cdef"
$TMUX2 refresh -t"$CLIENT" -L 8 || exit 1
# Horizontal clipping that starts in or near a wide character should not draw
# partial padding or stale cells.
$TMUX resizew -x20 -y6 || exit 1
$TMUX2 selectw -t:8 || exit 1
$TMUX2 resizew -t:8 -x26 -y6 || exit 1
$TMUX2 respawnp -k "printf 'abcdef\347\225\214GHIJKLMNOPQRSTUVWXYZ'; printf '\033[H'; exec sleep 100" || exit 1
$TMUX2 refresh -t"$CLIENT" -c || exit 1
sleep 1
$TMUX2 refresh -t"$CLIENT" -R 4 || exit 1
sleep 1
capture
check_line 1 "ef界GHIJKLMNOPQRSTUV"
$TMUX2 refresh -t"$CLIENT" -R 1 || exit 1
sleep 1
capture
check_line 1 "f界GHIJKLMNOPQRSTUVW"
$TMUX2 refresh -t"$CLIENT" -L 5 || exit 1
# Wrapped line redraw.
$TMUX resizew -x20 -y6 || exit 1
$TMUX2 selectw -t:3 || exit 1
sleep 1
capture
check_line 1 "wrap-ABCDEFGHIJKLMNO"
check_line 2 "Z"
# Selection over spaces should still paint attributes for otherwise empty cells.
$TMUX2 selectw -t:4 || exit 1
sleep 1
$TMUX2 copy-mode -H || exit 1
$TMUX2 send -X start-of-line || exit 1
$TMUX2 send -X -N 2 cursor-right || exit 1
$TMUX2 send -X begin-selection || exit 1
$TMUX2 send -X -N 3 cursor-right || exit 1
sleep 1
capture
check_line 1 "AA BB"
capturee
sed -n 1p $TMP | grep -q "$esc" || fail "selected spaces did not draw attributes"
# Selection on a short line should still draw attributes correctly. This line
# was previously expanded, then cleared and rewritten shorter, so cellsize
# remains larger than the visible text.
$TMUX2 selectw -t:9 || exit 1
sleep 1
$TMUX2 copy-mode -H || exit 1
$TMUX2 send -X history-top || exit 1
$TMUX2 send -X start-of-line || exit 1
$TMUX2 send -X begin-selection || exit 1
$TMUX2 send -X cursor-down || exit 1
sleep 1
capture
check_line 1 "XYZ"
check_line 2 "next"
captureen
sed -n 1p $TMP | grep -q "$esc" || \
fail "selected short-line tail did not draw attributes"
# ACS/charset cells should be redrawn correctly.
$TMUX resizew -x20 -y6 || exit 1
$TMUX2 selectw -t:11 || exit 1
sleep 1
capture
check_line 1 "│"
# A long run with the same attributes should flush the internal draw buffer.
$TMUX resizew -x1100 -y6 || exit 1
$TMUX2 resizew -t:12 -x1100 -y6 || exit 1
$TMUX2 selectw -t:12 || exit 1
sleep 1
capture
len=$(sed -n 1p $TMP | wc -c)
[ "$len" -ge 1100 ] || fail "long same-style line was truncated"
exit 0

1
tmux.1
View File

@@ -6902,6 +6902,7 @@ The following variables are available, where appropriate:
.It Li "keypad_flag" Ta "" Ta "Pane keypad flag" .It Li "keypad_flag" Ta "" Ta "Pane keypad flag"
.It Li "last_window_index" Ta "" Ta "Index of last window in session" .It Li "last_window_index" Ta "" Ta "Index of last window in session"
.It Li "line" Ta "" Ta "Line number in the list" .It Li "line" Ta "" Ta "Line number in the list"
.It Li "loop_index" Ta "" Ta "Index of item in the W:, P:, S:, or L: loop"
.It Li "loop_last_flag" Ta "" Ta "1 if last window, pane, session, client in the W:, P:, S:, or L: loop" .It Li "loop_last_flag" Ta "" Ta "1 if last window, pane, session, client in the W:, P:, S:, or L: loop"
.It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag" .It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag"
.It Li "mouse_any_flag" Ta "" Ta "Pane mouse any flag" .It Li "mouse_any_flag" Ta "" Ta "Pane mouse any flag"

View File

@@ -89,6 +89,30 @@ tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx,
tty_repeat_space(tty, nx); tty_repeat_space(tty, nx);
} }
/* Is this cell empty? */
static u_int
tty_draw_line_get_empty(const struct grid_cell *gc,
const struct grid_cell *last, u_int nx)
{
u_int empty = 0;
if (gc->data.width > nx)
empty = nx;
else if (gc->flags & GRID_FLAG_PADDING)
empty = 1;
else if (gc->flags & GRID_FLAG_SELECTED)
empty = 0;
else if (gc->bg == last->bg && gc->attr == 0 && gc->link == 0) {
if (gc->flags & GRID_FLAG_CLEARED)
empty = 1;
else if (gc->flags & GRID_FLAG_TAB)
empty = gc->data.width;
else if (gc->data.size == 1 && *gc->data.data == ' ')
empty = 1;
}
return (empty);
}
/* Draw a line from screen to tty. */ /* Draw a line from screen to tty. */
void void
tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
@@ -105,13 +129,14 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
size_t len; size_t len;
enum tty_draw_line_state current_state, next_state; enum tty_draw_line_state current_state, next_state;
struct tty_style_ctx default_style_ctx = { 0 }; struct tty_style_ctx default_style_ctx = { 0 };
const struct grid_cell *defaults;
if (style_ctx == NULL) { if (style_ctx == NULL) {
default_style_ctx.defaults = &grid_default_cell; default_style_ctx.defaults = &grid_default_cell;
default_style_ctx.hyperlinks = s->hyperlinks; default_style_ctx.hyperlinks = s->hyperlinks;
style_ctx = &default_style_ctx; style_ctx = &default_style_ctx;
} }
defaults = style_ctx->defaults;
/* /*
* py is the line in the screen to draw. px is the start x and nx is * py is the line in the screen to draw. px is the start x and nx is
@@ -138,8 +163,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
else else
ex = screen_size_x(s); ex = screen_size_x(s);
log_debug("%s: drawing %u-%u,%u (end %u) at %u,%u; defaults: fg=%d, " log_debug("%s: drawing %u-%u,%u (end %u) at %u,%u; defaults: fg=%d, "
"bg=%d", __func__, px, px + nx, py, ex, atx, aty, "bg=%d", __func__, px, px + nx, py, ex, atx, aty, defaults->fg,
style_ctx->defaults->fg, style_ctx->defaults->bg); defaults->bg);
/* Turn off cursor while redrawing and reset region and margins. */ /* Turn off cursor while redrawing and reset region and margins. */
flags = (tty->flags & TTY_NOCURSOR); flags = (tty->flags & TTY_NOCURSOR);
@@ -150,7 +175,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
/* Start with the default cell as the last cell. */ /* Start with the default cell as the last cell. */
memcpy(&last, &grid_default_cell, sizeof last); memcpy(&last, &grid_default_cell, sizeof last);
last.bg = style_ctx->defaults->bg; last.bg = defaults->bg;
tty_default_attributes(tty, 8, style_ctx); tty_default_attributes(tty, 8, style_ctx);
/* /*
@@ -172,7 +197,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
break; break;
} }
if (i == 0) if (i == 0)
bg = style_ctx->defaults->bg; bg = defaults->bg;
else { else {
bg = gc.bg; bg = gc.bg;
if (gc.flags & GRID_FLAG_SELECTED) { if (gc.flags & GRID_FLAG_SELECTED) {
@@ -183,7 +208,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
} }
tty_attributes(tty, &last, style_ctx); tty_attributes(tty, &last, style_ctx);
log_debug("%s: clearing %u padding cells", __func__, cx); log_debug("%s: clearing %u padding cells", __func__, cx);
tty_draw_line_clear(tty, atx, aty, cx, style_ctx->defaults, bg, 0); tty_draw_line_clear(tty, atx, aty, cx, defaults, bg, 0);
if (cx == ex) if (cx == ex)
goto out; goto out;
atx += cx; atx += cx;
@@ -218,43 +243,31 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
if (i > nx) if (i > nx)
fatalx("position %u > width %u", i, nx); fatalx("position %u > width %u", i, nx);
if (px >= ex || i >= ex - px) {
/* Outside the area being drawn. */
empty = nx - i;
gcp = &grid_default_cell;
} else {
/* Get the current cell. */ /* Get the current cell. */
grid_view_get_cell(gd, px + i, py, &gc); grid_view_get_cell(gd, px + i, py, &gc);
/* Work out empty cells. */
empty = tty_draw_line_get_empty(&gc, &last,
nx - i);
if (empty != 0)
gcp = &gc;
else {
/* Update for codeset if needed. */ /* Update for codeset if needed. */
gcp = tty_check_codeset(tty, &gc); gcp = tty_check_codeset(tty, &gc);
/* And for selection. */ /* And for selection. */
if (gcp->flags & GRID_FLAG_SELECTED) { if (gcp->flags & GRID_FLAG_SELECTED) {
memcpy(&ngc, gcp, sizeof ngc); memcpy(&ngc, gcp, sizeof ngc);
if (screen_select_cell(s, &ngc, gcp)) if (screen_select_cell(s, &ngc,
gcp))
gcp = &ngc; gcp = &ngc;
} }
}
/* Work out the the empty width. */
empty = 0;
if (px >= ex || i >= ex - px) {
/* Outside the area being drawn. */
empty = 1;
} else if (gcp->data.width > nx - i) {
/* Wide character that has been truncated. */
empty = nx - i;
} else if (gcp->flags & GRID_FLAG_PADDING) {
/* Orphan padding cell. */
empty = 1;
} else if (gcp->bg == last.bg && gcp->attr == 0 &&
gcp->link == 0) {
/*
* No attributes - empty if cleared, tab or
* space.
*/
if (gcp->flags & GRID_FLAG_CLEARED)
empty = 1;
else if (gcp->flags & GRID_FLAG_TAB)
empty = gcp->data.width;
else if (gcp->data.size == 1 &&
*gcp->data.data == ' ')
empty = 1;
} }
/* Work out the next state. */ /* Work out the next state. */
@@ -284,8 +297,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
if (current_state == TTY_DRAW_LINE_EMPTY) { if (current_state == TTY_DRAW_LINE_EMPTY) {
tty_attributes(tty, &last, style_ctx); tty_attributes(tty, &last, style_ctx);
tty_draw_line_clear(tty, atx + last_i, aty, tty_draw_line_clear(tty, atx + last_i, aty,
i - last_i, style_ctx->defaults, last.bg, i - last_i, defaults, last.bg, wrapped);
wrapped);
wrapped = 0; wrapped = 0;
} else if (next_state != TTY_DRAW_LINE_SAME && } else if (next_state != TTY_DRAW_LINE_SAME &&
len != 0) { len != 0) {

View File

@@ -330,13 +330,13 @@ window_buffer_sort(struct sort_criteria *sort_crit)
} }
static const char* window_buffer_help_lines[] = { static const char* window_buffer_help_lines[] = {
"\r\033[1m Enter \033[0m\016x\017 \033[0mPaste selected %1\n", "#[bold] Enter #[default]#[acs]x#[default] Paste selected %1",
"\r\033[1m p \033[0m\016x\017 \033[0mPaste selected %1\n", "#[bold] p #[default]#[acs]x#[default] Paste selected %1",
"\r\033[1m P \033[0m\016x\017 \033[0mPaste tagged %1s\n", "#[bold] P #[default]#[acs]x#[default] Paste tagged %1s",
"\r\033[1m d \033[0m\016x\017 \033[0mDelete selected %1\n", "#[bold] d #[default]#[acs]x#[default] Delete selected %1",
"\r\033[1m D \033[0m\016x\017 \033[0mDelete tagged %1s\n", "#[bold] D #[default]#[acs]x#[default] Delete tagged %1s",
"\r\033[1m e \033[0m\016x\017 \033[0mOpen %1 in editor\n", "#[bold] e #[default]#[acs]x#[default] Open %1 in editor",
"\r\033[1m f \033[0m\016x\017 \033[0mEnter a filter\n", "#[bold] f #[default]#[acs]x#[default] Enter a filter",
NULL NULL
}; };

View File

@@ -369,15 +369,15 @@ window_client_sort(struct sort_criteria *sort_crit)
} }
static const char* window_client_help_lines[] = { static const char* window_client_help_lines[] = {
"\r\033[1m i \033[0m\016x\017 \033[0mToggle info view\n", "#[bold] i #[default]#[acs]x#[default] Toggle info view",
"\r\033[1m Enter \033[0m\016x\017 \033[0mChoose selected %1\n", "#[bold] Enter #[default]#[acs]x#[default] Choose selected %1",
"\r\033[1m d \033[0m\016x\017 \033[0mDetach selected %1\n", "#[bold] d #[default]#[acs]x#[default] Detach selected %1",
"\r\033[1m D \033[0m\016x\017 \033[0mDetach tagged %1s\n", "#[bold] D #[default]#[acs]x#[default] Detach tagged %1s",
"\r\033[1m x \033[0m\016x\017 \033[0mDetach selected %1\n", "#[bold] x #[default]#[acs]x#[default] Detach selected %1",
"\r\033[1m X \033[0m\016x\017 \033[0mDetach tagged %1s\n", "#[bold] X #[default]#[acs]x#[default] Detach tagged %1s",
"\r\033[1m z \033[0m\016x\017 \033[0mSuspend selected %1\n", "#[bold] z #[default]#[acs]x#[default] Suspend selected %1",
"\r\033[1m Z \033[0m\016x\017 \033[0mSuspend tagged %1s\n", "#[bold] Z #[default]#[acs]x#[default] Suspend tagged %1s",
"\r\033[1m f \033[0m\016x\017 \033[0mEnter a filter\n", "#[bold] f #[default]#[acs]x#[default] Enter a filter",
NULL NULL
}; };

View File

@@ -869,15 +869,15 @@ window_customize_height(__unused void *modedata, __unused u_int height)
} }
static const char* window_customize_help_lines[] = { static const char* window_customize_help_lines[] = {
"\r\033[1m Enter, s \033[0m\016x\017 \033[0mSet %1 value\n", "#[bold] Enter, s #[default]#[acs]x#[default] Set %1 value",
"\r\033[1m S \033[0m\016x\017 \033[0mSet global %1 value\n", "#[bold] S #[default]#[acs]x#[default] Set global %1 value",
"\r\033[1m w \033[0m\016x\017 \033[0mSet window %1 value\n", "#[bold] w #[default]#[acs]x#[default] Set window %1 value",
"\r\033[1m d \033[0m\016x\017 \033[0mSet to default value\n", "#[bold] d #[default]#[acs]x#[default] Set to default value",
"\r\033[1m D \033[0m\016x\017 \033[0mSet tagged %1s to default value\n", "#[bold] D #[default]#[acs]x#[default] Set tagged %1s to default value",
"\r\033[1m u \033[0m\016x\017 \033[0mUnset an %1\n", "#[bold] u #[default]#[acs]x#[default] Unset an %1",
"\r\033[1m U \033[0m\016x\017 \033[0mUnset tagged %1s\n", "#[bold] U #[default]#[acs]x#[default] Unset tagged %1s",
"\r\033[1m f \033[0m\016x\017 \033[0mEnter a filter\n", "#[bold] f #[default]#[acs]x#[default] Enter a filter",
"\r\033[1m v \033[0m\016x\017 \033[0mToggle information\n", "#[bold] v #[default]#[acs]x#[default] Toggle information",
NULL NULL
}; };

View File

@@ -889,18 +889,18 @@ window_tree_sort(struct sort_criteria *sort_crit)
} }
static const char* window_tree_help_lines[] = { static const char* window_tree_help_lines[] = {
"\r\033[1m Enter \033[0m\016x\017 \033[0mChoose selected item\n", "#[bold] Enter #[default]#[acs]x#[default] Choose selected item",
"\r\033[1m S-Up \033[0m\016x\017 \033[0mSwap current and previous window\n", "#[bold] S-Up #[default]#[acs]x#[default] Swap current and previous window",
"\r\033[1m S-Down \033[0m\016x\017 \033[0mSwap current and next window\n", "#[bold] S-Down #[default]#[acs]x#[default] Swap current and next window",
"\r\033[1m x \033[0m\016x\017 \033[0mKill selected item\n", "#[bold] x #[default]#[acs]x#[default] Kill selected item",
"\r\033[1m X \033[0m\016x\017 \033[0mKill tagged items\n", "#[bold] X #[default]#[acs]x#[default] Kill tagged items",
"\r\033[1m < \033[0m\016x\017 \033[0mScroll previews left\n", "#[bold] < #[default]#[acs]x#[default] Scroll previews left",
"\r\033[1m > \033[0m\016x\017 \033[0mScroll previews right\n", "#[bold] > #[default]#[acs]x#[default] Scroll previews right",
"\r\033[1m m \033[0m\016x\017 \033[0mSet the marked pane\n", "#[bold] m #[default]#[acs]x#[default] Set the marked pane",
"\r\033[1m M \033[0m\016x\017 \033[0mClear the marked pane\n", "#[bold] M #[default]#[acs]x#[default] Clear the marked pane",
"\r\033[1m : \033[0m\016x\017 \033[0mRun a command for each tagged item\n", "#[bold] : #[default]#[acs]x#[default] Run a command for each tagged item",
"\r\033[1m f \033[0m\016x\017 \033[0mEnter a format\n", "#[bold] f #[default]#[acs]x#[default] Enter a format",
"\r\033[1m H \033[0m\016x\017 \033[0mJump to the starting pane\n", "#[bold] H #[default]#[acs]x#[default] Jump to the starting pane",
NULL NULL
}; };