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;
u_int pane;
int fg, bg;
size_t size = 0;
char *copy, *split;
@@ -184,8 +185,14 @@ cmd_refresh_report(struct tty *tty, const char *value)
if (wp == NULL)
goto out;
tty_keys_colours(tty, split, strlen(split), &size, &wp->control_fg,
&wp->control_bg);
fg = wp->control_fg;
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:
free(copy);

View File

@@ -224,6 +224,14 @@ control_notify_session_window_changed(struct session *s)
{
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) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;

View File

@@ -1856,15 +1856,6 @@ format_cb_keypad_flag(struct format_tree *ft)
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. */
static void *
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,
format_cb_last_window_index
},
{ "loop_last_flag", FORMAT_TABLE_STRING,
format_cb_loop_last_flag
},
{ "mouse_all_flag", FORMAT_TABLE_STRING,
format_cb_mouse_all_flag
},
@@ -4703,7 +4691,7 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
struct evbuffer *buffer;
size_t size;
struct session *s, **l;
int i, n, last = 0;
int i, n;
if (format_choose(es, fmt, &all, &active, 0) != 0) {
all = xstrdup(fmt);
@@ -4724,9 +4712,9 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
use = active;
else
use = all;
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, FORMAT_NONE, ft->flags|last);
nft = format_create(c, item, FORMAT_NONE, ft->flags);
format_add(nft, "loop_index", "%d", i);
format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, ft->c, s, NULL, NULL);
format_copy_state(&next, es, 0);
next.ft = nft;
@@ -4818,7 +4806,7 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
size_t size;
struct winlink *wl, **l;
struct window *w;
int i, n, last = 0;
int i, n;
if (ft->s == NULL) {
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;
else
use = all;
if (i == n - 1)
last = FORMAT_LAST;
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);
/* 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;
size_t size;
struct window_pane *wp, **l;
int i, n, last = 0;
int i, n;
if (ft->w == NULL) {
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;
else
use = all;
if (i == n - 1)
last = FORMAT_LAST;
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_copy_state(&next, es, 0);
next.ft = nft;
@@ -4955,7 +4943,7 @@ format_loop_clients(struct format_expand_state *es, const char *fmt)
char *expanded, *value;
struct evbuffer *buffer;
size_t size;
int i, n, last = 0;
int i, n;
buffer = evbuffer_new();
if (buffer == NULL)
@@ -4965,9 +4953,9 @@ format_loop_clients(struct format_expand_state *es, const char *fmt)
for (i = 0; i < n; i++) {
c = l[i];
format_log(es, "client loop: %s", c->name);
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, 0, ft->flags|last);
nft = format_create(c, item, 0, ft->flags);
format_add(nft, "loop_index", "%d", i);
format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, c, ft->s, ft->wl, ft->wp);
format_copy_state(&next, es, 0);
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;
/* subtract offset */
new->ra_x = new->ra_x > pox ? new->ra_x - pox : 0;
new->ra_y = new->ra_y > poy ? new->ra_y - poy : 0;
new->ra_x = si->ra_x > pox ? si->ra_x - pox : 0;
new->ra_y = si->ra_y > poy ? si->ra_y - poy : 0;
/* clamp to size */
new->ra_x = si->ra_x < psx ? si->ra_x : psx;
new->ra_y = si->ra_y < psy ? si->ra_y : psy;
new->ra_x = new->ra_x < psx ? new->ra_x : psx;
new->ra_y = new->ra_y < psy ? new->ra_y : psy;
/* resize */
new->ra_x = new->ra_x * xpixel / si->xpixel;
new->ra_y = new->ra_y * ypixel / si->ypixel;

View File

@@ -25,11 +25,10 @@
#include "tmux.h"
#define DEFAULT_SESSION_MENU \
" 'Next' 'n' {switch-client -n}" \
" 'Previous' 'p' {switch-client -p}" \
" #{S/t:#{?#{&&:#{<:#{loop_index},6},#{!:#{session_active}}},'Switch To #[underscore]#{session_name}' '' {switch-client -t=#{session_id}#} ,}}" \
" ''" \
" '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}" \
" ''" \
" 'New Session' 's' {new-session}" \
@@ -489,8 +488,8 @@ key_bindings_init(void)
"bind -n WheelUpStatus { previous-window }",
/* 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 M-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 { run -C \"display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU "\" }",
/* 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 "}",
@@ -581,6 +580,8 @@ key_bindings_init(void)
"bind -Tcopy-mode M-Down { send -X halfpage-down }",
"bind -Tcopy-mode C-Up { send -X scroll-up }",
"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. */
"bind -Tcopy-mode-vi '#' { send -FX search-backward -- '#{copy_cursor_word}' }",

View File

@@ -84,6 +84,7 @@ struct mode_tree_data {
int no_matches;
enum mode_tree_search_dir search_dir;
int search_icase;
int help;
};
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_draw_help(struct mode_tree_data *,
struct screen_write_ctx *);
static const struct menu_item mode_tree_menu_items[] = {
{ "Scroll Left", '<', NULL },
@@ -135,31 +138,31 @@ static const struct menu_item mode_tree_menu_items[] = {
};
static const char* mode_tree_help_start[] = {
"\r\033[1m Up, k \033[0m\016x\017 \033[0mMove cursor up\n",
"\r\033[1m Down, j \033[0m\016x\017 \033[0mMove cursor down\n",
"\r\033[1m g \033[0m\016x\017 \033[0mGo to top\n",
"\r\033[1m G \033[0m\016x\017 \033[0mGo to bottom\n",
"\r\033[1m PPage, C-b \033[0m\016x\017 \033[0mPage up\n",
"\r\033[1m NPage, C-f \033[0m\016x\017 \033[0mPage down\n",
"\r\033[1m Left, h \033[0m\016x\017 \033[0mCollapse %1\n",
"\r\033[1m Right, l \033[0m\016x\017 \033[0mExpand %1\n",
"\r\033[1m M-- \033[0m\016x\017 \033[0mCollapse all %1s\n",
"\r\033[1m M-+ \033[0m\016x\017 \033[0mExpand all %1s\n",
"\r\033[1m t \033[0m\016x\017 \033[0mToggle %1 tag\n",
"\r\033[1m T \033[0m\016x\017 \033[0mUntag all %1s\n",
"\r\033[1m C-t \033[0m\016x\017 \033[0mTag all %1s\n",
"\r\033[1m C-s \033[0m\016x\017 \033[0mSearch forward\n",
"\r\033[1m C-r \033[0m\016x\017 \033[0mSearch backward\n",
"\r\033[1m n \033[0m\016x\017 \033[0mRepeat search forward\n",
"\r\033[1m N \033[0m\016x\017 \033[0mRepeat search backward\n",
"\r\033[1m f \033[0m\016x\017 \033[0mFilter %1s\n",
"\r\033[1m O \033[0m\016x\017 \033[0mChange sort order\n",
"\r\033[1m r \033[0m\016x\017 \033[0mReverse sort order\n",
"\r\033[1m v \033[0m\016x\017 \033[0mToggle preview\n",
"#[bold] Up, k #[default]#[acs]x#[default] Move cursor up",
"#[bold] Down, j #[default]#[acs]x#[default] Move cursor down",
"#[bold] g #[default]#[acs]x#[default] Go to top",
"#[bold] G #[default]#[acs]x#[default] Go to bottom",
"#[bold] PPage, C-b #[default]#[acs]x#[default] Page up",
"#[bold] NPage, C-f #[default]#[acs]x#[default] Page down",
"#[bold] Left, h #[default]#[acs]x#[default] Collapse %1",
"#[bold] Right, l #[default]#[acs]x#[default] Expand %1",
"#[bold] M-- #[default]#[acs]x#[default] Collapse all %1s",
"#[bold] M-+ #[default]#[acs]x#[default] Expand all %1s",
"#[bold] t #[default]#[acs]x#[default] Toggle %1 tag",
"#[bold] T #[default]#[acs]x#[default] Untag all %1s",
"#[bold] C-t #[default]#[acs]x#[default] Tag all %1s",
"#[bold] C-s #[default]#[acs]x#[default] Search forward",
"#[bold] C-r #[default]#[acs]x#[default] Search backward",
"#[bold] n #[default]#[acs]x#[default] Repeat search forward",
"#[bold] N #[default]#[acs]x#[default] Repeat search backward",
"#[bold] f #[default]#[acs]x#[default] Filter %1s",
"#[bold] O #[default]#[acs]x#[default] Change sort order",
"#[bold] r #[default]#[acs]x#[default] Reverse sort order",
"#[bold] v #[default]#[acs]x#[default] Toggle preview",
NULL
};
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
};
#define MODE_TREE_HELP_DEFAULT_WIDTH 39
@@ -928,6 +931,8 @@ mode_tree_draw(struct mode_tree_data *mtd)
}
done:
if (mtd->help)
mode_tree_draw_help(mtd, &ctx);
screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
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
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;
u_int px, py, w, h = 0;
char *expanded;
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";
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)
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++)
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;
px = (c->tty.sx - w) / 2;
py = (c->tty.sy - h) / 2;
x = (sx - box_w) / 2;
y = (sy - box_h) / 2;
if (popup_display(POPUP_CLOSEANYKEY|POPUP_NOJOB, BOX_LINES_DEFAULT,
NULL, px, py, w, h, NULL, NULL, 0, NULL, NULL, NULL, c, s, NULL,
NULL, NULL, NULL) != 0)
return;
memcpy(&gc, &grid_default_cell, sizeof gc);
screen_write_cursormove(ctx, x, y, 0);
screen_write_box(ctx, box_w, box_h, BOX_LINES_DEFAULT, &gc, NULL);
popup_write(c, "\033[H\033[?25l\033[?7l\033)0", 17);
for (line = mode_tree_help_start; *line != NULL; line++) {
new_line = cmd_template_replace(*line, item, 1);
popup_write(c, new_line, strlen(new_line));
free(new_line);
y++;
x++;
for (line = mode_tree_help_start; *line != NULL; line++, y++)
mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w);
for (line = lines; line != NULL && *line != NULL; line++, y++)
mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w);
for (line = mode_tree_help_end; *line != NULL; line++, y++)
mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w);
}
for (line = lines; line != NULL && *line != NULL; line++) {
new_line = cmd_template_replace(*line, item, 1);
popup_write(c, new_line, strlen(new_line));
free(new_line);
}
for (line = mode_tree_help_end; *line != NULL; line++) {
new_line = cmd_template_replace(*line, item, 1);
popup_write(c, new_line, strlen(new_line));
free(new_line);
}
popup_write(c, "\033[H", 3);
static void
mode_tree_display_help(struct mode_tree_data *mtd)
{
mtd->help = 1;
mode_tree_draw(mtd);
}
int
@@ -1239,6 +1259,13 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
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 (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
*key = KEYC_NONE;
@@ -1304,7 +1331,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
return (1);
case KEYC_F1:
case 'h'|KEYC_CTRL:
mode_tree_display_help(mtd, c);
mode_tree_display_help(mtd);
break;
case KEYC_UP:
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 "last_window_index" Ta "" Ta "Index of last window in session"
.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 "mouse_all_flag" Ta "" Ta "Pane mouse all 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);
}
/* 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. */
void
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;
enum tty_draw_line_state current_state, next_state;
struct tty_style_ctx default_style_ctx = { 0 };
const struct grid_cell *defaults;
if (style_ctx == NULL) {
default_style_ctx.defaults = &grid_default_cell;
default_style_ctx.hyperlinks = s->hyperlinks;
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
@@ -138,8 +163,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
else
ex = screen_size_x(s);
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,
style_ctx->defaults->fg, style_ctx->defaults->bg);
"bg=%d", __func__, px, px + nx, py, ex, atx, aty, defaults->fg,
defaults->bg);
/* Turn off cursor while redrawing and reset region and margins. */
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. */
memcpy(&last, &grid_default_cell, sizeof last);
last.bg = style_ctx->defaults->bg;
last.bg = defaults->bg;
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;
}
if (i == 0)
bg = style_ctx->defaults->bg;
bg = defaults->bg;
else {
bg = gc.bg;
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);
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)
goto out;
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)
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. */
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. */
gcp = tty_check_codeset(tty, &gc);
/* And for selection. */
if (gcp->flags & GRID_FLAG_SELECTED) {
memcpy(&ngc, gcp, sizeof ngc);
if (screen_select_cell(s, &ngc, gcp))
if (screen_select_cell(s, &ngc,
gcp))
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. */
@@ -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) {
tty_attributes(tty, &last, style_ctx);
tty_draw_line_clear(tty, atx + last_i, aty,
i - last_i, style_ctx->defaults, last.bg,
wrapped);
i - last_i, defaults, last.bg, wrapped);
wrapped = 0;
} else if (next_state != TTY_DRAW_LINE_SAME &&
len != 0) {

View File

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

View File

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

View File

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

View File

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