mirror of
https://github.com/tmux/tmux.git
synced 2026-09-17 18:44:45 +00:00
Compare commits
88 Commits
4902-image
...
osc133_awa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ec4328380 | ||
|
|
8746405e34 | ||
|
|
8c0ac1fe05 | ||
|
|
dc65677ff9 | ||
|
|
2142818e25 | ||
|
|
cda0fe9f7b | ||
|
|
4cc9fc91fa | ||
|
|
081b79fd6e | ||
|
|
6294be4986 | ||
|
|
578e07fcbc | ||
|
|
93169e2e62 | ||
|
|
db24c029be | ||
|
|
a6f9a206b7 | ||
|
|
ce253864f8 | ||
|
|
79f1f43830 | ||
|
|
7b666e62be | ||
|
|
5d5f66239a | ||
|
|
4b1586fa81 | ||
|
|
9fa390aee6 | ||
|
|
ad632726bd | ||
|
|
1dd1717462 | ||
|
|
73db721dab | ||
|
|
6bb6c286a9 | ||
|
|
ca34d190b5 | ||
|
|
57a13664cc | ||
|
|
59dc0e75c4 | ||
|
|
2d6aed1d92 | ||
|
|
3dd575ce55 | ||
|
|
60e871658f | ||
|
|
c49dfdf496 | ||
|
|
9b0ad73a4b | ||
|
|
bd8218e85d | ||
|
|
8f3101d820 | ||
|
|
77309c07b0 | ||
|
|
c8e889694d | ||
|
|
b402687fcd | ||
|
|
0530a7d89a | ||
|
|
6bdb9ba54c | ||
|
|
f2bfd611e2 | ||
|
|
9c1d5a080b | ||
|
|
b28244e2fe | ||
|
|
8b3834c34f | ||
|
|
af3e4d2e5b | ||
|
|
c552d1bf16 | ||
|
|
6a1ecdcce3 | ||
|
|
39357c829c | ||
|
|
40381bdcd9 | ||
|
|
7bad4f7722 | ||
|
|
65e811570b | ||
|
|
dd31900f80 | ||
|
|
013ada58c8 | ||
|
|
78dad45f3b | ||
|
|
52b452948f | ||
|
|
267746603a | ||
|
|
b26b240613 | ||
|
|
8e48c889dd | ||
|
|
f4445a310a | ||
|
|
4157b64754 | ||
|
|
0d2aafe7b7 | ||
|
|
6f4c9b32af | ||
|
|
eb77a75ab7 | ||
|
|
0851650c6b | ||
|
|
1459c90a7f | ||
|
|
c5a5b99788 | ||
|
|
e23219ec04 | ||
|
|
0f29c47278 | ||
|
|
7c9fdcb770 | ||
|
|
0330d8ac57 | ||
|
|
0a7788b66f | ||
|
|
458d045bd2 | ||
|
|
a81154d6d9 | ||
|
|
cffc86dae5 | ||
|
|
4cfd155ffc | ||
|
|
670ea5d083 | ||
|
|
eaeb234dd4 | ||
|
|
5948722d5a | ||
|
|
d8ad7cd9c5 | ||
|
|
b49cc2d936 | ||
|
|
abb7108e25 | ||
|
|
d080378464 | ||
|
|
473eca949a | ||
|
|
f0eaa5e9d6 | ||
|
|
f44246c764 | ||
|
|
5d4d41da57 | ||
|
|
bacbb4d9d8 | ||
|
|
07a3a16ae7 | ||
|
|
f41288ee27 | ||
|
|
457b6cbefd |
3
.github/workflows/lock.yml
vendored
3
.github/workflows/lock.yml
vendored
@@ -17,8 +17,9 @@ jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'tmux/tmux'
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v6
|
||||
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '30'
|
||||
|
||||
6
.github/workflows/regress.yml
vendored
6
.github/workflows/regress.yml
vendored
@@ -38,7 +38,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: dependencies
|
||||
if: runner.os == 'Linux'
|
||||
@@ -81,7 +83,7 @@ jobs:
|
||||
|
||||
- name: logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: regress-logs-${{ matrix.name }}
|
||||
path: regress/logs/*.log
|
||||
|
||||
49
CHANGES
49
CHANGES
@@ -9,7 +9,12 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
- modal panes may be created with new-pane -O; a window can have one modal
|
||||
pane and it prevents interaction with the other panes while it is active.
|
||||
A modal pane is now used for the editor in buffer mode;
|
||||
-C closes the pane when the mouse is clicked outside it and -K sends all
|
||||
keys, including the prefix key, to the pane. A modal pane is now used for
|
||||
the editor in buffer mode;
|
||||
|
||||
- new-pane -A creates a floating pane which remains above a zoomed pane;
|
||||
modal panes do this by default;
|
||||
|
||||
- move-pane and resize-pane can now move and resize floating panes, including
|
||||
mouse dragging;
|
||||
@@ -24,6 +29,9 @@ CHANGES FROM 3.7c TO 3.8
|
||||
-k for choose commands kills the pane when the mode exits, which is useful
|
||||
for modes in floating panes;
|
||||
|
||||
- command-prompt -P opens a prompt inside a pane rather than on the status
|
||||
line;
|
||||
|
||||
- pane-border-status has top-floating and bottom-floating, and there are new
|
||||
default bindings under C-b g for common move and resize operations;
|
||||
|
||||
@@ -74,7 +82,7 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
- new hooks to cover areas including client create and destroy, pane
|
||||
activity, pane mode and prompt changes, pane movement and resizing, session
|
||||
group changes and window creation, close and zoom;
|
||||
group changes and window creation, close, swap and zoom;
|
||||
|
||||
- OSC 133 escape sequences now trigger events: pane-command-started,
|
||||
pane-command-finished and pane-shell-prompt.
|
||||
@@ -87,10 +95,23 @@ CHANGES FROM 3.7c TO 3.8
|
||||
while scrolling or while hovered and disappears after pane-scrollbars-timeout
|
||||
(Michael Grant).
|
||||
|
||||
* Extend the fill-character option so both inside and outside the window can be
|
||||
changed.
|
||||
* Add a clear-on-attach server option. When disabled, tmux does not enter the
|
||||
alternate screen on attach and instead scrolls the existing terminal content
|
||||
into the scrollback buffer so it remains accessible (issue 5508).
|
||||
|
||||
* Change set-option and set-hooks to use formats and add a -F flag to each.
|
||||
* Copy mode can now automatically refresh as pane content changes with the
|
||||
refresh-on, refresh-off and refresh-toggle commands. Automatic refresh is off
|
||||
by default; refresh-now refreshes once and is bound to r. These replace
|
||||
refresh-from-pane (issue 5165).
|
||||
|
||||
* Extend the fill-character option so both inside and outside the window can be
|
||||
changed, with a new default for unused areas inside the window.
|
||||
|
||||
* Change show-options and show-hooks to use formats and add a -F flag to each.
|
||||
|
||||
* Allow individual terminal features to be disabled by adding @ to their names
|
||||
in terminal-features, and add a utf8 feature for terminals which support
|
||||
UTF-8 output.
|
||||
|
||||
* Add a #{A/count:frames} modifier to show a series of frames as an animation
|
||||
in the status line (issue 5412 from Fernando Daciuk).
|
||||
@@ -105,6 +126,8 @@ CHANGES FROM 3.7c TO 3.8
|
||||
* Add new-window -E, respawn-pane -E and respawn-window -E as more convenient
|
||||
methods to create an empty pane (rather than using '' for the command).
|
||||
|
||||
* Add a default C-b T binding to change the current pane title.
|
||||
|
||||
* Menus now belong to the window, so appear on all clients.
|
||||
|
||||
* Dragging over an existing copy mode selection now adjusts it (Michael Grant).
|
||||
@@ -118,6 +141,13 @@ CHANGES FROM 3.7c TO 3.8
|
||||
* Copy mode no longer exits at the bottom while a selection is in progress
|
||||
(issue 5349).
|
||||
|
||||
* Add copy-mode-current-line-style to set the style of the line containing the
|
||||
cursor in copy mode (issue 5391).
|
||||
|
||||
* Copy mode word commands now recognise all Unicode whitespace characters; a
|
||||
space in word-separators matches any Unicode whitespace character (issue
|
||||
5562).
|
||||
|
||||
* Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
|
||||
nolink) (issues 4842 and 4280 from Moritz Angermann).
|
||||
|
||||
@@ -128,7 +158,9 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* Add or improve format variables including client_colours,
|
||||
pane_start_command_list, window_manual_width, window_manual_height,
|
||||
pane_last_output_time, pane_modal_flag and window_modal_pane.
|
||||
pane_last_output_time, pane_modal_flag, window_modal_pane, pane_private_modes,
|
||||
pane_output_generation, history_added, history_collected, history_generation,
|
||||
hook_fire_count and hook_fire_time.
|
||||
|
||||
* Add additional pane sort orders, and a z sort order for choose-tree so
|
||||
floating panes can be sorted by z-index.
|
||||
@@ -145,7 +177,10 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* Fix control mode clients hanging on exit if pty data was still queued (Ben
|
||||
Maurer, issue 5356), and avoid sending notifications to clients which are
|
||||
already exiting (Ben Maurer, issue 5357).
|
||||
already exiting (Ben Maurer, issue 5357). Queue notifications so they are not
|
||||
sent inside %begin/%end (issue 5458), do not let a stuck client prevent the
|
||||
server from exiting (issue 5444), and reset control mode offsets when a pane
|
||||
is respawned (issue 5498).
|
||||
|
||||
* Fix grouped sessions sometimes being left as unusable command targets while
|
||||
they are being killed (Bryce Miller, issue 5180).
|
||||
|
||||
@@ -5,8 +5,7 @@ CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c
|
||||
# Distribution tarball options.
|
||||
EXTRA_DIST = \
|
||||
CHANGES README COPYING example_tmux.conf \
|
||||
osdep-*.c mdoc2man.awk tmux.1 \
|
||||
tools/generate-image-diacritics.sh
|
||||
osdep-*.c mdoc2man.awk tmux.1
|
||||
dist_EXTRA_tmux_SOURCES = compat/*.[ch]
|
||||
|
||||
# Preprocessor flags.
|
||||
@@ -247,9 +246,9 @@ if HAVE_UTF8PROC
|
||||
nodist_tmux_SOURCES += compat/utf8proc.c
|
||||
endif
|
||||
|
||||
# Enable image support.
|
||||
if ENABLE_IMAGES
|
||||
dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c image-fallback.c
|
||||
# Enable sixel support.
|
||||
if ENABLE_SIXEL
|
||||
dist_tmux_SOURCES += image.c image-sixel.c
|
||||
endif
|
||||
|
||||
# Add bits for fuzzing if enabled.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: arguments.c,v 1.66 2026/06/26 09:54:56 nicm Exp $ */
|
||||
/* $OpenBSD: arguments.c,v 1.67 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -762,8 +762,6 @@ args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
|
||||
cmdq_error(item, "%s", error);
|
||||
free(error);
|
||||
}
|
||||
else
|
||||
cmdlist->references++;
|
||||
args_make_commands_free(state);
|
||||
return (cmdlist);
|
||||
}
|
||||
@@ -827,8 +825,10 @@ args_make_commands(struct args_command_state *state, int argc, char **argv,
|
||||
int i;
|
||||
|
||||
if (state->cmdlist != NULL) {
|
||||
if (argc == 0)
|
||||
if (argc == 0) {
|
||||
state->cmdlist->references++;
|
||||
return (state->cmdlist);
|
||||
}
|
||||
return (cmd_list_copy(state->cmdlist, argc, argv));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-command-prompt.c,v 1.75 2026/06/25 11:39:11 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-command-prompt.c,v 1.76 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -242,9 +242,11 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
cmd_free_argv(argc, argv);
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ const struct cmd_entry cmd_copy_mode_entry = {
|
||||
.name = "copy-mode",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "dekHMqSs:t:u", 0, 0, NULL },
|
||||
.usage = "[-dekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
|
||||
.args = { "UcdekHMqSs:t:u", 0, 0, NULL },
|
||||
.usage = "[-UcdekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.source = { 's', CMD_FIND_PANE, 0 },
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-display-menu.c,v 1.52 2026/07/14 19:07:03 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-display-menu.c,v 1.53 2026/08/31 07:46:55 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -591,6 +591,8 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_client_clear_overlay(tc);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (tc->flags & CLIENT_CONTROL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (!modify && tc->overlay_draw != NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-if-shell.c,v 1.86 2025/08/01 09:05:51 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-if-shell.c,v 1.87 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -98,6 +98,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_ERROR);
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
@@ -164,9 +165,11 @@ cmd_if_shell_callback(struct job *job)
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
@@ -300,7 +300,6 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc;
|
||||
int y, ly, x, lx;
|
||||
int old_xoff, old_yoff, old_sx, old_sy;
|
||||
|
||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||
if (wp == NULL) {
|
||||
@@ -322,17 +321,10 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
||||
ly = m->statusat - 1;
|
||||
|
||||
if (x != lx || y != ly) {
|
||||
old_xoff = wp->xoff;
|
||||
old_yoff = wp->yoff;
|
||||
old_sx = wp->sx;
|
||||
old_sy = wp->sy;
|
||||
|
||||
lc->g.xoff += x - lx;
|
||||
lc->g.yoff += y - ly;
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-new-window.c,v 1.103 2026/07/08 08:07:42 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-new-window.c,v 1.104 2026/09/03 21:04:11 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -60,7 +60,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct session *s = target->s;
|
||||
struct winlink *wl = target->wl, *new_wl = NULL;
|
||||
int idx = target->idx, before, count = args_count(args);
|
||||
int idx = target->idx, before;
|
||||
int count = args_count(args);
|
||||
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
||||
const char *template, *name;
|
||||
struct cmd_find_state fs;
|
||||
@@ -74,8 +75,10 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
|
||||
/*
|
||||
* If -S and -n are given and -t is not and a single window with this
|
||||
* name already exists, select it.
|
||||
* If -S is given, select an existing window instead of creating a
|
||||
* new one: preferring -t if it already points at a window, or
|
||||
* otherwise -n if one exists with that name. If neither matches,
|
||||
* fall through and create a window as normal.
|
||||
*/
|
||||
name = args_get(args, 'n');
|
||||
if (name != NULL) {
|
||||
@@ -88,32 +91,39 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
wname = clean_name(expanded, 0);
|
||||
free(expanded);
|
||||
}
|
||||
if (args_has(args, 'S') && wname != NULL && target->idx == -1) {
|
||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
if (strcmp(wl->window->name, expanded) != 0)
|
||||
continue;
|
||||
if (new_wl == NULL) {
|
||||
new_wl = wl;
|
||||
continue;
|
||||
if (args_has(args, 'S')) {
|
||||
if (idx != -1)
|
||||
new_wl = winlink_find_by_index(&s->windows, idx);
|
||||
else if (wname != NULL) {
|
||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
if (strcmp(wl->window->name, expanded) != 0)
|
||||
continue;
|
||||
if (new_wl == NULL) {
|
||||
new_wl = wl;
|
||||
continue;
|
||||
}
|
||||
cmdq_error(item, "multiple windows named %s",
|
||||
wname);
|
||||
free(wname);
|
||||
free(expanded);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
cmdq_error(item, "multiple windows named %s", wname);
|
||||
free(wname);
|
||||
free(expanded);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
free(expanded);
|
||||
if (new_wl != NULL) {
|
||||
free(wname);
|
||||
if (args_has(args, 'd'))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (session_set_current(s, new_wl) == 0)
|
||||
server_redraw_session(s);
|
||||
if (c != NULL && c->session != NULL)
|
||||
s->curw->window->latest = c;
|
||||
recalculate_sizes();
|
||||
}
|
||||
|
||||
/* Found an existing window to select instead of creating a new one. */
|
||||
if (new_wl != NULL) {
|
||||
free(wname);
|
||||
if (args_has(args, 'd'))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (session_set_current(s, new_wl) == 0)
|
||||
server_redraw_session(s);
|
||||
if (c != NULL && c->session != NULL)
|
||||
s->curw->window->latest = c;
|
||||
recalculate_sizes();
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
before = args_has(args, 'b');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-parse.y,v 1.58 2026/04/27 12:31:11 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-parse.y,v 1.59 2026/08/31 07:51:56 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -802,6 +802,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
|
||||
if (last == NULL) {
|
||||
pr->status = CMD_PARSE_SUCCESS;
|
||||
pr->cmdlist = cmd_list_new();
|
||||
cmd_parse_free_commands(cmds);
|
||||
return (1);
|
||||
}
|
||||
|
||||
@@ -814,6 +815,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
|
||||
pi->flags |= CMD_PARSE_NOALIAS;
|
||||
cmd_parse_build_commands(cmds, pi, pr);
|
||||
pi->flags &= ~CMD_PARSE_NOALIAS;
|
||||
cmd_parse_free_commands(cmds);
|
||||
return (1);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.67 2026/07/10 13:38:45 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.68 2026/08/31 07:44:39 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -92,6 +92,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
server_unzoom_window(w);
|
||||
lc = wp->layout_cell; /* may have been replaced by unzoom */
|
||||
|
||||
if (args_has(args, 'x')) {
|
||||
x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);
|
||||
@@ -237,7 +238,6 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
||||
int left, right;
|
||||
int new_xoff, new_yoff, resizes = 0;
|
||||
int old_xoff, old_yoff, old_sx, old_sy;
|
||||
|
||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||
if (wp == NULL) {
|
||||
@@ -248,10 +248,6 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
lc = wp->layout_cell;
|
||||
sx = wp->sx;
|
||||
sy = wp->sy;
|
||||
old_xoff = wp->xoff;
|
||||
old_yoff = wp->yoff;
|
||||
old_sx = (int)wp->sx;
|
||||
old_sy = (int)wp->sy;
|
||||
left = wp->xoff - 1;
|
||||
right = wp->xoff + sx;
|
||||
if (window_pane_scrollbar_reserve(wp) &&
|
||||
@@ -351,8 +347,7 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
}
|
||||
if (resizes != 0) {
|
||||
layout_fix_panes(w, NULL);
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.93 2026/07/17 12:42:51 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.94 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -233,9 +233,11 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
|
||||
if (cdata->item != NULL)
|
||||
|
||||
@@ -360,7 +360,6 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
||||
enum pane_lines lines;
|
||||
u_int sx, sy;
|
||||
int x, y, xoff, yoff, border;
|
||||
int old_xoff, old_yoff, old_sx, old_sy;
|
||||
|
||||
if (c->tty.mouse_last_pane == -1)
|
||||
return;
|
||||
@@ -416,15 +415,8 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
||||
if (sy < PANE_MINIMUM)
|
||||
sy = PANE_MINIMUM;
|
||||
|
||||
old_xoff = wp->xoff;
|
||||
old_yoff = wp->yoff;
|
||||
old_sx = wp->sx;
|
||||
old_sy = wp->sy;
|
||||
|
||||
layout_set_size(lc, sx, sy, xoff, yoff);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
|
||||
34
configure.ac
34
configure.ac
@@ -530,35 +530,15 @@ if test "x$enable_cgroups" = xyes; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Enable image support.
|
||||
AC_ARG_ENABLE(
|
||||
images,
|
||||
AS_HELP_STRING(--enable-images, enable Kitty/SIXEL/ASCII images)
|
||||
)
|
||||
|
||||
# Keep the old option as an alias for one compatibility cycle.
|
||||
# Enable sixel support.
|
||||
AC_ARG_ENABLE(
|
||||
sixel,
|
||||
AS_HELP_STRING(--enable-sixel, deprecated alias for --enable-images)
|
||||
AS_HELP_STRING(--enable-sixel, enable sixel images)
|
||||
)
|
||||
if test "x$enable_sixel" = xyes; then
|
||||
AC_MSG_WARN([--enable-sixel is deprecated, use --enable-images instead to enable.])
|
||||
enable_images=yes
|
||||
AC_DEFINE(ENABLE_SIXEL)
|
||||
fi
|
||||
if test "x$enable_images" = xyes; then
|
||||
PKG_CHECK_MODULES(
|
||||
IMAGE,
|
||||
[libpng >= 1.6 zlib],
|
||||
[
|
||||
AM_CPPFLAGS="$IMAGE_CFLAGS $AM_CPPFLAGS"
|
||||
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$IMAGE_LIBS $LIBS"
|
||||
],
|
||||
AC_MSG_ERROR("image support requires libpng 1.6 and zlib")
|
||||
)
|
||||
AC_DEFINE(ENABLE_IMAGES)
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_IMAGES, [test "x$enable_images" = xyes])
|
||||
AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes])
|
||||
|
||||
# Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
|
||||
AC_MSG_CHECKING(for b64_ntop)
|
||||
@@ -1157,10 +1137,10 @@ if test "x$enable_asan" = xyes; then
|
||||
else
|
||||
AC_MSG_NOTICE([ASAN: off])
|
||||
fi
|
||||
if test "x$enable_images" = xyes; then
|
||||
AC_MSG_NOTICE([images: on])
|
||||
if test "x$enable_sixel" = xyes; then
|
||||
AC_MSG_NOTICE([SIXEL: on])
|
||||
else
|
||||
AC_MSG_NOTICE([images: off])
|
||||
AC_MSG_NOTICE([SIXEL: off])
|
||||
fi
|
||||
if test "x$enable_debug" = xyes; then
|
||||
AC_MSG_NOTICE([debug: on])
|
||||
|
||||
16
control.c
16
control.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: control.c,v 1.66 2026/08/18 07:43:44 nicm Exp $ */
|
||||
/* $OpenBSD: control.c,v 1.67 2026/09/03 21:35:38 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -455,8 +455,12 @@ control_flush_deferred(struct client *c)
|
||||
void
|
||||
control_write(struct client *c, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char *line;
|
||||
struct control_state *cs = c->control_state;
|
||||
char *line;
|
||||
va_list ap;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
xvasprintf(&line, fmt, ap);
|
||||
@@ -479,6 +483,9 @@ control_write_guard(struct client *c, const char *guard, long t, u_int number,
|
||||
struct control_state *cs = c->control_state;
|
||||
char *line;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
if (strcmp(guard, "begin") == 0)
|
||||
cs->guard_depth++;
|
||||
|
||||
@@ -503,6 +510,9 @@ control_notify_write(struct client *c, const char *fmt, ...)
|
||||
va_list ap;
|
||||
char *line;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
xvasprintf(&line, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
72
format.c
72
format.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: format.c,v 1.413 2026/08/24 07:26:43 nicm Exp $ */
|
||||
/* $OpenBSD: format.c,v 1.415 2026/08/31 19:34:09 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -1887,6 +1887,37 @@ format_cb_cursor_blinking(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_added. */
|
||||
static void *
|
||||
format_cb_history_added(struct format_tree *ft)
|
||||
{
|
||||
if (ft->wp != NULL)
|
||||
return (format_printf("%u", ft->wp->base.grid->scroll_added));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_collected. */
|
||||
static void *
|
||||
format_cb_history_collected(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
|
||||
if (wp != NULL)
|
||||
return (format_printf("%u", wp->base.grid->scroll_collected));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_generation. */
|
||||
static void *
|
||||
format_cb_history_generation(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
|
||||
if (wp != NULL)
|
||||
return (format_printf("%u", wp->base.grid->scroll_generation));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_limit. */
|
||||
static void *
|
||||
format_cb_history_limit(struct format_tree *ft)
|
||||
@@ -2276,6 +2307,19 @@ format_cb_pane_last_output_time(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_output_generation. */
|
||||
static void *
|
||||
format_cb_pane_output_generation(struct format_tree *ft)
|
||||
{
|
||||
unsigned long long value;
|
||||
|
||||
if (ft->wp != NULL) {
|
||||
value = ft->wp->output_generation;
|
||||
return (format_printf("%llu", value));
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_last_prompt_time. */
|
||||
static void *
|
||||
format_cb_pane_last_prompt_time(struct format_tree *ft)
|
||||
@@ -3018,17 +3062,6 @@ format_cb_sixel_support(__unused struct format_tree *ft)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for image_support. */
|
||||
static void *
|
||||
format_cb_image_support(__unused struct format_tree *ft)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
return (xstrdup("1"));
|
||||
#else
|
||||
return (xstrdup("0"));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for active_window_index. */
|
||||
static void *
|
||||
format_cb_active_window_index(struct format_tree *ft)
|
||||
@@ -3674,12 +3707,21 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "cursor_y", FORMAT_TABLE_STRING,
|
||||
format_cb_cursor_y
|
||||
},
|
||||
{ "history_added", FORMAT_TABLE_STRING,
|
||||
format_cb_history_added
|
||||
},
|
||||
{ "history_all_bytes", FORMAT_TABLE_STRING,
|
||||
format_cb_history_all_bytes
|
||||
},
|
||||
{ "history_bytes", FORMAT_TABLE_STRING,
|
||||
format_cb_history_bytes
|
||||
},
|
||||
{ "history_collected", FORMAT_TABLE_STRING,
|
||||
format_cb_history_collected
|
||||
},
|
||||
{ "history_generation", FORMAT_TABLE_STRING,
|
||||
format_cb_history_generation
|
||||
},
|
||||
{ "history_limit", FORMAT_TABLE_STRING,
|
||||
format_cb_history_limit
|
||||
},
|
||||
@@ -3692,9 +3734,6 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "host_short", FORMAT_TABLE_STRING,
|
||||
format_cb_host_short
|
||||
},
|
||||
{ "image_support", FORMAT_TABLE_STRING,
|
||||
format_cb_image_support
|
||||
},
|
||||
{ "insert_flag", FORMAT_TABLE_STRING,
|
||||
format_cb_insert_flag
|
||||
},
|
||||
@@ -3863,6 +3902,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "pane_mode", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_mode
|
||||
},
|
||||
{ "pane_output_generation", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_output_generation
|
||||
},
|
||||
{ "pane_path", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_path
|
||||
},
|
||||
|
||||
122
grid.c
122
grid.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: grid.c,v 1.156 2026/08/03 12:58:53 nicm Exp $ */
|
||||
/* $OpenBSD: grid.c,v 1.158 2026/09/01 12:49:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -86,9 +86,6 @@ grid_check_is_clear(struct grid *gd)
|
||||
assert(gl->extdsize == 0);
|
||||
assert(gl->flags == 0);
|
||||
assert(gl->time == 0);
|
||||
#ifdef ENABLE_IMAGES
|
||||
assert(gl->images == NULL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -356,9 +353,6 @@ grid_free_line(struct grid *gd, u_int py)
|
||||
assert(gl->cellsize == 0 || gl->celldata != NULL);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_free_line(gd, gl);
|
||||
#endif
|
||||
free(gl->celldata);
|
||||
free(gl->extddata);
|
||||
memset(gl, 0, sizeof *gl);
|
||||
@@ -403,9 +397,6 @@ void
|
||||
grid_destroy(struct grid *gd)
|
||||
{
|
||||
grid_free_lines(gd, 0, gd->hsize + gd->sy);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_free(gd);
|
||||
#endif
|
||||
free(gd->linedata);
|
||||
free(gd);
|
||||
}
|
||||
@@ -738,9 +729,6 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
|
||||
|
||||
if (nx == 0 || ny == 0)
|
||||
return;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_damage(gd, px, py, nx, ny);
|
||||
#endif
|
||||
|
||||
if (px == 0 && nx == gd->sx) {
|
||||
grid_clear_lines(gd, py, ny, bg);
|
||||
@@ -776,10 +764,6 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
|
||||
void
|
||||
grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
|
||||
{
|
||||
struct grid_line *gl;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image_line *images;
|
||||
#endif
|
||||
u_int yy;
|
||||
|
||||
if (ny == 0)
|
||||
@@ -791,18 +775,8 @@ grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
|
||||
return;
|
||||
|
||||
for (yy = py; yy < py + ny; yy++) {
|
||||
gl = &gd->linedata[yy];
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_damage(gd, 0, yy, gd->sx, 1);
|
||||
images = gl->images;
|
||||
#endif
|
||||
free(gl->celldata);
|
||||
free(gl->extddata);
|
||||
memset(gl, 0, sizeof *gl);
|
||||
grid_free_line(gd, yy);
|
||||
grid_empty_line(gd, yy, bg);
|
||||
#ifdef ENABLE_IMAGES
|
||||
gl->images = images;
|
||||
#endif
|
||||
}
|
||||
if (py != 0)
|
||||
gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
|
||||
@@ -867,9 +841,6 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
|
||||
|
||||
grid_expand_line(gd, py, px + nx, 8);
|
||||
grid_expand_line(gd, py, dx + nx, 8);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_move_cells(gd, dx, px, py, nx);
|
||||
#endif
|
||||
memmove(&gl->celldata[dx], &gl->celldata[px],
|
||||
nx * sizeof *gl->celldata);
|
||||
if (dx + nx > gl->cellused)
|
||||
@@ -1242,6 +1213,7 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx,
|
||||
grid_get_cell(gd, xx, py, &gc);
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
continue;
|
||||
|
||||
if (lastgc != NULL && (flags & GRID_STRING_WITH_SEQUENCES)) {
|
||||
grid_string_cells_code(*lastgc, &gc, code, sizeof code,
|
||||
flags, s, &has_link);
|
||||
@@ -1308,18 +1280,11 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
{
|
||||
struct grid_line *dstl, *srcl;
|
||||
u_int yy;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int original_dy, original_sy;
|
||||
#endif
|
||||
|
||||
if (dy + ny > dst->hsize + dst->sy)
|
||||
ny = dst->hsize + dst->sy - dy;
|
||||
if (sy + ny > src->hsize + src->sy)
|
||||
ny = src->hsize + src->sy - sy;
|
||||
#ifdef ENABLE_IMAGES
|
||||
original_dy = dy;
|
||||
original_sy = sy;
|
||||
#endif
|
||||
grid_free_lines(dst, dy, ny);
|
||||
|
||||
for (yy = 0; yy < ny; yy++) {
|
||||
@@ -1327,9 +1292,6 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
dstl = &dst->linedata[dy];
|
||||
|
||||
memcpy(dstl, srcl, sizeof *dstl);
|
||||
#ifdef ENABLE_IMAGES
|
||||
dstl->images = NULL;
|
||||
#endif
|
||||
if (srcl->cellsize != 0) {
|
||||
dstl->celldata = xreallocarray(NULL,
|
||||
srcl->cellsize, sizeof *dstl->celldata);
|
||||
@@ -1349,9 +1311,6 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
sy++;
|
||||
dy++;
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_duplicate_lines(dst, original_dy, src, original_sy, ny);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Mark line as dead. */
|
||||
@@ -1362,30 +1321,6 @@ grid_reflow_dead(struct grid_line *gl)
|
||||
gl->flags = GRID_LINE_DEAD;
|
||||
}
|
||||
|
||||
/* Image rows are cell-aligned and must not be reflowed like text. */
|
||||
static int
|
||||
grid_reflow_has_image(struct grid_line *gl)
|
||||
{
|
||||
struct grid_cell gc;
|
||||
u_int i;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_grid_line_has_images(gl))
|
||||
return (1);
|
||||
#endif
|
||||
if (~gl->flags & GRID_LINE_EXTENDED)
|
||||
return (0);
|
||||
for (i = 0; i < gl->cellused; i++) {
|
||||
grid_get_cell1(gl, i, &gc);
|
||||
/* Kitty Unicode placeholder base character (U+10EEEE). */
|
||||
if (gc.data.size >= 4 && gc.data.data[0] == 0xf4 &&
|
||||
gc.data.data[1] == 0x8e && gc.data.data[2] == 0xbb &&
|
||||
gc.data.data[3] == 0xae)
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Add lines, return the first new one. */
|
||||
static struct grid_line *
|
||||
grid_reflow_add(struct grid *gd, u_int n)
|
||||
@@ -1448,10 +1383,6 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
break;
|
||||
line = yy + 1 + lines;
|
||||
|
||||
/* Do not join wrapped text to a cell-aligned image row. */
|
||||
if (grid_reflow_has_image(&gd->linedata[line]))
|
||||
break;
|
||||
|
||||
/* If the next line is empty, skip it. */
|
||||
if (~gd->linedata[line].flags & GRID_LINE_WRAPPED)
|
||||
wrapped = 0;
|
||||
@@ -1512,7 +1443,8 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
|
||||
/* Remove the lines that were completely consumed. */
|
||||
for (i = yy + 1; i < yy + 1 + lines; i++) {
|
||||
grid_free_line(gd, i);
|
||||
free(gd->linedata[i].celldata);
|
||||
free(gd->linedata[i].extddata);
|
||||
grid_reflow_dead(&gd->linedata[i]);
|
||||
}
|
||||
|
||||
@@ -1614,12 +1546,6 @@ grid_reflow(struct grid *gd, u_int sx)
|
||||
if (gl->flags & GRID_LINE_DEAD)
|
||||
continue;
|
||||
|
||||
/* Keep image layers at their original cell coordinates. */
|
||||
if (grid_reflow_has_image(gl)) {
|
||||
grid_reflow_move(target, gl);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Work out the width of this line. at is the point at which
|
||||
* the available width is hit, and width is the full line
|
||||
@@ -1709,7 +1635,7 @@ grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy)
|
||||
void
|
||||
grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
|
||||
{
|
||||
u_int yy, ay = 0;
|
||||
u_int yy, ay = 0, ey = gd->hsize + gd->sy - 1;
|
||||
|
||||
for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) {
|
||||
if (ay == wy)
|
||||
@@ -1723,7 +1649,7 @@ grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
|
||||
* until we find the end or the line now containing wx.
|
||||
*/
|
||||
if (wx == UINT_MAX) {
|
||||
while (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
|
||||
while (yy < ey && gd->linedata[yy].flags & GRID_LINE_WRAPPED)
|
||||
yy++;
|
||||
wx = gd->linedata[yy].cellused;
|
||||
} else {
|
||||
@@ -1785,21 +1711,29 @@ grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
|
||||
{
|
||||
struct grid_cell gc, tmp_gc;
|
||||
u_int pxx;
|
||||
int has_tab, has_space;
|
||||
|
||||
has_tab = (strchr(set, '\t') != NULL);
|
||||
has_space = (strchr(set, ' ') != NULL);
|
||||
|
||||
grid_get_cell(gd, px, py, &gc);
|
||||
if (strchr(set, '\t')) {
|
||||
if (gc.flags & GRID_FLAG_PADDING) {
|
||||
pxx = px;
|
||||
do
|
||||
grid_get_cell(gd, --pxx, py, &tmp_gc);
|
||||
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
|
||||
if (tmp_gc.flags & GRID_FLAG_TAB)
|
||||
return (tmp_gc.data.width - (px - pxx));
|
||||
} else if (gc.flags & GRID_FLAG_TAB)
|
||||
return (gc.data.width);
|
||||
}
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
if (gc.flags & GRID_FLAG_PADDING) {
|
||||
if (!has_tab && !has_space)
|
||||
return (0);
|
||||
pxx = px;
|
||||
do
|
||||
grid_get_cell(gd, --pxx, py, &tmp_gc);
|
||||
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
|
||||
if (((has_tab || has_space) &&
|
||||
(tmp_gc.flags & GRID_FLAG_TAB)) ||
|
||||
(has_space && utf8_has_whitespace(&tmp_gc.data)))
|
||||
return (tmp_gc.data.width - (px - pxx));
|
||||
return (0);
|
||||
}
|
||||
if ((has_tab || has_space) && (gc.flags & GRID_FLAG_TAB))
|
||||
return (gc.data.width);
|
||||
if (has_space && utf8_has_whitespace(&gc.data))
|
||||
return (gc.data.width == 0 ? 1 : gc.data.width);
|
||||
return (utf8_cstrhas(set, &gc.data));
|
||||
}
|
||||
|
||||
@@ -1826,6 +1760,8 @@ grid_line_flags_string(int flags)
|
||||
strlcat(s, "START_OUTPUT,", sizeof s);
|
||||
if (flags & GRID_LINE_END_OUTPUT)
|
||||
strlcat(s, "END_OUTPUT,", sizeof s);
|
||||
if (flags & GRID_LINE_END_OUTPUT_STATUS)
|
||||
strlcat(s, "END_OUTPUT_STATUS,", sizeof s);
|
||||
if (flags & GRID_LINE_HYPERLINK)
|
||||
strlcat(s, "HYPERLINK,", sizeof s);
|
||||
if (*s == '\0')
|
||||
|
||||
464
image-fallback.c
464
image-fallback.c
@@ -1,464 +0,0 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/* Character-cell fallback for clients without a graphical image protocol. */
|
||||
|
||||
enum image_glyph_detail {
|
||||
IMAGE_GLYPH_ASCII,
|
||||
IMAGE_GLYPH_SHADE5,
|
||||
IMAGE_GLYPH_SHADE8,
|
||||
IMAGE_GLYPH_HALF,
|
||||
IMAGE_GLYPH_QUADRANT,
|
||||
IMAGE_GLYPH_SEXTANT
|
||||
};
|
||||
|
||||
enum image_glyph_palette {
|
||||
IMAGE_GLYPH_PALETTE_8,
|
||||
IMAGE_GLYPH_PALETTE_16,
|
||||
IMAGE_GLYPH_PALETTE_256,
|
||||
IMAGE_GLYPH_PALETTE_RGB
|
||||
};
|
||||
|
||||
struct image_rgb {
|
||||
u_char r;
|
||||
u_char g;
|
||||
u_char b;
|
||||
};
|
||||
|
||||
struct image_glyph_data {
|
||||
u_char *shade5;
|
||||
u_char *shade8;
|
||||
};
|
||||
|
||||
static const struct image_rgb image_ansi_colours[16] = {
|
||||
{ 0x00, 0x00, 0x00 }, { 0x80, 0x00, 0x00 },
|
||||
{ 0x00, 0x80, 0x00 }, { 0x80, 0x80, 0x00 },
|
||||
{ 0x00, 0x00, 0x80 }, { 0x80, 0x00, 0x80 },
|
||||
{ 0x00, 0x80, 0x80 }, { 0xc0, 0xc0, 0xc0 },
|
||||
{ 0x80, 0x80, 0x80 }, { 0xff, 0x00, 0x00 },
|
||||
{ 0x00, 0xff, 0x00 }, { 0xff, 0xff, 0x00 },
|
||||
{ 0x00, 0x00, 0xff }, { 0xff, 0x00, 0xff },
|
||||
{ 0x00, 0xff, 0xff }, { 0xff, 0xff, 0xff }
|
||||
};
|
||||
|
||||
static u_int
|
||||
image_glyph_distance(struct image_rgb a, struct image_rgb b)
|
||||
{
|
||||
int r = a.r - b.r, g = a.g - b.g, bl = a.b - b.b;
|
||||
|
||||
return (r * r + g * g + bl * bl);
|
||||
}
|
||||
|
||||
static int
|
||||
image_glyph_low_saturation(struct image_rgb colour)
|
||||
{
|
||||
u_int minimum, maximum;
|
||||
|
||||
minimum = maximum = colour.r;
|
||||
if (colour.g < minimum)
|
||||
minimum = colour.g;
|
||||
if (colour.b < minimum)
|
||||
minimum = colour.b;
|
||||
if (colour.g > maximum)
|
||||
maximum = colour.g;
|
||||
if (colour.b > maximum)
|
||||
maximum = colour.b;
|
||||
return (maximum == 0 || (maximum - minimum) * 4 <= maximum);
|
||||
}
|
||||
|
||||
static u_int
|
||||
image_glyph_nearest_ansi(struct image_rgb colour, u_int colours)
|
||||
{
|
||||
u_int i, distance, best_distance = UINT_MAX, best = 0;
|
||||
int neutral = image_glyph_low_saturation(colour);
|
||||
|
||||
for (i = 0; i < colours; i++) {
|
||||
if (neutral && i != 0 && i != 7 &&
|
||||
(colours != 16 || (i != 8 && i != 15)))
|
||||
continue;
|
||||
distance = image_glyph_distance(colour, image_ansi_colours[i]);
|
||||
if (distance < best_distance) {
|
||||
best_distance = distance;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
return (best);
|
||||
}
|
||||
|
||||
static struct image_rgb
|
||||
image_glyph_quantize(struct image_rgb colour, enum image_glyph_palette palette,
|
||||
int *output)
|
||||
{
|
||||
struct image_rgb result;
|
||||
int value;
|
||||
u_int index;
|
||||
|
||||
if (palette == IMAGE_GLYPH_PALETTE_RGB) {
|
||||
*output = colour_join_rgb(colour.r, colour.g, colour.b);
|
||||
return (colour);
|
||||
}
|
||||
if (palette == IMAGE_GLYPH_PALETTE_256) {
|
||||
*output = colour_find_rgb(colour.r, colour.g, colour.b);
|
||||
value = colour_256toRGB(*output);
|
||||
colour_split_rgb(value, &result.r, &result.g, &result.b);
|
||||
return (result);
|
||||
}
|
||||
index = image_glyph_nearest_ansi(colour,
|
||||
palette == IMAGE_GLYPH_PALETTE_8 ? 8 : 16);
|
||||
result = image_ansi_colours[index];
|
||||
if (index < 8)
|
||||
*output = index;
|
||||
else
|
||||
*output = 90 + index - 8;
|
||||
return (result);
|
||||
}
|
||||
|
||||
static void
|
||||
image_glyph_fit_colours(const struct image_rgb *samples, u_int count,
|
||||
struct image_rgb centres[2])
|
||||
{
|
||||
u_int sum[2][3], counts[2], group, i, j, iteration, distance;
|
||||
u_int maximum = 0, first = 0, second = 0;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
for (j = i + 1; j < count; j++) {
|
||||
distance = image_glyph_distance(samples[i], samples[j]);
|
||||
if (distance > maximum) {
|
||||
maximum = distance;
|
||||
first = i;
|
||||
second = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
centres[0] = samples[first];
|
||||
centres[1] = samples[second];
|
||||
for (iteration = 0; iteration < 4; iteration++) {
|
||||
memset(sum, 0, sizeof sum);
|
||||
memset(counts, 0, sizeof counts);
|
||||
for (i = 0; i < count; i++) {
|
||||
group = image_glyph_distance(samples[i], centres[1]) <
|
||||
image_glyph_distance(samples[i], centres[0]);
|
||||
sum[group][0] += samples[i].r;
|
||||
sum[group][1] += samples[i].g;
|
||||
sum[group][2] += samples[i].b;
|
||||
counts[group]++;
|
||||
}
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (counts[i] == 0)
|
||||
continue;
|
||||
centres[i].r = (sum[i][0] + counts[i] / 2) / counts[i];
|
||||
centres[i].g = (sum[i][1] + counts[i] / 2) / counts[i];
|
||||
centres[i].b = (sum[i][2] + counts[i] / 2) / counts[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
image_glyph_set_acs(struct tty *tty, struct utf8_data *data, u_char key)
|
||||
{
|
||||
struct utf8_data *ud;
|
||||
const char *s = tty_acs_get(tty, key);
|
||||
|
||||
if (s == NULL)
|
||||
return (0);
|
||||
ud = utf8_fromcstr(s);
|
||||
if (ud[0].size == 0) {
|
||||
free(ud);
|
||||
return (0);
|
||||
}
|
||||
memcpy(data, &ud[0], sizeof *data);
|
||||
free(ud);
|
||||
return (1);
|
||||
}
|
||||
|
||||
static u_char
|
||||
image_glyph_block_key(enum image_glyph_detail detail, u_int mask)
|
||||
{
|
||||
static const u_char half[4] = {
|
||||
0, TTY_ACS_IMAGE_HALF_UPPER, TTY_ACS_IMAGE_HALF_LOWER,
|
||||
TTY_ACS_IMAGE_BLOCK
|
||||
};
|
||||
static const u_char quadrant[16] = {
|
||||
0, TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT, TTY_ACS_IMAGE_HALF_UPPER,
|
||||
TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT, TTY_ACS_IMAGE_HALF_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_HALF_RIGHT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_HALF_LOWER,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_BLOCK
|
||||
};
|
||||
|
||||
if (detail == IMAGE_GLYPH_HALF)
|
||||
return (half[mask]);
|
||||
if (detail == IMAGE_GLYPH_QUADRANT)
|
||||
return (quadrant[mask]);
|
||||
if (mask == 0)
|
||||
return (0);
|
||||
if (mask == 21)
|
||||
return (TTY_ACS_IMAGE_HALF_LEFT);
|
||||
if (mask == 42)
|
||||
return (TTY_ACS_IMAGE_HALF_RIGHT);
|
||||
if (mask == 63)
|
||||
return (TTY_ACS_IMAGE_BLOCK);
|
||||
return (tty_acs_image_sextant(mask));
|
||||
}
|
||||
|
||||
static u_char *
|
||||
image_glyph_make_shades(struct image *im, u_int levels)
|
||||
{
|
||||
struct image_glyph_data *data = im->fallback_data;
|
||||
const struct image_cell *cell;
|
||||
int *values, value, represented, error;
|
||||
size_t cells, index;
|
||||
u_int x, y, scan, level, minimum = 255, maximum = 0;
|
||||
int reverse;
|
||||
u_char *result;
|
||||
|
||||
if (data == NULL) {
|
||||
data = im->fallback_data = xcalloc(1, sizeof *data);
|
||||
}
|
||||
result = (levels == 5 ? data->shade5 : data->shade8);
|
||||
if (result != NULL)
|
||||
return (result);
|
||||
cells = (size_t)im->sx * im->sy;
|
||||
values = xcalloc(cells, sizeof *values);
|
||||
result = xcalloc(cells, 1);
|
||||
for (y = 0; y < im->sy; y++) {
|
||||
for (x = 0; x < im->sx; x++) {
|
||||
index = (size_t)y * im->sx + x;
|
||||
cell = image_get_cell(im, x, y);
|
||||
value = cell->whole.brightness;
|
||||
if ((u_int)value < minimum)
|
||||
minimum = value;
|
||||
if ((u_int)value > maximum)
|
||||
maximum = value;
|
||||
values[index] = value;
|
||||
}
|
||||
}
|
||||
if (maximum > minimum) {
|
||||
for (index = 0; index < cells; index++)
|
||||
values[index] = (values[index] - minimum) * 255 /
|
||||
(maximum - minimum);
|
||||
}
|
||||
for (y = 0; y < im->sy; y++) {
|
||||
reverse = (y & 1);
|
||||
for (scan = 0; scan < im->sx; scan++) {
|
||||
x = (reverse ? im->sx - scan - 1 : scan);
|
||||
index = (size_t)y * im->sx + x;
|
||||
value = values[index];
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
if (value > 255)
|
||||
value = 255;
|
||||
level = (value * (levels - 1) + 127) / 255;
|
||||
result[index] = level;
|
||||
represented = level * 255 / (levels - 1);
|
||||
error = value - represented;
|
||||
if (!reverse && x + 1 < im->sx)
|
||||
values[index + 1] += error * 7 / 16;
|
||||
if (reverse && x != 0)
|
||||
values[index - 1] += error * 7 / 16;
|
||||
if (y + 1 == im->sy)
|
||||
continue;
|
||||
if (!reverse && x != 0)
|
||||
values[index + im->sx - 1] += error * 3 / 16;
|
||||
if (reverse && x + 1 < im->sx)
|
||||
values[index + im->sx + 1] += error * 3 / 16;
|
||||
values[index + im->sx] += error * 5 / 16;
|
||||
if (!reverse && x + 1 < im->sx)
|
||||
values[index + im->sx + 1] += error / 16;
|
||||
if (reverse && x != 0)
|
||||
values[index + im->sx - 1] += error / 16;
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
if (levels == 5)
|
||||
data->shade5 = result;
|
||||
else
|
||||
data->shade8 = result;
|
||||
return (result);
|
||||
}
|
||||
|
||||
static enum image_glyph_palette
|
||||
image_glyph_get_palette(struct tty *tty)
|
||||
{
|
||||
int colours;
|
||||
|
||||
if (tty->term->flags & TERM_RGBCOLOURS)
|
||||
return (IMAGE_GLYPH_PALETTE_RGB);
|
||||
if (tty->term->flags & TERM_256COLOURS)
|
||||
return (IMAGE_GLYPH_PALETTE_256);
|
||||
colours = tty_term_number(tty->term, TTYC_COLORS);
|
||||
if (colours >= 256)
|
||||
return (IMAGE_GLYPH_PALETTE_256);
|
||||
if (colours >= 16)
|
||||
return (IMAGE_GLYPH_PALETTE_16);
|
||||
return (IMAGE_GLYPH_PALETTE_8);
|
||||
}
|
||||
|
||||
static enum image_glyph_detail
|
||||
image_glyph_get_detail(struct tty *tty, enum image_glyph_palette palette)
|
||||
{
|
||||
if (tty_acs_needed(tty))
|
||||
return (IMAGE_GLYPH_ASCII);
|
||||
if (tty->term->flags & TERM_IMAGE_SEXTANTS)
|
||||
return (IMAGE_GLYPH_SEXTANT);
|
||||
if (tty->term->flags & TERM_IMAGE_QUADRANTS)
|
||||
return (IMAGE_GLYPH_QUADRANT);
|
||||
if (palette != IMAGE_GLYPH_PALETTE_8)
|
||||
return (IMAGE_GLYPH_HALF);
|
||||
if (tty_term_has(tty->term, TTYC_NOBR))
|
||||
return (IMAGE_GLYPH_SHADE5);
|
||||
return (IMAGE_GLYPH_SHADE8);
|
||||
}
|
||||
|
||||
static void
|
||||
image_glyph_block(struct tty *tty, struct image *im, u_int x, u_int y,
|
||||
enum image_glyph_detail detail, enum image_glyph_palette palette,
|
||||
struct grid_cell *out)
|
||||
{
|
||||
const struct image_cell *cell = image_get_cell(im, x, y);
|
||||
struct image_rgb samples[6], centres[2], quantized[2];
|
||||
u_int columns, rows, sx, sy, ix, iy, i, n, mask;
|
||||
u_int x0, x1, y0, y1, red, green, blue, count;
|
||||
int colours[2];
|
||||
u_char key;
|
||||
|
||||
columns = (detail == IMAGE_GLYPH_HALF ? 1 : 2);
|
||||
rows = (detail == IMAGE_GLYPH_HALF ? 2 :
|
||||
detail == IMAGE_GLYPH_QUADRANT ? 2 : 3);
|
||||
i = 0;
|
||||
for (sy = 0; sy < rows; sy++) {
|
||||
for (sx = 0; sx < columns; sx++) {
|
||||
x0 = sx * IMAGE_SAMPLE_COLUMNS / columns;
|
||||
x1 = (sx + 1) * IMAGE_SAMPLE_COLUMNS / columns;
|
||||
y0 = sy * IMAGE_SAMPLE_ROWS / rows;
|
||||
y1 = (sy + 1) * IMAGE_SAMPLE_ROWS / rows;
|
||||
red = green = blue = count = 0;
|
||||
for (iy = y0; iy < y1; iy++) {
|
||||
for (ix = x0; ix < x1; ix++) {
|
||||
red += cell->samples[iy][ix].red;
|
||||
green += cell->samples[iy][ix].green;
|
||||
blue += cell->samples[iy][ix].blue;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
samples[i].r = red / count;
|
||||
samples[i].g = green / count;
|
||||
samples[i].b = blue / count;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
n = columns * rows;
|
||||
image_glyph_fit_colours(samples, n, centres);
|
||||
quantized[0] = image_glyph_quantize(centres[0], palette, &colours[0]);
|
||||
quantized[1] = image_glyph_quantize(centres[1], palette, &colours[1]);
|
||||
mask = 0;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (image_glyph_distance(samples[i], quantized[1]) <
|
||||
image_glyph_distance(samples[i], quantized[0]))
|
||||
mask |= (1U << i);
|
||||
}
|
||||
key = image_glyph_block_key(detail, mask);
|
||||
if (key == 0)
|
||||
utf8_set(&out->data, ' ');
|
||||
else if (!image_glyph_set_acs(tty, &out->data, key))
|
||||
utf8_set(&out->data, ' ');
|
||||
out->fg = colours[1];
|
||||
out->bg = colours[0];
|
||||
}
|
||||
|
||||
void
|
||||
image_get_fallback_cell(struct tty *tty, struct image *im, u_int x, u_int y,
|
||||
const struct grid_cell *gc, struct grid_cell *out,
|
||||
__unused const struct tty_style_ctx *style_ctx)
|
||||
{
|
||||
static const char ascii[] = " .:-=+*#%@";
|
||||
static const u_char shades[5] = { 0, TTY_ACS_IMAGE_SHADE_LIGHT,
|
||||
TTY_ACS_IMAGE_SHADE_MEDIUM, TTY_ACS_IMAGE_SHADE_DARK,
|
||||
TTY_ACS_IMAGE_BLOCK };
|
||||
static const u_char bold_shades[8] = { 0,
|
||||
TTY_ACS_IMAGE_SHADE_LIGHT, TTY_ACS_IMAGE_SHADE_LIGHT,
|
||||
TTY_ACS_IMAGE_SHADE_MEDIUM, TTY_ACS_IMAGE_SHADE_MEDIUM,
|
||||
TTY_ACS_IMAGE_SHADE_DARK, TTY_ACS_IMAGE_BLOCK,
|
||||
TTY_ACS_IMAGE_BLOCK };
|
||||
const struct image_cell *cell;
|
||||
enum image_glyph_palette palette;
|
||||
enum image_glyph_detail detail;
|
||||
u_char *levels, key;
|
||||
u_int level = 0;
|
||||
|
||||
memcpy(out, gc, sizeof *out);
|
||||
cell = image_get_cell(im, x, y);
|
||||
if (cell == NULL) {
|
||||
utf8_set(&out->data, ' ');
|
||||
return;
|
||||
}
|
||||
palette = image_glyph_get_palette(tty);
|
||||
detail = image_glyph_get_detail(tty, palette);
|
||||
if (detail == IMAGE_GLYPH_ASCII) {
|
||||
level = cell->whole.brightness * (sizeof ascii - 2) / 255;
|
||||
utf8_set(&out->data, ascii[level]);
|
||||
return;
|
||||
}
|
||||
if (detail == IMAGE_GLYPH_SHADE5 || detail == IMAGE_GLYPH_SHADE8) {
|
||||
levels = image_glyph_make_shades(im,
|
||||
detail == IMAGE_GLYPH_SHADE5 ? 5 : 8);
|
||||
level = levels[(size_t)y * im->sx + x];
|
||||
key = (detail == IMAGE_GLYPH_SHADE5 ? shades[level] :
|
||||
bold_shades[level]);
|
||||
if (key == 0)
|
||||
utf8_set(&out->data, ' ');
|
||||
else if (!image_glyph_set_acs(tty, &out->data, key))
|
||||
utf8_set(&out->data, ' ');
|
||||
out->fg = 7;
|
||||
out->bg = 0;
|
||||
out->attr &= ~GRID_ATTR_BRIGHT;
|
||||
if (detail == IMAGE_GLYPH_SHADE8 &&
|
||||
(level == 2 || level == 4 || level == 7))
|
||||
out->attr |= GRID_ATTR_BRIGHT;
|
||||
return;
|
||||
}
|
||||
image_glyph_block(tty, im, x, y, detail, palette, out);
|
||||
}
|
||||
|
||||
void
|
||||
image_free_fallback(struct image *im)
|
||||
{
|
||||
struct image_glyph_data *data = im->fallback_data;
|
||||
|
||||
if (data == NULL)
|
||||
return;
|
||||
free(data->shade5);
|
||||
free(data->shade8);
|
||||
free(data);
|
||||
}
|
||||
1308
image-kitty.c
1308
image-kitty.c
File diff suppressed because it is too large
Load Diff
1169
image-sixel.c
1169
image-sixel.c
File diff suppressed because it is too large
Load Diff
182
input.c
182
input.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: input.c,v 1.270 2026/08/17 20:04:00 nicm Exp $ */
|
||||
/* $OpenBSD: input.c,v 1.271 2026/08/31 19:34:09 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -145,9 +145,6 @@ struct input_ctx {
|
||||
*/
|
||||
struct evbuffer *since_ground;
|
||||
struct event ground_timer;
|
||||
#ifdef ENABLE_IMAGES
|
||||
void *kitty_state;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Helper functions. */
|
||||
@@ -186,9 +183,6 @@ static void input_enter_osc(struct input_ctx *);
|
||||
static void input_exit_osc(struct input_ctx *);
|
||||
static void input_enter_apc(struct input_ctx *);
|
||||
static void input_exit_apc(struct input_ctx *);
|
||||
#ifdef ENABLE_IMAGES
|
||||
static int input_handle_kitty(struct input_ctx *, const u_char *, size_t);
|
||||
#endif
|
||||
static void input_enter_rename(struct input_ctx *);
|
||||
static void input_exit_rename(struct input_ctx *);
|
||||
|
||||
@@ -922,9 +916,6 @@ input_free(struct input_ctx *ictx)
|
||||
event_del(&ictx->request_timer);
|
||||
|
||||
free(ictx->input_buf);
|
||||
#ifdef ENABLE_IMAGES
|
||||
kitty_free_state(ictx->kitty_state);
|
||||
#endif
|
||||
evbuffer_free(ictx->since_ground);
|
||||
event_del(&ictx->ground_timer);
|
||||
|
||||
@@ -947,7 +938,6 @@ input_reset(struct input_ctx *ictx, int clear)
|
||||
screen_write_start_pane(sctx, wp, &wp->base);
|
||||
else
|
||||
screen_write_start(sctx, &wp->base);
|
||||
sctx->flags |= SCREEN_WRITE_INPUT;
|
||||
screen_write_reset(sctx);
|
||||
screen_write_stop(sctx);
|
||||
}
|
||||
@@ -1059,6 +1049,7 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
wp->output_generation++;
|
||||
window_update_activity(wp->window);
|
||||
if (~wp->flags & PANE_ACTIVITY) {
|
||||
wp->flags |= PANE_ACTIVITY;
|
||||
@@ -1075,7 +1066,6 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
|
||||
screen_write_start_pane(sctx, wp, &wp->base);
|
||||
else
|
||||
screen_write_start(sctx, &wp->base);
|
||||
sctx->flags |= SCREEN_WRITE_INPUT;
|
||||
|
||||
log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id,
|
||||
ictx->state->name, len, (int)len, buf);
|
||||
@@ -1095,7 +1085,6 @@ input_parse_screen(struct input_ctx *ictx, struct screen *s,
|
||||
return;
|
||||
|
||||
screen_write_start_callback(sctx, s, cb, arg);
|
||||
sctx->flags |= SCREEN_WRITE_INPUT;
|
||||
input_parse(ictx, buf, len);
|
||||
screen_write_stop(sctx);
|
||||
}
|
||||
@@ -1595,7 +1584,7 @@ input_csi_dispatch(struct input_ctx *ictx)
|
||||
case -1:
|
||||
break;
|
||||
case 0:
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
input_reply(ictx, 1, "\033[?1;2;4c");
|
||||
#else
|
||||
input_reply(ictx, 1, "\033[?1;2c");
|
||||
@@ -2113,7 +2102,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
|
||||
static void
|
||||
input_csi_dispatch_sm_graphics(__unused struct input_ctx *ictx)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
int n, m, o;
|
||||
|
||||
if (ictx->param_list_len > 3)
|
||||
@@ -2638,10 +2627,10 @@ input_dcs_dispatch(struct input_ctx *ictx)
|
||||
const char prefix[] = "tmux;";
|
||||
const u_int prefixlen = (sizeof prefix) - 1;
|
||||
long long allow_passthrough = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct window *w;
|
||||
struct sixel_image *si;
|
||||
int p1, p2;
|
||||
int p2;
|
||||
#endif
|
||||
|
||||
if (wp == NULL)
|
||||
@@ -2654,18 +2643,15 @@ input_dcs_dispatch(struct input_ctx *ictx)
|
||||
return (0);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (wp != NULL && buf[0] == 'q' && ictx->interm_len == 0) {
|
||||
w = wp->window;
|
||||
if (input_split(ictx) != 0)
|
||||
return (0);
|
||||
p1 = input_get(ictx, 0, 0, 0);
|
||||
if (p1 == -1)
|
||||
p1 = 0;
|
||||
p2 = input_get(ictx, 1, 0, 0);
|
||||
if (p2 == -1)
|
||||
p2 = 0;
|
||||
si = sixel_parse(buf, len, p1, p2, w->xpixel, w->ypixel);
|
||||
si = sixel_parse(buf, len, p2, w->xpixel, w->ypixel);
|
||||
if (si != NULL)
|
||||
screen_write_sixelimage(sctx, si, ictx->cell.cell.bg);
|
||||
}
|
||||
@@ -2804,57 +2790,6 @@ input_enter_apc(struct input_ctx *ictx)
|
||||
}
|
||||
|
||||
/* APC terminator (ST) received. */
|
||||
#ifdef ENABLE_IMAGES
|
||||
static int
|
||||
input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
|
||||
{
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct window_pane *wp = ictx->wp;
|
||||
struct image *im;
|
||||
u_int image_id = 0, replace_id = 0, quiet = 0;
|
||||
u_int placement_id = 0;
|
||||
int32_t z = 0;
|
||||
char action = '\0', delete = '\0';
|
||||
int status;
|
||||
|
||||
if (wp == NULL)
|
||||
return (0);
|
||||
im = kitty_parse_image(&ictx->kitty_state, buf, len,
|
||||
wp->window->xpixel,
|
||||
wp->window->ypixel, &image_id, &replace_id, &quiet, &action,
|
||||
&delete, &placement_id, &z, &status);
|
||||
if (status == KITTY_PARSE_MORE)
|
||||
return (1);
|
||||
if (status != KITTY_PARSE_OK) {
|
||||
if (quiet < 2 && action != '\0') {
|
||||
if (status == KITTY_PARSE_MISSING)
|
||||
input_reply(ictx, 0, "\033_Gi=%u;ENOENT\033\\",
|
||||
image_id);
|
||||
else
|
||||
input_reply(ictx, 0, "\033_Gi=%u;EINVAL\033\\",
|
||||
image_id);
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
if (replace_id != 0)
|
||||
image_clear(sctx, replace_id);
|
||||
if (im != NULL) {
|
||||
if (action == 'd')
|
||||
image_clear_kitty(sctx, delete, image_id, placement_id, z);
|
||||
else
|
||||
image_write_kitty(sctx, im, ictx->cell.cell.bg, image_id,
|
||||
placement_id, z);
|
||||
image_free(image_get_id(im));
|
||||
if (quiet == 0 && image_id != 0)
|
||||
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
|
||||
} else if (action == 'd')
|
||||
image_clear_kitty(sctx, delete, image_id, placement_id, z);
|
||||
else if ((action == 't' || action == 'q' || action == 'u') && quiet == 0)
|
||||
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
|
||||
return (1);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
input_exit_apc(struct input_ctx *ictx)
|
||||
{
|
||||
@@ -2865,13 +2800,6 @@ input_exit_apc(struct input_ctx *ictx)
|
||||
return;
|
||||
log_debug("%s: \"%s\"", __func__, ictx->input_buf);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (ictx->input_len > 1 && ictx->input_buf[0] == 'G' &&
|
||||
input_handle_kitty(ictx, ictx->input_buf + 1,
|
||||
ictx->input_len - 1))
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (wp != NULL &&
|
||||
options_get_number(wp->options, "allow-set-title") &&
|
||||
screen_set_title(sctx->s, ictx->input_buf, 1)) {
|
||||
@@ -2932,12 +2860,6 @@ input_top_bit_set(struct input_ctx *ictx)
|
||||
{
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct utf8_data *ud = &ictx->utf8data;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell gc;
|
||||
struct image *im;
|
||||
u_int x, source_x, source_y, image_id, placement_id;
|
||||
int32_t z;
|
||||
#endif
|
||||
|
||||
ictx->flags &= ~INPUT_LAST;
|
||||
|
||||
@@ -2965,20 +2887,6 @@ input_top_bit_set(struct input_ctx *ictx)
|
||||
utf8_copy(&ictx->cell.cell.data, ud);
|
||||
screen_write_collect_add(sctx, &ictx->cell.cell);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (sctx->s->cx != 0) {
|
||||
x = sctx->s->cx - 1; /* cx-1 is the cell just written. */
|
||||
grid_view_get_cell(sctx->s->grid, x, sctx->s->cy, &gc);
|
||||
if (kitty_placeholder_to_image(ictx->kitty_state,
|
||||
sctx->s->grid, &gc, x, sctx->s->cy, &im, &source_x,
|
||||
&source_y, &image_id, &placement_id, &z)) {
|
||||
grid_view_set_cell(sctx->s->grid, x, sctx->s->cy, &gc);
|
||||
image_place_cell_kitty(sctx, im, x, sctx->s->cy, source_x,
|
||||
source_y, image_id, placement_id, z);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
utf8_copy(&ictx->last, &ictx->cell.cell.data);
|
||||
ictx->flags |= INPUT_LAST;
|
||||
|
||||
@@ -3286,13 +3194,14 @@ input_osc_112(struct input_ctx *ictx, const char *p)
|
||||
|
||||
/* Parse the OSC 133 D exit status. */
|
||||
static int
|
||||
input_osc_133_exit_status(const char *p)
|
||||
input_osc_133_exit_status(const char *p, int *present)
|
||||
{
|
||||
const char *end;
|
||||
char *copy;
|
||||
const char *errstr;
|
||||
char *endptr;
|
||||
long long status;
|
||||
|
||||
*present = 0;
|
||||
if (p[1] != ';' || p[2] == '\0' || strchr(p + 2, '=') == p + 2)
|
||||
return (0);
|
||||
end = strchr(p + 2, ';');
|
||||
@@ -3306,9 +3215,11 @@ input_osc_133_exit_status(const char *p)
|
||||
free(copy);
|
||||
return (0);
|
||||
}
|
||||
status = strtonum(copy, 0, 255, &errstr);
|
||||
*present = 1;
|
||||
errno = 0;
|
||||
status = (int)strtol(copy, &endptr, 10);
|
||||
free(copy);
|
||||
if (errstr != NULL)
|
||||
if (errno != 0 || endptr == copy || status < 0 || status > 255)
|
||||
return (255);
|
||||
return (status);
|
||||
}
|
||||
@@ -3353,6 +3264,22 @@ input_fire_command_event(struct window_pane *wp, const char *name)
|
||||
events_fire(name, ep);
|
||||
}
|
||||
|
||||
/* Check if an OSC 133 prompt is secondary or a continuation. */
|
||||
static int
|
||||
input_osc_133_secondary_prompt(const char *p)
|
||||
{
|
||||
const char *cp;
|
||||
|
||||
while ((cp = strstr(p, ";k=")) != NULL) {
|
||||
cp += 3;
|
||||
if ((*cp == 's' || *cp == 'c') &&
|
||||
(cp[1] == '\0' || cp[1] == ';'))
|
||||
return (1);
|
||||
p = cp;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Handle the OSC 133 sequence. */
|
||||
static void
|
||||
input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
@@ -3362,8 +3289,7 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
struct grid *gd = s->grid;
|
||||
u_int line = s->cy + gd->hsize;
|
||||
struct grid_line *gl = NULL;
|
||||
const char *cp;
|
||||
int status;
|
||||
int status, status_present;
|
||||
|
||||
if (line < gd->hsize + gd->sy)
|
||||
gl = grid_get_line(gd, line);
|
||||
@@ -3372,9 +3298,17 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
case 'A':
|
||||
case 'N':
|
||||
if (gl != NULL) {
|
||||
memset(&gl->osc133_data, 0, sizeof gl->osc133_data);
|
||||
gl->osc133_data.prompt_col = s->cx;
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
if (!(gl->flags & (GRID_LINE_START_PROMPT|
|
||||
GRID_LINE_SECOND_PROMPT))) {
|
||||
gl->osc133_data.prompt_col = s->cx;
|
||||
if (input_osc_133_secondary_prompt(p))
|
||||
gl->flags |= GRID_LINE_SECOND_PROMPT;
|
||||
else
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
log_debug("%s: osc133 %s at %u,%u", __func__,
|
||||
input_osc_133_secondary_prompt(p) ? "secondary prompt" :
|
||||
"prompt", s->cx, line);
|
||||
}
|
||||
}
|
||||
if (wp != NULL) {
|
||||
wp->last_prompt_time = time(NULL);
|
||||
@@ -3383,25 +3317,33 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
break;
|
||||
case 'P':
|
||||
if (gl != NULL) {
|
||||
cp = strstr(p, ";k=s");
|
||||
if (cp != NULL && (cp[4] == ';' || cp[4] == '\0'))
|
||||
gl->flags |= GRID_LINE_SECOND_PROMPT;
|
||||
else
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
gl->osc133_data.prompt_col = s->cx;
|
||||
if (!(gl->flags & (GRID_LINE_START_PROMPT|
|
||||
GRID_LINE_SECOND_PROMPT))) {
|
||||
gl->osc133_data.prompt_col = s->cx;
|
||||
if (input_osc_133_secondary_prompt(p))
|
||||
gl->flags |= GRID_LINE_SECOND_PROMPT;
|
||||
else
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
log_debug("%s: osc133 prompt at %u,%u", __func__, s->cx,
|
||||
line);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
case 'I':
|
||||
if (gl != NULL) {
|
||||
if (gl != NULL && !(gl->flags & GRID_LINE_START_COMMAND)) {
|
||||
gl->flags |= GRID_LINE_START_COMMAND;
|
||||
gl->osc133_data.cmd_col = s->cx;
|
||||
log_debug("%s: osc133 command at %u,%u", __func__, s->cx,
|
||||
line);
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
if (gl != NULL) {
|
||||
if (gl != NULL && !(gl->flags & GRID_LINE_START_OUTPUT)) {
|
||||
gl->flags |= GRID_LINE_START_OUTPUT;
|
||||
gl->osc133_data.out_start_col = s->cx;
|
||||
log_debug("%s: osc133 output start at %u,%u", __func__, s->cx,
|
||||
line);
|
||||
}
|
||||
if (wp != NULL) {
|
||||
wp->cmd_start_time = time(NULL);
|
||||
@@ -3412,7 +3354,7 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
}
|
||||
break;
|
||||
case 'D':
|
||||
status = input_osc_133_exit_status(p);
|
||||
status = input_osc_133_exit_status(p, &status_present);
|
||||
if (wp != NULL) {
|
||||
wp->cmd_end_time = time(NULL);
|
||||
wp->flags &= ~PANE_CMDRUNNING;
|
||||
@@ -3421,8 +3363,12 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
}
|
||||
if (gl != NULL) {
|
||||
gl->flags |= GRID_LINE_END_OUTPUT;
|
||||
if (status_present)
|
||||
gl->flags |= GRID_LINE_END_OUTPUT_STATUS;
|
||||
gl->osc133_data.out_end_col = s->cx;
|
||||
gl->osc133_data.exit_status = status;
|
||||
log_debug("%s: osc133 output end at %u,%u (status %d)",
|
||||
__func__, s->cx, line, status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@
|
||||
" '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}' '<' {send -X history-top}" \
|
||||
" '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}' '>' {send -X history-bottom}" \
|
||||
" ''" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},#{?copy_line_numbers,Hide Line Numbers,Show Line Numbers},}' 'L' {send -X line-numbers-toggle}" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},#{?copy_line_numbers,Line Numbers Off,Line Numbers On},}' 'L' {send -X line-numbers-toggle}" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},#{?copy_fold_view,Fold View Off,Fold View On},}' 'O' {send -X fold-view-toggle}" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},Edit,}' 'e' {if -F '#{selection_present}' {send -X open-selection} {send -X open-output}}" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},#{?refresh_active,Refresh Off,Refresh On},}' 'r' {send -X refresh-toggle}" \
|
||||
" ''" \
|
||||
" '#{?#{&&:#{buffer_size},#{!:#{pane_in_mode}}},Paste #[underscore]#{=/9/...:buffer_sample},}' 'p' {paste-buffer}" \
|
||||
@@ -575,6 +577,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode C-l { send -X recentre-top-bottom }",
|
||||
"bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }",
|
||||
"bind -Tcopy-mode C-n { send -X cursor-down }",
|
||||
"bind -Tcopy-mode C-o { send -X select-output }",
|
||||
"bind -Tcopy-mode C-p { send -X cursor-up }",
|
||||
"bind -Tcopy-mode C-r { command-prompt -P -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }",
|
||||
"bind -Tcopy-mode C-s { command-prompt -P -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }",
|
||||
@@ -583,15 +586,19 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode Escape { send -X cancel }",
|
||||
"bind -Tcopy-mode C-[ { send -X cancel }",
|
||||
"bind -Tcopy-mode Space { send -X page-down }",
|
||||
"bind -Tcopy-mode Tab { send -X toggle-output }",
|
||||
"bind -Tcopy-mode BTab { send -X toggle-output -a }",
|
||||
"bind -Tcopy-mode , { send -X jump-reverse }",
|
||||
"bind -Tcopy-mode \\; { send -X jump-again }",
|
||||
"bind -Tcopy-mode F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode L { send -X line-numbers-toggle }",
|
||||
"bind -Tcopy-mode N { send -X search-reverse }",
|
||||
"bind -Tcopy-mode O { send -X fold-view-toggle }",
|
||||
"bind -Tcopy-mode P { send -X toggle-position }",
|
||||
"bind -Tcopy-mode R { send -X rectangle-toggle }",
|
||||
"bind -Tcopy-mode T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode X { send -X set-mark }",
|
||||
"bind -Tcopy-mode e { if -F '#{selection_present}' { send -X open-selection } { send -X open-output } }",
|
||||
"bind -Tcopy-mode f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }",
|
||||
"bind -Tcopy-mode n { send -X search-again }",
|
||||
@@ -600,7 +607,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode Home { send -X start-of-line }",
|
||||
"bind -Tcopy-mode End { send -X end-of-line }",
|
||||
"bind -Tcopy-mode MouseDown1Pane select-pane",
|
||||
"bind -Tcopy-mode MouseDown1Pane { select-pane; send -X toggle-output -m }",
|
||||
"bind -Tcopy-mode MouseDrag1Pane { select-pane; send -X begin-selection }",
|
||||
"bind -Tcopy-mode MouseDragEnd1Pane { send -X copy-pipe-and-cancel }",
|
||||
"bind -Tcopy-mode WheelUpPane { select-pane; send -N5 -X scroll-up }",
|
||||
@@ -660,6 +667,8 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi Escape { send -X clear-selection }",
|
||||
"bind -Tcopy-mode-vi C-[ { send -X clear-selection }",
|
||||
"bind -Tcopy-mode-vi Space { send -X begin-selection }",
|
||||
"bind -Tcopy-mode-vi Tab { send -X toggle-output }",
|
||||
"bind -Tcopy-mode-vi BTab { send -X toggle-output -a }",
|
||||
"bind -Tcopy-mode-vi '$' { send -X end-of-line }",
|
||||
"bind -Tcopy-mode-vi , { send -X jump-reverse }",
|
||||
"bind -Tcopy-mode-vi / { command-prompt -P -T search -p'(search down)' { send -X search-forward -- '%%' } }",
|
||||
@@ -687,7 +696,10 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi K { send -X scroll-up }",
|
||||
"bind -Tcopy-mode-vi L { send -X bottom-line }",
|
||||
"bind -Tcopy-mode-vi M { send -X middle-line }",
|
||||
"bind -Tcopy-mode-vi M-e { if -F '#{selection_present}' { send -X open-selection } { send -X open-output } }",
|
||||
"bind -Tcopy-mode-vi M-o { send -X select-output }",
|
||||
"bind -Tcopy-mode-vi N { send -X search-reverse }",
|
||||
"bind -Tcopy-mode-vi O { send -X fold-view-toggle }",
|
||||
"bind -Tcopy-mode-vi P { send -X toggle-position }",
|
||||
"bind -Tcopy-mode-vi T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi V { send -X select-line }",
|
||||
@@ -715,7 +727,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi % { send -X next-matching-bracket }",
|
||||
"bind -Tcopy-mode-vi Home { send -X start-of-line }",
|
||||
"bind -Tcopy-mode-vi End { send -X end-of-line }",
|
||||
"bind -Tcopy-mode-vi MouseDown1Pane { select-pane }",
|
||||
"bind -Tcopy-mode-vi MouseDown1Pane { select-pane; send -X toggle-output -m }",
|
||||
"bind -Tcopy-mode-vi MouseDrag1Pane { select-pane; send -X begin-selection }",
|
||||
"bind -Tcopy-mode-vi MouseDragEnd1Pane { send -X copy-pipe-and-cancel }",
|
||||
"bind -Tcopy-mode-vi WheelUpPane { select-pane; send -N5 -X scroll-up }",
|
||||
|
||||
45
layout.c
45
layout.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: layout.c,v 1.97 2026/08/20 09:19:24 nicm Exp $ */
|
||||
/* $OpenBSD: layout.c,v 1.98 2026/08/25 18:38:05 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -492,6 +492,7 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
||||
sx = PANE_MINIMUM;
|
||||
else
|
||||
sx = sx - sb_w - sb_pad;
|
||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||
}
|
||||
|
||||
window_pane_resize(wp, sx, sy);
|
||||
@@ -499,11 +500,8 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
||||
if (wp->xoff != old_xoff ||
|
||||
wp->yoff != old_yoff ||
|
||||
wp->sx != old_sx ||
|
||||
wp->sy != old_sy) {
|
||||
wp->sy != old_sy)
|
||||
changed = 1;
|
||||
if (window_pane_scrollbar_reserve(wp))
|
||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
redraw_invalidate_scene(w);
|
||||
@@ -541,6 +539,10 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
|
||||
|
||||
status = window_get_pane_status(w);
|
||||
|
||||
/* Floating cells do not take space from the tiled layout. */
|
||||
if (!layout_cell_is_tiled(lc) && !layout_cell_has_tiled_child(lc))
|
||||
return (0);
|
||||
|
||||
if (lc->type == LAYOUT_WINDOWPANE) {
|
||||
/* Space available in this cell only. */
|
||||
if (type == LAYOUT_LEFTRIGHT) {
|
||||
@@ -570,6 +572,9 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
|
||||
/* Different type: minimum of available space in child cells. */
|
||||
minimum = UINT_MAX;
|
||||
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
|
||||
if (!layout_cell_is_tiled(lcchild) &&
|
||||
!layout_cell_has_tiled_child(lcchild))
|
||||
continue;
|
||||
available = layout_resize_check(w, lcchild, type);
|
||||
if (available < minimum)
|
||||
minimum = available;
|
||||
@@ -663,7 +668,7 @@ layout_resize_set_size(struct window *w, struct layout_cell *lc,
|
||||
|
||||
/* Find and return the nearest neighbour to a cell in a specific direction. */
|
||||
static struct layout_cell *
|
||||
layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction)
|
||||
layout_cell_get_neighbour_dir(struct layout_cell *lc, int direction)
|
||||
{
|
||||
struct layout_cell *lcn = lc;
|
||||
|
||||
@@ -697,9 +702,9 @@ layout_cell_get_neighbour(struct layout_cell *lc)
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
|
||||
direction = !direction;
|
||||
|
||||
lcother = layout_cell_get_neighbour_direction(lc, direction);
|
||||
lcother = layout_cell_get_neighbour_dir(lc, direction);
|
||||
if (lcother == NULL)
|
||||
lcother = layout_cell_get_neighbour_direction(lc, !direction);
|
||||
lcother = layout_cell_get_neighbour_dir(lc, !direction);
|
||||
|
||||
return (lcother);
|
||||
}
|
||||
@@ -862,7 +867,7 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
|
||||
size = lc->g.sx;
|
||||
else
|
||||
size = lc->g.sy;
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
|
||||
if (layout_cell_is_last_tiled(lc))
|
||||
change = size - new_size;
|
||||
else
|
||||
change = new_size - size;
|
||||
@@ -987,11 +992,11 @@ layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
|
||||
if (lcparent == NULL)
|
||||
return;
|
||||
|
||||
/* If this is the last cell, move back one. */
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) {
|
||||
do
|
||||
lc = TAILQ_PREV(lc, layout_cells, entry);
|
||||
while (lc->flags & LAYOUT_CELL_FLOATING);
|
||||
/* If this is the last tiled cell, move back one. */
|
||||
if (layout_cell_is_last_tiled(lc)) {
|
||||
lc = layout_cell_get_neighbour_dir(lc, 0);
|
||||
if (lc == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
layout_resize_layout(wp->window, lc, type, change, opposite);
|
||||
@@ -1009,22 +1014,22 @@ layout_resize_pane_grow(struct window *w, struct layout_cell *lc,
|
||||
lcadd = lc;
|
||||
|
||||
/* Look towards the tail for a suitable cell for reduction. */
|
||||
lcremove = TAILQ_NEXT(lc, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lc, 1);
|
||||
while (lcremove != NULL) {
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size > 0)
|
||||
break;
|
||||
lcremove = TAILQ_NEXT(lcremove, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 1);
|
||||
}
|
||||
|
||||
/* If none found, look towards the head. */
|
||||
if (opposite && lcremove == NULL) {
|
||||
lcremove = TAILQ_PREV(lc, layout_cells, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lc, 0);
|
||||
while (lcremove != NULL) {
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size > 0)
|
||||
break;
|
||||
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
|
||||
}
|
||||
}
|
||||
if (lcremove == NULL)
|
||||
@@ -1052,13 +1057,13 @@ layout_resize_pane_shrink(struct window *w, struct layout_cell *lc,
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size != 0)
|
||||
break;
|
||||
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
|
||||
} while (lcremove != NULL);
|
||||
if (lcremove == NULL)
|
||||
return (0);
|
||||
|
||||
/* And add onto the next cell (from the original cell). */
|
||||
lcadd = TAILQ_NEXT(lc, entry);
|
||||
lcadd = layout_cell_get_neighbour_dir(lc, 1);
|
||||
if (lcadd == NULL)
|
||||
return (0);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: options-table.c,v 1.242 2026/07/27 08:03:01 nicm Exp $ */
|
||||
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -114,7 +114,6 @@ static const char *options_table_theme_list[] = {
|
||||
static const char *options_table_copy_mode_line_numbers_list[] = {
|
||||
"off", "default", "absolute", "relative", "hybrid", NULL
|
||||
};
|
||||
|
||||
/* Status line format. */
|
||||
#define OPTIONS_TABLE_STATUS_FORMAT1 \
|
||||
"#[align=left range=left #{E:status-left-style}]" \
|
||||
@@ -305,6 +304,16 @@ const struct options_table_entry options_table[] = {
|
||||
"When this is reached, the oldest buffer is deleted."
|
||||
},
|
||||
|
||||
{ .name = "clear-on-attach",
|
||||
.type = OPTIONS_TABLE_FLAG,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.default_num = 1,
|
||||
.text = "Whether to use the alternate screen and clear it when "
|
||||
"a client is attached. When disabled, tmux does not "
|
||||
"enter the alternate screen on attach so terminal "
|
||||
"content before tmux remains in scrollback."
|
||||
},
|
||||
|
||||
{ .name = "command-alias",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
@@ -541,7 +550,6 @@ const struct options_table_entry options_table[] = {
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_ARRAY,
|
||||
.default_str = "xterm*:clipboard:ccolour:cstyle:focus:title,"
|
||||
"xterm-kitty*:kitty,"
|
||||
"screen*:title,"
|
||||
"rxvt*:ignorefkeys",
|
||||
.separator = ",",
|
||||
@@ -1241,7 +1249,8 @@ const struct options_table_entry options_table[] = {
|
||||
* underscore.
|
||||
*/
|
||||
.default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~",
|
||||
.text = "Characters considered to separate words."
|
||||
.text = "Characters considered to separate words; a space matches "
|
||||
"any character with the Unicode White_Space property."
|
||||
},
|
||||
|
||||
/* Window options. */
|
||||
@@ -1374,6 +1383,13 @@ const struct options_table_entry options_table[] = {
|
||||
.text = "Style of search matches in copy mode."
|
||||
},
|
||||
|
||||
{ .name = "copy-mode-exit-status-format",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.default_str = "#{?exit_status,#[fg=themered]!, }",
|
||||
.text = "Format of OSC 133 exit status indicator in copy mode."
|
||||
},
|
||||
|
||||
{ .name = "copy-mode-current-match-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
|
||||
65
popup.c
65
popup.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: popup.c,v 1.76 2026/07/14 19:07:03 nicm Exp $ */
|
||||
/* $OpenBSD: popup.c,v 1.77 2026/08/28 08:02:16 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -79,7 +79,8 @@ popup_free(struct popup_data *pd)
|
||||
|
||||
if (pd->job != NULL)
|
||||
job_free(pd->job);
|
||||
input_free(pd->ictx);
|
||||
if (pd->ictx != NULL)
|
||||
input_free(pd->ictx);
|
||||
|
||||
free(pd->r.ranges);
|
||||
screen_free(&pd->s);
|
||||
@@ -135,8 +136,7 @@ popup_reapply_styles(struct popup_data *pd)
|
||||
}
|
||||
|
||||
static void
|
||||
popup_redraw_cb(const struct tty_ctx *ttyctx, __unused u_int py,
|
||||
__unused u_int ny)
|
||||
popup_redraw_cb(const struct tty_ctx *ttyctx)
|
||||
{
|
||||
struct popup_data *pd = ttyctx->arg;
|
||||
|
||||
@@ -321,60 +321,11 @@ popup_resize_cb(__unused struct client *c, void *data)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Report damage for a popup's rectangle, given in raw client/tty
|
||||
* coordinates. Status-line cells are outside the window scene - they have no
|
||||
* corresponding window content and redraw_damage_window() can't reach them -
|
||||
* so if the popup's rectangle overlaps the status line, force it to redraw
|
||||
* separately. The rest of the rectangle is clipped to the actual pane area
|
||||
* (above or below the status line, whichever side it's on) before being
|
||||
* translated into window coordinates and reported the normal way, the same
|
||||
* way mouse coordinates are translated elsewhere (e.g. cmd-join-pane.c,
|
||||
* cmd-split-window.c).
|
||||
*/
|
||||
static void
|
||||
popup_damage(struct client *c, u_int px, u_int py, u_int sx, u_int sy)
|
||||
{
|
||||
struct window *w;
|
||||
int statusat;
|
||||
u_int ox, oy, osx, osy, lines, top, bottom, y0, y1;
|
||||
|
||||
if (c->session == NULL)
|
||||
return;
|
||||
w = c->session->curw->window;
|
||||
|
||||
lines = status_line_size(c);
|
||||
statusat = status_at_line(c);
|
||||
if (statusat >= 0 && py < (u_int)statusat + lines &&
|
||||
py + sy > (u_int)statusat)
|
||||
c->flags |= (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS);
|
||||
|
||||
if (statusat == 0) {
|
||||
top = lines;
|
||||
bottom = c->tty.sy;
|
||||
} else if (statusat > 0) {
|
||||
top = 0;
|
||||
bottom = statusat;
|
||||
} else {
|
||||
top = 0;
|
||||
bottom = c->tty.sy;
|
||||
}
|
||||
y0 = (py > top) ? py : top;
|
||||
y1 = (py + sy < bottom) ? py + sy : bottom;
|
||||
if (y0 >= y1)
|
||||
return;
|
||||
|
||||
tty_window_offset(&c->tty, &ox, &oy, &osx, &osy);
|
||||
redraw_damage_window(w, px + ox, y0 - top + oy, sx, y1 - y0);
|
||||
}
|
||||
|
||||
static void
|
||||
popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||
struct mouse_event *m)
|
||||
{
|
||||
u_int px, py;
|
||||
u_int old_px = pd->px, old_py = pd->py;
|
||||
u_int old_sx = pd->sx, old_sy = pd->sy;
|
||||
|
||||
if (!MOUSE_DRAG(m->b))
|
||||
pd->dragging = OFF;
|
||||
@@ -397,9 +348,7 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||
pd->dy = m->y - pd->py;
|
||||
pd->ppx = px;
|
||||
pd->ppy = py;
|
||||
|
||||
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
||||
c->flags |= CLIENT_REDRAWOVERLAY;
|
||||
server_redraw_client(c);
|
||||
} else if (pd->dragging == SIZE) {
|
||||
if (pd->border_lines == BOX_LINES_NONE) {
|
||||
if (m->x < pd->px + 1)
|
||||
@@ -426,9 +375,7 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||
if (pd->job != NULL)
|
||||
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
||||
}
|
||||
|
||||
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
||||
c->flags |= CLIENT_REDRAWOVERLAY;
|
||||
server_redraw_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ $TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
TMP2=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$TMP2"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
trap 'rm -f "$TMP" "$TMP2" "$TMP".*; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
@@ -270,6 +270,27 @@ if [ $? -eq 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A write error after a successful open must be reported to the server.
|
||||
dd if=/dev/zero of="$TMP.big" bs=100000 count=1 2>/dev/null
|
||||
check_ok load-buffer -b write-error "$TMP.big"
|
||||
mkfifo "$TMP.fifo" || exit 1
|
||||
dd if="$TMP.fifo" of="$TMP.got" bs=1 count=8 2>/dev/null &
|
||||
reader=$!
|
||||
exec 3>"$TMP.fifo"
|
||||
out=$($TMUX save-buffer -b write-error "$TMP.fifo" 3>&- 2>&1)
|
||||
status=$?
|
||||
exec 3>&-
|
||||
wait "$reader"
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "save-buffer after write error succeeded."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(wc -c <"$TMP.got")" -ne 8 ]; then
|
||||
echo "save-buffer write error test wrote wrong amount."
|
||||
exit 1
|
||||
fi
|
||||
check_ok delete-buffer -b write-error
|
||||
|
||||
# save-buffer - writes to stdout and load-buffer - reads from stdin.
|
||||
out=$($TMUX save-buffer -b sb -)
|
||||
if [ "$out" != "data" ]; then
|
||||
|
||||
0
regress/cfg-client-lost.sh
Executable file → Normal file
0
regress/cfg-client-lost.sh
Executable file → Normal file
@@ -290,4 +290,92 @@ wait_count ': G4' 7
|
||||
fail "window 1 of zzz not killed"
|
||||
exit_mode q
|
||||
|
||||
# --- help and information preview --------------------------------------------
|
||||
# The complete tree help is taller than the 24-line test terminal, where the
|
||||
# help renderer deliberately leaves the tree unchanged.
|
||||
$TMUX2 resize-window -t out:0 -x 80 -y 50 || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
# The inner session has a one-line status, leaving a 49-line window.
|
||||
[ "$($TMUX display-message -p -t aaa:0 '#{window_height}')" -ge 49 ] &&
|
||||
break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "attached client did not resize for tree help"
|
||||
$TMUX choose-tree -t aaa:0 -F 'G5' -O index || exit 1
|
||||
wait_count ': G5' 7
|
||||
$TMUX2 send-keys -t out:0 F1 || fail "send-keys F1 failed"
|
||||
wait_for 'Swap current and previous window'
|
||||
# The first key closes help; the second exits the tree.
|
||||
$TMUX send-keys -t aaa:0 q q || fail "send-keys help exit failed"
|
||||
wait_mode aaa:0 0
|
||||
|
||||
$TMUX choose-tree -t aaa:0 -F 'G6' -O index || exit 1
|
||||
wait_count ': G6' 7
|
||||
$TMUX send-keys -t aaa:0 i || fail "send-keys info failed"
|
||||
wait_for 'Session'
|
||||
$TMUX send-keys -t aaa:0 i || fail "send-keys preview failed"
|
||||
exit_mode q
|
||||
|
||||
# --- window swapping and command prompt for current/tagged items -------------
|
||||
# Restrict a window-only tree to two disposable windows in aaa. J (the
|
||||
# Shift-Down equivalent) swaps their window objects while leaving their indexes
|
||||
# in place.
|
||||
$TMUX new-window -d -t aaa: -n swap-a 'cat' || exit 1
|
||||
$TMUX new-window -d -t aaa: -n swap-b 'cat' || exit 1
|
||||
aidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \
|
||||
awk -F: '$2 == "swap-a" {print $1}')
|
||||
bidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \
|
||||
awk -F: '$2 == "swap-b" {print $1}')
|
||||
$TMUX choose-tree -w -t aaa:0 -O index -F 'WT #{window_name}' \
|
||||
-f '#{m:swap-*,#{window_name}}' || exit 1
|
||||
wait_count 'WT swap-' 2
|
||||
$TMUX send-keys -t aaa:0 g j J || fail "window tree swap failed"
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
aname=$($TMUX display-message -p -t "aaa:$aidx" '#{window_name}')
|
||||
bname=$($TMUX display-message -p -t "aaa:$bidx" '#{window_name}')
|
||||
[ "$aname:$bname" = 'swap-b:swap-a' ] && break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "window tree did not swap the two windows"
|
||||
|
||||
# ':' runs an entered command for the current item and rebuilds the tree when
|
||||
# its queued command completes.
|
||||
$TMUX set-option -g @tree-command '' || exit 1
|
||||
$TMUX send-keys -t aaa:0 : || fail "window tree command prompt failed"
|
||||
wait_for '(current)'
|
||||
$TMUX send-keys -t aaa:0 -l "set-option -g @tree-command '%%'" || exit 1
|
||||
$TMUX send-keys -t aaa:0 Enter || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
value=$($TMUX show-option -gqv @tree-command)
|
||||
[ -n "$value" ] && break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "window tree entered command did not run"
|
||||
case "$value" in
|
||||
=aaa:*.) ;;
|
||||
*) fail "window tree command target was '$value'" ;;
|
||||
esac
|
||||
|
||||
# Tag both disposable windows and kill them through the tagged confirmation
|
||||
# callback. The mode-hosting window is outside the filter and survives.
|
||||
$TMUX send-keys -t aaa:0 g j t j t X || fail "tagged kill keys failed"
|
||||
wait_for 'Kill 2 tagged?'
|
||||
$TMUX send-keys -t aaa:0 y || fail "tagged kill confirmation failed"
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
left=$($TMUX list-windows -t aaa -F '#{window_name}' | \
|
||||
grep -c '^swap-' || true)
|
||||
[ "$left" -eq 0 ] && break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "tagged windows were not killed"
|
||||
exit_mode q
|
||||
|
||||
exit 0
|
||||
|
||||
96
regress/clear-on-attach.sh
Normal file
96
regress/clear-on-attach.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/bin/sh
|
||||
|
||||
# With clear-on-attach disabled tmux must preserve the terminal contents by
|
||||
# scrolling them away, rather than entering and clearing the alternate screen.
|
||||
# Capture the bytes written by a real attached client for both settings.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
INNER_ON="$TEST_TMUX -LtestIon$$ -f/dev/null"
|
||||
OUTER_ON="$TEST_TMUX -LtestOon$$ -f/dev/null"
|
||||
INNER_OFF="$TEST_TMUX -LtestIoff$$ -f/dev/null"
|
||||
OUTER_OFF="$TEST_TMUX -LtestOoff$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER_ON kill-server 2>/dev/null
|
||||
$INNER_ON kill-server 2>/dev/null
|
||||
$OUTER_OFF kill-server 2>/dev/null
|
||||
$INNER_OFF kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$current_inner list-clients 2>/dev/null | grep -q . && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
capture_attach()
|
||||
{
|
||||
setting=$1
|
||||
output=$2
|
||||
go=$DIR/go-$setting
|
||||
if [ "$setting" = on ]; then
|
||||
current_inner=$INNER_ON
|
||||
current_outer=$OUTER_ON
|
||||
else
|
||||
current_inner=$INNER_OFF
|
||||
current_outer=$OUTER_OFF
|
||||
fi
|
||||
|
||||
$current_inner new-session -d -s inner -x40 -y10 \
|
||||
'exec sleep 100' || exit 1
|
||||
$current_inner set-option -g status off || exit 1
|
||||
$current_inner set-option -g clear-on-attach "$setting" || exit 1
|
||||
$current_outer new-session -d -s outer -x40 -y10 \
|
||||
"while [ ! -e '$go' ]; do sleep 0.01; done; exec $current_inner attach-session -t inner" || exit 1
|
||||
$current_outer set-option -g status off || exit 1
|
||||
$current_outer pipe-pane -O "cat >'$output'" || exit 1
|
||||
touch "$go"
|
||||
wait_for_client
|
||||
sleep 0.5
|
||||
$current_outer pipe-pane -O || exit 1
|
||||
}
|
||||
|
||||
on=$DIR/on
|
||||
off=$DIR/off
|
||||
capture_attach on "$on"
|
||||
capture_attach off "$off"
|
||||
|
||||
smcup=$(printf '\033[?1049h')
|
||||
grep -Fq "$smcup" "$on" || fail "clear-on-attach on did not use smcup"
|
||||
if grep -Fq "$smcup" "$off"; then
|
||||
fail "clear-on-attach off used smcup"
|
||||
fi
|
||||
|
||||
# Some screen terminfo entries have indn (CSI Ps S), while older ones only
|
||||
# have ind (newline). The preserving path uses indn once or repeats ind once
|
||||
# for every line in the terminal plus one.
|
||||
escape=$(printf '\033')
|
||||
if ! LC_ALL=C grep -Eq "${escape}\\[[0-9]+S" "$off"; then
|
||||
hex=$(od -An -tx1 -v "$off" | tr -d ' \n')
|
||||
case "$hex" in
|
||||
*0a0a0a0a0a0a0a0a0a0a0a*) ;;
|
||||
*) fail "clear-on-attach off did not scroll the old contents away" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
exit 0
|
||||
82
regress/client-key-order-focus.sh
Normal file
82
regress/client-key-order-focus.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/bin/sh
|
||||
|
||||
# send-keys -K injects a key into a client. Commands produced by that key must
|
||||
# be inserted immediately after send-keys, before later commands in the same
|
||||
# command list. Focus events arriving between a prefix and its following key
|
||||
# must be forwarded without cancelling the prefix table.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
client=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$client" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_for_option()
|
||||
{
|
||||
option=$1
|
||||
expected=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
actual=$($INNER show-option -gv "$option" 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "$option is '$actual', expected '$expected'"
|
||||
}
|
||||
|
||||
cleanup
|
||||
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g focus-events on || exit 1
|
||||
$OUTER new-session -d -s outer -x40 -y10 "$INNER attach -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
wait_for_client
|
||||
|
||||
# The binding generated by send-keys -K must run before the command which
|
||||
# follows send-keys in this command list. Before the queue-ordering fix this
|
||||
# produces AB rather than BA.
|
||||
$INNER set-option -g @order '' || exit 1
|
||||
$INNER bind-key -n a set-option -gF @order '#{@order}B' || exit 1
|
||||
$INNER send-keys -K -c "$client" a \; \
|
||||
set-option -gF @order '#{@order}A' || exit 1
|
||||
wait_for_option @order BA
|
||||
|
||||
# Put the real attached client in the prefix table, deliver FocusIn, then the
|
||||
# bound key. FocusIn is not bindable and must not reset the prefix table.
|
||||
$INNER bind-key x set-option -g @focus-prefix yes || exit 1
|
||||
$OUTER send-keys C-b || exit 1
|
||||
focus_in=$(printf '\033[I')
|
||||
$OUTER send-keys -l "$focus_in" || exit 1
|
||||
$OUTER send-keys x || exit 1
|
||||
wait_for_option @focus-prefix yes
|
||||
|
||||
exit 0
|
||||
101
regress/clock-mode.sh
Normal file
101
regress/clock-mode.sh
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise clock-mode initialization, all four styles, its one-second timer,
|
||||
# resize into the compact renderer, arbitrary-key exit, and cleanup.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER display-message -p -t clock:0 '#{pane_mode}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane mode is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
wait_hashes()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -q '#' && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "large clock did not render"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s clock -x80 -y24 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -w clock-mode-colour red || exit 1
|
||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t clock" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
for style in 12 24 12-with-seconds 24-with-seconds; do
|
||||
$INNER set-option -w -t clock:0 clock-mode-style "$style" || exit 1
|
||||
$INNER clock-mode -t clock:0 || exit 1
|
||||
wait_mode clock-mode
|
||||
wait_hashes
|
||||
# Let at least one timer callback observe a new second.
|
||||
[ "$style" != 12-with-seconds ] || sleep 1.2
|
||||
$INNER send-keys -t clock:0 x || exit 1
|
||||
wait_mode ''
|
||||
done
|
||||
|
||||
# Resizing an active mode below the large-glyph threshold selects the compact
|
||||
# renderer. A visible time contains a colon and no block-clock hash.
|
||||
$INNER clock-mode -t clock:0 || exit 1
|
||||
wait_mode clock-mode
|
||||
$INNER resize-window -t clock:0 -x20 -y5 || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Eq '[0-9][0-9]?:[0-9][0-9]' && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "compact clock did not render after resize"
|
||||
printf '%s\n' "$captured" | grep -q '#' &&
|
||||
fail "compact clock still used large glyphs"
|
||||
$INNER send-keys -t clock:0 Enter || exit 1
|
||||
wait_mode ''
|
||||
|
||||
$INNER has-session -t clock || fail "server died during clock-mode tests"
|
||||
exit 0
|
||||
81
regress/collapse-output.sh
Normal file
81
regress/collapse-output.sh
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo hi\\n\\033]133;C\\007hello\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
$TMUX send-keys -X search-backward hello || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != hello ] || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != separator ] || exit 1
|
||||
|
||||
$TMUX send-keys BTab || exit 1
|
||||
$TMUX send-keys -X search-backward hello || exit 1
|
||||
shown=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$shown" = hello ] || exit 1
|
||||
$TMUX send-keys -X select-line || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
selected=$($TMUX show-buffer)
|
||||
[ "$selected" = hello ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX kill-server
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"first\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != separator ] || exit 1
|
||||
$TMUX send-keys BTab || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
shown=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$shown" = separator ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX kill-server
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007P> \\033]133;B\\007for i in 1 2 3; do\\n\\033]133;A;k=s\\007> \\033]133;B\\007echo \\$i\\n\\033]133;P;k=s\\007> \\033]133;B\\007done\\033]133;C\\007\\nRESULT\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007P> \\033]133;B\\007\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
$TMUX send-keys -X search-backward RESULT || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != RESULT ] || exit 1
|
||||
$TMUX send-keys -X search-backward 'echo $i' || exit 1
|
||||
shown=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$shown" = 'echo $i' ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward RESULT || exit 1
|
||||
$TMUX send-keys Tab || exit 1
|
||||
cursor=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$cursor" = done ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX kill-server
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007P0> \\033]133;B\\007\\r\\n\\033]133;D;0\\007empty-separator\\n\\033]133;A\\007P1> \\033]133;B\\007\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
collapsed=$($TMUX capture-pane -p)
|
||||
case "$collapsed" in
|
||||
*"+ P0>"*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -X search-backward empty-separator || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != empty-separator ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
150
regress/confirm-before-lock.sh
Normal file
150
regress/confirm-before-lock.sh
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise confirm-before's waiting and background callbacks, custom keys and
|
||||
# prompts, default-yes handling and errors. Also cover all three lock command
|
||||
# entry points with a harmless lock-command which leaves a marker.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_capture()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Fq "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$marker'"
|
||||
}
|
||||
|
||||
wait_option()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER show-option -gqv @confirmed 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "@confirmed is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
wait_file()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
[ -f "$marker" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "lock command did not create $marker"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s test -x80 -y24 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status on || exit 1
|
||||
$INNER set-option -g status-position bottom || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t test" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
client=$($INNER list-clients -F '#{client_name}')
|
||||
[ -n "$client" ] || fail "inner client did not attach"
|
||||
|
||||
$INNER bind-key -n M-a confirm-before \
|
||||
'set-option -g @confirmed accepted' || exit 1
|
||||
$INNER bind-key -n M-b confirm-before -b -p 'Background?' \
|
||||
'set-option -g @confirmed background' || exit 1
|
||||
$INNER bind-key -n M-c confirm-before -c x -p 'Custom?' \
|
||||
'set-option -g @confirmed custom' || exit 1
|
||||
$INNER bind-key -n M-y confirm-before -y -p 'Default?' \
|
||||
'set-option -g @confirmed default' || exit 1
|
||||
|
||||
# A non-confirming response closes the waiting prompt without running its
|
||||
# command; reopening and accepting inserts the command after the waiting item.
|
||||
$INNER set-option -g @confirmed sentinel || exit 1
|
||||
$OUTER send-keys M-a || exit 1
|
||||
wait_capture "Confirm 'set-option'? (y/n)"
|
||||
$OUTER send-keys n || exit 1
|
||||
sleep 0.2
|
||||
wait_option sentinel
|
||||
$OUTER send-keys M-a || exit 1
|
||||
wait_capture "Confirm 'set-option'? (y/n)"
|
||||
$OUTER send-keys y || exit 1
|
||||
wait_option accepted
|
||||
|
||||
# -b has no waiting queue item and appends its command to the client queue.
|
||||
$OUTER send-keys M-b || exit 1
|
||||
wait_capture 'Background?'
|
||||
$OUTER send-keys y || exit 1
|
||||
wait_option background
|
||||
|
||||
# A custom printable key is accepted, and -y makes Enter affirmative.
|
||||
$OUTER send-keys M-c || exit 1
|
||||
wait_capture 'Custom?'
|
||||
$OUTER send-keys x || exit 1
|
||||
wait_option custom
|
||||
$OUTER send-keys M-y || exit 1
|
||||
wait_capture 'Default?'
|
||||
$OUTER send-keys Enter || exit 1
|
||||
wait_option default
|
||||
|
||||
# Reject invalid multi-character and control confirmation keys.
|
||||
if $INNER confirm-before -b -t "$client" -c xx 'display-message x' \
|
||||
>/dev/null 2>&1; then
|
||||
fail "multi-character confirm key was accepted"
|
||||
fi
|
||||
if $INNER confirm-before -b -t "$client" -c "$(printf '\001')" \
|
||||
'display-message x' >/dev/null 2>&1; then
|
||||
fail "control confirm key was accepted"
|
||||
fi
|
||||
|
||||
# lock-client, lock-session and lock-server all send lock-command to the real
|
||||
# attached client. The command returns immediately and unlocks the client.
|
||||
for kind in client session server; do
|
||||
marker="$DIR/locked-$kind"
|
||||
$INNER set-option -t test lock-command "printf locked >$marker" || exit 1
|
||||
case "$kind" in
|
||||
client) $INNER lock-client -t "$client" || exit 1 ;;
|
||||
session) $INNER lock-session -t test || exit 1 ;;
|
||||
server) $INNER lock-server || exit 1 ;;
|
||||
esac
|
||||
wait_file "$marker"
|
||||
done
|
||||
|
||||
$INNER has-session -t test || fail "server died during prompt or lock tests"
|
||||
exit 0
|
||||
0
regress/control-client-exit-stalled.sh
Executable file → Normal file
0
regress/control-client-exit-stalled.sh
Executable file → Normal file
63
regress/control-client-popup.sh
Normal file
63
regress/control-client-popup.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Popups require a tty overlay and cannot be displayed by a control client.
|
||||
# A popup command from control mode must be ignored cleanly, leaving the
|
||||
# client command queue and server usable.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
FIFO=$DIR/input
|
||||
OUT=$DIR/output
|
||||
RAN=$DIR/popup-ran
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&-
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
$TMUX new-session -d -s control -x40 -y10 'exec sleep 100' || exit 1
|
||||
$TMUX -C attach-session -t control <"$FIFO" >"$OUT" 2>&1 &
|
||||
control_pid=$!
|
||||
exec 3>"$FIFO"
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$TMUX list-clients -F '#{client_flags}' 2>/dev/null |
|
||||
grep -q 'control-mode' && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "control client did not attach"
|
||||
|
||||
printf '%s\n' "display-popup -E 'touch $RAN'" >&3
|
||||
printf '%s\n' "display-message -p CONTROL-POPUP-ALIVE" >&3
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
grep -q 'CONTROL-POPUP-ALIVE' "$OUT" 2>/dev/null && break
|
||||
if ! kill -0 "$control_pid" 2>/dev/null; then
|
||||
fail "control client exited after display-popup"
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "command after control-client popup did not run"
|
||||
$TMUX has-session -t control || fail "server exited after control-client popup"
|
||||
[ ! -e "$RAN" ] || fail "control client started a popup command"
|
||||
|
||||
exit 0
|
||||
38
regress/control-client-run-shell-exit.sh
Normal file
38
regress/control-client-run-shell-exit.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A waited run-shell command can complete after a control client has closed its
|
||||
# input and exited. The command queue still references the client until the job
|
||||
# callback runs, but control_stop has already destroyed c->control_state. Late
|
||||
# command output must be discarded instead of dereferencing the stopped control
|
||||
# state and killing the server.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
OUT=$(mktemp)
|
||||
trap "$TMUX kill-server 2>/dev/null; rm -f $OUT" 0 1 15
|
||||
|
||||
$TMUX -f/dev/null new-session -d -s main || exit 1
|
||||
|
||||
printf '%s\n' 'run-shell "sleep 0.2; echo TEST"' |
|
||||
$TMUX -f/dev/null -C >"$OUT" 2>&1
|
||||
|
||||
sleep 1
|
||||
|
||||
$TMUX has-session -t main 2>/dev/null || {
|
||||
echo "server exited after late run-shell output"
|
||||
cat "$OUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
grep -q '^%exit' "$OUT" || {
|
||||
echo "control client did not exit"
|
||||
cat "$OUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
0
regress/control-notify-guard.sh
Executable file → Normal file
0
regress/control-notify-guard.sh
Executable file → Normal file
253
regress/copy-mode-commands.sh
Normal file
253
regress/copy-mode-commands.sh
Normal file
@@ -0,0 +1,253 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise the broad non-mouse copy-mode command surface: navigation, search,
|
||||
# jumps, state toggles, line/end-of-line copies, append, and pipe variants.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
x()
|
||||
{
|
||||
$TMUX send-keys -t copy:0 -X "$@" || fail "copy command failed: $*"
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($TMUX display-message -p -t copy:0 '#{pane_in_mode}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane_in_mode is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
fresh()
|
||||
{
|
||||
$TMUX copy-mode -t copy:0 || exit 1
|
||||
wait_mode 1
|
||||
x history-top
|
||||
x start-of-line
|
||||
}
|
||||
|
||||
select_text()
|
||||
{
|
||||
fresh
|
||||
x begin-selection
|
||||
x cursor-right
|
||||
x cursor-right
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s copy -x40 -y8 \
|
||||
"sh -c 'i=0; while [ \$i -lt 40 ]; do printf \" line-%02d needle (a[b]c) end\\n\" \$i; i=\$((i + 1)); done; printf \"\\nparagraph two needle\\n\\nparagraph three\\n\"; printf \"\\033]133;A\\007prompt-one\\n\\033]133;C\\007output-one\\n\\033]133;A\\007prompt-two\\n\\033]133;C\\007output-two\\n\"; exec sleep 100'" ||
|
||||
exit 1
|
||||
$TMUX set-option -g status off || exit 1
|
||||
$TMUX set-option -g history-limit 200 || exit 1
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
history=$($TMUX display-message -p -t copy:0 '#{history_size}')
|
||||
[ "$history" -ge 30 ] && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "history did not fill"
|
||||
|
||||
fresh
|
||||
|
||||
# Copy-mode-specific format callbacks expose the word, line, hyperlink and
|
||||
# active search match at the cursor.
|
||||
formats=$($TMUX display-message -p -t copy:0 \
|
||||
'#{copy_cursor_word}|#{copy_cursor_line}|#{copy_cursor_hyperlink}|#{search_match}') ||
|
||||
exit 1
|
||||
[ -n "$formats" ] || fail "copy-mode cursor formats were empty"
|
||||
|
||||
# Cursor, viewport and paragraph commands. Each is asserted by successful
|
||||
# dispatch and the final mode-liveness check; detailed word/selection geometry
|
||||
# is covered by the existing copy-mode tests.
|
||||
for command in back-to-indentation bottom-line top-line middle-line \
|
||||
cursor-centre-vertical cursor-centre-horizontal end-of-line history-bottom \
|
||||
history-top halfpage-down halfpage-up page-down page-up next-paragraph \
|
||||
previous-paragraph next-matching-bracket previous-matching-bracket; do
|
||||
x "$command"
|
||||
done
|
||||
x goto-line 15
|
||||
x set-mark
|
||||
x cursor-down
|
||||
x jump-to-mark
|
||||
|
||||
# OSC 133 prompt and output markers support shell-integration navigation.
|
||||
x history-top
|
||||
x next-prompt
|
||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
||||
*prompt-one*) ;;
|
||||
*) fail "next-prompt did not find prompt-one" ;;
|
||||
esac
|
||||
x next-prompt
|
||||
x previous-prompt
|
||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
||||
*prompt-one*) ;;
|
||||
*) fail "previous-prompt did not return to prompt-one" ;;
|
||||
esac
|
||||
x next-prompt -o
|
||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
||||
*output-one*) ;;
|
||||
*) fail "next-prompt -o did not find output-one" ;;
|
||||
esac
|
||||
|
||||
# Character jumps remember their direction and character for repeat/reverse.
|
||||
x jump-forward e
|
||||
x jump-again
|
||||
x jump-reverse
|
||||
x jump-backward e
|
||||
x jump-to-forward e
|
||||
x jump-to-backward e
|
||||
|
||||
# Direct, text and incremental searches cover both directions and the saved
|
||||
# search used by again/reverse.
|
||||
x search-forward needle
|
||||
x search-again
|
||||
x search-reverse
|
||||
x search-backward needle
|
||||
x search-forward-text needle
|
||||
x search-backward-text needle
|
||||
x search-forward-incremental '=needle'
|
||||
x search-forward-incremental '+needle'
|
||||
x search-backward-incremental '=needle'
|
||||
x search-backward-incremental '-needle'
|
||||
|
||||
# Stateful display and behaviour toggles.
|
||||
for command in rectangle-on rectangle-off rectangle-toggle \
|
||||
line-numbers-on line-numbers-off line-numbers-toggle \
|
||||
refresh-on refresh-now refresh-off refresh-toggle \
|
||||
scroll-exit-on scroll-exit-off scroll-exit-toggle toggle-position; do
|
||||
x "$command"
|
||||
done
|
||||
wait_mode 1
|
||||
x cancel
|
||||
wait_mode 0
|
||||
|
||||
# Line and end-of-line copy commands work without an explicit selection.
|
||||
fresh
|
||||
x select-line
|
||||
x copy-selection-and-cancel
|
||||
case "$($TMUX show-buffer)" in
|
||||
*line-00*) ;;
|
||||
*) fail "select-line did not select the current line" ;;
|
||||
esac
|
||||
|
||||
fresh
|
||||
x copy-line
|
||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-line produced an empty buffer"
|
||||
x cancel
|
||||
|
||||
fresh
|
||||
x copy-end-of-line
|
||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-end-of-line produced an empty buffer"
|
||||
x cancel
|
||||
|
||||
# Append variants retain or close the mode as advertised.
|
||||
$TMUX set-buffer -b append-buffer prefix || exit 1
|
||||
select_text
|
||||
x append-selection
|
||||
appended=$($TMUX show-buffer -b append-buffer)
|
||||
case "$appended" in
|
||||
prefix*) ;;
|
||||
*) fail "append-selection produced '$appended'" ;;
|
||||
esac
|
||||
x cancel
|
||||
|
||||
$TMUX set-buffer -b append-cancel-buffer prefix || exit 1
|
||||
select_text
|
||||
x append-selection-and-cancel
|
||||
wait_mode 0
|
||||
appended=$($TMUX show-buffer -b append-cancel-buffer)
|
||||
case "$appended" in
|
||||
prefix*) ;;
|
||||
*) fail "append-selection-and-cancel produced '$appended'" ;;
|
||||
esac
|
||||
|
||||
# The explicit cancel variants close copy mode.
|
||||
fresh
|
||||
x copy-line-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x copy-end-of-line-and-cancel
|
||||
wait_mode 0
|
||||
select_text
|
||||
x copy-selection-and-cancel
|
||||
wait_mode 0
|
||||
|
||||
# Pipe wrappers have distinct command handlers. /dev/null keeps the test
|
||||
# deterministic while exercising asynchronous pipe job creation and cleanup.
|
||||
fresh
|
||||
x copy-pipe-line 'cat >/dev/null'
|
||||
x cancel
|
||||
fresh
|
||||
x copy-pipe-line-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
fresh
|
||||
x copy-pipe-end-of-line 'cat >/dev/null'
|
||||
x cancel
|
||||
fresh
|
||||
x copy-pipe-end-of-line-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
|
||||
select_text
|
||||
x copy-pipe-no-clear 'cat >/dev/null'
|
||||
x pipe-no-clear 'cat >/dev/null'
|
||||
x copy-pipe 'cat >/dev/null'
|
||||
x pipe 'cat >/dev/null'
|
||||
x cancel
|
||||
select_text
|
||||
x copy-pipe-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
select_text
|
||||
x pipe-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
|
||||
# Commands which cancel on downward movement have separate handlers.
|
||||
fresh
|
||||
x history-bottom
|
||||
x bottom-line
|
||||
x cursor-down-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x history-bottom
|
||||
x halfpage-down-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x history-bottom
|
||||
x page-down-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x history-bottom
|
||||
x bottom-line
|
||||
x scroll-down-and-cancel
|
||||
wait_mode 0
|
||||
|
||||
$TMUX has-session -t copy || fail "server died during copy-mode commands"
|
||||
exit 0
|
||||
102
regress/copy-mode-exit-status.sh
Normal file
102
regress/copy-mode-exit-status.sh
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check OSC 133 exit status indicators in copy mode using a real client.
|
||||
|
||||
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 -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
fail() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
capture() {
|
||||
$TMUX capture-pane -pS0 -E- >$TMP || exit 1
|
||||
}
|
||||
|
||||
check_grep() {
|
||||
grep -Fq "$1" $TMP || fail "missing pattern: $1"
|
||||
}
|
||||
|
||||
check_no_grep() {
|
||||
grep -Fq "$1" $TMP && fail "unexpected pattern: $1"
|
||||
}
|
||||
|
||||
$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 new-session -d -x80 -y10 \
|
||||
"printf '\033]133;A\007p\$ \033]133;B\007one\n\033]133;C\007out1\n\033]133;D;0\007\033]133;A\007p\$ \033]133;B\007two\n\033]133;C\007out2\n\033]133;D;123\007\033]133;A\007p\$ \033]133;B\007silent\n\033]133;C\007\033]133;D;7\007\033]133;A\007p\$ \033]133;B\007three\n\033]133;C\007out3\n\033]133;D\007\033]133;A\007p\$ \033]133;B\007'; exec sleep 100" || \
|
||||
exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -g copy-mode-position-format '#[align=left]POS' || exit 1
|
||||
$TMUX2 list-keys -Tcopy-mode O | grep -Fq 'fold-view-toggle' || exit 1
|
||||
$TMUX2 list-keys -Tcopy-mode-vi O | grep -Fq 'fold-view-toggle' || exit 1
|
||||
|
||||
$TMUX new-session -d -x80 -y10 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send -l "$TMUX2 attach" || exit 1
|
||||
$TMUX send Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX2 copy-mode -U || exit 1
|
||||
$TMUX2 send -X history-top || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep "!- p\$ two"
|
||||
check_grep "! p\$ silent"
|
||||
check_no_grep "!+ p\$ silent"
|
||||
check_grep "POS"
|
||||
|
||||
# Rebuilding with collapsed output must not expand copy-mode formats before
|
||||
# the viewport has been restored for the shorter backing grid.
|
||||
$TMUX2 send -X collapse-output -a || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep "!+ p\$ two"
|
||||
check_grep "! p\$ silent"
|
||||
check_no_grep "!+ p\$ silent"
|
||||
$TMUX2 send -X line-numbers-on || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep " 3 !+ p\$ two"
|
||||
$TMUX2 send -X fold-view-toggle || exit 1
|
||||
[ "$($TMUX2 display -p '#{copy_fold_view}')" = 0 ] || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep "out2"
|
||||
check_no_grep "!+ p\$ two"
|
||||
check_grep " 3 p\$ two"
|
||||
$TMUX2 send -X fold-view-toggle || exit 1
|
||||
[ "$($TMUX2 display -p '#{copy_fold_view}')" = 1 ] || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep " 3 !- p\$ two"
|
||||
|
||||
# A format wider than the standard three-column gutter is not truncated.
|
||||
$TMUX2 send -X cancel || exit 1
|
||||
$TMUX2 set -g copy-mode-exit-status-format \
|
||||
'#[align=right]#{?exit_status,!#{exit_status}, }' || exit 1
|
||||
$TMUX2 copy-mode -c || exit 1
|
||||
$TMUX2 send -X history-top || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep "!123+ p\$ two"
|
||||
|
||||
$TMUX2 send -X select-line || exit 1
|
||||
$TMUX2 send -X copy-selection || exit 1
|
||||
selection=$($TMUX2 show-buffer)
|
||||
case $selection in
|
||||
*RC=*) fail "exit status was copied" ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
81
regress/copy-mode-selection-mode.sh
Normal file
81
regress/copy-mode-selection-mode.sh
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Changing an existing character selection to line mode must reset both ends
|
||||
# to complete lines, including when the cursor subsequently crosses the fixed
|
||||
# end. An invalid regular-expression search must also discard marks left by a
|
||||
# previous successful search.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
$TMUX new-session -d -x30 -y6 \
|
||||
"printf 'alpha\nbeta\ngamma\ndelta\n'; exec sleep 100" || exit 1
|
||||
$TMUX set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
# A valid regular-expression search creates marks. Replacing it with an
|
||||
# invalid expression, growing the mode, and scrolling must not leave an old
|
||||
# mark array indexed with the new geometry (the original bug was an ASan heap
|
||||
# overflow in this sequence).
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -X search-forward beta || exit 1
|
||||
[ "$($TMUX display-message -p '#{search_present}')" = 1 ] ||
|
||||
fail "successful search did not create marks"
|
||||
$TMUX send-keys -X search-forward '[' || exit 1
|
||||
$TMUX resize-window -x40 -y8 || exit 1
|
||||
$TMUX send-keys -X scroll-down || exit 1
|
||||
[ "$($TMUX display-message -p '#{search_present}')" = 0 ] ||
|
||||
fail "invalid regular expression left stale marks after resize"
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Select part of alpha through part of beta, then change the existing
|
||||
# selection to line mode. Both lines, and no partial columns, must be copied.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X selection-mode line || exit 1
|
||||
[ "$($TMUX display-message -p '#{selection_mode}')" = line ] ||
|
||||
fail "selection did not change to line mode"
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
expected=$(printf 'alpha\nbeta')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "line-mode selection did not expand to complete lines"
|
||||
|
||||
# Start with the cursor above the fixed end so selection-mode itself takes the
|
||||
# reverse-direction path, then extend it through another line.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X selection-mode line || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
expected=$(printf 'alpha\nbeta\ngamma')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "reversed line-mode selection did not keep complete lines"
|
||||
|
||||
exit 0
|
||||
163
regress/copy-mode-unicode-whitespace.sh
Normal file
163
regress/copy-mode-unicode-whitespace.sh
Normal file
@@ -0,0 +1,163 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
[ -z "$TMUX" ] || $TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
goto_cell()
|
||||
{
|
||||
row=$1
|
||||
column=$2
|
||||
|
||||
$TMUX send-keys -X history-top || fail "$mode: history-top failed"
|
||||
$TMUX send-keys -X start-of-line || fail "$mode: start-of-line failed"
|
||||
[ "$row" -eq 0 ] ||
|
||||
$TMUX send-keys -X -N "$row" cursor-down ||
|
||||
fail "$mode: cursor-down failed"
|
||||
[ "$column" -eq 0 ] ||
|
||||
$TMUX send-keys -X -N "$column" cursor-right ||
|
||||
fail "$mode: cursor-right failed"
|
||||
}
|
||||
|
||||
check_cursor()
|
||||
{
|
||||
expected=$1
|
||||
actual=$($TMUX display-message -p '#{copy_cursor_x},#{copy_cursor_y}')
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected cursor $expected, got $actual"
|
||||
}
|
||||
|
||||
check_word()
|
||||
{
|
||||
row=$1
|
||||
column=$2
|
||||
expected=$3
|
||||
|
||||
goto_cell "$row" "$column"
|
||||
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
|
||||
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
|
||||
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
|
||||
actual=$($TMUX show-buffer 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected word '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
check_next_word()
|
||||
{
|
||||
row=$1
|
||||
expected=$2
|
||||
|
||||
goto_cell "$row" 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: next-word failed"
|
||||
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
|
||||
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
|
||||
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
|
||||
actual=$($TMUX show-buffer 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected next word '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
for mode in emacs vi; do
|
||||
TMUX="$TEST_TMUX -Lunicode-whitespace-$mode-$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x16 -y20 \
|
||||
"printf 'aa bb\naa\tbb\naa\302\240bb\naa\343\200\200bb\naa:bb\naaaaaaaaaaaaaaa\302\240b\nabcdefghijklmnopq\nhardone\nhardtwo\naa\341\232\200bb\naa\342\200\200bb\naa\342\200\250bb\naa\342\200\251bb\naa\342\200\257bb\naa\342\201\237bb\nxa\302\205b c\n'; exec cat" ||
|
||||
fail "$mode: new-session failed"
|
||||
$TMUX set-option -g window-size manual || fail "$mode: set size failed"
|
||||
$TMUX set-window-option -g mode-keys "$mode" ||
|
||||
fail "$mode: set mode-keys failed"
|
||||
$TMUX set-window-option -g word-separators "" ||
|
||||
fail "$mode: clear word-separators failed"
|
||||
$TMUX copy-mode || fail "$mode: copy-mode failed"
|
||||
|
||||
# ASCII space and TAB retain their existing behaviour.
|
||||
goto_cell 0 0
|
||||
$TMUX send-keys -X next-space || fail "$mode: ASCII next-space failed"
|
||||
check_cursor 3,0
|
||||
goto_cell 1 0
|
||||
$TMUX send-keys -X next-space || fail "$mode: TAB next-space failed"
|
||||
check_cursor 8,1
|
||||
$TMUX send-keys -X previous-space ||
|
||||
fail "$mode: TAB previous-space failed"
|
||||
check_cursor 0,1
|
||||
|
||||
# NBSP is whitespace for selection and word movement.
|
||||
goto_cell 2 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: NBSP next-word failed"
|
||||
check_cursor 3,2
|
||||
$TMUX send-keys -X previous-word ||
|
||||
fail "$mode: NBSP previous-word failed"
|
||||
check_cursor 0,2
|
||||
check_word 2 0 aa
|
||||
goto_cell 2 0
|
||||
$TMUX send-keys -X next-word-end ||
|
||||
fail "$mode: NBSP next-word-end failed"
|
||||
if [ "$mode" = emacs ]; then
|
||||
check_cursor 2,2
|
||||
else
|
||||
check_cursor 1,2
|
||||
fi
|
||||
|
||||
# U+3000 is two columns wide; both its leading and padding cells must be
|
||||
# skipped as one whitespace character in either direction.
|
||||
goto_cell 3 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: U+3000 next-word failed"
|
||||
check_cursor 4,3
|
||||
$TMUX send-keys -X previous-word ||
|
||||
fail "$mode: U+3000 previous-word failed"
|
||||
check_cursor 0,3
|
||||
goto_cell 3 0
|
||||
$TMUX send-keys -X next-space-end ||
|
||||
fail "$mode: U+3000 next-space-end failed"
|
||||
if [ "$mode" = emacs ]; then
|
||||
check_cursor 2,3
|
||||
else
|
||||
check_cursor 1,3
|
||||
fi
|
||||
|
||||
# Other characters in word-separators remain literal when space enables
|
||||
# Unicode whitespace matching.
|
||||
$TMUX set-window-option -g word-separators ': ' ||
|
||||
fail "$mode: set custom word-separators failed"
|
||||
goto_cell 4 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: literal next-word failed"
|
||||
check_cursor 2,4
|
||||
$TMUX set-window-option -g word-separators "" ||
|
||||
fail "$mode: restore word-separators failed"
|
||||
|
||||
# Whitespace in the last column remains a boundary across a soft wrap.
|
||||
goto_cell 5 0
|
||||
$TMUX send-keys -X next-space ||
|
||||
fail "$mode: wrapped NBSP next-space failed"
|
||||
check_cursor 0,6
|
||||
|
||||
# A word split only by a soft wrap remains one word, while a hard line
|
||||
# boundary still terminates selection.
|
||||
check_word 7 0 abcdefghijklmnopq
|
||||
check_word 9 0 hardone
|
||||
|
||||
# Check the remaining representative White_Space ranges and U+0085
|
||||
# combined into a cell with another character.
|
||||
check_next_word 11 bb
|
||||
check_next_word 12 bb
|
||||
check_next_word 13 bb
|
||||
check_next_word 14 bb
|
||||
check_next_word 15 bb
|
||||
check_next_word 16 bb
|
||||
goto_cell 17 0
|
||||
$TMUX send-keys -X next-word ||
|
||||
fail "$mode: combined U+0085 next-word failed"
|
||||
check_cursor 2,17
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
done
|
||||
|
||||
exit 0
|
||||
118
regress/find-window.sh
Normal file
118
regress/find-window.sh
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise find-window's content, name and title filters in every combination,
|
||||
# glob/regex and case-insensitive suffixes, plus zoom restoration.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER display-message -p -t find:base.0 '#{pane_mode}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane mode is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
run_find()
|
||||
{
|
||||
label=$1
|
||||
expected=$2
|
||||
shift 2
|
||||
$INNER find-window -t find:base.0 "$@" || fail "$label failed"
|
||||
wait_mode tree-mode
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Fq "$expected" && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "$label did not show '$expected'"
|
||||
$INNER send-keys -t find:base.0 q || exit 1
|
||||
wait_mode ''
|
||||
}
|
||||
|
||||
$INNER new-session -d -s find -n base -x80 -y24 \
|
||||
"sh -c 'printf \"BaseBody\\n\"; exec sleep 100'" || exit 1
|
||||
$INNER split-window -d -h -t find:base \
|
||||
"sh -c 'printf \"OtherBody\\n\"; exec sleep 100'" || exit 1
|
||||
$INNER new-window -d -t find: -n NameNeedle 'exec sleep 100' || exit 1
|
||||
$INNER new-window -d -t find: -n body \
|
||||
"sh -c 'printf \"BodyNeedle\\n\"; exec sleep 100'" || exit 1
|
||||
$INNER new-window -d -t find: -n titled 'exec sleep 100' || exit 1
|
||||
$INNER select-pane -t find:titled.0 -T TitleNeedle || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t find:base" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
# Wait until the body text is in the pane history before searching it.
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t find:body.0 | grep -Fq BodyNeedle && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "pane content was not ready"
|
||||
|
||||
# No flags means C+N+T. Then cover all three pairs and all three singletons.
|
||||
run_find default NameNeedle NameNeedle
|
||||
run_find content-name NameNeedle -C -N NameNeedle
|
||||
run_find content-title titled -C -T TitleNeedle
|
||||
run_find name-title titled -N -T TitleNeedle
|
||||
run_find content-only body -C BodyNeedle
|
||||
run_find name-only NameNeedle -N NameNeedle
|
||||
run_find title-only titled -T TitleNeedle
|
||||
|
||||
# Glob case folding and all regular-expression suffix variants.
|
||||
run_find insensitive NameNeedle -N -i nameneedle
|
||||
run_find regex NameNeedle -N -r '^NameNeedle$'
|
||||
run_find regex-insensitive NameNeedle -N -r -i '^nameneedle$'
|
||||
|
||||
# -Z temporarily zooms the target window and restores its prior state on exit.
|
||||
$INNER find-window -Z -t find:base.0 -N NameNeedle || exit 1
|
||||
wait_mode tree-mode
|
||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 1 ] ||
|
||||
fail "find-window -Z did not zoom"
|
||||
$INNER send-keys -t find:base.0 q || exit 1
|
||||
wait_mode ''
|
||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 0 ] ||
|
||||
fail "find-window -Z did not restore zoom"
|
||||
|
||||
exit 0
|
||||
@@ -1,108 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a floating-pane drag bug: cmd_resize_pane_redraw_floating()
|
||||
# (cmd-resize-pane.c) reported damage for just a dragged floating pane's
|
||||
# content rectangle, not the one-cell border frame drawn around it (see the
|
||||
# "floating" case in screen-redraw.c, which draws that frame at
|
||||
# xoff-1/yoff-1 through xoff+sx/yoff+sy - one cell outside the pane's own
|
||||
# content area). Damage scoped to only the content area left the frame's
|
||||
# previous position undrawn as the pane moved, so dragging it left a trail
|
||||
# of un-erased border frames behind - visible as several "corners" stacked
|
||||
# up rather than just the pane's current one.
|
||||
#
|
||||
# This bug has nothing to do with images - it reproduces with a plain
|
||||
# floating pane and no image support required.
|
||||
|
||||
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 -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||
#
|
||||
# Write a plain (unmodified) SGR button-1 press, drag update and release at
|
||||
# 1-based positions to the outer pane holding the inner client - this
|
||||
# matches the default MouseDown1Border/MouseDrag1Border bindings used to
|
||||
# move or resize a floating pane by its border.
|
||||
drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g mouse on
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"'
|
||||
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||
fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
|
||||
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
# Sanity check: exactly one floating pane, so exactly one top-left corner,
|
||||
# before dragging anything.
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
n=$(grep -o '┌' $TMP | wc -l)
|
||||
[ "$n" -eq 1 ] || fail "sanity: expected 1 corner before drag, found $n"
|
||||
|
||||
# Drag the floating pane by its top border (row FTOP-1, some column within
|
||||
# its width) down several rows in a few separate steps, then release. A
|
||||
# single drag() call already does press/motion/release, so call it several
|
||||
# times in a row to simulate a multi-step real drag.
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
STARTROW=$FTOP
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((STARTROW + i + 1))
|
||||
drag $((GRABCOL + 1)) $((STARTROW + i)) $((GRABCOL + 1)) $newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
|
||||
# Exactly one top-left corner should remain - the pane's current position.
|
||||
# This is expected to fail before the fix: multiple corners (a trail of
|
||||
# un-erased frames) would remain from the intermediate drag positions.
|
||||
n=$(grep -o '┌' $TMP | wc -l)
|
||||
[ "$n" -eq 1 ] || fail "expected exactly 1 corner after drag, found $n (ghost frames left behind)"
|
||||
|
||||
exit 0
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: dragging a floating pane across another pane's ordinary
|
||||
# content must not redraw that other pane's scrollbar, unless the drag
|
||||
# actually crosses the scrollbar's own strip.
|
||||
#
|
||||
# cmd_resize_pane_redraw_floating() (cmd-resize-pane.c) used to flag
|
||||
# PANE_REDRAWSCROLLBAR on any pane whose whole *body* intersected the
|
||||
# floating pane's old or new rectangle, rather than just its narrow
|
||||
# scrollbar strip - so dragging a floating pane back and forth over an
|
||||
# ordinary tiled pane's content (never touching its scrollbar) still
|
||||
# needlessly redrew that pane's scrollbar on every motion step. See
|
||||
# tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by giving the non-dragged pane a distinctive scrollbar
|
||||
# colour and counting how many times its SGR code appears in the client's
|
||||
# raw output while the floating pane is dragged vertically over that pane's
|
||||
# body, well clear of its scrollbar column: with the fix, it should never
|
||||
# reappear after the initial draw.
|
||||
|
||||
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 -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||
drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g mouse on || fail "set mouse failed"
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"' || fail "set default-command failed"
|
||||
$TMUX set -g pane-scrollbars on || fail "set pane-scrollbars failed"
|
||||
|
||||
$TMUX split-window -h -t inner 'sh -c "sleep 100"' || fail "split-window failed"
|
||||
|
||||
PANES=$($TMUX list-panes -t inner -F '#{pane_id} #{pane_left}')
|
||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||
[ -n "$LEFT" ] || fail "could not identify left pane"
|
||||
|
||||
# A distinctive scrollbar colour for the non-dragged (left) pane only.
|
||||
$TMUX set -p -t "$LEFT" pane-scrollbars-style 'fg=colour201,bg=colour17' ||
|
||||
fail "set pane-scrollbars-style failed"
|
||||
|
||||
ALEFT=$($TMUX display-message -p -t "$LEFT" '#{pane_left}')
|
||||
ATOP=$($TMUX display-message -p -t "$LEFT" '#{pane_top}')
|
||||
AWIDTH=$($TMUX display-message -p -t "$LEFT" '#{pane_width}')
|
||||
AHEIGHT=$($TMUX display-message -p -t "$LEFT" '#{pane_height}')
|
||||
[ "$AWIDTH" -gt 15 ] || fail "left pane too narrow for this test ($AWIDTH)"
|
||||
|
||||
# A small floating pane placed well inside the left pane's content area,
|
||||
# clear of its (right-hand) scrollbar column by several columns.
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 8 -y 5 \
|
||||
-X $((ALEFT + 2)) -Y $((ATOP + 2))) || fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
[ $((FLEFT + FWIDTH + 3)) -lt $((ALEFT + AWIDTH)) ] ||
|
||||
fail "sanity: floating pane too close to the scrollbar column"
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which draws the scrollbars) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 60 -y 20 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
# Sanity check: the distinctive scrollbar colour reaches the client at all.
|
||||
grep -qa '48;5;201' $TMP || fail "sanity: scrollbar colour never reached the client"
|
||||
: >$TMP
|
||||
|
||||
# Drag the floating pane straight up and down by its top border, staying at
|
||||
# a fixed column the whole time - this never crosses the left pane's
|
||||
# scrollbar strip, only its ordinary content.
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
row=$FTOP
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((row + 1))
|
||||
drag $GRABCOL $row $GRABCOL $newrow
|
||||
row=$newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((row - 1))
|
||||
drag $GRABCOL $row $GRABCOL $newrow
|
||||
row=$newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
# The scrollbar colour should never reappear - its geometry never changed,
|
||||
# and the drag never crossed its column. This is expected to fail before
|
||||
# the fix - see the header comment.
|
||||
n=$(grep -ac '48;5;201' $TMP)
|
||||
[ "$n" -eq 0 ] ||
|
||||
fail "left pane's scrollbar was redrawn $n times while dragging over its body only"
|
||||
|
||||
exit 0
|
||||
@@ -199,5 +199,74 @@ must_fail $TMUX resize-pane -t "$id" -x 0
|
||||
must_fail $TMUX resize-pane -t "$id" -y 0
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# --- Tiled pane resize with floating cells in the layout ---
|
||||
|
||||
$TMUX set-option -w -u pane-border-lines || exit 1
|
||||
base=$($TMUX display-message -p '#{pane_id}')
|
||||
|
||||
# A floating cell after the last tiled cell must not become the recipient of a
|
||||
# relative or absolute resize. This is the sequence from GitHub issue 5135.
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 50% -y 50% -X 50% -Y 50% 'sleep 100') ||
|
||||
fail "new-pane for tiled resize test failed"
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for tiled resize test failed"
|
||||
|
||||
$TMUX resize-pane -t "$lower" -U 5 || fail "relative tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 13
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
|
||||
|
||||
$TMUX resize-pane -t "$lower" -y 11 || fail "absolute tiled reset failed"
|
||||
$TMUX resize-pane -t "$lower" -y 16 || fail "absolute tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# A floating cell between tiled siblings must be skipped when finding the cell
|
||||
# which donates space to a resize.
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for middle floating cell test failed"
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 20 -y 8 -X 30 -Y 8 'sleep 100') ||
|
||||
fail "new-pane for middle floating cell test failed"
|
||||
|
||||
$TMUX resize-pane -t "$base" -D 3 || fail "resize past floating cell failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 6
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# A small floating child must not limit the available tiled space in a nested
|
||||
# layout with a different split direction.
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for nested floating cell test failed"
|
||||
right=$($TMUX split-window -dhPF '#{pane_id}' -t "$lower" 'sleep 100') ||
|
||||
fail "horizontal split for nested floating cell test failed"
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$lower" \
|
||||
-x 20 -y 3 -X 30 -Y 10 'sleep 100') ||
|
||||
fail "new-pane for nested floating cell test failed"
|
||||
|
||||
$TMUX resize-pane -t "$base" -D 3 || fail "nested tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$right" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$right" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 1
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$right" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Resizing a floating pane must refresh session status formats which depend on
|
||||
# its geometry, not only the pane scene and borders.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lfloating-status-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has_status()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
tail -1 "$CAPTURE" | grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client status did not show $marker"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x 50 -y 12 'sleep 100' || exit 1
|
||||
FLOAT=$($INNER new-pane -PF '#{pane_id}' -x 10 -y 5 -X 5 -Y 3 \
|
||||
'sleep 100') || fail "could not create floating pane"
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g status on || exit 1
|
||||
$INNER set-option -g status-position bottom || exit 1
|
||||
$INNER set-option -g status-left 'WIDTH=#{pane_width}' || exit 1
|
||||
$INNER set-option -g status-right '' || exit 1
|
||||
$INNER set-option -g status-interval 0 || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 50 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
OLD_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
wait_outer_has_status "WIDTH=$OLD_WIDTH"
|
||||
|
||||
RIGHT=$($INNER display-message -p -t "$FLOAT" '#{pane_right}')
|
||||
TOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
X=$((RIGHT + 2))
|
||||
Y=$((TOP + 2))
|
||||
|
||||
# Grab the right frame and enlarge the floating pane.
|
||||
mouse 0 "$X" "$Y" M
|
||||
mouse 32 "$((X + 1))" "$Y" M
|
||||
mouse 32 "$((X + 8))" "$Y" M
|
||||
mouse 0 "$((X + 8))" "$Y" m
|
||||
|
||||
NEW_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
[ "$NEW_WIDTH" -ne "$OLD_WIDTH" ] || fail "floating pane was not resized"
|
||||
wait_outer_has_status "WIDTH=$NEW_WIDTH"
|
||||
|
||||
exit 0
|
||||
@@ -71,7 +71,10 @@ cursor_very_visible
|
||||
cursor_x
|
||||
cursor_y
|
||||
history_all_bytes
|
||||
history_added
|
||||
history_bytes
|
||||
history_collected
|
||||
history_generation
|
||||
history_limit
|
||||
history_size
|
||||
host
|
||||
@@ -103,6 +106,11 @@ pane_at_right
|
||||
pane_at_top
|
||||
pane_bg
|
||||
pane_bottom
|
||||
pane_command_duration
|
||||
pane_command_end_time
|
||||
pane_command_running
|
||||
pane_command_start_time
|
||||
pane_command_status
|
||||
pane_current_command
|
||||
pane_current_path
|
||||
pane_dead
|
||||
@@ -120,16 +128,21 @@ pane_index
|
||||
pane_input_off
|
||||
pane_key_mode
|
||||
pane_last
|
||||
pane_last_output_time
|
||||
pane_last_prompt_time
|
||||
pane_left
|
||||
pane_marked
|
||||
pane_marked_set
|
||||
pane_modal_flag
|
||||
pane_mode
|
||||
pane_output_generation
|
||||
pane_path
|
||||
pane_pb_progress
|
||||
pane_pb_state
|
||||
pane_pid
|
||||
pane_pipe
|
||||
pane_pipe_pid
|
||||
pane_private_modes
|
||||
pane_right
|
||||
pane_search_string
|
||||
pane_start_command
|
||||
@@ -141,6 +154,8 @@ pane_title
|
||||
pane_top
|
||||
pane_tty
|
||||
pane_unseen_changes
|
||||
pane_unzoomed_height
|
||||
pane_unzoomed_width
|
||||
pane_width
|
||||
pane_x
|
||||
pane_y
|
||||
@@ -206,7 +221,10 @@ window_layout
|
||||
window_linked
|
||||
window_linked_sessions
|
||||
window_linked_sessions_list
|
||||
window_manual_height
|
||||
window_manual_width
|
||||
window_marked_flag
|
||||
window_modal_pane
|
||||
window_name
|
||||
window_offset_x
|
||||
window_offset_y
|
||||
|
||||
@@ -218,11 +218,13 @@ $TMUX set-hook -gu pane-mode-changed || fail "unset pane-mode-changed failed"
|
||||
|
||||
# pane-exited when a pane's command exits with remain-on-exit off.
|
||||
$TMUX set -g @x 0 || fail "set @x failed"
|
||||
$TMUX set-hook -g pane-exited 'set -gF @x "#{hook}:#{hook_pane}"' ||
|
||||
$TMUX set-hook -g pane-exited \
|
||||
'set -gF @x "#{hook}:#{hook_pane}:#{hook_exit_status}:#{hook_exit_success}"' ||
|
||||
fail "set-hook pane-exited failed"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'true') ||
|
||||
fail "split-window true failed"
|
||||
wait_for @x "pane-exited:$pane"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \
|
||||
'trap "" HUP; exec </dev/null >/dev/null 2>&1; sleep 1; exit 42') ||
|
||||
fail "split-window exit 42 failed"
|
||||
wait_for @x "pane-exited:$pane:42:0"
|
||||
$TMUX set-hook -gu pane-exited || fail "unset pane-exited failed"
|
||||
|
||||
# pane-died when a pane's command exits with remain-on-exit on.
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a border-status caching bug: window_make_pane_status()
|
||||
# (window-border.c) gates the physical redraw of a pane's border-status
|
||||
# title on a logical content diff (grid_compare against a cached copy), not
|
||||
# on whether the physical screen cells were disturbed by something else in
|
||||
# the meantime - such as a floating pane's own border, drawn on top of a
|
||||
# tiled pane's border-status row, sliding across it and then away again.
|
||||
# See tmux-image-redraw-known-bugs.md ("border-status text cache ignores
|
||||
# physical damage") for the full write-up.
|
||||
#
|
||||
# Reproduction: a tiled pane with pane-border-status on has a floating pane
|
||||
# dragged, by mouse, from directly over its border-status row to somewhere
|
||||
# else. The tiled pane's title should reappear once the floating pane has
|
||||
# moved off it; without the fix it stays blank.
|
||||
#
|
||||
# The drag starts and ends away from row 0 rather than grabbing the
|
||||
# floating pane's border while it is already sitting on row 0: when a
|
||||
# floating pane's own border-status row exactly coincides with the tiled
|
||||
# pane's row 0, mouse hit-testing on that row attributes clicks to the
|
||||
# tiled pane, not the floating one on top of it (an unrelated tmux quirk,
|
||||
# not what this test is about). That only matters for the initial press,
|
||||
# though - once a drag is under way, further motion events go straight to
|
||||
# the already-bound per-pane callback without re-resolving which pane owns
|
||||
# the coordinates, so starting the grab on an unambiguous row and dragging
|
||||
# through row 0 works fine.
|
||||
|
||||
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 -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag COL ROW [ROW ...]
|
||||
#
|
||||
# Write one continuous mouse-1 gesture at column COL: a press at the first
|
||||
# ROW, a drag update at each subsequent ROW in turn, and a release at the
|
||||
# last ROW - all 1-based positions sent to the outer pane holding the
|
||||
# inner client. This matches the default MouseDown1Border/MouseDrag1Border
|
||||
# bindings used to move or resize a floating pane by its border.
|
||||
#
|
||||
# This must be one continuous press/drag/.../release gesture, not several
|
||||
# separate drag() calls chained together: releasing clears the per-pane
|
||||
# drag callback binding, so a later press has to be re-hit-tested from
|
||||
# scratch, which only works reliably away from row 0 (see the header
|
||||
# comment). A drag already under way is not re-hit-tested per motion
|
||||
# event, so it can safely pass through row 0 as an intermediate waypoint.
|
||||
drag()
|
||||
{
|
||||
col="$1"
|
||||
shift
|
||||
|
||||
row="$1"
|
||||
shift
|
||||
seq=$(printf '\033[<0;%s;%sM' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
|
||||
while [ $# -gt 1 ]; do
|
||||
row="$1"
|
||||
shift
|
||||
seq=$(printf '\033[<32;%s;%sM' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.15
|
||||
done
|
||||
|
||||
row="$1"
|
||||
seq=$(printf '\033[<0;%s;%sm' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g mouse on
|
||||
$TMUX set -g pane-border-status top
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"'
|
||||
|
||||
BASE=$($TMUX list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$BASE" ] || fail "No base pane."
|
||||
$TMUX select-pane -t "$BASE" -T TILEDTITLE || fail "set base title failed"
|
||||
|
||||
# Float a pane well clear of row 0 to start - its own top border must not
|
||||
# coincide with the base pane's border-status row for the initial mouse
|
||||
# press to unambiguously hit it (see the header comment). -x/-y are the
|
||||
# pane's outer size including its 2-cell border frame, so request 2 more
|
||||
# than the desired 14x3 content area (see layout_floating_args_parse()).
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5 -T FLOATTITLE) ||
|
||||
fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
|
||||
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
# Sanity check: the base pane's title is visible now (the float starts
|
||||
# well clear of row 0).
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
head -1 $TMP | grep -q TILEDTITLE ||
|
||||
fail "base title not visible before the drag - test setup is wrong"
|
||||
|
||||
# Drag the float up by its top border (an unambiguous row - not row 0) so
|
||||
# it ends up covering row 0, hiding the base pane's title behind its own
|
||||
# border, then continue the same drag back down again so it ends clear of
|
||||
# row 0 once more - one continuous gesture the whole way (see drag()'s
|
||||
# comment for why).
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
waypoints="$FTOP"
|
||||
row=$FTOP
|
||||
while [ "$row" != 1 ]; do
|
||||
row=$((row - 1))
|
||||
waypoints="$waypoints $row"
|
||||
done
|
||||
while [ "$row" != "$FTOP" ]; do
|
||||
row=$((row + 1))
|
||||
waypoints="$waypoints $row"
|
||||
done
|
||||
drag $((GRABCOL + 1)) $waypoints
|
||||
drag $((GRABCOL + 1)) 1 $((GRABCOL + 1)) "$FTOP"
|
||||
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
|
||||
# The base pane's title should be visible again now the floating pane has
|
||||
# moved off its border-status row. This is expected to fail before the fix
|
||||
# - see the header comment.
|
||||
head -1 $TMP | grep -q TILEDTITLE ||
|
||||
fail "base pane title still blank after floating pane moved away"
|
||||
|
||||
exit 0
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a fixed bug: on a client detected as SIXEL-capable, a
|
||||
# pane-content redraw (PANE_REDRAW) erased the image backend's rectangle
|
||||
# using the redrawing pane's raw nominal geometry (wp->xoff/yoff/sx/sy)
|
||||
# instead of the cells it actually owns in the current scene. When a
|
||||
# floating pane occluded part of that rectangle, the erase blanked the
|
||||
# floating pane's on-screen area, and since the subsequent text redraw
|
||||
# correctly only refills cells the redrawing pane owns, nothing repainted
|
||||
# it back in - the floating pane's content stayed blank.
|
||||
#
|
||||
# Fixed in screen-redraw.c:redraw_draw_pane_lines() by erasing only the
|
||||
# cell ranges the pane actually owns per line (its REDRAW_SPAN_PANE spans),
|
||||
# not its raw rectangle. See tmux-sixel-erase-ignores-occlusion.md and
|
||||
# IMAGE-REDRAW-DISCUSSION.md for the full write-up.
|
||||
#
|
||||
# Confirmed by direct A/B test against this exact sequence: reliably wipes
|
||||
# the floating pane's content on the unfixed code, reliably leaves it
|
||||
# intact on the fixed code.
|
||||
|
||||
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 -Lifcw$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Lifcw-inner$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX2 new-session -d -x 60 -y 20 "sh -c 'i=0; while [ \$i -lt 30 ]; do printf \"TILE%02d\\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" \
|
||||
|| exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y3 -T FLOATTITLE "sh -c 'printf FLOATCONTENT; exec sleep 100'" \
|
||||
|| exit 1
|
||||
[ "$($TMUX2 display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
|
||||
# The attaching client must be detected as SIXEL-capable - this bug does
|
||||
# not reproduce on a fallback or Kitty client.
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
$TMUX new-session -d -x 60 -y 20 || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
# Sanity check: the floating pane's content is visible before anything
|
||||
# disturbs it.
|
||||
$TMUX capturep -p >$TMP || exit 1
|
||||
grep -q 'FLOATCONTENT' $TMP || exit 1
|
||||
|
||||
# Click the tiled pane (making it active), enter copy-mode scrolled up
|
||||
# (matching a PageUp binding), move up one line (matching an Up binding),
|
||||
# then exit copy-mode - the exit is what triggers window_pane_reset_mode(),
|
||||
# which is what actually caused the erase.
|
||||
$TMUX2 select-pane -t%0 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 copy-mode -u -t%0 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t%0 -X cursor-up || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t%0 -X cancel || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX capturep -p >$TMP || exit 1
|
||||
grep -q 'FLOATCONTENT' $TMP || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -1,110 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: an image wider than the pane at the time it was
|
||||
# displayed must show more of itself once the pane grows wide enough,
|
||||
# instead of staying clipped to its original width forever.
|
||||
#
|
||||
# image_write() (image.c) clips an image's width to whatever fit in the
|
||||
# pane when it was first displayed, and never revisits that decision -
|
||||
# unlike height, which recovers naturally through ordinary scrollback,
|
||||
# there is no "scroll right", so the clipped columns were permanently
|
||||
# discarded. window_pane_resize() (window.c) now calls
|
||||
# image_grid_resize_width() after a pane grows wider, which extends each
|
||||
# existing placement's spans - using the image's own retained, immutable
|
||||
# pixel data - up to whichever is smaller: the image's full width or the
|
||||
# new pane width. See tmux-image-redraw-known-bugs.md for the full
|
||||
# write-up.
|
||||
#
|
||||
# This is checked via the SIXEL raster widths reported in the client's raw
|
||||
# output before and after widening the window. The redraw triggered by the
|
||||
# resize is damage-based (only the newly-uncovered columns are dirtied), so
|
||||
# it does not redraw the whole row as one wider raster - it sends the
|
||||
# already-correct clipped portion's width again untouched, plus a *second*,
|
||||
# separate raster covering just the newly-added columns. So rather than
|
||||
# looking for a single wider raster, this checks that the widths seen
|
||||
# across both redraws, added together, account for the fixture's full
|
||||
# pixel width - i.e. the previously-clipped remainder actually appeared.
|
||||
|
||||
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 -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
FULL=$(grep -oa '"1;1;[0-9]*;[0-9]*' $FIXTURE | head -1 | cut -d';' -f3)
|
||||
[ -n "$FULL" ] || fail "could not read fixture's own raster width"
|
||||
|
||||
# The fixture is 360px wide - narrow enough that a 12-column pane clips it,
|
||||
# comfortably below the 360px it would take to show in full.
|
||||
$TMUX new-session -d -s inner -x 12 -y 40 "cat '$FIXTURE'; exec sh" || exit 1
|
||||
sleep 0.5
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which sends the image) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 12 -y 40 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
# The outer client is just another tmux client receiving raw PTY output
|
||||
# that happens to contain sixel DCS sequences - left alone, it would
|
||||
# independently decode and place its own second, differently-sized image
|
||||
# on top of the inner session's, confounding the raster-width measurement
|
||||
# below. Disable sixel on the outer client so only the inner session's
|
||||
# placement exists.
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || fail "disable outer sixel failed"
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
before=$(grep -oa '"1;1;[0-9]*;' $TMP | grep -o '[0-9]*' | sort -n | tail -1)
|
||||
[ -n "$before" ] || fail "sanity: image never reached the client"
|
||||
[ "$before" -lt 360 ] || fail "sanity: image was not clipped by the narrow pane ($before)"
|
||||
: >$TMP
|
||||
|
||||
# Widen the window well past the image's full width - resize the outer
|
||||
# client, since the inner session's displayed size follows whatever
|
||||
# terminal size its attached client actually has.
|
||||
$TMUX2 resize-window -t "$OUTER" -x 30 -y 40 || fail "resize-window failed"
|
||||
sleep 1
|
||||
$TMUX refresh-client || fail "refresh-client failed"
|
||||
sleep 1
|
||||
|
||||
# The widths seen after the resize, added together (deduplicated - the
|
||||
# same chunk may legitimately be retransmitted), should account for the
|
||||
# fixture's full pixel width: this is expected to fail before the fix,
|
||||
# where the newly-uncovered columns are never redrawn at all and only the
|
||||
# original clipped width ever appears - see the header comment.
|
||||
after_total=$(grep -oa '"1;1;[0-9]*;' $TMP | grep -o '[0-9]*' | sort -nu |
|
||||
awk '{s+=$1} END{print s+0}')
|
||||
[ "$after_total" -gt 0 ] ||
|
||||
fail "no image raster was sent at all after widening the window"
|
||||
[ "$after_total" -ge $((FULL - 20)) ] ||
|
||||
fail "image stayed clipped at ${before}px after widening the window (widths summed to only ${after_total}px, expected close to ${FULL}px)"
|
||||
|
||||
exit 0
|
||||
@@ -1,107 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: an image taller than the pane, at the moment it is first
|
||||
# displayed, must remain visible when scrolling back up through history -
|
||||
# not show blank space for the part that was scrolled off screen before the
|
||||
# user ever got to see it.
|
||||
#
|
||||
# image_write() (image.c) handles this case by scrolling the screen up
|
||||
# (via screen_write_scrollup()) to make room, then only ever wrote spans
|
||||
# for the rows that ended up on screen afterwards - the rows that were
|
||||
# immediately scrolled off (origin_y of them) were pushed into history as
|
||||
# blank, spanless rows and their image data was discarded. Unlike width,
|
||||
# which has no "scroll right" to recover a permanent clip, height already
|
||||
# has ordinary scrollback, so this was pure waste. image_write() now also
|
||||
# calls image_extend_row() for those origin_y history rows (capped to
|
||||
# gd->hsize, since screen_write_scrollup() may not have pushed a real
|
||||
# history row for every one of them - see tmux-uint-subtraction-underflow-
|
||||
# care.md), so scrolling back up recovers the image instead of showing
|
||||
# empty space. See tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by comparing how many separate SIXEL rasters the client
|
||||
# receives right after entering copy mode (a baseline - copy mode itself
|
||||
# triggers a redraw of the still-visible rows) against how many it
|
||||
# receives after scrolling to the very top of history: with the fix, more
|
||||
# rasters appear (covering the newly-revealed, previously-scrolled-off
|
||||
# rows); without it, scrolling reveals nothing new.
|
||||
|
||||
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 -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
|
||||
# A short (5-row) pane and a wide (30-column) one - wide enough that the
|
||||
# fixture's width is not clipped, so only the height/scrolloff behaviour is
|
||||
# exercised, and short enough that most of the image is scrolled off
|
||||
# screen while it is still being displayed.
|
||||
$TMUX new-session -d -s inner -x 30 -y 5 "cat '$FIXTURE'; exec sh" || exit 1
|
||||
sleep 0.5
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which sends the image) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 30 -y 5 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
# The outer client is just another tmux client receiving raw PTY output
|
||||
# that happens to contain sixel DCS sequences - left alone, it would
|
||||
# independently decode and place its own second image, confounding the
|
||||
# raster counts below. Disable sixel on the outer client so only the
|
||||
# inner session's placement exists.
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || fail "disable outer sixel failed"
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] ||
|
||||
fail "sanity: image did not push anything into history - pane wasn't short enough"
|
||||
|
||||
# Enter copy mode and measure the baseline raster count this alone causes
|
||||
# (redrawing whatever's still on screen), without having scrolled yet.
|
||||
: >$TMP
|
||||
$TMUX copy-mode -t inner || fail "copy-mode failed"
|
||||
sleep 1
|
||||
baseline=$(grep -oac '"1;1;[0-9]*;[0-9]*' $TMP)
|
||||
|
||||
# Scroll all the way to the top of history - this is expected to fail
|
||||
# before the fix (no more rasters than the baseline ever appear, since the
|
||||
# scrolled-off rows were left blank) - see the header comment.
|
||||
: >$TMP
|
||||
$TMUX send-keys -t inner -X history-top || fail "history-top failed"
|
||||
sleep 1
|
||||
after=$(grep -oac '"1;1;[0-9]*;[0-9]*' $TMP)
|
||||
|
||||
[ "$after" -gt 0 ] ||
|
||||
fail "no image raster was sent at all after scrolling to history-top"
|
||||
[ "$after" -ge "$baseline" ] ||
|
||||
fail "scrolling to history-top redrew fewer rasters ($after) than just entering copy mode did ($baseline) - unexpected either way"
|
||||
|
||||
exit 0
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: extending a copy-mode selection by cursor movement, with
|
||||
# the view otherwise unmoved (no scrolling), must not retransmit an image
|
||||
# whose row the cursor passes through.
|
||||
#
|
||||
# window_copy_write_one() (window-copy.c) used to write text/highlight
|
||||
# styling directly over image-covered cells, which - since a character
|
||||
# write typically clears whatever pixel content a terminal was showing
|
||||
# there - erased the image with nothing to redraw it back in. Separately,
|
||||
# window_copy_write_line()'s call to image_redraw_area() used to fire
|
||||
# unconditionally on every redraw, so even after fixing the erasure, the
|
||||
# image would still be needlessly recomposited (and briefly flash) on
|
||||
# every single cursor step even though nothing about it had changed. See
|
||||
# tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by counting DCS (\033P) sequences in the client's raw
|
||||
# output during the cursor movement: with the fix, extending a selection
|
||||
# without scrolling never touches the image, so none should appear.
|
||||
|
||||
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 -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
# A small, distinctive SIXEL raster (26x26 pixels) at the top of the pane,
|
||||
# matching the fixture already used in image-support.sh, followed by
|
||||
# enough plain lines that the cursor can move down through the image and
|
||||
# past it without the view needing to scroll.
|
||||
SIXEL='\033Pq"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
$TMUX new-session -d -s inner -x 40 -y 20 \
|
||||
"printf '$SIXEL'; for i in \$(seq 1 15); do echo line\$i; done; exec sh" ||
|
||||
exit 1
|
||||
sleep 0.5
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which sends the image) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 40 -y 20 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
# Sanity check: the image reached the client at all.
|
||||
grep -qa '"1;1;26;26' $TMP || fail "sanity: image never reached the client"
|
||||
: >$TMP
|
||||
|
||||
# Enter copy-mode, scroll to the top (where the image is) and select down
|
||||
# through it one cursor step at a time - the view does not need to scroll
|
||||
# for any of this, since the image is already at the top of what is
|
||||
# visible.
|
||||
$TMUX copy-mode -t inner || fail "copy-mode failed"
|
||||
$TMUX send-keys -X history-top || fail "history-top failed"
|
||||
sleep 0.2
|
||||
: >$TMP
|
||||
$TMUX send-keys -X begin-selection || fail "begin-selection failed"
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
$TMUX send-keys -X cursor-down || fail "cursor-down failed"
|
||||
sleep 0.15
|
||||
i=$((i + 1))
|
||||
done
|
||||
sleep 0.5
|
||||
|
||||
# No DCS sequence should have been sent - the image's row was never
|
||||
# disturbed by any of this. This is expected to fail before the fix - see
|
||||
# the header comment.
|
||||
dcs=$(grep -ac "$(printf '\033P')" $TMP)
|
||||
[ "$dcs" -eq 0 ] ||
|
||||
fail "image was retransmitted ($dcs times) while just moving the selection cursor"
|
||||
|
||||
exit 0
|
||||
@@ -1,362 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Grid-resident image layers: Kitty input, history, copy and overwrite.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Limage$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -x 20 -y 8 "
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,m=1;/wAA/wD/\033\\'
|
||||
printf '\033_Gm=0;AP8AAP///////w==\033\\'
|
||||
i=0
|
||||
while [ \$i -lt 10 ]; do printf '\nline-%d' \$i; i=\$((i + 1)); done
|
||||
sleep 10"
|
||||
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
|
||||
# Images scroll as grid cells, while capture output contains ordinary spaces.
|
||||
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
|
||||
$TMUX capture-pane -pS- >$TMP || exit 1
|
||||
grep '_G' $TMP >/dev/null && exit 1
|
||||
|
||||
# Copy mode duplicates the backing grid, including image layers.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Ordinary text updates the image underlay through the normal grid write path.
|
||||
# Deleting the image reveals the newly written text.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,i=8;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033[HXY'
|
||||
printf '\033_Ga=d,d=i,q=2,i=8\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# The protocol default is transmit-only; a later placement uses the retained
|
||||
# immutable image and advances by the same canonical one-cell footprint.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Gq=2,f=32,s=1,v=1,c=1,r=1,i=9;/wAA/w==\033\\'
|
||||
printf '\033_Ga=p,q=2,i=9\033\\'
|
||||
printf '\033_Ga=d,d=I,q=2,i=9\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:2 '#{cursor_y}')" = 1 ] || exit 1
|
||||
|
||||
# PNG Kitty input uses the shared image decoder and canonical cell sizing.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Ga=T,q=2,f=100;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURf8AAP///0EdNBEAAAABYktHRAH/Ai3eAAAAB3RJTUUH6ggCDAECH324BwAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 1 ] || exit 1
|
||||
|
||||
# SIXEL input reaches the same grid layer and copy-mode paths.
|
||||
$TMUX new-window -d "cat '$FIXTURE'; sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:4 '#{cursor_y}')" -gt 0 ] || exit 1
|
||||
$TMUX copy-mode -t:4 || exit 1
|
||||
$TMUX send-keys -t:4 -X history-top || exit 1
|
||||
$TMUX capture-pane -pt:4 >$TMP || exit 1
|
||||
|
||||
# A 26-pixel raster occupies two default 16-pixel cells, but the final cell
|
||||
# remains only partially filled instead of stretching the raster to 32 pixels.
|
||||
# A client without an image feature exposes this in the sampled text backend.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# Re-emitting the image preserves the 26-pixel raster rather than expanding
|
||||
# it to the two complete 16-pixel grid cells occupied by the image.
|
||||
# terminal-features is only detected when a client attaches, so switch to a
|
||||
# SIXEL-capable client by detaching and reattaching rather than toggling the
|
||||
# option on the client already attached above.
|
||||
$TMUX2 set -u terminal-features || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX2 detach-client || exit 1
|
||||
sleep 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
grep -a '"1;1;26;26' $TMP >/dev/null || exit 1
|
||||
|
||||
# A selection redraw uses the single-cell path. It must leave ASCII image
|
||||
# cells visible (graphical clients skip these cells to avoid erasing pixels).
|
||||
# Switch back to a fallback client the same way.
|
||||
$TMUX2 set -u terminal-features || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || exit 1
|
||||
$TMUX2 detach-client || exit 1
|
||||
sleep 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX2 copy-mode || exit 1
|
||||
$TMUX2 send-keys -X history-top || exit 1
|
||||
$TMUX2 send-keys -X start-of-line || exit 1
|
||||
$TMUX2 send-keys -X begin-selection || exit 1
|
||||
$TMUX2 send-keys -X cursor-right || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# A retained Kitty image may be placed repeatedly using source rectangles.
|
||||
# Crop the green and white right column from a red/green/blue/white image.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033_Ga=t,q=2,f=32,s=2,v=2,i=10;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033_Ga=p,q=2,i=10,x=1,y=0,w=1,h=2,c=1,r=2\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "*" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "@" ] || exit 1
|
||||
|
||||
# C=1 places the image without moving the cursor or scrolling. The four-row
|
||||
# image is clipped at the bottom of this four-row pane, leaving its white last
|
||||
# row off screen.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[2;3H'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:1 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{cursor_x},#{cursor_y}')" = "2,1" ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = " -" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " *" ] || exit 1
|
||||
|
||||
# With normal cursor movement, scrolling is calculated from the full image
|
||||
# height. Rows which scrolled above the pane are then cropped from the top, so
|
||||
# the bottom three source rows remain visible and the cursor is on the last row.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[3;1H'
|
||||
printf '\033_Ga=T,q=2,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:2 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{cursor_y}')" = 3 ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "-" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "*" ] || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = "@" ] || exit 1
|
||||
|
||||
# Image rows remain cell-aligned when a narrower terminal causes text
|
||||
# reflow. The ten-column rows are clipped to five columns, not split into four
|
||||
# wrapped rows.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=2,c=10,r=2;/wAA//////8=\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:3 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 5 -y 4 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{window_width}x#{window_height}')" = "5x4" ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "....." ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "@@@@@" ] || exit 1
|
||||
[ -z "$(sed -n 3p $TMP)" ] || exit 1
|
||||
|
||||
# Kitty virtual placements use U+10EEEE placeholder cells. Keep their rows at
|
||||
# fixed coordinates when narrowing the terminal, clipping instead of reflowing
|
||||
# the second half onto the following row.
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 new-window -d "
|
||||
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
|
||||
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:4 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 5 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -n "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP | wc -c)" = 61 ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
# A placement ID supplied with transmit-and-place is reused by a later place.
|
||||
# This is the sequence used by chawan: the newline moves the cursor down before
|
||||
# the second command, so the folder moves down one row without leaving a copy.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=T,f=100,m=0;iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAACmUlEQVR4XmOgF2CEWVRfX8/w//9/BkZGRgy7YeKNjY1kuwtsam1tLQMTE5MG0MBsoEWW6KYBxf8DxScD6UVNTU1kWcZYU1MD0qjLzMx8EmgYJy5TgJb8/ffvn+2vX7+Od3Z2kmwZC9AnIE0FIEuAhl0H4ulA9k9kk4Bi4UB1TkA8H2iRYXl5+Xd2dna8loGCG2Q2LLgZQXED1PEIaLjs379/g4Ds9VDLwQYBxRiAPpFmZWW9DFQjCDRgLxDfJeQloJpdQHPWA9X9A1kGs+gZ0BBJoKQtUOIIeqSDgheoKR6IF5ASZkAH3gKaCzLzFQsxGltaWhjq6uoWguIJ6hi82oCG8wIV+AEdpga0LBfIrmUh1oWg1AYM5iVA9UuI1JMGVDcTiCNIsghkOChIQ0NDwfasXr0ap33QeL8GUgD0HTeIZmIgAUAtYQZqYYZZSKx2oi2CWaKpqbkIhEm1jOg40tLSAjleEBgUUSAGkJ8PpN8Q6yOCFoF8AvQBTvOAqZHh+vXrDPjijGAcIcXJUqBPQOXda5iNQP5rkBiQv5SYYMTrIxUVFZC5IkDCCk8QWQHVgdS8xBeMeBNDe3s7A7CwfQl0uSmwKDIBYhdQGQbCIDZIDCQHUgNSS7ZFII2gUgEU6UADzwJz+kUgDa6zoOyzIDmoGgaKLAJpBheKkArxC7BIeQDCQPEvIEuJrQyJzkdQA38ADbcHYaBFP0ipcVGqCWDYBwPxOkYs1TkDCeA/pEkQBDRnLZD9GKhVjgUkCGTsBgomAekWIJYG4p8MlAFQrZgJMgJo/m4QzQJ1fT9QIBLI1gTiSRRaAm/gAM38DjSvH2QeuHECLW1xNk7IsRhoyXGgJVOBem+AExPMEHzNLTItYiAlVTJQCwAAJxkMkgbHo2UAAAAASUVORK5CYII=\033\\'
|
||||
printf '\n'
|
||||
sleep 1
|
||||
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=p;\033\\'
|
||||
printf '\n'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -n "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
# A nonzero Kitty placement ID identifies one placement of an image. Reusing
|
||||
# the same image and placement IDs moves it rather than leaving the old cells.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=11;/////w==\033\\'
|
||||
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
|
||||
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
|
||||
|
||||
# Deleting one placement ID leaves other placements of the image intact.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=12;/////w==\033\\'
|
||||
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=12,p=7,c=2,r=1\033\\'
|
||||
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=12,p=8,c=2,r=1\033\\'
|
||||
printf '\033_Ga=d,d=i,q=2,i=12,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
|
||||
|
||||
# Image layers retain the cells beneath them. Deleting a transparent image
|
||||
# must reveal the original text rather than replacing it with spaces.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf 'XY'
|
||||
printf '\033[H'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=1,c=2,r=1,i=13,p=7;////AA==\033\\'
|
||||
printf '\033_Ga=d,d=i,q=2,i=13,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 capture-pane -pS0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# Text written over a Kitty placement updates its underlay without deleting
|
||||
# the image. Deleting the placement afterwards reveals the updated text.
|
||||
TEXT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf 'test\r'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=2,v=2,c=2,r=2,i=14,p=7;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\r'
|
||||
printf 'test\n'
|
||||
printf 'test\n'
|
||||
$TEST_TMUX wait-for image-text-delete-$$
|
||||
printf '\033_Ga=d,d=i,q=2,i=14,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$TEXT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = ".*st" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = " @st" ] || exit 1
|
||||
$TMUX2 wait-for -S image-text-delete-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "test" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "test" ] || exit 1
|
||||
|
||||
# Kitty z-indexes and SIXEL's temporal plane are properties of the input, not
|
||||
# of the outer terminal. Exercise the logical scene through the fallback
|
||||
# backend: higher nonnegative z-indexes cover lower ones and text, ordinary
|
||||
# negative z-indexes cover backgrounds but not text, and very negative ones
|
||||
# remain below the background.
|
||||
Z_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=21;/wAA/w==\033\\'
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=22;/////w==\033\\'
|
||||
printf 'X\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=1,z=2,c=1,r=1\033\\'
|
||||
printf '\033_Ga=p,q=2,C=1,i=22,p=2,z=1,c=1,r=1\033\\'
|
||||
printf '\033[2;1H\033[41m \033[0m\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=3,z=-1,c=1,r=1\033\\'
|
||||
printf '\033[3;1HY\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=4,z=-1,c=1,r=1\033\\'
|
||||
printf '\033[4;1H\033[41m \033[0m\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=5,z=-1073741825,c=1,r=1\033\\'
|
||||
$TEST_TMUX wait-for image-z-top-$$
|
||||
printf '\033_Ga=d,d=z,q=2,z=2\033\\'
|
||||
$TEST_TMUX wait-for image-z-lower-$$
|
||||
printf '\033_Ga=d,d=z,q=2,z=1\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$Z_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "." ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "." ] || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = "Y" ] || exit 1
|
||||
[ -z "$(sed -n 4p $TMP)" ] || exit 1
|
||||
$TMUX2 wait-for -S image-z-top-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "@" ] || exit 1
|
||||
$TMUX2 wait-for -S image-z-lower-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "X" ] || exit 1
|
||||
|
||||
# A weighted median at the maximum channel level must still leave colours on
|
||||
# both sides of the split. This skewed black, grey and white image used to stop
|
||||
# palette generation after one colour instead of producing three.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 20 -y 4 "
|
||||
printf '\033_Ga=T,q=2,C=1,f=24,s=100,v=1,c=10,r=1;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 20 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
grep -a '#2;2;' $TMP >/dev/null || exit 1
|
||||
|
||||
# A pane redraw on a SIXEL terminal must not clear the status line, which is
|
||||
# outside the window scene and may not be redrawn after the pane scrolls.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 20 -y 8 || exit 1
|
||||
$TMUX2 set -g 'status-format[0]' 'STATUS' || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 20 -y 8 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -l 'seq 30' || exit 1
|
||||
$TMUX2 send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
[ "$(tail -n 1 $TMP)" = "STATUS" ] || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -5,9 +5,12 @@
|
||||
start_pane alternate 10 3 'MAIN\033[?1049hALT\033[?1049lZ\n'
|
||||
check_capture alternate 'MAINZ'
|
||||
|
||||
start_pane osc133 20 8 'xx\033]133;A\007p>\033]133;B\007cmd\nxy\033]133;P;k=s\007more\nzz\033]133;C\007out\033]133;D;7\007\nq\033]133;C\007bad\033]133;D;-1\007\nqq\033]133;C\007big\033]133;D;300\007\nzzz\033]133;C\007ok\033]133;D\007\n'
|
||||
start_pane osc133 20 12 'xx\033]133;A\007p>\033]133;B\007cmd\nxy\033]133;P;k=s\007more\nxz\033]133;A;k=s\007more\nxw\033]133;P;k=c\007more\nxv\033]133;A;k=c\007more\nzz\033]133;C\007out\033]133;D;7\007\nq\033]133;C\007bad\033]133;D;-1\007\nqq\033]133;C\007big\033]133;D;300\007\nzzz\033]133;C\007ok\033]133;D\007\n'
|
||||
check_capture osc133 'xxp>cmd
|
||||
xymore
|
||||
xzmore
|
||||
xwmore
|
||||
xvmore
|
||||
zzout
|
||||
qbad
|
||||
qqbig
|
||||
@@ -15,10 +18,13 @@ zzzok'
|
||||
check_raw_matches osc133 \
|
||||
'L 0 \(0\) flags=START_PROMPT,START_COMMAND\[[0-9a-f]+\].* osc133=2,4,0,0,0' \
|
||||
'L 1 \(1\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
|
||||
'L 2 \(2\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,2,5,7' \
|
||||
'L 3 \(3\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,1,4,255' \
|
||||
'L 4 \(4\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,2,5,255' \
|
||||
'L 5 \(5\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,3,5,0'
|
||||
'L 2 \(2\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
|
||||
'L 3 \(3\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
|
||||
'L 4 \(4\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
|
||||
'L 5 \(5\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,2,5,7' \
|
||||
'L 6 \(6\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,1,4,255' \
|
||||
'L 7 \(7\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,2,5,255' \
|
||||
'L 8 \(8\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,3,5,0'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
|
||||
126
regress/layout-set.sh
Normal file
126
regress/layout-set.sh
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise every predefined layout, next/previous cycling, lookup and restore
|
||||
# paths, one-pane early returns, minimum-size handling and floating panes.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
||||
SEEN="$DIR/layouts"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
layout()
|
||||
{
|
||||
$TMUX display-message -p -t layouts:many '#{window_layout}'
|
||||
}
|
||||
|
||||
check_layout()
|
||||
{
|
||||
name=$1
|
||||
$TMUX select-layout -t layouts:many "$name" ||
|
||||
fail "select-layout $name failed"
|
||||
value=$(layout)
|
||||
[ -n "$value" ] || fail "$name produced an empty layout"
|
||||
if grep -Fxq "$value" "$SEEN"; then
|
||||
fail "$name produced the same layout as an earlier preset"
|
||||
fi
|
||||
printf '%s\n' "$value" >>"$SEEN"
|
||||
[ "$($TMUX list-panes -t layouts:many -F x | wc -l)" -eq 5 ] ||
|
||||
fail "$name lost a pane"
|
||||
[ "$($TMUX list-panes -t layouts:many -F '#{pane_floating_flag}' | \
|
||||
grep -c '^1$')" -eq 1 ] || fail "$name did not preserve the floating pane"
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s layouts -n many -x100 -y40 'exec sleep 100' || exit 1
|
||||
$TMUX set-option -g window-size manual || exit 1
|
||||
$TMUX split-window -d -h -t layouts:many 'exec sleep 100' || exit 1
|
||||
$TMUX split-window -d -v -t layouts:many 'exec sleep 100' || exit 1
|
||||
$TMUX split-window -d -h -t layouts:many 'exec sleep 100' || exit 1
|
||||
floating=$($TMUX new-pane -dP -F '#{pane_id}' -t layouts:many \
|
||||
-x20 -y8 -X10 -Y5 'exec sleep 100') || exit 1
|
||||
: >"$SEEN"
|
||||
|
||||
# Options exercise percentage and explicit secondary-size calculations.
|
||||
$TMUX set-option -w -t layouts:many main-pane-height 30% || exit 1
|
||||
$TMUX set-option -w -t layouts:many other-pane-height 12 || exit 1
|
||||
$TMUX set-option -w -t layouts:many main-pane-width 35% || exit 1
|
||||
$TMUX set-option -w -t layouts:many other-pane-width 20 || exit 1
|
||||
|
||||
for name in even-horizontal even-vertical main-horizontal \
|
||||
main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do
|
||||
check_layout "$name"
|
||||
done
|
||||
|
||||
# Exact and unique-prefix lookup work; ambiguous and invalid names fail.
|
||||
$TMUX select-layout -t layouts:many even-h || fail "unique layout prefix failed"
|
||||
$TMUX select-layout -t layouts:many main >/dev/null 2>&1 &&
|
||||
fail "ambiguous layout prefix succeeded"
|
||||
$TMUX select-layout -t layouts:many no-such-layout >/dev/null 2>&1 &&
|
||||
fail "invalid layout name succeeded"
|
||||
|
||||
# Cycle through all layouts in both directions, including wraparound. The
|
||||
# aliases and select-layout flags share layout_set_next/previous.
|
||||
first=$(layout)
|
||||
i=0
|
||||
while [ "$i" -lt 7 ]; do
|
||||
$TMUX next-layout -t layouts:many || exit 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$(layout)" = "$first" ] || fail "next-layout did not wrap"
|
||||
i=0
|
||||
while [ "$i" -lt 7 ]; do
|
||||
$TMUX previous-layout -t layouts:many || exit 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$(layout)" = "$first" ] || fail "previous-layout did not wrap"
|
||||
$TMUX select-layout -t layouts:many -n || exit 1
|
||||
$TMUX select-layout -t layouts:many -p || exit 1
|
||||
|
||||
# No argument reapplies the last preset; -o restores the previous layout; -E
|
||||
# spreads the current cell without changing the pane set.
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX select-layout -t layouts:many tiled || exit 1
|
||||
tiled=$(layout)
|
||||
$TMUX select-layout -t layouts:many even-horizontal || exit 1
|
||||
$TMUX select-layout -t layouts:many -o || exit 1
|
||||
[ "$(layout)" = "$tiled" ] || fail "select-layout -o did not restore layout"
|
||||
$TMUX select-layout -t layouts:many || exit 1
|
||||
$TMUX select-layout -t layouts:many -E || exit 1
|
||||
|
||||
# Each arranger has a deliberate one-pane early return.
|
||||
$TMUX new-window -d -t layouts: -n one 'exec sleep 100' || exit 1
|
||||
one=$($TMUX display-message -p -t layouts:one '#{window_layout}')
|
||||
for name in even-horizontal even-vertical main-horizontal \
|
||||
main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do
|
||||
$TMUX select-layout -t layouts:one "$name" || exit 1
|
||||
[ "$($TMUX display-message -p -t layouts:one '#{window_layout}')" = "$one" ] ||
|
||||
fail "$name changed a one-pane layout"
|
||||
done
|
||||
|
||||
# Force the minimum-size paths without requiring a terminal client.
|
||||
$TMUX resize-window -t layouts:many -x10 -y6 || exit 1
|
||||
for name in even-horizontal even-vertical main-horizontal \
|
||||
main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do
|
||||
$TMUX select-layout -t layouts:many "$name" || exit 1
|
||||
done
|
||||
|
||||
$TMUX has-session -t layouts || fail "server died during layout tests"
|
||||
exit 0
|
||||
46
regress/list-commands.sh
Normal file
46
regress/list-commands.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise list-commands for the full table, a single command, aliases, custom
|
||||
# and empty formats, and lookup errors.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
# Keep a server alive across commands. Otherwise a STARTSERVER-only command
|
||||
# may exit between invocations and leave a short socket-cleanup race.
|
||||
$TMUX new-session -d -s list-commands 'exec sleep 100' || exit 1
|
||||
|
||||
all=$($TMUX list-commands) || exit 1
|
||||
printf '%s\n' "$all" | grep -q '^attach-session (attach)' || exit 1
|
||||
printf '%s\n' "$all" | grep -q '^list-commands (lscm)' || exit 1
|
||||
|
||||
one=$($TMUX list-commands -F \
|
||||
'#{command_list_name}|#{command_list_alias}|#{command_list_usage}' \
|
||||
list-commands) || exit 1
|
||||
case "$one" in
|
||||
'list-commands|lscm|'*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
|
||||
# Aliases resolve to their command, while an empty expansion prints no line.
|
||||
[ "$($TMUX list-commands -F '#{command_list_name}' lscm)" = list-commands ] ||
|
||||
exit 1
|
||||
[ -z "$($TMUX list-commands -F '' list-commands)" ] || exit 1
|
||||
|
||||
$TMUX list-commands tmux-no-command-$$ >/dev/null 2>&1 && exit 1
|
||||
$TMUX list-commands list >/dev/null 2>&1 && exit 1
|
||||
exit 0
|
||||
0
regress/mode-kill.sh
Executable file → Normal file
0
regress/mode-kill.sh
Executable file → Normal file
File diff suppressed because one or more lines are too long
168
regress/output-commands.sh
Normal file
168
regress/output-commands.sh
Normal file
@@ -0,0 +1,168 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -L${TEST_SOCKET:-testO$$} -f/dev/null"
|
||||
OUT=/tmp/tmux-output-commands-$$
|
||||
EXPECTED=$(printf 'one\ntwo')
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -f "$OUT"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\ntwo\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007broken\\n\\033]133;C\\007unfinished\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
|
||||
$TMUX send-keys -X pipe-output "wc -c >$OUT" || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 10 ] || exit 1
|
||||
|
||||
$TMUX send-keys -X copy-pipe-output "wc -c >$OUT" output || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 10 ] || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
|
||||
$TMUX send-keys -X select-output || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
copied=$($TMUX show-buffer)
|
||||
[ "$copied" = "$EXPECTED" ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward one || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
copied=$($TMUX show-buffer)
|
||||
[ "$copied" = "$EXPECTED" ] || exit 1
|
||||
|
||||
$TMUX send-keys -X pipe-output "wc -l >$OUT" || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 2 ] || exit 1
|
||||
|
||||
$TMUX send-keys -X copy-pipe-output "wc -l >$OUT" output || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 2 ] || exit 1
|
||||
copied=$($TMUX show-buffer)
|
||||
[ "$copied" = "$EXPECTED" ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
$TMUX send-keys -X search-backward echo || exit 1
|
||||
$TMUX send-keys -X select-output || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
selected=$($TMUX show-buffer)
|
||||
[ "$selected" = "$EXPECTED" ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX set-buffer -b keep unchanged || exit 1
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward unfinished || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n plain "printf 'alpha\\nbeta\\n'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-output -a || exit 1
|
||||
all=$($TMUX show-buffer)
|
||||
case "$all" in
|
||||
*alpha*beta*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
$TMUX set-buffer -b keep unchanged || exit 1
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer -b keep)" = unchanged ] || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X select-output || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X select-output -a || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-selection || exit 1
|
||||
all=$($TMUX show-buffer)
|
||||
case "$all" in
|
||||
*alpha*beta*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n empty "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007true\\033]133;C\\033]133;D;0\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -t :empty || exit 1
|
||||
$TMUX send-keys -t :empty.0 -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :empty.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n prompt "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -U -t :prompt || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -c -t :prompt || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X expand-output || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X -N 10 cursor-down || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X select-output || exit 1
|
||||
[ "$($TMUX display-message -p -t :prompt.0 '#{selection_present}')" = 1 ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n below "printf '\\033]133;A\\007p\\$ \\033]133;B\\007old\\n\\033]133;C\\007old1\\nold2\\nold3\\nold4\\nold5\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007ps\\n\\033]133;C\\007PID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -c -t :below || exit 1
|
||||
$TMUX send-keys -t :below.0 -X search-backward ps || exit 1
|
||||
$TMUX send-keys -t :below.0 -X expand-output || exit 1
|
||||
$TMUX send-keys -t :below.0 -X -N 100 cursor-down || exit 1
|
||||
$TMUX send-keys -t :below.0 C-o || exit 1
|
||||
[ "$($TMUX display-message -p -t :below.0 '#{selection_present}')" = 1 ] || exit 1
|
||||
$TMUX send-keys -t :below.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\\n1 pts/0')" ] || exit 1
|
||||
$TMUX send-keys -t :below.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n clear "printf 'old1\\nold2\\nold3\\nold4\\nold5\\nold6\\n\\033]133;A\\007p\\$ \\033]133;B\\007echo 1; clear; ps\\n\\033]133;C\\0071\\n\\033[H\\033[2JPID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -t :clear || exit 1
|
||||
$TMUX send-keys -t :clear.0 C-o || exit 1
|
||||
$TMUX send-keys -t :clear.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\\n1 pts/0')" ] || exit 1
|
||||
$TMUX send-keys -t :clear.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n same "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -U -t :same || exit 1
|
||||
$TMUX send-keys -t :same.0 -X search-backward one || exit 1
|
||||
$TMUX send-keys -t :same.0 -X select-output || exit 1
|
||||
$TMUX send-keys -t :same.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :same.0 -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-output -a || exit 1
|
||||
all=$($TMUX show-buffer)
|
||||
case "$all" in
|
||||
*alpha*beta*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -1,97 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving a popup away from a pane prompt must recompose the prompt over the
|
||||
# pane contents restored by damage redraw.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-prompt-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
POPUP_PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
$INNER command-prompt -b -P -t "$CLIENT" -p 'PROMPT-MARK>' \
|
||||
'display-message -- %1' || exit 1
|
||||
wait_outer_has PROMPT-MARK
|
||||
|
||||
$INNER display-popup -t "$CLIENT" -x 0 -y 10 -w 16 -h 3 -E \
|
||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP-MARKER
|
||||
|
||||
mouse 0 10 8 M
|
||||
mouse 32 11 8 M
|
||||
mouse 32 25 4 M
|
||||
mouse 0 25 4 m
|
||||
wait_outer_has PROMPT-MARK
|
||||
|
||||
exit 0
|
||||
@@ -1,155 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving and resizing a popup must restore its old rectangle, including pane
|
||||
# border status, on the attached client's terminal.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-drag-outer-$$ -f/dev/null"
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
RAW=$DIR/raw
|
||||
POPUP_PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_rows_match()
|
||||
{
|
||||
first=$1
|
||||
last=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
||||
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer rows $first-$last were not restored"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
# A left-button border drag moves the popup. The first motion starts the drag;
|
||||
# the second changes its position.
|
||||
move_popup()
|
||||
{
|
||||
mouse 0 10 1 M
|
||||
mouse 32 11 1 M
|
||||
mouse 32 26 8 M
|
||||
mouse 0 26 8 m
|
||||
}
|
||||
|
||||
# A right-button bottom-right-border drag resizes the popup.
|
||||
resize_popup()
|
||||
{
|
||||
mouse 2 "$1" "$2" M
|
||||
mouse 34 "$3" "$4" M
|
||||
mouse 34 "$5" "$6" M
|
||||
mouse 2 "$5" "$6" m
|
||||
}
|
||||
|
||||
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; printf \"\\033[19;45H\\033[38;5;201mSCOPE\\033[0m\"; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -w pane-border-status top || exit 1
|
||||
$INNER set-option -w pane-border-format 'DAMAGE-STATUS-RESTORED' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has SCOPE
|
||||
grep -qa '38;5;201' "$RAW" || fail "scope colour was not drawn initially"
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
grep -q DAMAGE-STATUS-RESTORED "$BASE" ||
|
||||
fail "pane border status was not visible before popup"
|
||||
|
||||
$INNER display-popup -t "$CLIENT" -x 0 -y 0 -w 28 -h 6 -E \
|
||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP-MARKER
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
||||
|
||||
move_popup
|
||||
wait_rows_match 1 6
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
grep -qa '38;5;201' "$RAW" &&
|
||||
fail "popup move redrew content outside its old and new rectangles"
|
||||
grep -q DAMAGE-STATUS-RESTORED "$CAPTURE" ||
|
||||
fail "pane border status was not restored after popup move"
|
||||
|
||||
# The moved popup is 28x6 at zero-based 16,7. Shrink it to 21x3, then require
|
||||
# the three rows vacated at the bottom to match the original scene.
|
||||
resize_popup 44 13 43 13 38 11
|
||||
wait_rows_match 11 13
|
||||
|
||||
# Grow it back to 28x6 and require a single, intact popup frame.
|
||||
resize_popup 37 10 36 10 45 14
|
||||
wait_outer_has POPUP-MARKER
|
||||
corners=$(grep -o '┌' "$CAPTURE" | wc -l)
|
||||
[ "$corners" -eq 1 ] || fail "expected one popup frame, found $corners"
|
||||
|
||||
exit 0
|
||||
@@ -1,165 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving a popup away from the tmux status area must restore status and window
|
||||
# cells. Window damage alone cannot describe cells outside the window scene,
|
||||
# and top status lines must be removed before translating to window rows.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
INNER=
|
||||
OUTER=
|
||||
POPUP_PID=
|
||||
N=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup_scene()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
POPUP_PID=
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
cleanup_scene
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_rows_restored()
|
||||
{
|
||||
first=$1
|
||||
last=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
||||
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "rows $first-$last under the old popup were not restored"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
setup()
|
||||
{
|
||||
position=$1
|
||||
lines=$2
|
||||
|
||||
cleanup_scene
|
||||
N=$((N + 1))
|
||||
INNER="$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-status-outer-$$-$N -f/dev/null"
|
||||
|
||||
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
if [ "$lines" -eq 1 ]; then
|
||||
$INNER set-option -g status on || exit 1
|
||||
else
|
||||
$INNER set-option -g status "$lines" || exit 1
|
||||
fi
|
||||
$INNER set-option -g status-position "$position" || exit 1
|
||||
$INNER set-option -g status-format[0] \
|
||||
'STATUS-LINE-MARK-01234567890123456789012' || exit 1
|
||||
if [ "$lines" -gt 1 ]; then
|
||||
$INNER set-option -g status-format[1] \
|
||||
'SECOND-STATUS-MARK-012345678901234567890' || exit 1
|
||||
fi
|
||||
$INNER set-option -g status-interval 0 || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has STATUS-LINE-MARK
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
}
|
||||
|
||||
open_popup()
|
||||
{
|
||||
y=$1
|
||||
height=$2
|
||||
$INNER display-popup -t "$CLIENT" -x 0 -y "$y" -w 16 -h "$height" -E \
|
||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP-MARKER
|
||||
}
|
||||
|
||||
if [ "${STATUS_CASE:-all}" != top ]; then
|
||||
# A popup clipped against a one-line bottom status must restore that line.
|
||||
setup bottom 1
|
||||
open_popup 10 3
|
||||
mouse 0 10 8 M
|
||||
mouse 32 11 8 M
|
||||
mouse 32 25 4 M
|
||||
mouse 0 25 4 m
|
||||
wait_rows_restored 10 10
|
||||
fi
|
||||
|
||||
if [ "${STATUS_CASE:-all}" != bottom ]; then
|
||||
# A popup beginning in a two-line top status also covers the first window
|
||||
# rows. Restore both coordinate spaces after it moves.
|
||||
setup top 2
|
||||
open_popup 1 4
|
||||
mouse 0 10 1 M
|
||||
mouse 32 11 1 M
|
||||
mouse 32 25 7 M
|
||||
mouse 0 25 7 m
|
||||
wait_rows_restored 1 4
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,126 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Damage at a popup edge must redraw complete grid characters. Drawing only a
|
||||
# wide character's base or padding cell leaves a two-cell hole behind.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-wide-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
POPUP_PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_old_rows_restored()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
sed -n '3,5p' "$BASE" >"$DIR/want"
|
||||
sed -n '3,5p' "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "wide characters under the old popup edge were not restored"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
binmode STDOUT, ':encoding(UTF-8)';
|
||||
$| = 1;
|
||||
for my $row (1 .. 10) {
|
||||
print "\e[$row;1H", chr(0x754c) x 20;
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has '界界界'
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
|
||||
$INNER display-popup -t "$CLIENT" -x 5 -y 5 -w 10 -h 3 -E \
|
||||
"sh -c 'printf POPUP; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP
|
||||
|
||||
# The first motion starts the drag; the second moves the popup away from its
|
||||
# old rectangle. Its odd x coordinate bisects the underlying double-width
|
||||
# cells at both edges.
|
||||
mouse 0 10 3 M
|
||||
mouse 32 11 3 M
|
||||
mouse 32 28 7 M
|
||||
mouse 0 28 7 m
|
||||
wait_old_rows_restored
|
||||
|
||||
exit 0
|
||||
165
regress/prompt-words-history.sh
Normal file
165
regress/prompt-words-history.sh
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Cover prompt word movement in emacs and vi modes, ambiguous inline command
|
||||
# completion, and the show/clear-prompt-history command surface.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_result()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER show-option -gqv @result 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "prompt result is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
bind_prompt()
|
||||
{
|
||||
initial=$1
|
||||
$INNER bind-key -n M-r command-prompt -I "$initial" -p '(word)' \
|
||||
"set-option -g @result '%%'" || exit 1
|
||||
}
|
||||
|
||||
run_prompt()
|
||||
{
|
||||
want=$1
|
||||
shift
|
||||
$INNER set-option -g @result sentinel || exit 1
|
||||
$OUTER send-keys M-r || exit 1
|
||||
sleep 0.2
|
||||
$OUTER send-keys "$@" || exit 1
|
||||
$OUTER send-keys Enter || exit 1
|
||||
wait_result "$want"
|
||||
}
|
||||
|
||||
run_vi_prompt()
|
||||
{
|
||||
want=$1
|
||||
shift
|
||||
$INNER set-option -g @result sentinel || exit 1
|
||||
$OUTER send-keys M-r || exit 1
|
||||
sleep 0.2
|
||||
# Send Escape separately so the terminal's escape-time handling does not
|
||||
# combine it with the first vi command as a Meta key.
|
||||
$OUTER send-keys Escape || exit 1
|
||||
# The inner client must see Escape as a complete key, not the prefix of a
|
||||
# Meta sequence (the default escape-time is 500 milliseconds).
|
||||
sleep 0.7
|
||||
$OUTER send-keys "$@" || exit 1
|
||||
$OUTER send-keys Enter || exit 1
|
||||
wait_result "$want"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s prompt -x80 -y24 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status on || exit 1
|
||||
$INNER set-option -g status-position bottom || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t prompt" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
# Emacs Meta-f stops after the first word; Meta-b returns to the start of the
|
||||
# previous word. Inserting a marker makes the cursor position observable.
|
||||
$INNER set-option -g status-keys emacs || exit 1
|
||||
bind_prompt 'one two'
|
||||
run_prompt 'oneX two' Home M-f X
|
||||
bind_prompt 'one two'
|
||||
run_prompt 'one Xtwo' M-b X
|
||||
|
||||
# Vi translation and the distinct separator-aware and WORD motions.
|
||||
$INNER set-option -g status-keys vi || exit 1
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'one-two Xthree' b i X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'one-two Xthree' B i X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'oneX-two three' 0 w i X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'one-two Xthree' 0 W i X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'oneX-two three' 0 e a X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'one-twoX three' 0 E a X
|
||||
|
||||
# "show-" has several command matches and no longer common prefix. Tab keeps
|
||||
# the input and draws the sorted candidates inline.
|
||||
$INNER set-option -g status-keys emacs || exit 1
|
||||
bind_prompt ''
|
||||
$OUTER send-keys M-r || exit 1
|
||||
sleep 0.2
|
||||
$OUTER send-keys -l 'show-' || exit 1
|
||||
$OUTER send-keys Tab || exit 1
|
||||
sleep 0.2
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Fq 'show-buffer' ||
|
||||
fail "ambiguous completion list was not drawn"
|
||||
printf '%s\n' "$captured" | grep -Fq 'show-environment' ||
|
||||
fail "ambiguous completion list was incomplete"
|
||||
$OUTER send-keys Escape || exit 1
|
||||
|
||||
# Add entries to both history rings through real prompts.
|
||||
bind_prompt 'history-command'
|
||||
run_prompt 'history-command'
|
||||
$INNER bind-key -n M-s command-prompt -T search -I history-search \
|
||||
-p '(search-history)' "set-option -g @result '%%'" || exit 1
|
||||
$OUTER send-keys M-s || exit 1
|
||||
$OUTER send-keys Enter || exit 1
|
||||
wait_result history-search
|
||||
|
||||
command_history=$($INNER show-prompt-history -T command) || exit 1
|
||||
printf '%s\n' "$command_history" | grep -Fq 'history-command' ||
|
||||
fail "command history entry missing"
|
||||
search_history=$($INNER show-prompt-history -T search) || exit 1
|
||||
printf '%s\n' "$search_history" | grep -Fq 'history-search' ||
|
||||
fail "search history entry missing"
|
||||
all_history=$($INNER show-prompt-history) || exit 1
|
||||
printf '%s\n' "$all_history" | grep -Fq 'History for command:' || exit 1
|
||||
printf '%s\n' "$all_history" | grep -Fq 'History for search:' || exit 1
|
||||
|
||||
$INNER show-prompt-history -T invalid >/dev/null 2>&1 &&
|
||||
fail "invalid show history type succeeded"
|
||||
$INNER clear-prompt-history -T invalid >/dev/null 2>&1 &&
|
||||
fail "invalid clear history type succeeded"
|
||||
$INNER clear-prompt-history -T command || exit 1
|
||||
$INNER show-prompt-history -T command | grep -Fq history-command &&
|
||||
fail "type-specific history clear failed"
|
||||
$INNER clear-prompt-history || exit 1
|
||||
$INNER show-prompt-history | grep -Fq history-search &&
|
||||
fail "all-history clear failed"
|
||||
|
||||
exit 0
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check a redraw callback which has no accompanying client redraw flags. A
|
||||
# wrapped row crossing a panned viewport cannot use the direct tty path.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Ldamage-only-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
TRIGGER=$DIR/trigger
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane did not contain $marker"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
for my $row (1 .. 12) {
|
||||
print "\e[$row;1H", 'o' x 79;
|
||||
}
|
||||
print "\e[1;1H";
|
||||
while (!-e $ENV{TRIGGER}) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
my $second = ('B' x 24) . 'DAMAGE-ONLY' . ('B' x 45);
|
||||
print "\e[5;1H", ('A' x 80), $second;
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 80 -y 12 \
|
||||
"TRIGGER='$TRIGGER' perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
$INNER refresh-client -t "$CLIENT" -R 20 || exit 1
|
||||
wait_outer_has oooooooooo
|
||||
|
||||
: >"$TRIGGER"
|
||||
wait_inner_has DAMAGE-ONLY
|
||||
wait_outer_has DAMAGE-ONLY
|
||||
|
||||
exit 0
|
||||
@@ -1,186 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Redraw a moved floating pane on both attached clients viewing the same
|
||||
# window. Window redraw work must not be consumed by only one client.
|
||||
#
|
||||
# Uses ASCII pane borders (rather than the default UTF-8 box-drawing) because
|
||||
# this test nests a real tmux client inside another tmux's pane to get a
|
||||
# genuine terminal to capture from; that nested-tmux relay has been observed
|
||||
# to mis-render a cell that previously held a multi-byte UTF-8 border
|
||||
# character being overwritten later by plain content, on the outer instance's
|
||||
# own interpretation, independent of anything the inner tmux sends. That is a
|
||||
# nested-test-harness artifact, not a real tmux bug - confirmed by replaying
|
||||
# the exact same drag sequence against a real terminal (xterm), where it
|
||||
# never reproduces. ASCII borders avoid the artifact entirely.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lredraw-multi-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
count=$($INNER list-clients 2>/dev/null | wc -l)
|
||||
[ "$count" -eq 2 ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "two inner clients did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
target=$1
|
||||
marker=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer pane $target did not show $marker"
|
||||
}
|
||||
|
||||
wait_float_left()
|
||||
{
|
||||
comparison=$1
|
||||
limit=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
left=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
if [ "$comparison" = gt ] && [ "$left" -gt "$limit" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$comparison" = lt ] && [ "$left" -lt "$limit" ]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "floating pane did not move"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$2" "$3" "$4" "$5")
|
||||
$OUTER send-keys -t "$1" -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
drag_float()
|
||||
{
|
||||
target=$1
|
||||
startcol=$2
|
||||
startrow=$3
|
||||
endcol=$4
|
||||
mouse "$target" 0 "$startcol" "$startrow" M
|
||||
mouse "$target" 32 "$endcol" "$startrow" M
|
||||
mouse "$target" 0 "$endcol" "$startrow" m
|
||||
}
|
||||
|
||||
assert_scene()
|
||||
{
|
||||
target=$1
|
||||
base=$2
|
||||
firstcol=$3
|
||||
lastcol=$4
|
||||
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
||||
|
||||
# With ASCII (simple) borders every corner and junction is the same
|
||||
# '+', so one rectangular floating pane always draws exactly 4 of
|
||||
# them; more means a stale frame was left behind somewhere.
|
||||
corners=$(grep -o '+' "$CAPTURE" | wc -l)
|
||||
[ "$corners" -eq 4 ] ||
|
||||
fail "outer pane $target had $corners floating frames"
|
||||
|
||||
sed -n '6,11p' "$base" | cut -c"$firstcol-$lastcol" >"$DIR/want"
|
||||
sed -n '6,11p' "$CAPTURE" | cut -c"$firstcol-$lastcol" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" ||
|
||||
fail "outer pane $target did not restore the old floating area"
|
||||
}
|
||||
|
||||
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g default-command 'sleep 100' || exit 1
|
||||
$INNER set-option -g pane-border-lines simple || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER split-window -h -t outer:0.0 'sleep 100' || exit 1
|
||||
|
||||
PANES=$($OUTER list-panes -t outer:0 -F '#{pane_id} #{pane_left}')
|
||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||
RIGHT=$(echo "$PANES" | sort -k2 -n | tail -1 | cut -d' ' -f1)
|
||||
[ -n "$LEFT" ] && [ -n "$RIGHT" ] || fail "could not find outer panes"
|
||||
|
||||
$OUTER respawn-pane -k -t "$LEFT" \
|
||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
$OUTER respawn-pane -k -t "$RIGHT" \
|
||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_clients
|
||||
wait_outer_has "$LEFT" BG-ROW-19
|
||||
wait_outer_has "$RIGHT" BG-ROW-19
|
||||
$OUTER capture-pane -p -t "$LEFT" >"$DIR/base-left" || exit 1
|
||||
$OUTER capture-pane -p -t "$RIGHT" >"$DIR/base-right" || exit 1
|
||||
|
||||
FLOAT=$($INNER new-pane -dPF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||
fail "could not create floating pane"
|
||||
wait_outer_has "$LEFT" '+'
|
||||
wait_outer_has "$RIGHT" '+'
|
||||
|
||||
FTOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||
|
||||
# Move right through one client and require both clients to restore the old
|
||||
# left-hand footprint.
|
||||
drag_float "$LEFT" "$GRABCOL" "$FTOP" $((GRABCOL + 30))
|
||||
wait_float_left gt 30
|
||||
assert_scene "$LEFT" "$DIR/base-left" 1 20
|
||||
assert_scene "$RIGHT" "$DIR/base-right" 1 20
|
||||
|
||||
# Move back through the other client and check the old right-hand footprint.
|
||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||
drag_float "$RIGHT" "$GRABCOL" "$FTOP" $((GRABCOL - 30))
|
||||
wait_float_left lt 10
|
||||
assert_scene "$LEFT" "$DIR/base-left" 35 60
|
||||
assert_scene "$RIGHT" "$DIR/base-right" 35 60
|
||||
|
||||
exit 0
|
||||
@@ -1,208 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check that full and region screen-write fallbacks update an attached client,
|
||||
# not only tmux's internal pane grid.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
EMITTER=$DIR/emitter.pl
|
||||
CAPTURE=$DIR/capture
|
||||
INNER=
|
||||
OUTER=
|
||||
N=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_outer_lacks()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" || return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client still showed $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane did not contain $marker"
|
||||
}
|
||||
|
||||
wait_inner_lacks()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" || return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane still contained $marker"
|
||||
}
|
||||
|
||||
setup()
|
||||
{
|
||||
mode=$1
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
N=$((N + 1))
|
||||
INNER="$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lredraw-write-outer-$$-$N -f/dev/null"
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 12 \
|
||||
"MODE=$mode READY='$DIR/ready-$N' TRIGGER='$DIR/trigger-$N' perl '$EMITTER'" ||
|
||||
exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
}
|
||||
|
||||
trigger()
|
||||
{
|
||||
: >"$DIR/trigger-$N-${1:-1}"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
my $mode = $ENV{MODE};
|
||||
my $ready = $ENV{READY};
|
||||
my $trigger = $ENV{TRIGGER};
|
||||
|
||||
sub fill_screen {
|
||||
my ($prefix) = @_;
|
||||
print "\e[2J\e[H";
|
||||
for my $row (0 .. 11) {
|
||||
printf "\e[%d;1H%s-ROW-%02d", $row + 1, $prefix, $row;
|
||||
}
|
||||
}
|
||||
|
||||
if ($mode eq 'ris') {
|
||||
fill_screen('RIS');
|
||||
} elsif ($mode eq 'alternate') {
|
||||
fill_screen('BASE');
|
||||
} elsif ($mode eq 'scroll') {
|
||||
fill_screen('SCROLL');
|
||||
} else {
|
||||
die "unknown mode $mode\n";
|
||||
}
|
||||
|
||||
open my $fh, '>', $ready or die "$ready: $!\n";
|
||||
close $fh;
|
||||
while (!-e "$trigger-1") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
if ($mode eq 'ris') {
|
||||
print "\ec";
|
||||
} elsif ($mode eq 'alternate') {
|
||||
print "\e[?1049h";
|
||||
fill_screen('ALT');
|
||||
while (!-e "$trigger-2") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
print "\e[?1049l";
|
||||
} else {
|
||||
print "\e[12;1H\r\nSCROLL-NEW";
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
# RIS clears the complete screen. The source pane and attached client must both
|
||||
# lose every old row.
|
||||
setup ris
|
||||
wait_outer_has RIS-ROW-11
|
||||
trigger
|
||||
wait_inner_lacks RIS-ROW
|
||||
wait_outer_lacks RIS-ROW
|
||||
|
||||
# Leaving the alternate screen restores every row of the base screen.
|
||||
setup alternate
|
||||
wait_outer_has BASE-ROW-11
|
||||
trigger
|
||||
wait_inner_has ALT-ROW-11
|
||||
wait_outer_has ALT-ROW-11
|
||||
wait_outer_lacks BASE-ROW
|
||||
trigger 2
|
||||
wait_inner_has BASE-ROW-11
|
||||
wait_outer_has BASE-ROW-11
|
||||
wait_outer_lacks ALT-ROW
|
||||
|
||||
# Scrolling a pane which is narrower than the terminal redraws its complete
|
||||
# region. Check the physical client row by row after the source grid shifts.
|
||||
setup scroll
|
||||
$INNER split-window -h -t inner:0 'sleep 100' || exit 1
|
||||
wait_outer_has SCROLL-ROW-11
|
||||
trigger
|
||||
wait_inner_has SCROLL-NEW
|
||||
wait_outer_has SCROLL-NEW
|
||||
row=1
|
||||
while [ "$row" -le 11 ]; do
|
||||
expected=$(printf 'SCROLL-ROW-%02d' "$row")
|
||||
actual=$(sed -n "${row}p" "$CAPTURE")
|
||||
case "$actual" in
|
||||
"$expected"*) ;;
|
||||
*) fail "outer row $row was not redrawn as $expected" ;;
|
||||
esac
|
||||
row=$((row + 1))
|
||||
done
|
||||
actual=$(sed -n '12p' "$CAPTURE")
|
||||
case "$actual" in
|
||||
SCROLL-NEW*) ;;
|
||||
*) fail "outer bottom row was not redrawn as SCROLL-NEW" ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
96
regress/screen-redraw-scrollbars-auto-hide.sh
Normal file
96
regress/screen-redraw-scrollbars-auto-hide.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Auto-hide scrollbars appear on activity or pointer hover, then disappear
|
||||
# after pane-scrollbars-timeout. Compare captures of the same copy-mode view so
|
||||
# only the overlay scrollbar changes.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
OUTER="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
INNER="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
HIDDEN=$DIR/hidden
|
||||
VISIBLE=$DIR/visible
|
||||
AFTER=$DIR/after
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -pe -t outer:0.0 >"$1" || exit 1
|
||||
}
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER list-clients 2>/dev/null | grep -q . && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x40 -y12 \
|
||||
"sh -c 'seq 50; exec sleep 100'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-window-option pane-scrollbars auto-hide || exit 1
|
||||
$INNER set-window-option pane-scrollbars-timeout 3000 || exit 1
|
||||
$INNER set-window-option pane-scrollbars-style \
|
||||
'bg=colour196,fg=colour231,width=1,pad=0' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x40 -y12 "$INNER attach -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
wait_for_client
|
||||
|
||||
# Enter copy mode and choose a stable view. The initial show timer is allowed
|
||||
# to expire before taking the hidden reference capture.
|
||||
$INNER copy-mode -H || exit 1
|
||||
$INNER send-keys -X history-top || exit 1
|
||||
sleep 3.5
|
||||
capture "$HIDDEN"
|
||||
|
||||
# A copy-mode page movement shows the overlay and starts its timer. Capture
|
||||
# that view immediately, then again after the timeout without moving it.
|
||||
$INNER send-keys -X page-down || exit 1
|
||||
sleep 0.1
|
||||
capture "$VISIBLE"
|
||||
sleep 3.5
|
||||
capture "$AFTER"
|
||||
cmp -s "$VISIBLE" "$AFTER" && fail "scrollbar did not hide after timeout"
|
||||
|
||||
# Use the post-movement hidden scene as the reference for pointer hover. SGR
|
||||
# mouse column 40 is the right-hand scrollbar; moving to column 5 restarts the
|
||||
# timer without changing the copy-mode view.
|
||||
cp "$AFTER" "$HIDDEN"
|
||||
hover=$(printf '\033[<35;40;5M')
|
||||
$OUTER send-keys -l "$hover" || exit 1
|
||||
sleep 0.1
|
||||
capture "$VISIBLE"
|
||||
cmp -s "$HIDDEN" "$VISIBLE" && fail "scrollbar did not appear on hover"
|
||||
away=$(printf '\033[<35;5;5M')
|
||||
$OUTER send-keys -l "$away" || exit 1
|
||||
sleep 3.5
|
||||
capture "$AFTER"
|
||||
cmp -s "$HIDDEN" "$AFTER" || fail "scrollbar did not hide after hover"
|
||||
|
||||
exit 0
|
||||
112
regress/server-access.sh
Normal file
112
regress/server-access.sh
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise the unprivileged server-access surface: user and group ACL entries,
|
||||
# read-only/writable transitions, listing, removal, implied addition, and
|
||||
# command errors. Actual connections from another UID require privileges and
|
||||
# deliberately do not form part of this test.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
OUT=$(mktemp) || exit 1
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -f "$OUT"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
expect_fail()
|
||||
{
|
||||
message=$1
|
||||
shift
|
||||
if $TMUX "$@" >"$OUT" 2>&1; then
|
||||
fail "$message: command unexpectedly succeeded"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_entry()
|
||||
{
|
||||
entry=$1
|
||||
$TMUX server-access -l >"$OUT" || exit 1
|
||||
grep -Fxq "$entry" "$OUT" ||
|
||||
fail "missing ACL entry '$entry'"
|
||||
}
|
||||
|
||||
assert_no_entry()
|
||||
{
|
||||
entry=$1
|
||||
$TMUX server-access -l >"$OUT" || exit 1
|
||||
if grep -Fxq "$entry" "$OUT"; then
|
||||
fail "unexpected ACL entry '$entry'"
|
||||
fi
|
||||
}
|
||||
|
||||
owner_uid=$(id -u)
|
||||
owner_name=$(id -un)
|
||||
|
||||
# nobody is the non-owner test identity. Derive its primary group rather than
|
||||
# assuming whether the system calls it nobody, nogroup, or something else.
|
||||
acl_user=nobody
|
||||
acl_group=$(id -gn "$acl_user" 2>/dev/null) ||
|
||||
fail "the nobody account is unavailable"
|
||||
[ "$owner_name" != "$acl_user" ] || fail "test cannot run as nobody"
|
||||
|
||||
$TMUX new-session -d -s access 'exec sleep 100' || exit 1
|
||||
|
||||
# The owner is installed as writable at server startup (root is intentionally
|
||||
# omitted from display output).
|
||||
if [ "$owner_uid" != 0 ]; then
|
||||
assert_entry "$owner_name (U,W)"
|
||||
fi
|
||||
|
||||
# User ACL lifecycle, including duplicate and missing-entry errors.
|
||||
$TMUX server-access -a "$acl_user" || exit 1
|
||||
assert_entry "$acl_user (U,W)"
|
||||
expect_fail "duplicate user addition" server-access -a "$acl_user"
|
||||
$TMUX server-access -r "$acl_user" || exit 1
|
||||
assert_entry "$acl_user (U,R)"
|
||||
$TMUX server-access -w "$acl_user" || exit 1
|
||||
assert_entry "$acl_user (U,W)"
|
||||
$TMUX server-access -d "$acl_user" || exit 1
|
||||
assert_no_entry "$acl_user (U,W)"
|
||||
expect_fail "removing absent user" server-access -d "$acl_user"
|
||||
|
||||
# -r and -w imply -a when an entry is absent.
|
||||
$TMUX server-access -r "$acl_user" || exit 1
|
||||
assert_entry "$acl_user (U,R)"
|
||||
$TMUX server-access -d "$acl_user" || exit 1
|
||||
|
||||
# The same lifecycle for a group exercises the separate ACL key space.
|
||||
$TMUX server-access -g -a -r "$acl_group" || exit 1
|
||||
assert_entry "$acl_group (G,R)"
|
||||
$TMUX server-access -g -w "$acl_group" || exit 1
|
||||
assert_entry "$acl_group (G,W)"
|
||||
expect_fail "duplicate group addition" server-access -g -a "$acl_group"
|
||||
$TMUX server-access -g -d "$acl_group" || exit 1
|
||||
assert_no_entry "$acl_group (G,W)"
|
||||
expect_fail "removing absent group" server-access -g -d "$acl_group"
|
||||
|
||||
# Parser and lookup errors, plus the immutable owner/root entries.
|
||||
expect_fail "missing ACL subject" server-access -a
|
||||
expect_fail "unknown user" server-access -a "tmux-no-user-$$"
|
||||
expect_fail "unknown group" server-access -g -a "tmux-no-group-$$"
|
||||
expect_fail "conflicting add/delete" server-access -a -d "$acl_user"
|
||||
expect_fail "conflicting read/write" server-access -r -w "$acl_user"
|
||||
expect_fail "changing server owner" server-access -r "$owner_name"
|
||||
if [ "$owner_name" != root ]; then
|
||||
expect_fail "changing root access" server-access -a root
|
||||
fi
|
||||
|
||||
$TMUX has-session -t access || fail "server exited during ACL updates"
|
||||
exit 0
|
||||
0
regress/server-socket-error.sh
Executable file → Normal file
0
regress/server-socket-error.sh
Executable file → Normal file
0
regress/set-hook-E.sh
Executable file → Normal file
0
regress/set-hook-E.sh
Executable file → Normal file
@@ -1,81 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Switching windows in the same session must redraw the attached client. The
|
||||
# session object is shared, so its current winlink cannot be compared after it
|
||||
# has already been changed.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lswitch-redraw-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 8 \
|
||||
"printf '\033[2J\033[HA-WINDOW'; exec sleep 100" || exit 1
|
||||
$INNER new-window -d -t inner:1 \
|
||||
"printf '\033[2J\033[HB-WINDOW'; exec sleep 100" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null attach-session -t inner:0" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has A-WINDOW
|
||||
|
||||
$INNER switch-client -c "$CLIENT" -t inner:1.0 || exit 1
|
||||
[ "$($INNER display-message -p -t inner '#{window_index}')" -eq 1 ] ||
|
||||
fail "server did not select window 1"
|
||||
wait_outer_has B-WINDOW
|
||||
|
||||
exit 0
|
||||
203
regress/switch-mode.sh
Normal file
203
regress/switch-mode.sh
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise switch-mode's session and window lists with a real attached client:
|
||||
# fuzzy filtering, movement and selection, the default and custom commands,
|
||||
# no-match cancellation, resize, zoom restoration, and -k mode teardown.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_capture()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Fq "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$marker'"
|
||||
}
|
||||
|
||||
wait_count()
|
||||
{
|
||||
marker=$1
|
||||
want=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
count=$(printf '%s\n' "$captured" | grep -Fc "$marker")
|
||||
[ "$count" -eq "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "found $count '$marker' rows, expected $want"
|
||||
}
|
||||
|
||||
wait_format()
|
||||
{
|
||||
target=$1
|
||||
format=$2
|
||||
want=$3
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER display-message -p -t "$target" "$format" \
|
||||
2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "$target format $format is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
wait_client_session()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER list-clients -F '#{client_session}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "client session is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
# Three alphabetically ordered sessions. alpha:main has two panes so -Z can be
|
||||
# observed; the two pick-* windows make a deterministic two-row window filter.
|
||||
$INNER new-session -d -s alpha -n main -x60 -y15 'exec sleep 100' || exit 1
|
||||
$INNER split-window -d -h -t alpha:main 'exec sleep 100' || exit 1
|
||||
$INNER new-window -d -t alpha: -n pick-one 'exec sleep 100' || exit 1
|
||||
$INNER new-session -d -s bravo -n pick-two -x60 -y15 \
|
||||
'exec sleep 100' || exit 1
|
||||
$INNER new-session -d -s charlie -n last -x60 -y15 \
|
||||
'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER select-window -t alpha:main || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x60 -y15 "$INNER attach -t alpha" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
wait_client_session alpha
|
||||
|
||||
# Session rows are sorted alpha, bravo, charlie. Up from the first row wraps to
|
||||
# charlie; Enter runs a custom command with the selected session target.
|
||||
$INNER set-option -g @picked '' || exit 1
|
||||
$INNER switch-mode -s -t alpha:main.0 -F 'SESSION #{session_name}' \
|
||||
"set-option -g @picked '%%'" || exit 1
|
||||
wait_count 'SESSION ' 3
|
||||
$INNER send-keys -t alpha:main.0 Up Enter || exit 1
|
||||
wait_format alpha:main.0 '#{@picked}' '=charlie:'
|
||||
wait_format alpha:main.0 '#{pane_in_mode}' 0
|
||||
|
||||
# Fuzzy filtering keeps only bravo for "brv". The default command switches the
|
||||
# real client to that session and closes the mode.
|
||||
$INNER switch-mode -s -t alpha:main.0 -F 'SESSION #{session_name}' || exit 1
|
||||
wait_count 'SESSION ' 3
|
||||
$INNER send-keys -t alpha:main.0 -l brv || exit 1
|
||||
wait_count 'SESSION ' 1
|
||||
printf '%s\n' "$captured" | grep -Fq 'SESSION bravo' ||
|
||||
fail "fuzzy session filter did not select bravo"
|
||||
$INNER send-keys -t alpha:main.0 Enter || exit 1
|
||||
wait_client_session bravo
|
||||
client=$($INNER list-clients -F '#{client_name}')
|
||||
$INNER switch-client -c "$client" -t alpha || exit 1
|
||||
wait_client_session alpha
|
||||
|
||||
# Window mode sees pick-one then pick-two for the "pick" filter. Down selects
|
||||
# the second row, and the custom command receives its window target.
|
||||
$INNER set-option -g @picked '' || exit 1
|
||||
$INNER switch-mode -w -t alpha:main.0 -F \
|
||||
'WINDOW #{session_name}:#{window_name}' \
|
||||
"set-option -g @picked '%%'" || exit 1
|
||||
wait_capture 'WINDOW '
|
||||
$INNER send-keys -t alpha:main.0 -l pick || exit 1
|
||||
wait_count 'WINDOW ' 2
|
||||
$INNER send-keys -t alpha:main.0 Down Enter || exit 1
|
||||
picked=$($INNER show-option -gv @picked)
|
||||
case "$picked" in
|
||||
=bravo:0.) ;;
|
||||
*) fail "window selection produced '$picked', expected '=bravo:0.'" ;;
|
||||
esac
|
||||
|
||||
# Navigation with an empty result set must be harmless, and Escape cancels
|
||||
# without running the custom command.
|
||||
$INNER set-option -g @picked unchanged || exit 1
|
||||
$INNER switch-mode -s -t alpha:main.0 -F 'NONE #{session_name}' \
|
||||
"set-option -g @picked '%%'" || exit 1
|
||||
wait_capture 'NONE '
|
||||
$INNER send-keys -t alpha:main.0 -l no-such-session || exit 1
|
||||
wait_count 'NONE ' 0
|
||||
$INNER send-keys -t alpha:main.0 Up Down PPage NPage Home End || exit 1
|
||||
wait_format alpha:main.0 '#{pane_mode}' switch-mode
|
||||
$INNER send-keys -t alpha:main.0 Escape || exit 1
|
||||
wait_format alpha:main.0 '#{pane_in_mode}' 0
|
||||
[ "$($INNER show-option -gv @picked)" = unchanged ] ||
|
||||
fail "cancelled switch-mode ran its command"
|
||||
|
||||
# -Z temporarily zooms an unzoomed window. Resizing while the mode is open
|
||||
# rebuilds and redraws it; exiting restores the original unzoomed state.
|
||||
$INNER switch-mode -Zs -t alpha:main.0 -F 'RESIZE #{session_name}' || exit 1
|
||||
wait_capture 'RESIZE alpha'
|
||||
wait_format alpha:main.0 '#{window_zoomed_flag}' 1
|
||||
$INNER resize-window -t alpha:main -x50 -y12 || exit 1
|
||||
wait_format alpha:main.0 '#{window_width}x#{window_height}' 50x12
|
||||
wait_format alpha:main.0 '#{pane_mode}' switch-mode
|
||||
$INNER send-keys -t alpha:main.0 Escape || exit 1
|
||||
wait_format alpha:main.0 '#{window_zoomed_flag}' 0
|
||||
|
||||
# If the window was already zoomed, leaving -Z mode must keep it zoomed.
|
||||
$INNER resize-pane -Z -t alpha:main.0 || exit 1
|
||||
$INNER switch-mode -Zs -t alpha:main.0 -F 'ZOOMED #{session_name}' || exit 1
|
||||
wait_capture 'ZOOMED alpha'
|
||||
$INNER send-keys -t alpha:main.0 Escape || exit 1
|
||||
wait_format alpha:main.0 '#{window_zoomed_flag}' 1
|
||||
$INNER resize-pane -Z -t alpha:main.0 || exit 1
|
||||
|
||||
# -k uses the shared mode teardown flag. Put it on a disposable pane and make
|
||||
# sure exiting the mode removes that pane.
|
||||
kill_pane=$($INNER split-window -d -P -F '#{pane_id}' -t alpha:main \
|
||||
'exec sleep 100') || exit 1
|
||||
$INNER switch-mode -ks -t "$kill_pane" -F 'KILL #{session_name}' || exit 1
|
||||
wait_format "$kill_pane" '#{pane_mode}' switch-mode
|
||||
$INNER send-keys -t "$kill_pane" Escape || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER list-panes -s -t alpha -F '#{pane_id}' 2>/dev/null | \
|
||||
grep -q -x "$kill_pane" || break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "switch-mode -k did not kill its pane"
|
||||
|
||||
exit 0
|
||||
160
regress/sync-output-cursor.sh
Normal file
160
regress/sync-output-cursor.sh
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A synchronized update elsewhere in the pane must not hide a stationary
|
||||
# cursor while tmux waits for the end of the update.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
TEST_TMUX="$TEST_TMUX" sh "$0" pane || exit 1
|
||||
TEST_TMUX="$TEST_TMUX" sh "$0" prompt || exit 1
|
||||
exit 0
|
||||
fi
|
||||
case $1 in
|
||||
pane|prompt) MODE=$1 ;;
|
||||
*) echo "usage: $0 [pane|prompt]" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
|
||||
INNER="$TEST_TMUX -Li$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lo$$ -f/dev/null"
|
||||
CLIENT_BYTES=$DIR/client-bytes
|
||||
CONTROL=$DIR/control
|
||||
EMITTER=$DIR/emitter.pl
|
||||
ASSERT=$DIR/assert-cursor.pl
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER list-clients -F '#{client_termfeatures}' 2>/dev/null |
|
||||
grep -q 'sync' && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "sync-capable client did not attach"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $control = $ENV{CONTROL};
|
||||
(my $dir = $control) =~ s{/[^/]+$}{};
|
||||
|
||||
# Draw a prompt and leave its cursor stationary on the last line.
|
||||
syswrite STDOUT, "\e[2J\e[HWorking .\e[24;1H> ";
|
||||
open my $ready, '>', "$dir/ready" or die "$dir/ready: $!\n";
|
||||
close $ready;
|
||||
while (!-e $control) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
# Deliberately split BSU, content, and ESU across pane reads. This mirrors an
|
||||
# animation producer which begins a frame before it has finished rendering it.
|
||||
for my $spinner (qw(| / - \\)) {
|
||||
syswrite STDOUT, "\e[?2026h";
|
||||
select undef, undef, undef, 0.05;
|
||||
syswrite STDOUT, "\e[1;9H$spinner\e[24;3H";
|
||||
select undef, undef, undef, 0.01;
|
||||
syswrite STDOUT, "\e[?2026l";
|
||||
select undef, undef, undef, 0.15;
|
||||
}
|
||||
select undef, undef, undef, 5;
|
||||
PERL
|
||||
|
||||
cat >"$ASSERT" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $path = shift;
|
||||
open my $fh, '<:raw', $path or die "$path: $!\n";
|
||||
local $/;
|
||||
my $bytes = <$fh>;
|
||||
my ($bsu, $esu, $hide, $show) =
|
||||
("\e[?2026h", "\e[?2026l", "\e[?25l", "\e[?25h");
|
||||
my ($sync, $outside, $hides, $shows) = (0, 0, 0, 0);
|
||||
|
||||
for (my $i = 0; $i < length($bytes);) {
|
||||
if (substr($bytes, $i, length($bsu)) eq $bsu) {
|
||||
$sync++;
|
||||
$i += length($bsu);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($esu)) eq $esu) {
|
||||
$sync-- if $sync;
|
||||
$i += length($esu);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($hide)) eq $hide) {
|
||||
$hides++;
|
||||
$outside++ unless $sync;
|
||||
$i += length($hide);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($show)) eq $show) {
|
||||
$shows++;
|
||||
$outside++ unless $sync;
|
||||
$i += length($show);
|
||||
next;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
printf "%s cursor hides: %d; cursor shows: %d; changes outside sync: %d\n",
|
||||
$ENV{MODE}, $hides, $shows, $outside;
|
||||
die "stationary cursor changed outside synchronized output\n" if $outside;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 80 -y 24 \
|
||||
"CONTROL='$CONTROL' perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -as terminal-features '*:sync' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 80 -y 24 \
|
||||
"$TEST_TMUX -Li$$ -f/dev/null attach-session -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
wait_for_client
|
||||
|
||||
if [ "$MODE" = prompt ]; then
|
||||
$OUTER send-keys -t outer:0.0 C-b : || exit 1
|
||||
sleep 0.2
|
||||
fi
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ] && [ ! -e "$DIR/ready" ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ -e "$DIR/ready" ] || fail "application emitter did not become ready"
|
||||
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$CLIENT_BYTES'" || exit 1
|
||||
: >"$CONTROL"
|
||||
sleep 2
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
|
||||
MODE=$MODE perl "$ASSERT" "$CLIENT_BYTES"
|
||||
0
regress/tab-cell-background.sh
Executable file → Normal file
0
regress/tab-cell-background.sh
Executable file → Normal file
58
regress/terminal-feature-utf8.sh
Normal file
58
regress/terminal-feature-utf8.sh
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
|
||||
# The utf8 terminal feature must mark an attached client as UTF-8 even when
|
||||
# neither its environment nor -u does so. It must also be reported by the
|
||||
# client terminal-feature format lookup.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
cleanup
|
||||
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
|
||||
# A clean C-locale environment avoids the ordinary UTF-8 detection paths.
|
||||
# -T utf8 is therefore solely responsible for setting client_utf8.
|
||||
client_command="env -i PATH=/bin:/usr/bin TERM=screen LC_ALL=C $TEST_TMUX -T utf8 -LtestI$$ -f/dev/null attach-session -t inner"
|
||||
$OUTER new-session -d -s outer -x40 -y10 "$client_command" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
client=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
utf8=$($INNER list-clients -F '#{client_utf8}' 2>/dev/null)
|
||||
[ -n "$client" ] && [ "$utf8" = 1 ] && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "utf8 terminal feature did not set client_utf8"
|
||||
|
||||
features=$($INNER list-clients -F '#{client_termfeatures}')
|
||||
case ",$features," in
|
||||
*,utf8,*) ;;
|
||||
*) fail "utf8 is missing from client_termfeatures: $features" ;;
|
||||
esac
|
||||
|
||||
present=$($INNER display-message -c "$client" -p '#{I/f:utf8}')
|
||||
[ "$present" = 1 ] || fail "utf8 terminal feature lookup returned '$present'"
|
||||
|
||||
exit 0
|
||||
0
regress/theme-report.sh
Executable file → Normal file
0
regress/theme-report.sh
Executable file → Normal file
@@ -8,7 +8,7 @@
|
||||
# This exercises:
|
||||
# - new-window placement: next free index, explicit index, index in use with
|
||||
# and without -k, -a (after) and -b (before) insertion with shuffling, and
|
||||
# -S selecting an existing window by name instead of creating;
|
||||
# -S selecting an existing window by name or target instead of creating;
|
||||
# - move-window to a free index, to an occupied index with and without -k,
|
||||
# -a insertion and -r renumbering (including base-index);
|
||||
# - renumber-windows closing gaps;
|
||||
@@ -140,6 +140,36 @@ check_ok new-window -S -t W: -n w3
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '5:w3'
|
||||
|
||||
# -S with a target-window that already exists selects it directly, ignoring
|
||||
# -n (with -d it does not switch).
|
||||
check_ok select-window -t W:0
|
||||
check_ok new-window -S -t W:9 -n ignored
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '9:w9k'
|
||||
check_ok select-window -t W:0
|
||||
check_ok new-window -S -d -t W:9 -n ignored
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '0:wB'
|
||||
|
||||
# -S with a target-window that does not yet exist still creates it normally.
|
||||
check_ok new-window -S -t W:20 -n w20
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k 20:w20'
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '20:w20'
|
||||
check_ok kill-window -t W:20
|
||||
|
||||
# -S also works with a relative target-window (+N): selects the window at
|
||||
# that offset if it exists, or creates it there if not.
|
||||
check_ok select-window -t W:0
|
||||
check_ok new-window -S -t W:+1
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '1:w0'
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
|
||||
check_ok select-window -t W:0
|
||||
check_ok new-window -S -t W:+6 -n rel6
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '6:rel6'
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 6:rel6 9:w9k'
|
||||
check_ok kill-window -t W:6
|
||||
|
||||
# Clean up to a known arrangement.
|
||||
check_ok kill-window -t W:wB
|
||||
check_ok kill-window -t W:wA
|
||||
|
||||
6
resize.c
6
resize.c
@@ -386,12 +386,10 @@ recalculate_size(struct window *w, int now)
|
||||
* got a resize scheduled, then use the new size; otherwise the old.
|
||||
*/
|
||||
if (w->flags & WINDOW_RESIZE) {
|
||||
if (!now && changed && w->new_sx == sx && w->new_sy == sy &&
|
||||
w->new_xpixel == xpixel && w->new_ypixel == ypixel)
|
||||
if (!now && changed && w->new_sx == sx && w->new_sy == sy)
|
||||
changed = 0;
|
||||
} else {
|
||||
if (!now && changed && w->sx == sx && w->sy == sy &&
|
||||
w->xpixel == xpixel && w->ypixel == ypixel)
|
||||
if (!now && changed && w->sx == sx && w->sy == sy)
|
||||
changed = 0;
|
||||
}
|
||||
|
||||
|
||||
615
screen-redraw.c
615
screen-redraw.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: screen-redraw.c,v 1.157 2026/07/24 08:49:23 nicm Exp $ */
|
||||
/* $OpenBSD: screen-redraw.c,v 1.158 2026/09/01 19:50:58 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -99,12 +99,6 @@ enum redraw_span_type {
|
||||
#define REDRAW_ALL 0x7fffffff
|
||||
#define REDRAW_IS_ALL(flags) ((flags) == REDRAW_ALL)
|
||||
|
||||
enum redraw_image_phase {
|
||||
REDRAW_IMAGES_BEFORE,
|
||||
REDRAW_TEXT,
|
||||
REDRAW_IMAGES_AFTER
|
||||
};
|
||||
|
||||
/* UTF-8 isolate characters. */
|
||||
#define REDRAW_START_ISOLATE "\342\201\246"
|
||||
#define REDRAW_END_ISOLATE "\342\201\251"
|
||||
@@ -211,24 +205,6 @@ struct redraw_scene {
|
||||
u_int oy;
|
||||
};
|
||||
|
||||
/* A single damaged window-coordinate rectangle. */
|
||||
struct redraw_damage {
|
||||
u_int x;
|
||||
u_int y;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
int flags;
|
||||
#define REDRAW_DAMAGE_SCROLL 0x1
|
||||
|
||||
TAILQ_ENTRY(redraw_damage) entry;
|
||||
};
|
||||
|
||||
/*
|
||||
* Cap on the number of pending damage rectangles per window before they are
|
||||
* collapsed into a single rectangle covering their union.
|
||||
*/
|
||||
#define REDRAW_DAMAGE_MAX 16
|
||||
|
||||
/* Cell for building the scene. */
|
||||
struct redraw_build_cell {
|
||||
struct redraw_span_data data;
|
||||
@@ -1081,13 +1057,6 @@ redraw_free_scene(struct redraw_scene *scene)
|
||||
free(scene);
|
||||
}
|
||||
|
||||
/* Does a client's cached scene show this window? */
|
||||
int
|
||||
redraw_client_has_window(struct client *c, struct window *w)
|
||||
{
|
||||
return (c->redraw_scene != NULL && c->redraw_scene->w == w);
|
||||
}
|
||||
|
||||
/* Mark a window's cached redraw scenes as out of date. */
|
||||
void
|
||||
redraw_invalidate_scene(struct window *w)
|
||||
@@ -1095,184 +1064,6 @@ redraw_invalidate_scene(struct window *w)
|
||||
w->redraw_scene_generation++;
|
||||
}
|
||||
|
||||
/* Free all pending damage for a window. */
|
||||
void
|
||||
redraw_free_damage(struct window *w)
|
||||
{
|
||||
struct redraw_damage *rd, *rd1;
|
||||
|
||||
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||
free(rd);
|
||||
}
|
||||
w->damage_count = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
w->image_scroll_pending = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Collapse all pending damage for a window into one rectangle - its union. */
|
||||
static void
|
||||
redraw_collapse_damage(struct window *w)
|
||||
{
|
||||
struct redraw_damage *rd, *rd1, *first;
|
||||
u_int x0, y0, x1, y1;
|
||||
|
||||
first = TAILQ_FIRST(&w->damage);
|
||||
if (first == NULL)
|
||||
return;
|
||||
x0 = first->x;
|
||||
y0 = first->y;
|
||||
x1 = first->x + first->sx;
|
||||
y1 = first->y + first->sy;
|
||||
|
||||
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||
if (rd->x < x0)
|
||||
x0 = rd->x;
|
||||
if (rd->y < y0)
|
||||
y0 = rd->y;
|
||||
if (rd->x + rd->sx > x1)
|
||||
x1 = rd->x + rd->sx;
|
||||
if (rd->y + rd->sy > y1)
|
||||
y1 = rd->y + rd->sy;
|
||||
first->flags &= rd->flags;
|
||||
if (rd != first) {
|
||||
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||
free(rd);
|
||||
}
|
||||
}
|
||||
|
||||
first->x = x0;
|
||||
first->y = y0;
|
||||
first->sx = x1 - x0;
|
||||
first->sy = y1 - y0;
|
||||
w->damage_count = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Record a damaged window-coordinate rectangle. Clips it to the window,
|
||||
* merges it with an existing rectangle where doing so does not make the
|
||||
* result substantially larger than the two combined, and collapses the
|
||||
* whole list to its union once it grows past a modest cap.
|
||||
*
|
||||
* This only records damage - nothing consumes it yet.
|
||||
*/
|
||||
static void
|
||||
redraw_damage_window_flags(struct window *w, u_int x, u_int y, u_int sx,
|
||||
u_int sy, int flags)
|
||||
{
|
||||
struct redraw_damage *rd;
|
||||
u_int x0, y0, x1, y1, area, union_area;
|
||||
|
||||
if (x >= w->sx || y >= w->sy)
|
||||
return;
|
||||
if (x + sx > w->sx)
|
||||
sx = w->sx - x;
|
||||
if (y + sy > w->sy)
|
||||
sy = w->sy - y;
|
||||
if (sx == 0 || sy == 0)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||
/* Skip unless overlapping or directly adjacent. */
|
||||
if (x > rd->x + rd->sx || rd->x > x + sx ||
|
||||
y > rd->y + rd->sy || rd->y > y + sy)
|
||||
continue;
|
||||
|
||||
x0 = (x < rd->x) ? x : rd->x;
|
||||
y0 = (y < rd->y) ? y : rd->y;
|
||||
x1 = (x + sx > rd->x + rd->sx) ? x + sx : rd->x + rd->sx;
|
||||
y1 = (y + sy > rd->y + rd->sy) ? y + sy : rd->y + rd->sy;
|
||||
|
||||
area = sx * sy + rd->sx * rd->sy;
|
||||
union_area = (x1 - x0) * (y1 - y0);
|
||||
if (union_area > 2 * area)
|
||||
continue;
|
||||
|
||||
rd->x = x0;
|
||||
rd->y = y0;
|
||||
rd->sx = x1 - x0;
|
||||
rd->sy = y1 - y0;
|
||||
rd->flags &= flags;
|
||||
return;
|
||||
}
|
||||
|
||||
rd = xcalloc(1, sizeof *rd);
|
||||
rd->x = x;
|
||||
rd->y = y;
|
||||
rd->sx = sx;
|
||||
rd->sy = sy;
|
||||
rd->flags = flags;
|
||||
TAILQ_INSERT_TAIL(&w->damage, rd, entry);
|
||||
w->damage_count++;
|
||||
|
||||
if (w->damage_count > REDRAW_DAMAGE_MAX)
|
||||
redraw_collapse_damage(w);
|
||||
}
|
||||
|
||||
void
|
||||
redraw_damage_window(struct window *w, u_int x, u_int y, u_int sx, u_int sy)
|
||||
{
|
||||
redraw_damage_window_flags(w, x, y, sx, sy, 0);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Record damage caused by an image moving with a terminal scroll. */
|
||||
void
|
||||
redraw_damage_window_scroll(struct window *w, u_int x, u_int y, u_int sx,
|
||||
u_int sy)
|
||||
{
|
||||
if (!w->image_scroll_pending) {
|
||||
w->image_scroll_epoch++;
|
||||
w->image_scroll_pending = 1;
|
||||
}
|
||||
redraw_damage_window_flags(w, x, y, sx, sy, REDRAW_DAMAGE_SCROLL);
|
||||
}
|
||||
|
||||
/* Remember whether this client used terminal scrolling for the damage. */
|
||||
void
|
||||
redraw_image_scroll_result(struct tty *tty, const struct tty_ctx *ctx,
|
||||
int failed)
|
||||
{
|
||||
struct window_pane *wp = ctx->arg;
|
||||
struct window *w;
|
||||
|
||||
if (wp == NULL || (w = wp->window) == NULL || !w->image_scroll_pending)
|
||||
return;
|
||||
if (tty->image_scroll_window != w ||
|
||||
tty->image_scroll_epoch != w->image_scroll_epoch) {
|
||||
tty->image_scroll_window = w;
|
||||
tty->image_scroll_epoch = w->image_scroll_epoch;
|
||||
tty->image_scroll_failed = 0;
|
||||
}
|
||||
if (failed)
|
||||
tty->image_scroll_failed = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Record damage for every pane border-status row in a window. */
|
||||
static void
|
||||
redraw_damage_window_pane_status(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
u_int y;
|
||||
int status;
|
||||
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
status = window_pane_get_pane_status(wp);
|
||||
if (status == PANE_STATUS_TOP) {
|
||||
if (wp->yoff == 0)
|
||||
continue;
|
||||
y = wp->yoff - 1;
|
||||
} else if (status == PANE_STATUS_BOTTOM)
|
||||
y = wp->yoff + wp->sy;
|
||||
else
|
||||
continue;
|
||||
if (y < w->sy)
|
||||
redraw_damage_window(w, 0, y, w->sx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark all cached redraw scenes as out of date. */
|
||||
void
|
||||
redraw_invalidate_all_scenes(void)
|
||||
@@ -1315,8 +1106,7 @@ redraw_get_scene(struct client *c)
|
||||
/* Draw a pane span. */
|
||||
static void
|
||||
redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
|
||||
struct redraw_span *span, u_int x, u_int y, u_int n,
|
||||
enum redraw_image_phase phase)
|
||||
struct redraw_span *span, u_int x, u_int y, u_int n)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct client *c = scene->c;
|
||||
@@ -1334,14 +1124,7 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
|
||||
|
||||
px = span->data.p.px + (x - span->x);
|
||||
py = span->data.p.py;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (phase != REDRAW_TEXT)
|
||||
image_draw_line(tty, s, px, py, n, x, y,
|
||||
phase == REDRAW_IMAGES_BEFORE, &style_ctx);
|
||||
else
|
||||
#endif
|
||||
if (phase == REDRAW_TEXT)
|
||||
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
|
||||
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
|
||||
}
|
||||
|
||||
/* Get default border style for spans without a pane. */
|
||||
@@ -1593,15 +1376,10 @@ redraw_draw_menu_span(struct redraw_draw_ctx *dctx,
|
||||
tty_draw_line(tty, s, px, span->data.m.py, n, x, y, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw a span, restricted to [clip_x, clip_x + clip_n) - a caller drawing
|
||||
* the whole span passes the span's own x/width here; a caller drawing only
|
||||
* a damaged sub-range passes that range instead. Overlay clipping (menus,
|
||||
* popups) is then applied on top of this, exactly as before.
|
||||
*/
|
||||
/* Draw a span. */
|
||||
static void
|
||||
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||
u_int y, u_int clip_x, u_int clip_n, enum redraw_image_phase phase)
|
||||
u_int y)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct redraw_span_data *data = &span->data;
|
||||
@@ -1615,19 +1393,17 @@ redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||
if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS)
|
||||
return;
|
||||
|
||||
r = tty_check_overlay_range(tty, clip_x, y, clip_n);
|
||||
r = tty_check_overlay_range(tty, span->x, y, span->width);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
rr = &r->ranges[i];
|
||||
if (rr->nx == 0)
|
||||
continue;
|
||||
x = rr->px;
|
||||
n = rr->nx;
|
||||
if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE)
|
||||
continue;
|
||||
|
||||
switch (span->data.type) {
|
||||
case REDRAW_SPAN_PANE:
|
||||
redraw_draw_pane_span(dctx, span, x, y, n, phase);
|
||||
redraw_draw_pane_span(dctx, span, x, y, n);
|
||||
break;
|
||||
case REDRAW_SPAN_BORDER:
|
||||
case REDRAW_SPAN_EMPTY:
|
||||
@@ -1668,55 +1444,24 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
||||
if (bottom > (int)scene->sy)
|
||||
bottom = scene->sy;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* Only erase cells this pane currently owns in the scene, not its raw
|
||||
* geometry - a floating pane may be occluding part of this pane's
|
||||
* rectangle, and erasing under it would leave those cells blank with
|
||||
* nothing to redraw them back in.
|
||||
*/
|
||||
if (flags & REDRAW_PANE) {
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
if (flags & REDRAW_PANE) {
|
||||
spans = &line->spans[REDRAW_SPAN_PANE];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.p.wp == wp) {
|
||||
image_redraw_start(&scene->c->tty,
|
||||
span->x, cy, span->width, 1);
|
||||
}
|
||||
if (span->data.p.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_IMAGES_AFTER; phase++) {
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
if (flags & REDRAW_PANE) {
|
||||
spans = &line->spans[REDRAW_SPAN_PANE];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.p.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy, span->x,
|
||||
span->width, phase);
|
||||
}
|
||||
}
|
||||
if (phase == REDRAW_TEXT &&
|
||||
(flags & REDRAW_PANE_SCROLLBAR)) {
|
||||
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.sb.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy, span->x,
|
||||
span->width, phase);
|
||||
}
|
||||
if (flags & REDRAW_PANE_SCROLLBAR) {
|
||||
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.sb.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1732,17 +1477,13 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
struct redraw_span *span;
|
||||
u_int y, cy, type;
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_IMAGES_AFTER; phase++) {
|
||||
for (y = 0; y < scene->sy; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE)
|
||||
continue;
|
||||
for (y = 0; y < scene->sy; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
if (!REDRAW_IS_ALL(flags)) {
|
||||
switch (type) {
|
||||
case REDRAW_SPAN_PANE:
|
||||
@@ -1779,9 +1520,7 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
}
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry)
|
||||
redraw_draw_span(dctx, span, cy, span->x, span->width,
|
||||
phase);
|
||||
}
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1802,8 +1541,7 @@ redraw_draw_menu_lines(struct redraw_draw_ctx *dctx)
|
||||
else
|
||||
cy = y;
|
||||
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry)
|
||||
redraw_draw_span(dctx, span, cy, span->x, span->width,
|
||||
REDRAW_TEXT);
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1883,24 +1621,6 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx,
|
||||
dctx->flags |= REDRAW_ISOLATES;
|
||||
}
|
||||
|
||||
/* Build a pane prompt into a one-line screen. */
|
||||
static void
|
||||
redraw_make_pane_prompt(struct window_pane *wp, struct screen *screen)
|
||||
{
|
||||
struct screen_write_ctx ctx;
|
||||
struct prompt_draw_data pdd;
|
||||
|
||||
screen_init(screen, wp->sx, 1, 0);
|
||||
screen_write_start(&ctx, screen);
|
||||
pdd.ctx = &ctx;
|
||||
pdd.cursor_x = &wp->prompt_cx;
|
||||
pdd.area_x = 0;
|
||||
pdd.area_width = wp->sx;
|
||||
pdd.prompt_line = 0;
|
||||
prompt_draw(wp->prompt, &pdd);
|
||||
screen_write_stop(&ctx);
|
||||
}
|
||||
|
||||
/* Draw a pane's prompt over its content. */
|
||||
static void
|
||||
redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
@@ -1909,6 +1629,8 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
struct client *c = scene->c;
|
||||
struct tty *tty = &c->tty;
|
||||
struct screen screen;
|
||||
struct screen_write_ctx ctx;
|
||||
struct prompt_draw_data pdd;
|
||||
int ox = scene->ox, oy = scene->oy;
|
||||
int sx = scene->sx, sy = scene->sy;
|
||||
int line, cy, px, offset, width, wy;
|
||||
@@ -1941,7 +1663,16 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
if (px + width > sx)
|
||||
width = sx - px;
|
||||
|
||||
redraw_make_pane_prompt(wp, &screen);
|
||||
screen_init(&screen, wp->sx, 1, 0);
|
||||
screen_write_start(&ctx, &screen);
|
||||
pdd.ctx = &ctx;
|
||||
pdd.cursor_x = &wp->prompt_cx;
|
||||
pdd.area_x = 0;
|
||||
pdd.area_width = wp->sx;
|
||||
pdd.prompt_line = 0;
|
||||
prompt_draw(wp->prompt, &pdd);
|
||||
screen_write_stop(&ctx);
|
||||
|
||||
tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL);
|
||||
screen_free(&screen);
|
||||
}
|
||||
@@ -1973,8 +1704,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
redraw = status_prompt_redraw(c);
|
||||
else
|
||||
redraw = status_redraw(c);
|
||||
if (!redraw && (~c->flags & CLIENT_REDRAWSTATUSALWAYS) &&
|
||||
!REDRAW_IS_ALL(flags)) {
|
||||
if (!redraw && !REDRAW_IS_ALL(flags)) {
|
||||
flags &= ~REDRAW_STATUS;
|
||||
if (flags == 0)
|
||||
return;
|
||||
@@ -2008,8 +1738,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
else
|
||||
loop->flags &= ~PANE_NEWSTATUS;
|
||||
|
||||
width = redraw_pane_status_width(&dctx, loop,
|
||||
&first);
|
||||
width = redraw_pane_status_width(&dctx, loop, &first);
|
||||
if (width == 0)
|
||||
continue;
|
||||
|
||||
@@ -2040,28 +1769,13 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
tty_sync_start(tty);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (wp == NULL && (flags & REDRAW_PANE)) {
|
||||
y = 0;
|
||||
if (dctx.flags & REDRAW_STATUS_TOP)
|
||||
y = dctx.status_lines;
|
||||
image_redraw_start(tty, 0, y, scene->sx, scene->sy);
|
||||
}
|
||||
#endif
|
||||
tty_sync_start(tty); /* end in server_client_reset_state */
|
||||
tty_update_mode(tty, tty->mode & ~CURSOR_MODES, NULL);
|
||||
|
||||
if (wp != NULL)
|
||||
redraw_draw_pane_lines(&dctx, wp, flags);
|
||||
else
|
||||
redraw_draw_lines(&dctx, flags);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if ((flags & REDRAW_PANE) &&
|
||||
(image_backend_flags(tty) &
|
||||
(IMAGE_BACKEND_GRAPHICAL|IMAGE_BACKEND_SCROLLS)) ==
|
||||
IMAGE_BACKEND_GRAPHICAL)
|
||||
redraw_damage_window_pane_status(scene->w);
|
||||
#endif
|
||||
|
||||
if (flags & REDRAW_PANE) {
|
||||
if (wp != NULL)
|
||||
@@ -2100,7 +1814,15 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
c->overlay_draw(c, c->overlay_data);
|
||||
|
||||
tty_reset(tty);
|
||||
tty_sync_end(tty);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (wp != NULL)
|
||||
tty_draw_images(c, wp);
|
||||
else {
|
||||
TAILQ_FOREACH(loop, &scene->w->panes, entry)
|
||||
tty_draw_images(c, loop);
|
||||
}
|
||||
#endif
|
||||
|
||||
log_debug("%s: finished @%u redraw", c->name, scene->w->id);
|
||||
}
|
||||
@@ -2153,7 +1875,7 @@ redraw_screen(struct client *c)
|
||||
} else {
|
||||
if (c->flags & CLIENT_REDRAWBORDERS)
|
||||
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
|
||||
if (c->flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))
|
||||
if (c->flags & CLIENT_REDRAWSTATUS)
|
||||
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
|
||||
if (c->flags & CLIENT_REDRAWOVERLAY)
|
||||
flags |= REDRAW_OVERLAY;
|
||||
@@ -2181,230 +1903,3 @@ redraw_pane_scrollbar(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR);
|
||||
}
|
||||
|
||||
/*
|
||||
* A REDRAW_SPAN_STATUS span within a damaged rectangle needs its content
|
||||
* rebuilt and force-drawn regardless of whether that content has logically
|
||||
* changed. window_make_pane_status()'s grid_compare() only tells us
|
||||
* whether the *content* changed, not whether the physical cells were
|
||||
* disturbed by something else (e.g. a floating pane sliding across this
|
||||
* row) - and being inside a damage rectangle already proves that happened.
|
||||
* Without this, redraw_draw_span() silently skips REDRAW_SPAN_STATUS spans
|
||||
* whenever PANE_NEWSTATUS is not set, leaving a pane's border-status title
|
||||
* blank until some unrelated redraw happens to touch it (e.g. a focus
|
||||
* change or window resize).
|
||||
*/
|
||||
static void
|
||||
redraw_damage_refresh_status(struct redraw_draw_ctx *dctx,
|
||||
struct window_pane *wp)
|
||||
{
|
||||
struct redraw_span *first;
|
||||
u_int width;
|
||||
|
||||
if (wp->flags & PANE_NEWSTATUS)
|
||||
return;
|
||||
width = redraw_pane_status_width(dctx, wp, &first);
|
||||
if (width == 0)
|
||||
return;
|
||||
window_make_pane_status(wp, dctx->scene->c, width, first);
|
||||
wp->flags |= PANE_NEWSTATUS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Grow a clipped span range by one cell on either edge that isn't already at
|
||||
* the span's own boundary. A clip edge that lands mid-character (this is a
|
||||
* damage rectangle, so its edges are geometric and have no idea what's in
|
||||
* the grid) may be sitting on the second, padding half of a wide character
|
||||
* whose first half falls just outside the requested range - growing by one
|
||||
* cell is always enough to pull the whole character back in, since no grid
|
||||
* cell is ever wider than two columns, and clamping to the span's own x and
|
||||
* width keeps this from bleeding into a neighbouring span.
|
||||
*/
|
||||
static void
|
||||
redraw_damage_grow_span_clip(struct redraw_span *span, u_int *xp, u_int *endp)
|
||||
{
|
||||
if (*xp > span->x)
|
||||
(*xp)--;
|
||||
if (*endp < span->x + span->width)
|
||||
(*endp)++;
|
||||
}
|
||||
|
||||
/* Recompose a pane's prompt over a damaged section of its display row. */
|
||||
static void
|
||||
redraw_damage_draw_pane_prompt(struct redraw_draw_ctx *dctx,
|
||||
struct redraw_span *span, u_int y, u_int x, u_int n)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct window_pane *wp = span->data.p.wp;
|
||||
struct tty *tty = &scene->c->tty;
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *rr;
|
||||
struct screen screen;
|
||||
u_int i, px, width, prompt_y;
|
||||
|
||||
if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0)
|
||||
return;
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
prompt_y = 0;
|
||||
else
|
||||
prompt_y = wp->sy - 1;
|
||||
if (span->data.p.py != prompt_y)
|
||||
return;
|
||||
|
||||
redraw_make_pane_prompt(wp, &screen);
|
||||
r = tty_check_overlay_range(tty, x, y, n);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
rr = &r->ranges[i];
|
||||
if (rr->nx == 0)
|
||||
continue;
|
||||
px = span->data.p.px + (rr->px - span->x);
|
||||
if (px >= screen_size_x(&screen))
|
||||
continue;
|
||||
width = rr->nx;
|
||||
if (width > screen_size_x(&screen) - px)
|
||||
width = screen_size_x(&screen) - px;
|
||||
tty_draw_line(tty, &screen, px, 0, width, rr->px, y, NULL);
|
||||
}
|
||||
screen_free(&screen);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compose exactly the cells within a damaged rectangle (already in this
|
||||
* client's own scene coordinates), rather than a whole pane. For each row
|
||||
* in range, every span of every type whose x-range intersects the
|
||||
* rectangle is drawn restricted to just the intersected sub-range.
|
||||
*/
|
||||
static void
|
||||
redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||
u_int sx, u_int sy, int skip_images)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct redraw_line *line;
|
||||
struct redraw_spans *spans;
|
||||
struct redraw_span *span;
|
||||
u_int cy, yy, clip_x, clip_end, type;
|
||||
|
||||
if (x >= scene->sx || y >= scene->sy)
|
||||
return;
|
||||
if (x + sx > scene->sx)
|
||||
sx = scene->sx - x;
|
||||
if (y + sy > scene->sy)
|
||||
sy = scene->sy - y;
|
||||
if (sx == 0 || sy == 0)
|
||||
return;
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_IMAGES_AFTER; phase++) {
|
||||
if (skip_images && phase != REDRAW_TEXT)
|
||||
continue;
|
||||
for (yy = y; yy < y + sy; yy++) {
|
||||
line = &scene->lines[yy];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + yy;
|
||||
else
|
||||
cy = yy;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE)
|
||||
continue;
|
||||
if (type == REDRAW_SPAN_STATUS)
|
||||
continue;
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
clip_x = (span->x > x) ? span->x : x;
|
||||
clip_end = (span->x + span->width < x + sx) ?
|
||||
span->x + span->width : x + sx;
|
||||
if (clip_end <= clip_x)
|
||||
continue;
|
||||
if (type == REDRAW_SPAN_STATUS) {
|
||||
redraw_damage_refresh_status(dctx,
|
||||
span->data.st.wp);
|
||||
}
|
||||
redraw_damage_grow_span_clip(span, &clip_x,
|
||||
&clip_end);
|
||||
redraw_draw_span(dctx, span, cy, clip_x,
|
||||
clip_end - clip_x, phase);
|
||||
if (phase == REDRAW_TEXT && type == REDRAW_SPAN_PANE) {
|
||||
redraw_damage_draw_pane_prompt(dctx,
|
||||
span, cy, clip_x,
|
||||
clip_end - clip_x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* SIXEL image output may disturb status cells; compose them last. */
|
||||
for (yy = y; yy < y + sy; yy++) {
|
||||
line = &scene->lines[yy];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + yy;
|
||||
else
|
||||
cy = yy;
|
||||
spans = &line->spans[REDRAW_SPAN_STATUS];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
clip_x = (span->x > x) ? span->x : x;
|
||||
clip_end = (span->x + span->width < x + sx) ?
|
||||
span->x + span->width : x + sx;
|
||||
if (clip_end <= clip_x)
|
||||
continue;
|
||||
redraw_damage_refresh_status(dctx, span->data.st.wp);
|
||||
redraw_damage_grow_span_clip(span, &clip_x, &clip_end);
|
||||
redraw_draw_span(dctx, span, cy, clip_x, clip_end - clip_x,
|
||||
REDRAW_TEXT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Consume a client's window's pending damage by composing exactly the
|
||||
* damaged cells, after clipping each rectangle to what this client can see
|
||||
* and translating it into this client's own scene coordinates.
|
||||
*
|
||||
* Unlike redraw_pane(), this does not redraw a whole pane's worth of cells
|
||||
* for a small disturbance - only the cells within the (clipped) rectangle
|
||||
* are touched, via redraw_draw_damage_rect().
|
||||
*/
|
||||
void
|
||||
redraw_client_damage(struct client *c)
|
||||
{
|
||||
struct window *w = c->session->curw->window;
|
||||
struct redraw_scene *scene;
|
||||
struct redraw_draw_ctx dctx;
|
||||
struct redraw_damage *rd;
|
||||
u_int ox, oy, sx, sy, x0, y0, x1, y1;
|
||||
int skip_images;
|
||||
|
||||
if (TAILQ_EMPTY(&w->damage))
|
||||
return;
|
||||
|
||||
scene = redraw_get_scene(c);
|
||||
if (scene == NULL)
|
||||
return;
|
||||
redraw_set_draw_context(&dctx, scene);
|
||||
redraw_get_window_offset(c, &ox, &oy, &sx, &sy);
|
||||
|
||||
tty_sync_start(&c->tty);
|
||||
tty_update_mode(&c->tty, c->tty.mode & ~CURSOR_MODES, NULL);
|
||||
|
||||
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||
skip_images = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if ((rd->flags & REDRAW_DAMAGE_SCROLL) &&
|
||||
(image_backend_flags(&c->tty) & IMAGE_BACKEND_SCROLLS) &&
|
||||
c->tty.image_scroll_window == w &&
|
||||
c->tty.image_scroll_epoch == w->image_scroll_epoch &&
|
||||
!c->tty.image_scroll_failed)
|
||||
skip_images = 1;
|
||||
#endif
|
||||
x0 = (rd->x > ox) ? rd->x : ox;
|
||||
y0 = (rd->y > oy) ? rd->y : oy;
|
||||
x1 = (rd->x + rd->sx < ox + sx) ? rd->x + rd->sx : ox + sx;
|
||||
y1 = (rd->y + rd->sy < oy + sy) ? rd->y + rd->sy : oy + sy;
|
||||
if (x0 >= x1 || y0 >= y1)
|
||||
continue;
|
||||
log_debug("%s: %s composing damage %u,%u %ux%u", __func__,
|
||||
c->name, x0 - ox, y0 - oy, x1 - x0, y1 - y0);
|
||||
redraw_draw_damage_rect(&dctx, x0 - ox, y0 - oy, x1 - x0,
|
||||
y1 - y0, skip_images);
|
||||
}
|
||||
}
|
||||
|
||||
196
screen-write.c
196
screen-write.c
@@ -34,10 +34,6 @@ static void screen_write_collect_clear(struct screen_write_ctx *, u_int,
|
||||
static void screen_write_collect_scroll(struct screen_write_ctx *, u_int);
|
||||
static void screen_write_collect_flush(struct screen_write_ctx *, int,
|
||||
const char *);
|
||||
#ifdef ENABLE_IMAGES
|
||||
static void screen_write_image_damage(struct screen_write_ctx *, u_int,
|
||||
u_int, u_int, u_int);
|
||||
#endif
|
||||
static int screen_write_overwrite(struct screen_write_ctx *,
|
||||
struct grid_cell *, u_int);
|
||||
static int screen_write_combine(struct screen_write_ctx *,
|
||||
@@ -124,20 +120,14 @@ screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy)
|
||||
evtimer_add(&w->offset_timer, &tv);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when a write could not be applied directly to the terminal and
|
||||
* needs a redraw instead. Report damage for the requested rows. wp->yoff is
|
||||
* already adjusted past any top pane-border-status row, so wp->yoff + py is
|
||||
* the correct window-coordinate row.
|
||||
*/
|
||||
/* Do a full redraw. */
|
||||
static void
|
||||
screen_write_redraw_cb(const struct tty_ctx *ttyctx, u_int py, u_int ny)
|
||||
screen_write_redraw_cb(const struct tty_ctx *ttyctx)
|
||||
{
|
||||
struct window_pane *wp = ttyctx->arg;
|
||||
|
||||
if (wp == NULL)
|
||||
return;
|
||||
redraw_damage_window(wp->window, wp->xoff, wp->yoff + py, wp->sx, ny);
|
||||
if (wp != NULL)
|
||||
wp->flags |= PANE_REDRAW;
|
||||
}
|
||||
|
||||
/* Update context for client. */
|
||||
@@ -381,19 +371,6 @@ screen_write_init(struct screen_write_ctx *ctx, struct screen *s)
|
||||
ctx->bg = 8;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Damage temporal images when application input writes text cells. */
|
||||
static void
|
||||
screen_write_image_damage(struct screen_write_ctx *ctx, u_int x, u_int y,
|
||||
u_int sx, u_int sy)
|
||||
{
|
||||
image_redraw_area(ctx, x, y, sx, sy);
|
||||
if (ctx->flags & SCREEN_WRITE_INPUT)
|
||||
image_grid_damage(ctx->s->grid, x, ctx->s->grid->hsize + y,
|
||||
sx, sy);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Initialize writing with a pane. */
|
||||
void
|
||||
screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp,
|
||||
@@ -1354,11 +1331,9 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
|
||||
memcpy(&gc, &grid_default_cell, sizeof gc);
|
||||
utf8_set(&gc.data, 'E');
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
if (ctx->flags & SCREEN_WRITE_INPUT)
|
||||
image_grid_damage(s->grid, 0, s->grid->hsize,
|
||||
screen_size_x(s), screen_size_y(s));
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
for (yy = 0; yy < screen_size_y(s); yy++) {
|
||||
@@ -1403,8 +1378,9 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1443,8 +1419,9 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1483,8 +1460,9 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1517,8 +1495,9 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
if (ny == 0)
|
||||
ny = 1;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
if (s->cy < s->rupper || s->cy > s->rlower) {
|
||||
@@ -1584,8 +1563,9 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
if (ny == 0)
|
||||
ny = 1;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
if (s->cy < s->rupper || s->cy > s->rlower) {
|
||||
@@ -1656,8 +1636,9 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
|
||||
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
flags = gl->flags & GRID_LINE_OSC133_FLAGS;
|
||||
@@ -1694,8 +1675,9 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
|
||||
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg);
|
||||
@@ -1720,8 +1702,9 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
if (s->cx > sx - 1)
|
||||
@@ -1773,8 +1756,9 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_scroll(ctx, 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
|
||||
@@ -1824,6 +1808,9 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_line *gl;
|
||||
#ifdef ENABLE_SIXEL
|
||||
int redraw = 0;
|
||||
#endif
|
||||
u_int rupper = s->rupper, rlower = s->rlower;
|
||||
|
||||
gl = grid_get_line(gd, gd->hsize + s->cy);
|
||||
@@ -1844,12 +1831,13 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (rlower == screen_size_y(s) - 1)
|
||||
image_redraw_scroll(ctx, 1);
|
||||
redraw = image_scroll_up(s, 1);
|
||||
else
|
||||
image_redraw_area(ctx, 0, rupper, screen_size_x(s),
|
||||
rlower - rupper);
|
||||
redraw = image_check_line(s, rupper, rlower - rupper);
|
||||
if (redraw && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
|
||||
@@ -1875,8 +1863,9 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
ctx->bg = bg;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_scroll(ctx, lines);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_scroll_up(s, lines) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
for (i = 0; i < lines; i++) {
|
||||
@@ -1903,8 +1892,9 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
else if (lines > s->rlower - s->rupper + 1)
|
||||
lines = s->rlower - s->rupper + 1;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_scroll(ctx, lines);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
for (i = 0; i < lines; i++)
|
||||
@@ -1943,8 +1933,9 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2023,8 +2014,9 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, 0, screen_size_x(s), s->cy + 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2095,8 +2087,9 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2162,7 +2155,7 @@ screen_write_fullredraw(struct screen_write_ctx *ctx)
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||
if (ttyctx.redraw_cb != NULL)
|
||||
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||
ttyctx.redraw_cb(&ttyctx);
|
||||
}
|
||||
|
||||
/* Trim collected items. */
|
||||
@@ -2599,8 +2592,9 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
screen_write_image_damage(ctx, s->cx, s->cy, ci->used, 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x,
|
||||
@@ -2730,11 +2724,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
return;
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 0);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Input semantics decide which image layers this text damages. */
|
||||
screen_write_image_damage(ctx, s->cx, s->cy, width, 1);
|
||||
#endif
|
||||
|
||||
/* Handle overwriting of UTF-8 characters. */
|
||||
gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
|
||||
if (gl->flags & GRID_LINE_EXTENDED) {
|
||||
@@ -2962,9 +2951,6 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
force_wide = 0;
|
||||
|
||||
/* Set the new cell. */
|
||||
#ifdef ENABLE_IMAGES
|
||||
screen_write_image_damage(ctx, cx - n, cy, n, 1);
|
||||
#endif
|
||||
grid_view_set_cell(gd, cx - n, cy, &last);
|
||||
if (force_wide)
|
||||
grid_view_set_padding(gd, cx - 1, cy, last.bg);
|
||||
@@ -3100,21 +3086,63 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len,
|
||||
tty_write(tty_cmd_rawstring, &ttyctx);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Write a SIXEL image. */
|
||||
void
|
||||
screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
|
||||
u_int bg)
|
||||
{
|
||||
struct image *im;
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct tty_ctx ttyctx;
|
||||
u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines;
|
||||
struct sixel_image *new;
|
||||
|
||||
im = sixel_to_image(si);
|
||||
if (im == NULL) {
|
||||
sixel_free(si);
|
||||
if (screen_size_y(s) == 1)
|
||||
return;
|
||||
|
||||
sixel_size_in_cells(si, &x, &y);
|
||||
if (x > screen_size_x(s) || y > screen_size_y(s) - 1) {
|
||||
if (x > screen_size_x(s) - cx)
|
||||
sx = screen_size_x(s) - cx;
|
||||
else
|
||||
sx = x;
|
||||
if (y > screen_size_y(s) - 1)
|
||||
sy = screen_size_y(s) - 1;
|
||||
else
|
||||
sy = y;
|
||||
new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1);
|
||||
sixel_free(si);
|
||||
if (new == NULL)
|
||||
return;
|
||||
sixel_size_in_cells(new, &x, &y);
|
||||
si = new;
|
||||
}
|
||||
image_write_sixel(ctx, im, bg);
|
||||
image_free(image_get_id(im));
|
||||
|
||||
sy = screen_size_y(s) - cy;
|
||||
if (sy <= y) {
|
||||
lines = y - sy + 1;
|
||||
if (image_scroll_up(s, lines) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
for (i = 0; i < lines; i++) {
|
||||
grid_view_scroll_region_up(gd, 0, screen_size_y(s) - 1,
|
||||
bg);
|
||||
screen_write_collect_scroll(ctx, bg);
|
||||
}
|
||||
ctx->scrolled += lines;
|
||||
if (lines > cy)
|
||||
screen_write_cursormove(ctx, -1, 0, 0);
|
||||
else
|
||||
screen_write_cursormove(ctx, -1, cy - lines, 0);
|
||||
}
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 0);
|
||||
ttyctx.image = image_store(s, si);
|
||||
|
||||
tty_write(tty_cmd_sixelimage, &ttyctx);
|
||||
|
||||
screen_write_cursormove(ctx, 0, cy + y, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3147,7 +3175,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||
if (ttyctx.redraw_cb != NULL)
|
||||
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||
ttyctx.redraw_cb(&ttyctx);
|
||||
}
|
||||
|
||||
/* Turn alternate screen off. */
|
||||
@@ -3172,5 +3200,5 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||
if (ttyctx.redraw_cb != NULL)
|
||||
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||
ttyctx.redraw_cb(&ttyctx);
|
||||
}
|
||||
|
||||
50
screen.c
50
screen.c
@@ -93,6 +93,11 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
|
||||
s->tabs = NULL;
|
||||
s->sel = NULL;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
TAILQ_INIT(&s->images);
|
||||
TAILQ_INIT(&s->saved_images);
|
||||
#endif
|
||||
|
||||
s->write_list = NULL;
|
||||
s->hyperlinks = NULL;
|
||||
|
||||
@@ -126,6 +131,11 @@ screen_reinit(struct screen *s, int check)
|
||||
|
||||
screen_clear_selection(s);
|
||||
screen_free_titles(s);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
#endif
|
||||
|
||||
screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0);
|
||||
screen_reset_hyperlinks(s);
|
||||
}
|
||||
@@ -144,6 +154,10 @@ screen_reset_hyperlinks(struct screen *s)
|
||||
void
|
||||
screen_free(struct screen *s)
|
||||
{
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
free(s->sel);
|
||||
free(s->tabs);
|
||||
free(s->path);
|
||||
@@ -160,6 +174,17 @@ screen_free(struct screen *s)
|
||||
hyperlinks_free(s->hyperlinks);
|
||||
screen_free_titles(s);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/*
|
||||
* Images saved when entering the alternate screen stay linked in the
|
||||
* global list; move them back so they are freed and unlinked here, or
|
||||
* a later eviction would write through this freed screen.
|
||||
*/
|
||||
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
|
||||
TAILQ_FOREACH(im, &s->images, entry)
|
||||
im->list = &s->images;
|
||||
image_free_all(s);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Reset tabs to default, eight spaces apart. */
|
||||
@@ -352,6 +377,10 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow,
|
||||
if (sy != screen_size_y(s))
|
||||
screen_resize_y(s, sy, eat_empty, &cy);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
#endif
|
||||
|
||||
if (reflow)
|
||||
screen_reflow(s, sx, &cx, &cy, cursor);
|
||||
|
||||
@@ -663,6 +692,10 @@ int
|
||||
screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
{
|
||||
u_int sx, sy;
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
if (SCREEN_IS_ALTERNATE(s))
|
||||
return 0;
|
||||
sx = screen_size_x(s);
|
||||
@@ -676,6 +709,12 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
}
|
||||
memcpy(&s->saved_cell, gc, sizeof s->saved_cell);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
TAILQ_CONCAT(&s->saved_images, &s->images, entry);
|
||||
TAILQ_FOREACH(im, &s->saved_images, entry)
|
||||
im->list = &s->saved_images;
|
||||
#endif
|
||||
|
||||
grid_view_clear(s->grid, 0, 0, sx, sy, 8);
|
||||
|
||||
s->saved_flags = s->grid->flags;
|
||||
@@ -689,6 +728,10 @@ int
|
||||
screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
{
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If the current size is different, temporarily resize to the old size
|
||||
* before copying back.
|
||||
@@ -731,6 +774,13 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
grid_destroy(s->saved_grid);
|
||||
s->saved_grid = NULL;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
|
||||
TAILQ_FOREACH(im, &s->images, entry)
|
||||
im->list = &s->images;
|
||||
#endif
|
||||
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
s->cx = screen_size_x(s) - 1;
|
||||
if (s->cy > screen_size_y(s) - 1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server-client.c,v 1.507 2026/08/24 21:17:19 nicm Exp $ */
|
||||
/* $OpenBSD: server-client.c,v 1.510 2026/09/01 19:50:58 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -473,33 +473,7 @@ server_client_set_session(struct client *c, struct session *s)
|
||||
tty_update_client_offset(c);
|
||||
status_timer_start(c);
|
||||
server_client_fire_session_changed(c, old);
|
||||
|
||||
/*
|
||||
* A full redraw is only needed if the client's session or
|
||||
* current window actually changed - not if this merely
|
||||
* confirmed the client is still looking at the same window
|
||||
* (as happens when switch-client -t targets a pane in the
|
||||
* already-current window, e.g. clicking a pane name in a
|
||||
* second #{P:} status line: the default MouseDown1Status
|
||||
* binding resolves that click to switch-client -t=, which
|
||||
* reaches here regardless of whether anything besides the
|
||||
* active pane changed). Redrawing unconditionally here
|
||||
* forced a full window redraw for what should have been
|
||||
* just an active-pane change, already handled narrowly by
|
||||
* window_set_active_pane() and window_redraw_active_switch()
|
||||
* before this is reached.
|
||||
*
|
||||
* old and s may be the same session object, whose curw was
|
||||
* already updated to the new window before this function was
|
||||
* called - old->curw and s->curw would then read the same,
|
||||
* already-current value, so comparing them can never detect
|
||||
* a same-session window change. Compare against the client's
|
||||
* own cached scene instead, which only reflects what it has
|
||||
* actually drawn.
|
||||
*/
|
||||
if (old == NULL || old != s ||
|
||||
!redraw_client_has_window(c, s->curw->window))
|
||||
server_redraw_client(c);
|
||||
server_redraw_client(c);
|
||||
}
|
||||
|
||||
server_check_unattached();
|
||||
@@ -552,6 +526,8 @@ server_client_lost(struct client *c)
|
||||
free(c->title);
|
||||
free(c->path);
|
||||
free((void *)c->cwd);
|
||||
free(c->exit_session);
|
||||
free(c->exit_message);
|
||||
|
||||
evtimer_del(&c->repeat_timer);
|
||||
evtimer_del(&c->click_timer);
|
||||
@@ -1486,19 +1462,8 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
||||
/*
|
||||
* Mouse drag is in progress, so fire the callback (now that
|
||||
* the mouse event is valid).
|
||||
*
|
||||
* Start a synchronized-output region here rather than
|
||||
* leaving it to whatever redraw eventually follows: a drag
|
||||
* callback may write directly via the pane's fast path
|
||||
* immediately, with any correction only arriving later via
|
||||
* redraw_client_damage(), which opens its own sync region.
|
||||
* Since tty_sync_end() is only called once, at the very end
|
||||
* of this client's pass in server_client_reset_state(),
|
||||
* starting it here merges both into one atomic terminal
|
||||
* update instead of two visible frames.
|
||||
*/
|
||||
if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) {
|
||||
tty_sync_start(&c->tty);
|
||||
c->tty.mouse_drag_update(c, m);
|
||||
goto out;
|
||||
}
|
||||
@@ -1534,6 +1499,10 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
||||
TAILQ_EMPTY(&wp->modes))
|
||||
goto forward_key;
|
||||
|
||||
/* Focus events are not keys and cannot be bound. */
|
||||
if (key == KEYC_FOCUS_IN || key == KEYC_FOCUS_OUT)
|
||||
goto forward_key;
|
||||
|
||||
/*
|
||||
* Work out the current key table. If the pane is in a mode, use
|
||||
* the mode table instead of the default key table.
|
||||
@@ -1931,12 +1900,7 @@ server_client_loop(void)
|
||||
|
||||
/*
|
||||
* Any windows will have been redrawn as part of clients, so clear
|
||||
* their flags now. A client whose redraw was deferred this pass
|
||||
* (waiting for outstanding tty output to drain) has already
|
||||
* escalated to CLIENT_REDRAWWINDOW or CLIENT_REDRAWSCROLLBARS in
|
||||
* server_client_check_redraw() to cover whatever it is about to
|
||||
* lose here, so PANE_REDRAW/PANE_REDRAWSCROLLBAR and window damage
|
||||
* can simply be cleared unconditionally.
|
||||
* their flags now.
|
||||
*/
|
||||
RB_FOREACH(w, windows, &windows) {
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
@@ -1947,8 +1911,6 @@ server_client_loop(void)
|
||||
wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR|
|
||||
PANE_ACTIVITY);
|
||||
}
|
||||
redraw_free_damage(w);
|
||||
|
||||
check_window_name(w);
|
||||
}
|
||||
|
||||
@@ -2282,7 +2244,7 @@ server_client_reset_state(struct client *c)
|
||||
cy += status_line_size(c);
|
||||
}
|
||||
|
||||
if ((pane_mode & MODE_SYNC) || !cursor)
|
||||
if (!cursor)
|
||||
mode &= ~MODE_CURSOR;
|
||||
}
|
||||
} else if (c->overlay_mode == NULL || s == NULL)
|
||||
@@ -2290,6 +2252,10 @@ server_client_reset_state(struct client *c)
|
||||
if (~pane_mode & MODE_SYNC) {
|
||||
log_debug("%s: cursor to %u,%u", __func__, cx, cy);
|
||||
tty_cursor(tty, cx, cy);
|
||||
} else {
|
||||
mode &= ~CURSOR_MODES;
|
||||
mode |= tty->mode & CURSOR_MODES;
|
||||
s = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2451,8 +2417,6 @@ server_client_check_exit(struct client *c, int force)
|
||||
proc_send(c->peer, c->exit_msgtype, -1, name, strlen(name) + 1);
|
||||
break;
|
||||
}
|
||||
free(c->exit_session);
|
||||
free(c->exit_message);
|
||||
}
|
||||
|
||||
/* Redraw timer callback. */
|
||||
@@ -2495,8 +2459,6 @@ server_client_any_pane_redraw(struct client *c)
|
||||
|
||||
if (c->flags & CLIENT_REDRAWWINDOW)
|
||||
return (1);
|
||||
if (!TAILQ_EMPTY(&w->damage))
|
||||
return (1);
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (wp->flags & (PANE_REDRAW|PANE_REDRAWSCROLLBAR))
|
||||
return (1);
|
||||
@@ -2542,12 +2504,7 @@ server_client_check_redraw(struct client *c)
|
||||
/*
|
||||
* If there is outstanding data, defer the redraw until it has been
|
||||
* consumed. We can just add a timer to get out of the event loop and
|
||||
* end up back here. server_client_loop() clears PANE_REDRAW,
|
||||
* PANE_REDRAWSCROLLBAR and window damage unconditionally every pass,
|
||||
* so escalate to a coarser, persistent client flag that survives
|
||||
* that clear and forces a full catch-up redraw once this client is
|
||||
* unblocked, rather than trying to keep the fine-grained state
|
||||
* around for a retry.
|
||||
* end up back here.
|
||||
*/
|
||||
n = EVBUFFER_LENGTH(tty->out);
|
||||
if (n != 0 || (tty->flags & TTY_BLOCK)) {
|
||||
@@ -2561,8 +2518,6 @@ server_client_check_redraw(struct client *c)
|
||||
log_debug("redraw timer started");
|
||||
evtimer_add(&ev, &tv);
|
||||
}
|
||||
if (!TAILQ_EMPTY(&w->damage))
|
||||
c->flags |= CLIENT_REDRAWWINDOW;
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (wp->flags & PANE_REDRAW) {
|
||||
c->flags |= CLIENT_REDRAWWINDOW;
|
||||
@@ -2596,19 +2551,6 @@ server_client_check_redraw(struct client *c)
|
||||
redraw_pane_scrollbar(c, wp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Window damage is also what makes server_client_any_pane_
|
||||
* redraw() decide a redraw is needed at all, independently of
|
||||
* any CLIENT_ALLREDRAWFLAGS bit. Every current damage source
|
||||
* happens to set one of those flags too, so the block below
|
||||
* always consumes it - but consume it here too in case that
|
||||
* ever stops holding, since server_client_loop() clears
|
||||
* window damage unconditionally every pass regardless of
|
||||
* whether it was actually drawn.
|
||||
*/
|
||||
if (!TAILQ_EMPTY(&w->damage) && (~c->flags & CLIENT_ALLREDRAWFLAGS))
|
||||
redraw_client_damage(c);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2622,7 +2564,6 @@ server_client_check_redraw(struct client *c)
|
||||
}
|
||||
server_client_set_progress_bar(c);
|
||||
redraw_screen(c);
|
||||
redraw_client_damage(c);
|
||||
}
|
||||
|
||||
/* Put the tty back how it was. */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server-fn.c,v 1.151 2026/08/20 09:19:24 nicm Exp $ */
|
||||
/* $OpenBSD: server-fn.c,v 1.152 2026/09/03 19:12:36 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -379,9 +379,9 @@ server_destroy_pane(struct window_pane *wp, int notify)
|
||||
wp->pipe_fd = -1;
|
||||
}
|
||||
|
||||
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
|
||||
if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY))
|
||||
if (~wp->flags & PANE_STATUSREADY)
|
||||
return;
|
||||
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
|
||||
switch (remain_on_exit) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
4
sort.c
4
sort.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: sort.c,v 1.9 2026/06/29 07:45:09 nicm Exp $ */
|
||||
/* $OpenBSD: sort.c,v 1.10 2026/08/25 07:23:30 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Dane Jensen <dhcjensen@gmail.com>
|
||||
@@ -32,7 +32,7 @@ sort_qsort(void *l, u_int len, u_int size, int (*cmp)(const void *,
|
||||
u_int i;
|
||||
void *tmp, **ll;
|
||||
|
||||
if (sort_crit->order == SORT_END)
|
||||
if (len < 2 || sort_crit->order == SORT_END)
|
||||
return;
|
||||
|
||||
if (sort_crit->order == SORT_ORDER) {
|
||||
|
||||
6
spawn.c
6
spawn.c
@@ -701,7 +701,7 @@ spawn_editor_finish(struct window_pane *wp)
|
||||
|
||||
struct spawn_editor_state *
|
||||
spawn_editor(struct client *c, const char *buf, size_t len,
|
||||
spawn_finish_edit_cb cb, void *arg)
|
||||
const char *editor, spawn_finish_edit_cb cb, void *arg)
|
||||
{
|
||||
struct spawn_editor_state *es;
|
||||
struct spawn_context sc = { 0 };
|
||||
@@ -715,13 +715,13 @@ spawn_editor(struct client *c, const char *buf, size_t len,
|
||||
FILE *f;
|
||||
char *cmd, *cause = NULL;
|
||||
char path[] = _PATH_TMP "tmux.XXXXXXXX";
|
||||
const char *editor;
|
||||
int fd;
|
||||
|
||||
if (w->modal != NULL)
|
||||
return (NULL);
|
||||
|
||||
editor = options_get_string(global_options, "editor");
|
||||
if (editor == NULL)
|
||||
editor = options_get_string(global_options, "editor");
|
||||
fd = mkstemp(path);
|
||||
if (fd == -1)
|
||||
return (NULL);
|
||||
|
||||
3
style.c
3
style.c
@@ -30,8 +30,7 @@
|
||||
|
||||
/* Default style. */
|
||||
static struct style style_default = {
|
||||
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0
|
||||
},
|
||||
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 },
|
||||
0,
|
||||
0,
|
||||
|
||||
|
||||
233
tmux.1
233
tmux.1
@@ -1,4 +1,4 @@
|
||||
.\" $OpenBSD: tmux.1,v 1.1158 2026/08/20 09:19:24 nicm Exp $
|
||||
.\" $OpenBSD: tmux.1,v 1.1164 2026/09/03 21:04:11 nicm Exp $
|
||||
.\"
|
||||
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
.\"
|
||||
@@ -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: August 20 2026 $
|
||||
.Dd $Mdocdate: September 3 2026 $
|
||||
.Dt TMUX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -2004,6 +2004,25 @@ Same as
|
||||
.Ic copy\-pipe
|
||||
but also exit copy mode.
|
||||
.It Xo
|
||||
.Ic copy\-pipe\-output
|
||||
.Op Fl aCP
|
||||
.Op Ar command
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the current OSC 133 command output, pipe it to
|
||||
.Ar command ,
|
||||
and clear the selection.
|
||||
.Ar prefix
|
||||
is used to name the new paste buffer.
|
||||
.It Xo
|
||||
.Ic copy\-output
|
||||
.Op Fl aCP
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the current OSC 133 command output and clear the selection.
|
||||
.Ar prefix
|
||||
is used to name the new paste buffer.
|
||||
.It Xo
|
||||
.Ic copy\-selection
|
||||
.Op Fl CP
|
||||
.Op Ar prefix
|
||||
@@ -2169,6 +2188,27 @@ Hide line numbers in copy mode.
|
||||
.Xc
|
||||
Toggle line numbers in copy mode.
|
||||
.It Xo
|
||||
.Ic collapse\-output
|
||||
.Op Fl a
|
||||
.Xc
|
||||
Collapse the output for the command on the cursor line.
|
||||
With
|
||||
.Fl a ,
|
||||
collapse all command output in the copy-mode buffer.
|
||||
Text before the first OSC 133 prompt is treated as an initial command output
|
||||
region.
|
||||
.It Xo
|
||||
.Ic expand\-output
|
||||
.Op Fl a
|
||||
.Xc
|
||||
Expand the output for the command on the cursor line.
|
||||
With
|
||||
.Fl a ,
|
||||
expand all command output in the copy-mode buffer.
|
||||
.Pp
|
||||
The Tab key toggles output for the command on the cursor line and S-Tab
|
||||
toggles all command output.
|
||||
.It Xo
|
||||
.Ic middle\-line
|
||||
(vi: M)
|
||||
(emacs: M\-r)
|
||||
@@ -2208,20 +2248,46 @@ Move to the end of the next word.
|
||||
.Xc
|
||||
Same as
|
||||
.Ic next\-word
|
||||
but use a space alone as the word separator.
|
||||
but treat all non-whitespace characters as part of a word.
|
||||
.It Xo
|
||||
.Ic next\-space\-end
|
||||
(vi: E)
|
||||
.Xc
|
||||
Same as
|
||||
.Ic next\-word\-end
|
||||
but use a space alone as the word separator.
|
||||
but treat all non-whitespace characters as part of a word.
|
||||
.It Xo
|
||||
.Ic other\-end
|
||||
(vi: o)
|
||||
.Xc
|
||||
Switch at which end of the selection the cursor sits.
|
||||
.It Xo
|
||||
.Ic open\-output
|
||||
.Op Fl a
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the current OSC 133 command output in an editor.
|
||||
If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise, the
|
||||
.Ic editor
|
||||
option is used.
|
||||
.It Xo
|
||||
.Ic open\-selection
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the current selection in an editor.
|
||||
If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise, the
|
||||
.Ic editor
|
||||
option is used.
|
||||
In emacs mode,
|
||||
.Ql e ,
|
||||
and in vi mode,
|
||||
.Ql M\-e ,
|
||||
opens the selection if one is present, otherwise the command output.
|
||||
.It Xo
|
||||
.Ic page\-down
|
||||
(vi: C\-f)
|
||||
(emacs: PageDown)
|
||||
@@ -2262,6 +2328,14 @@ Same as
|
||||
.Ic pipe
|
||||
but also exit copy mode.
|
||||
.It Xo
|
||||
.Ic pipe\-output
|
||||
.Op Fl a
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Pipe the current OSC 133 command output to
|
||||
.Ar command
|
||||
and clear the selection.
|
||||
.It Xo
|
||||
.Ic previous\-matching\-bracket
|
||||
(emacs: M\-C\-b)
|
||||
.Xc
|
||||
@@ -2289,7 +2363,7 @@ Move to the previous word.
|
||||
.Xc
|
||||
Same as
|
||||
.Ic previous\-word
|
||||
but use a space alone as the word separator.
|
||||
but treat all non-whitespace characters as part of a word.
|
||||
.It Xo
|
||||
.Ic rectangle\-on
|
||||
.Xc
|
||||
@@ -2456,6 +2530,16 @@ backward becomes forward).
|
||||
.Xc
|
||||
Select the current line.
|
||||
.It Xo
|
||||
.Ic select\-output
|
||||
.Op Fl a
|
||||
.Xc
|
||||
Select the current OSC 133 command output.
|
||||
If it is collapsed, it is expanded first.
|
||||
The default emacs binding is
|
||||
.Ql C\-o
|
||||
and the default vi binding is
|
||||
.Ql M\-o .
|
||||
.It Xo
|
||||
.Ic select\-word
|
||||
.Xc
|
||||
Select the current word.
|
||||
@@ -2494,6 +2578,12 @@ Toggle the visibility of the position indicator in the top right.
|
||||
Move to the top line.
|
||||
.El
|
||||
.Pp
|
||||
The output commands use OSC 133 output text from the C marker to the D marker.
|
||||
If there is no D marker, the output ends at the end of the copy-mode buffer.
|
||||
With
|
||||
.Fl a ,
|
||||
they use the entire copy-mode buffer instead.
|
||||
.Pp
|
||||
The search commands come in several varieties:
|
||||
.Ql search\-forward
|
||||
and
|
||||
@@ -2539,6 +2629,15 @@ Finding the beginning of command output requires the shell to emit an escape
|
||||
sequence (\e033]133;C\e033\e\e) to tell tmux where the output begins.
|
||||
If the shell does not send these escape sequences, these commands do nothing.
|
||||
.Pp
|
||||
The output commands operate on text from an OSC 133
|
||||
.Ql C
|
||||
marker up to its matching
|
||||
.Ql D
|
||||
marker, or the current end of the buffer if there is no
|
||||
.Ql D
|
||||
marker.
|
||||
They do nothing if the cursor is not associated with an output range.
|
||||
.Pp
|
||||
Copy commands may take an optional buffer prefix argument which is used
|
||||
to generate the buffer name (the default is
|
||||
.Ql buffer
|
||||
@@ -2574,8 +2673,8 @@ Word separators can be customized with the
|
||||
session option.
|
||||
Next word moves to the start of the next word, next word end to the end of the
|
||||
next word and previous word to the start of the previous word.
|
||||
The three next and previous space keys work similarly but use a space alone as
|
||||
the word separator.
|
||||
The three next and previous space keys work similarly but treat all
|
||||
non-whitespace characters as part of a word.
|
||||
Setting
|
||||
.Em word\-separators
|
||||
to the empty string makes next/previous word equivalent to next/previous space.
|
||||
@@ -2601,12 +2700,19 @@ The synopsis for the
|
||||
command is:
|
||||
.Bl -tag -width Ds
|
||||
.It Xo Ic copy\-mode
|
||||
.Op Fl dekHMqSu
|
||||
.Op Fl UcdekHMqSu
|
||||
.Op Fl s Ar src\-pane
|
||||
.Op Fl t Ar target\-pane
|
||||
.Xc
|
||||
Enter copy mode.
|
||||
.Pp
|
||||
.Fl c
|
||||
enters copy mode with OSC 133 command output collapsed.
|
||||
.Fl U
|
||||
enters copy mode with OSC 133 output controls shown and all output expanded.
|
||||
When output is collapsed, the prompt and entered command are shown and text
|
||||
from the output start marker to the next primary prompt marker is hidden.
|
||||
.Pp
|
||||
.Fl u
|
||||
enters copy mode and scrolls one page up and
|
||||
.Fl d
|
||||
@@ -2647,6 +2753,60 @@ bind PageDown copy\-mode \-ed
|
||||
kills the pane when the mode is exited.
|
||||
.El
|
||||
.Pp
|
||||
The following commands operate on OSC 133 command output. In copy mode, they
|
||||
use the output on the cursor line; otherwise, they use the most recent output
|
||||
in the target pane. If an output has a C marker but no D marker, it ends at the
|
||||
end of the pane buffer. If there is no OSC 133 output, they do nothing unless
|
||||
.Fl a
|
||||
is given, which uses the entire buffer.
|
||||
.Bl -tag -width Ds
|
||||
.It Xo Ic select\-output
|
||||
.Op Fl a
|
||||
.Op Fl t Ar target\-pane
|
||||
.Xc
|
||||
Enter copy mode if necessary and select the output.
|
||||
.It Xo Ic copy\-output
|
||||
.Op Fl aCP
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the output to a paste buffer.
|
||||
.It Xo Ic copy\-pipe\-output
|
||||
.Op Fl aCP
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the output to a paste buffer and pipe it to
|
||||
.Ar command .
|
||||
.It Xo Ic pipe\-output
|
||||
.Op Fl a
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Pipe the output to
|
||||
.Ar command .
|
||||
.It Xo Ic open\-output
|
||||
.Op Fl a
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the output in an editor. If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise the
|
||||
.Ic editor
|
||||
option is used.
|
||||
.It Xo Ic open\-selection
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the current copy-mode selection in an editor. If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise the
|
||||
.Ic editor
|
||||
option is used.
|
||||
.El
|
||||
.Pp
|
||||
A number of preset arrangements of panes are available, these are called
|
||||
layouts.
|
||||
These may be selected with the
|
||||
@@ -3590,13 +3750,20 @@ represents the window to be created; if the target already exists an error is
|
||||
shown, unless the
|
||||
.Fl k
|
||||
flag is used, in which case it is destroyed.
|
||||
.Ar window\-name
|
||||
is the name given to the new window, if any.
|
||||
.Pp
|
||||
If
|
||||
.Fl S
|
||||
is given and a window named
|
||||
.Ar window\-name
|
||||
already exists, it is selected (unless
|
||||
is given, an existing window is selected instead of creating a new one
|
||||
(unless
|
||||
.Fl d
|
||||
is also given in which case the command does nothing).
|
||||
is also given in which case the command does nothing):
|
||||
.Ar target\-window
|
||||
is preferred if it already identifies an existing window; otherwise a
|
||||
window named
|
||||
.Ar window\-name
|
||||
is selected if one exists.
|
||||
.Pp
|
||||
.Ar shell\-command
|
||||
is the command to execute.
|
||||
@@ -4754,6 +4921,15 @@ for backspace.
|
||||
Set the number of buffers; as new buffers are added to the top of the stack,
|
||||
old ones are removed from the bottom if necessary to maintain this maximum
|
||||
length.
|
||||
.It Xo Ic clear\-on\-attach
|
||||
.Op Ic on | off
|
||||
.Xc
|
||||
When enabled (the default), the client terminal switches to the alternate
|
||||
screen and clears it when a client is attached.
|
||||
When disabled,
|
||||
.Nm
|
||||
does not enter the alternate screen; instead the existing terminal content is
|
||||
scrolled into the scrollback buffer so it remains accessible.
|
||||
.It Xo Ic command\-alias[]
|
||||
.Ar name=value
|
||||
.Xc
|
||||
@@ -5070,20 +5246,12 @@ Supports extended keys.
|
||||
Supports focus reporting.
|
||||
.It hyperlinks
|
||||
Supports OSC 8 hyperlinks.
|
||||
.It image-quadrants
|
||||
Allows image fallbacks to use Unicode quadrant block characters.
|
||||
.It image-sextants
|
||||
Allows image fallbacks to use Unicode sextant block characters.
|
||||
.It ignorefkeys
|
||||
Ignore function keys from
|
||||
.Xr terminfo 5
|
||||
and use the
|
||||
.Nm
|
||||
internal set only.
|
||||
.It kitty
|
||||
Supports Kitty graphics using Unicode placeholders.
|
||||
The terminal must support the Kitty Unicode placeholder extension, not only
|
||||
the basic Kitty graphics protocol.
|
||||
.It margins
|
||||
Supports DECSLRM margins.
|
||||
.It mouse
|
||||
@@ -5112,6 +5280,8 @@ Supports
|
||||
title setting.
|
||||
.It usstyle
|
||||
Allows underscore style and colour to be set.
|
||||
.It utf8
|
||||
Supports UTF\-8 output.
|
||||
.El
|
||||
.It Ic terminal\-overrides[] Ar string
|
||||
Allow terminal descriptions read using
|
||||
@@ -5646,6 +5816,11 @@ If set to both, a bell and a message are produced.
|
||||
Sets the session's conception of what characters are considered word
|
||||
separators, for the purposes of the next and previous word commands in
|
||||
copy mode.
|
||||
A space in
|
||||
.Ar string
|
||||
matches any character with the Unicode
|
||||
.Em White_Space
|
||||
property.
|
||||
.El
|
||||
.Pp
|
||||
Available window options are:
|
||||
@@ -5783,6 +5958,20 @@ section.
|
||||
.It Ic copy\-mode\-position\-format Ar format
|
||||
Format of the position indicator in copy mode.
|
||||
.Pp
|
||||
.It Ic copy\-mode\-exit\-status\-format Ar format
|
||||
Format of the OSC 133 exit status indicator in the copy-mode gutter.
|
||||
The default displays a theme red
|
||||
.Ql !
|
||||
for a nonzero status and a space otherwise.
|
||||
The gutter grows to fit the expanded format.
|
||||
The format is drawn to the left of the collapse control, and its alignment
|
||||
applies within that area.
|
||||
The format is expanded with
|
||||
.Ql exit_status
|
||||
and
|
||||
.Ql exit_status_present ,
|
||||
which is set only when the OSC 133 end-of-output marker includes a status.
|
||||
.Pp
|
||||
.It Ic copy\-mode\-position\-style Ar style
|
||||
Set the style of the position indicator in copy mode.
|
||||
For how to specify
|
||||
@@ -7315,7 +7504,10 @@ The following variables are available, where appropriate:
|
||||
.It Li "cursor_very_visible" Ta "" Ta "1 if the cursor is in very visible mode"
|
||||
.It Li "cursor_x" Ta "" Ta "Cursor X position in pane"
|
||||
.It Li "cursor_y" Ta "" Ta "Cursor Y position in pane"
|
||||
.It Li "history_added" Ta "" Ta "Number of lines scrolled into pane history"
|
||||
.It Li "history_bytes" Ta "" Ta "Number of bytes in window history"
|
||||
.It Li "history_collected" Ta "" Ta "Number of oldest pane history lines discarded at the history limit"
|
||||
.It Li "history_generation" Ta "" Ta "Generation incremented when pane history is cleared or reflowed"
|
||||
.It Li "history_limit" Ta "" Ta "Maximum window history lines"
|
||||
.It Li "history_size" Ta "" Ta "Size of history in lines"
|
||||
.It Li "hook" Ta "" Ta "Name of running hook, if any"
|
||||
@@ -7414,6 +7606,7 @@ The following variables are available, where appropriate:
|
||||
.It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set"
|
||||
.It Li "pane_modal_flag" Ta "" Ta "1 if pane is modal"
|
||||
.It Li "pane_mode" Ta "" Ta "Name of pane mode, if any"
|
||||
.It Li "pane_output_generation" Ta "" Ta "Generation incremented when pane output is processed"
|
||||
.It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)"
|
||||
.It Li "pane_pid" Ta "" Ta "PID of first process in pane"
|
||||
.It Li "pane_pipe" Ta "" Ta "1 if pane is being piped"
|
||||
|
||||
254
tmux.h
254
tmux.h
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tmux.h,v 1.1430 2026/08/24 21:17:19 nicm Exp $ */
|
||||
/* $OpenBSD: tmux.h,v 1.1433 2026/09/01 12:49:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -68,7 +68,6 @@ struct options_array_item;
|
||||
struct options_entry;
|
||||
struct prompt;
|
||||
struct window_pane_prompt;
|
||||
struct redraw_damage;
|
||||
struct redraw_scene;
|
||||
struct redraw_span;
|
||||
struct screen_write_citem;
|
||||
@@ -76,12 +75,7 @@ struct screen_write_cline;
|
||||
struct screen_write_ctx;
|
||||
struct session;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image;
|
||||
struct image_backend;
|
||||
struct image_rect;
|
||||
struct image_line;
|
||||
struct image_store;
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct sixel_image;
|
||||
#endif
|
||||
|
||||
@@ -489,6 +483,7 @@ enum tty_code_code {
|
||||
TTYC_ICH1,
|
||||
TTYC_IL,
|
||||
TTYC_IL1,
|
||||
TTYC_IND,
|
||||
TTYC_INDN,
|
||||
TTYC_INVIS,
|
||||
TTYC_KCBT,
|
||||
@@ -820,6 +815,7 @@ struct colour_palette {
|
||||
#define GRID_LINE_START_OUTPUT 0x40
|
||||
#define GRID_LINE_END_OUTPUT 0x80
|
||||
#define GRID_LINE_HYPERLINK 0x100
|
||||
#define GRID_LINE_END_OUTPUT_STATUS 0x200
|
||||
|
||||
/* All OSC 133 flags. */
|
||||
#define GRID_LINE_OSC133_FLAGS \
|
||||
@@ -827,7 +823,8 @@ struct colour_palette {
|
||||
GRID_LINE_SECOND_PROMPT| \
|
||||
GRID_LINE_START_COMMAND| \
|
||||
GRID_LINE_START_OUTPUT| \
|
||||
GRID_LINE_END_OUTPUT)
|
||||
GRID_LINE_END_OUTPUT| \
|
||||
GRID_LINE_END_OUTPUT_STATUS)
|
||||
|
||||
/* Grid string flags. */
|
||||
#define GRID_STRING_WITH_SEQUENCES 0x1
|
||||
@@ -864,7 +861,7 @@ struct colour_palette {
|
||||
struct grid_cell {
|
||||
struct utf8_data data;
|
||||
u_short attr;
|
||||
u_short flags;
|
||||
u_char flags;
|
||||
int fg;
|
||||
int bg;
|
||||
int us;
|
||||
@@ -875,7 +872,7 @@ struct grid_cell {
|
||||
struct grid_extd_entry {
|
||||
utf8_char data;
|
||||
u_short attr;
|
||||
u_short flags;
|
||||
u_char flags;
|
||||
int fg;
|
||||
int bg;
|
||||
int us;
|
||||
@@ -893,7 +890,7 @@ struct grid_cell_entry {
|
||||
u_char data;
|
||||
} data;
|
||||
};
|
||||
u_short flags;
|
||||
u_char flags;
|
||||
} __packed;
|
||||
|
||||
/* OSC 133 data for a grid line. */
|
||||
@@ -917,9 +914,6 @@ struct grid_line {
|
||||
u_int time;
|
||||
struct osc133_data osc133_data;
|
||||
u_short flags;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image_line *images;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Entire grid of cells. */
|
||||
@@ -939,9 +933,6 @@ struct grid {
|
||||
u_int scroll_generation;
|
||||
|
||||
struct grid_line *linedata;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image_store *images;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Virtual cursor in a grid. */
|
||||
@@ -1033,65 +1024,24 @@ struct style {
|
||||
u_int link;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Private ACS keys used by the text image backend. */
|
||||
#define TTY_ACS_IMAGE_SHADE_LIGHT 0x80
|
||||
#define TTY_ACS_IMAGE_SHADE_MEDIUM 0x81
|
||||
#define TTY_ACS_IMAGE_SHADE_DARK 0x82
|
||||
#define TTY_ACS_IMAGE_BLOCK 0x83
|
||||
#define TTY_ACS_IMAGE_HALF_UPPER 0x84
|
||||
#define TTY_ACS_IMAGE_HALF_LOWER 0x85
|
||||
#define TTY_ACS_IMAGE_HALF_LEFT 0x86
|
||||
#define TTY_ACS_IMAGE_HALF_RIGHT 0x87
|
||||
#define TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT 0x88
|
||||
#define TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT 0x89
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT 0x8a
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT 0x8b
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT 0x8c
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT 0x8d
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT 0x8e
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT 0x8f
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT 0x90
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT 0x91
|
||||
#define TTY_ACS_IMAGE_SEXTANT_FIRST 0x92
|
||||
#define TTY_ACS_IMAGE_SEXTANT_LAST 0xcd
|
||||
|
||||
struct image_sample {
|
||||
u_char red;
|
||||
u_char green;
|
||||
u_char blue;
|
||||
u_char alpha;
|
||||
u_char brightness;
|
||||
};
|
||||
#define IMAGE_SAMPLE_COLUMNS 2
|
||||
#define IMAGE_SAMPLE_ROWS 6
|
||||
struct image_cell {
|
||||
struct image_sample whole;
|
||||
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
|
||||
};
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Image. */
|
||||
struct image {
|
||||
u_int id;
|
||||
u_int references;
|
||||
u_int flags;
|
||||
u_int parent_id;
|
||||
u_int source_id;
|
||||
u_int width;
|
||||
u_int height;
|
||||
u_int canvas_width;
|
||||
u_int canvas_height;
|
||||
struct screen *s;
|
||||
struct sixel_image *data;
|
||||
char *fallback;
|
||||
|
||||
u_int px;
|
||||
u_int py;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
size_t stride;
|
||||
size_t size;
|
||||
u_char *pixels;
|
||||
struct sixel_image *sixel;
|
||||
struct image_cell *cells;
|
||||
void *fallback_data;
|
||||
|
||||
RB_ENTRY(image) entry;
|
||||
struct images *list;
|
||||
TAILQ_ENTRY (image) entry;
|
||||
|
||||
TAILQ_ENTRY (image) all_entry;
|
||||
};
|
||||
RB_HEAD(images, image);
|
||||
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
TAILQ_HEAD(images, image);
|
||||
#endif
|
||||
|
||||
/* Cursor style. */
|
||||
@@ -1150,6 +1100,11 @@ struct screen {
|
||||
bitstr_t *tabs;
|
||||
struct screen_sel *sel;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct images images;
|
||||
struct images saved_images;
|
||||
#endif
|
||||
|
||||
struct screen_write_cline *write_list;
|
||||
|
||||
struct hyperlinks *hyperlinks;
|
||||
@@ -1167,7 +1122,6 @@ struct screen_write_ctx {
|
||||
#define SCREEN_WRITE_SYNC 0x1
|
||||
#define SCREEN_WRITE_OBSCURED 0x2
|
||||
#define SCREEN_WRITE_CHECKED_IF_OBSCURED 0x4
|
||||
#define SCREEN_WRITE_INPUT 0x8
|
||||
|
||||
screen_write_init_ctx_cb init_ctx_cb;
|
||||
void *arg;
|
||||
@@ -1405,9 +1359,10 @@ struct window_pane {
|
||||
char tty[TTY_NAME_MAX];
|
||||
int status;
|
||||
struct timeval dead_time;
|
||||
struct cmdq_item *wait_item; /* new-pane -W: waiting for pane exit */
|
||||
struct cmdq_item *wait_item;
|
||||
struct spawn_editor_state *editor;
|
||||
|
||||
uint64_t output_generation;
|
||||
time_t last_output_time;
|
||||
time_t last_prompt_time;
|
||||
time_t cmd_start_time;
|
||||
@@ -1473,7 +1428,6 @@ struct window_pane {
|
||||
TAILQ_HEAD(window_panes, window_pane);
|
||||
TAILQ_HEAD(window_panes_zindex, window_pane);
|
||||
RB_HEAD(window_pane_tree, window_pane);
|
||||
TAILQ_HEAD(redraw_damages, redraw_damage);
|
||||
|
||||
/* Window structure. */
|
||||
struct window {
|
||||
@@ -1517,13 +1471,6 @@ struct window {
|
||||
|
||||
uint64_t redraw_scene_generation;
|
||||
|
||||
struct redraw_damages damage;
|
||||
u_int damage_count;
|
||||
#ifdef ENABLE_IMAGES
|
||||
uint64_t image_scroll_epoch;
|
||||
int image_scroll_pending;
|
||||
#endif
|
||||
|
||||
struct menu_data *menu;
|
||||
u_int menu_last_px;
|
||||
u_int menu_last_py;
|
||||
@@ -1791,11 +1738,6 @@ struct tty_term {
|
||||
#define TERM_VT100LIKE 0x20
|
||||
#define TERM_SIXEL 0x40
|
||||
#define TERM_INVALIDMS 0x80
|
||||
#define TERM_KITTY 0x100
|
||||
#ifdef ENABLE_IMAGES
|
||||
#define TERM_IMAGE_QUADRANTS 0x200
|
||||
#define TERM_IMAGE_SEXTANTS 0x400
|
||||
#endif
|
||||
int flags;
|
||||
|
||||
LIST_ENTRY(tty_term) entry;
|
||||
@@ -1898,17 +1840,10 @@ struct tty {
|
||||
|
||||
struct event key_timer;
|
||||
struct tty_key *key_tree;
|
||||
#ifdef ENABLE_IMAGES
|
||||
const struct image_backend *image_backend;
|
||||
void *image_data;
|
||||
struct window *image_scroll_window;
|
||||
uint64_t image_scroll_epoch;
|
||||
int image_scroll_failed;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Terminal command context. */
|
||||
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *, u_int, u_int);
|
||||
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *);
|
||||
typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *);
|
||||
struct tty_ctx {
|
||||
struct screen *s;
|
||||
@@ -1941,7 +1876,7 @@ struct tty_ctx {
|
||||
size_t size;
|
||||
} sel;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *image;
|
||||
#endif
|
||||
};
|
||||
@@ -3061,6 +2996,10 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_setselection(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_rawstring(struct tty *, const struct tty_ctx *);
|
||||
#ifdef ENABLE_SIXEL
|
||||
void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *);
|
||||
void tty_draw_images(struct client *, struct window_pane *);
|
||||
#endif
|
||||
void tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
|
||||
void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *);
|
||||
|
||||
@@ -3102,7 +3041,6 @@ void tty_default_features(struct client *, const char *, u_int);
|
||||
/* tty-acs.c */
|
||||
int tty_acs_needed(struct tty *);
|
||||
const char *tty_acs_get(struct tty *, u_char);
|
||||
u_char tty_acs_image_sextant(u_int);
|
||||
int tty_acs_reverse_get(struct tty *, const char *, size_t);
|
||||
const struct utf8_data *tty_acs_double_borders(int);
|
||||
const struct utf8_data *tty_acs_heavy_borders(int);
|
||||
@@ -3705,7 +3643,7 @@ void screen_write_setselection(struct screen_write_ctx *, const char *,
|
||||
u_char *, u_int);
|
||||
void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
|
||||
int);
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
void screen_write_sixelimage(struct screen_write_ctx *,
|
||||
struct sixel_image *, u_int);
|
||||
#endif
|
||||
@@ -3719,17 +3657,8 @@ void redraw_screen(struct client *);
|
||||
void redraw_pane(struct client *, struct window_pane *);
|
||||
void redraw_pane_scrollbar(struct client *, struct window_pane *);
|
||||
void redraw_free_scene(struct redraw_scene *);
|
||||
int redraw_client_has_window(struct client *, struct window *);
|
||||
void redraw_invalidate_scene(struct window *);
|
||||
void redraw_invalidate_all_scenes(void);
|
||||
void redraw_damage_window(struct window *, u_int, u_int, u_int, u_int);
|
||||
#ifdef ENABLE_IMAGES
|
||||
void redraw_damage_window_scroll(struct window *, u_int, u_int, u_int,
|
||||
u_int);
|
||||
void redraw_image_scroll_result(struct tty *, const struct tty_ctx *, int);
|
||||
#endif
|
||||
void redraw_free_damage(struct window *);
|
||||
void redraw_client_damage(struct client *);
|
||||
int redraw_get_status_border_cell_type(struct redraw_span **, u_int);
|
||||
|
||||
/* screen.c */
|
||||
@@ -3902,8 +3831,6 @@ int window_pane_get_pane_status(struct window_pane *);
|
||||
struct style_range *window_pane_status_get_range(struct window_pane *, u_int,
|
||||
u_int);
|
||||
int window_pane_is_floating(struct window_pane *);
|
||||
void window_pane_redraw_floating(struct window *,
|
||||
struct window_pane *, int, int, int, int);
|
||||
|
||||
/* window-border.c */
|
||||
void window_set_fill_cells(struct window *);
|
||||
@@ -4182,6 +4109,7 @@ void session_update_history(struct session *);
|
||||
/* utf8.c */
|
||||
enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *);
|
||||
enum utf8_state utf8_fromwc(wchar_t wc, struct utf8_data *);
|
||||
int utf8_has_whitespace(const struct utf8_data *);
|
||||
void utf8_update_width_cache(void);
|
||||
utf8_char utf8_build_one(u_char);
|
||||
enum utf8_state utf8_from_data(const struct utf8_data *, utf8_char *);
|
||||
@@ -4295,7 +4223,7 @@ struct winlink *spawn_window(struct spawn_context *, char **);
|
||||
struct window_pane *spawn_pane(struct spawn_context *, char **);
|
||||
typedef void (*spawn_finish_edit_cb)(char *, size_t, void *);
|
||||
struct spawn_editor_state *spawn_editor(struct client *, const char *, size_t,
|
||||
spawn_finish_edit_cb, void *);
|
||||
const char *, spawn_finish_edit_cb, void *);
|
||||
void spawn_cancel_editor(struct spawn_editor_state *);
|
||||
pid_t spawn_get_editor_pid(struct spawn_editor_state *);
|
||||
void spawn_editor_finish(struct window_pane *);
|
||||
@@ -4303,108 +4231,17 @@ void spawn_editor_finish(struct window_pane *);
|
||||
/* regsub.c */
|
||||
char *regsub(const char *, const char *, const char *, int);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* image.c */
|
||||
#define IMAGE_BACKEND_GRAPHICAL 0x1
|
||||
#define IMAGE_BACKEND_SCROLLS 0x2
|
||||
int image_free_all(struct screen *);
|
||||
struct image *image_store(struct screen *, struct sixel_image *);
|
||||
int image_check_line(struct screen *, u_int, u_int);
|
||||
int image_check_area(struct screen *, u_int, u_int, u_int, u_int);
|
||||
int image_scroll_up(struct screen *, u_int);
|
||||
|
||||
struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int,
|
||||
u_char *);
|
||||
struct image *image_create_view(struct image *, u_int, u_int, u_int,
|
||||
u_int, u_int, u_int, u_int, u_int, u_int, u_int);
|
||||
struct image *image_find(u_int);
|
||||
u_int image_get_id(const struct image *);
|
||||
void image_get_size(const struct image *, u_int *, u_int *);
|
||||
void image_get_canvas_size(const struct image *, u_int *, u_int *);
|
||||
void image_get_size_in_cells(const struct image *, u_int *, u_int *);
|
||||
const u_char *image_get_pixels(const struct image *, size_t *, size_t *);
|
||||
void image_set_no_cursor(struct image *);
|
||||
struct sixel_image *image_get_sixel(const struct image *);
|
||||
void image_set_sixel(struct image *, struct sixel_image *);
|
||||
void image_ref(u_int);
|
||||
void image_free(u_int);
|
||||
void image_write_sixel(struct screen_write_ctx *, struct image *,
|
||||
u_int);
|
||||
void image_write_kitty(struct screen_write_ctx *, struct image *,
|
||||
u_int, u_int, u_int, int32_t);
|
||||
void image_get_pixel_rect(const struct image *, u_int, u_int,
|
||||
u_int, u_int, u_int *, u_int *, u_int *, u_int *);
|
||||
void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *,
|
||||
u_int *);
|
||||
u_char *image_base64_decode(const char *, size_t, size_t, size_t *);
|
||||
u_char *image_png_decode(const u_char *, size_t, size_t, u_int *,
|
||||
u_int *);
|
||||
void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
|
||||
u_int, u_int);
|
||||
void image_redraw_all(struct screen_write_ctx *);
|
||||
void image_redraw_scroll(struct screen_write_ctx *, u_int);
|
||||
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
int image_backend_flags(struct tty *);
|
||||
void image_tty_update(struct tty *);
|
||||
void image_tty_geometry_changed(struct tty *);
|
||||
void image_tty_free(struct tty *, int);
|
||||
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
|
||||
u_int, u_int, u_int, int, const struct tty_style_ctx *);
|
||||
void image_get_fallback_cell(struct tty *, struct image *, u_int,
|
||||
u_int, const struct grid_cell *, struct grid_cell *,
|
||||
const struct tty_style_ctx *);
|
||||
const struct image_cell *image_get_cell(struct image *, u_int, u_int);
|
||||
void image_free_fallback(struct image *);
|
||||
int image_get_fallback_at(struct tty *, struct screen *, u_int,
|
||||
u_int, const struct grid_cell *, struct grid_cell *,
|
||||
const struct tty_style_ctx *);
|
||||
struct image *image_rect_get_image(const struct image_rect *);
|
||||
const struct grid_cell *image_rect_get_cell(
|
||||
const struct image_rect *);
|
||||
void image_rect_get_coords(const struct image_rect *,
|
||||
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
|
||||
int32_t image_rect_get_z(const struct image_rect *);
|
||||
void image_clear(struct screen_write_ctx *, u_int);
|
||||
void image_clear_kitty(struct screen_write_ctx *, char, u_int,
|
||||
u_int, int32_t);
|
||||
void image_grid_damage(struct grid *, u_int, u_int, u_int, u_int);
|
||||
void image_grid_free_line(struct grid *, struct grid_line *);
|
||||
void image_grid_free(struct grid *);
|
||||
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
|
||||
u_int);
|
||||
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
|
||||
u_int, u_int);
|
||||
void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
|
||||
u_int, u_int, u_int, u_int);
|
||||
void image_grid_resize_width(struct grid *, u_int);
|
||||
int image_grid_line_has_images(const struct grid_line *);
|
||||
int image_grid_check_area(struct grid *, u_int, u_int, u_int,
|
||||
u_int);
|
||||
int image_grid_get_source(struct grid *, u_int, u_int,
|
||||
struct image *, u_int *, u_int *);
|
||||
void image_place_cell_kitty(struct screen_write_ctx *, struct image *,
|
||||
u_int, u_int, u_int, u_int, u_int, u_int, int32_t);
|
||||
#define KITTY_PARSE_ERROR -1
|
||||
#define KITTY_PARSE_OK 0
|
||||
#define KITTY_PARSE_MORE 1
|
||||
#define KITTY_PARSE_MISSING 2
|
||||
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
|
||||
u_int, u_int *, u_int *, u_int *, char *, char *, u_int *,
|
||||
int32_t *, int *);
|
||||
int kitty_placeholder_to_image(void *, struct grid *,
|
||||
struct grid_cell *, u_int, u_int, struct image **, u_int *,
|
||||
u_int *, u_int *, u_int *, int32_t *);
|
||||
void kitty_free_state(void *);
|
||||
void kitty_draw_rect(struct tty *,
|
||||
const struct image_rect *, const struct tty_style_ctx *);
|
||||
void kitty_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
void kitty_free_output_state(struct tty *, int);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* image-sixel.c */
|
||||
#define SIXEL_COLOUR_REGISTERS 1024
|
||||
void sixel_draw_rect(struct tty *,
|
||||
const struct image_rect *, const struct tty_style_ctx *);
|
||||
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
void sixel_free_output(struct tty *, int);
|
||||
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,
|
||||
u_int);
|
||||
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int);
|
||||
void sixel_free(struct sixel_image *);
|
||||
void sixel_log(struct sixel_image *);
|
||||
void sixel_size_in_cells(struct sixel_image *, u_int *, u_int *);
|
||||
@@ -4413,7 +4250,6 @@ struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int,
|
||||
char *sixel_print(struct sixel_image *, struct sixel_image *,
|
||||
size_t *);
|
||||
struct screen *sixel_to_screen(struct sixel_image *);
|
||||
struct image *sixel_to_image(struct sixel_image *);
|
||||
#endif
|
||||
|
||||
/* server-acl.c */
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Generate the Unicode diacritic table used by Kitty image placeholders.
|
||||
#
|
||||
# Input must be UnicodeData.txt from Unicode 6.0.0:
|
||||
# https://www.unicode.org/Public/6.0.0/ucd/UnicodeData.txt
|
||||
# SHA-256: 90b45b777346bef027556f9c6cb3ea5d7d745bd60d6762855c08d8a22d34f771
|
||||
#
|
||||
# The selection follows the public Kitty graphics protocol: nonspacing marks
|
||||
# with canonical combining class 230, NSM bidi class, no decomposition, minus
|
||||
# characters which may be combined or normalized in ways unsuitable for a
|
||||
# row or column index.
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "usage: $0 UnicodeData.txt" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
awk -F ';' '
|
||||
BEGIN {
|
||||
omit["0300"] = omit["0301"] = omit["0302"] = 1
|
||||
omit["0303"] = omit["0304"] = omit["0306"] = 1
|
||||
omit["0307"] = omit["0308"] = omit["0309"] = 1
|
||||
omit["030A"] = omit["030B"] = omit["030C"] = 1
|
||||
omit["030F"] = omit["0311"] = omit["0313"] = 1
|
||||
omit["0314"] = omit["0342"] = omit["0653"] = 1
|
||||
omit["0654"] = 1
|
||||
}
|
||||
$3 == "Mn" && $4 == "230" && $5 == "NSM" && $6 == "" && !omit[$1] {
|
||||
point[++count] = $1
|
||||
}
|
||||
END {
|
||||
if (count != 297) {
|
||||
printf "expected 297 diacritics, found %u\n", count > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
print "static const uint32_t kitty_diacritics[] = {"
|
||||
for (i = 1; i <= count; i++) {
|
||||
if ((i - 1) % 8 == 0)
|
||||
printf "\t"
|
||||
printf "0x%s", point[i]
|
||||
if (i != count)
|
||||
printf ","
|
||||
if (i == count || i % 8 == 0)
|
||||
printf "\n"
|
||||
else
|
||||
printf " "
|
||||
}
|
||||
print "};"
|
||||
}
|
||||
' "$1"
|
||||
@@ -1,104 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Compact visual check for image subcell geometry and layering. Run inside a
|
||||
# tmux pane on Kitty or a SIXEL terminal; it needs at least 90 columns/24 rows.
|
||||
|
||||
if [ -z "$TMUX" ]; then
|
||||
echo "Run this script inside tmux." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
columns=$(tput cols 2>/dev/null || echo 0)
|
||||
lines=$(tput lines 2>/dev/null || echo 0)
|
||||
if [ "$columns" -lt 90 ] || [ "$lines" -lt 24 ]; then
|
||||
echo "This test needs at least 90 columns and 24 rows." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
at()
|
||||
{
|
||||
printf '\033[%s;%sH' "$1" "$2"
|
||||
}
|
||||
|
||||
kitty()
|
||||
{
|
||||
printf '\033_G%s\033\\' "$1"
|
||||
}
|
||||
|
||||
kitty_solid()
|
||||
{
|
||||
id=$1
|
||||
rgba=$2
|
||||
payload=$(i=0; while [ "$i" -lt 256 ]; do
|
||||
printf "$rgba"
|
||||
i=$((i + 1))
|
||||
done | base64 | tr -d '\n')
|
||||
kitty "a=t,q=2,f=32,s=16,v=16,i=$id;$payload"
|
||||
}
|
||||
|
||||
sixel_folder()
|
||||
{
|
||||
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\\'
|
||||
}
|
||||
|
||||
feature_info=$(tmux display-message -p \
|
||||
'term=#{client_termname} features=#{client_termfeatures} cells=#{client_cell_width}x#{client_cell_height}')
|
||||
|
||||
printf '\033[2J\033[H\033[?25l'
|
||||
at 1 2
|
||||
printf 'tmux image edge-case check'
|
||||
at 2 2
|
||||
printf '%s' "$feature_info"
|
||||
|
||||
kitty_solid 201 '\377\000\000\377'
|
||||
kitty_solid 202 '\000\377\000\377'
|
||||
kitty_solid 203 '\000\000\377\377'
|
||||
|
||||
# The 26-pixel folder occupies whole grid cells, but its visible right and
|
||||
# bottom edges must remain partial rather than stretching to those boundaries.
|
||||
at 4 2
|
||||
printf '1. partial SIXEL: folder should be the same size as direct output'
|
||||
at 4 72
|
||||
sixel_folder
|
||||
|
||||
# Chawan emits X/Y for its image padding. The red square must begin slightly
|
||||
# below and right of the [img] anchor, never over it.
|
||||
at 8 2
|
||||
printf '2. Kitty X/Y: RED begins below/right of the [img] anchor'
|
||||
at 8 72
|
||||
printf '[img]'
|
||||
at 9 72
|
||||
kitty 'a=p,q=2,C=1,i=201,p=1,x=0,y=0,w=16,h=16,c=3,r=2,X=6,Y=6'
|
||||
|
||||
# Positive Kitty z overlays text and other lower z images; negative z is
|
||||
# below text. The shared red/blue edge makes any wrong order obvious.
|
||||
at 12 2
|
||||
printf '3. Kitty z: BLUE covers RED; GREEN stays behind TEXT'
|
||||
at 12 72
|
||||
printf 'TEXT'
|
||||
at 12 72
|
||||
kitty 'a=p,q=2,C=1,i=202,p=2,x=0,y=0,w=16,h=16,c=4,r=2,z=-1'
|
||||
at 14 72
|
||||
kitty 'a=p,q=2,C=1,i=201,p=3,x=0,y=0,w=16,h=16,c=4,r=2,z=1'
|
||||
at 14 74
|
||||
kitty 'a=p,q=2,C=1,i=203,p=4,x=0,y=0,w=16,h=16,c=4,r=2,z=2'
|
||||
|
||||
# SIXEL is temporal: a later image covers text, while later text destroys the
|
||||
# affected image cells. Both results should match on Kitty and SIXEL output.
|
||||
at 18 2
|
||||
printf '4. SIXEL after text: folder covers FOLDER'
|
||||
at 18 72
|
||||
printf 'FOLDER'
|
||||
at 18 72
|
||||
sixel_folder
|
||||
at 21 2
|
||||
printf '5. SIXEL before text: TEXT WINS, no folder remnant'
|
||||
at 21 72
|
||||
sixel_folder
|
||||
at 21 72
|
||||
printf 'TEXT WINS'
|
||||
|
||||
at 23 2
|
||||
printf 'Take one screenshot, then press Enter to return to the shell.'
|
||||
printf '\033[?25h'
|
||||
IFS= read -r _image_edge_reply
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# One-screen visual check for tmux image input and layering. Run this inside a
|
||||
# tmux pane attached to Kitty, WezTerm, Microsoft Terminal, or a basic terminal.
|
||||
|
||||
if [ -z "$TMUX" ]; then
|
||||
echo "Run this script inside tmux." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
columns=$(tput cols 2>/dev/null || echo 0)
|
||||
lines=$(tput lines 2>/dev/null || echo 0)
|
||||
if [ "$columns" -lt 72 ] || [ "$lines" -lt 23 ]; then
|
||||
echo "This test needs at least 72 columns and 23 rows." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
at()
|
||||
{
|
||||
printf '\033[%s;%sH' "$1" "$2"
|
||||
}
|
||||
|
||||
kitty()
|
||||
{
|
||||
printf '\033_G%s\033\\' "$1"
|
||||
}
|
||||
|
||||
# Transmit a 16x16 solid RGBA texture. A one-pixel texture is unsuitable here
|
||||
# because Kitty filters its transparent texture border into scaled edges.
|
||||
kitty_solid()
|
||||
{
|
||||
id=$1
|
||||
rgba=$2
|
||||
payload=$(i=0; while [ "$i" -lt 256 ]; do
|
||||
printf "$rgba"
|
||||
i=$((i + 1))
|
||||
done | base64 | tr -d '\n')
|
||||
kitty "a=t,q=2,f=32,s=16,v=16,i=$id;$payload"
|
||||
}
|
||||
|
||||
# A small 26x26 transparent SIXEL folder, used by the supplied bptato tests.
|
||||
sixel_folder()
|
||||
{
|
||||
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\\'
|
||||
}
|
||||
|
||||
feature_info=$(tmux display-message -p \
|
||||
'term=#{client_termname} features=#{client_termfeatures} cells=#{client_cell_width}x#{client_cell_height}')
|
||||
|
||||
printf '\033[2J\033[H\033[?25l'
|
||||
at 1 2
|
||||
printf 'tmux image visual check'
|
||||
at 2 2
|
||||
printf '%s' "$feature_info"
|
||||
|
||||
# Red is placed after its text at a positive Kitty z-index, so it must cover
|
||||
# the text. Green is placed below the text at a negative Kitty z-index.
|
||||
kitty_solid 101 '\377\000\000\377'
|
||||
kitty_solid 102 '\000\377\000\377'
|
||||
kitty_solid 103 '\000\000\377\377'
|
||||
kitty_solid 104 '\377\000\000\177'
|
||||
|
||||
at 4 2
|
||||
printf '1. positive Kitty z: RED must hide TEXT UNDER at right'
|
||||
at 4 62
|
||||
printf 'TEXT UNDER'
|
||||
at 4 60
|
||||
kitty 'a=p,q=2,C=1,i=101,p=1,x=1,y=1,w=14,h=14,c=18,r=2,z=1'
|
||||
|
||||
at 7 2
|
||||
printf '2. negative Kitty z: GREEN remains behind TEXT ON TOP'
|
||||
at 7 60
|
||||
kitty 'a=p,q=2,C=1,i=102,p=2,x=1,y=1,w=14,h=14,c=18,r=2,z=-1'
|
||||
at 7 62
|
||||
printf 'TEXT ON TOP'
|
||||
|
||||
at 10 2
|
||||
printf '3. Kitty z overlap: RED left, BLUE on top at right'
|
||||
at 11 55
|
||||
kitty 'a=p,q=2,C=1,i=101,p=3,x=1,y=1,w=14,h=14,c=18,r=3,z=1'
|
||||
at 11 64
|
||||
kitty 'a=p,q=2,C=1,i=103,p=4,x=1,y=1,w=14,h=14,c=9,r=3,z=2'
|
||||
|
||||
at 15 2
|
||||
printf '4. alpha: half-transparent RED, dithered on SIXEL'
|
||||
at 15 60
|
||||
kitty 'a=p,q=2,C=1,i=104,p=5,x=1,y=1,w=14,h=14,c=18,r=2,z=1'
|
||||
|
||||
# SIXEL semantics are temporal: later text destroys a SIXEL image, while a
|
||||
# later SIXEL image appears over existing text.
|
||||
at 18 2
|
||||
printf '5. SIXEL after text: folder covers FOLDER at right'
|
||||
at 18 65
|
||||
printf 'FOLDER'
|
||||
at 18 65
|
||||
sixel_folder
|
||||
|
||||
at 21 2
|
||||
printf '6. SIXEL before text: no folder remains behind TEXT WINS'
|
||||
at 21 65
|
||||
sixel_folder
|
||||
at 21 65
|
||||
printf 'TEXT WINS'
|
||||
|
||||
at 23 2
|
||||
printf 'Take one screenshot, then press Enter to return to the shell.'
|
||||
at 23 66
|
||||
printf '\033[?25h'
|
||||
IFS= read -r _image_visual_reply
|
||||
67
tty-acs.c
67
tty-acs.c
@@ -64,65 +64,9 @@ static const struct tty_acs_entry tty_acs_table[] = {
|
||||
{ '{', "\317\200" }, /* greek pi */
|
||||
{ '|', "\342\211\240" }, /* not-equal */
|
||||
{ '}', "\302\243" }, /* UK pound sign */
|
||||
{ '~', "\302\267" }, /* bullet */
|
||||
#ifdef ENABLE_IMAGES
|
||||
{ TTY_ACS_IMAGE_SHADE_LIGHT, "\342\226\221" },
|
||||
{ TTY_ACS_IMAGE_SHADE_MEDIUM, "\342\226\222" },
|
||||
{ TTY_ACS_IMAGE_SHADE_DARK, "\342\226\223" },
|
||||
{ TTY_ACS_IMAGE_BLOCK, "\342\226\210" },
|
||||
{ TTY_ACS_IMAGE_HALF_UPPER, "\342\226\200" },
|
||||
{ TTY_ACS_IMAGE_HALF_LOWER, "\342\226\204" },
|
||||
{ TTY_ACS_IMAGE_HALF_LEFT, "\342\226\214" },
|
||||
{ TTY_ACS_IMAGE_HALF_RIGHT, "\342\226\220" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT, "\342\226\226" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT, "\342\226\227" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT, "\342\226\230" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT,
|
||||
"\342\226\231" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT, "\342\226\232" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT,
|
||||
"\342\226\233" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT,
|
||||
"\342\226\234" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT, "\342\226\235" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT, "\342\226\236" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT,
|
||||
"\342\226\237" }
|
||||
#endif
|
||||
{ '~', "\302\267" } /* bullet */
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
static char tty_acs_image_sextants[60][5];
|
||||
|
||||
static void
|
||||
tty_acs_image_sextants_init(void)
|
||||
{
|
||||
u_int cp, i;
|
||||
|
||||
if (tty_acs_image_sextants[0][0] != '\0')
|
||||
return;
|
||||
for (i = 0; i < nitems(tty_acs_image_sextants); i++) {
|
||||
cp = 0x1fb00 + i;
|
||||
tty_acs_image_sextants[i][0] = 0xf0 | (cp >> 18);
|
||||
tty_acs_image_sextants[i][1] = 0x80 | ((cp >> 12) & 0x3f);
|
||||
tty_acs_image_sextants[i][2] = 0x80 | ((cp >> 6) & 0x3f);
|
||||
tty_acs_image_sextants[i][3] = 0x80 | (cp & 0x3f);
|
||||
}
|
||||
}
|
||||
|
||||
u_char
|
||||
tty_acs_image_sextant(u_int mask)
|
||||
{
|
||||
if (mask == 0 || mask == 21 || mask == 42 || mask >= 63)
|
||||
return (0);
|
||||
if (mask < 21)
|
||||
return (TTY_ACS_IMAGE_SEXTANT_FIRST + mask - 1);
|
||||
if (mask < 42)
|
||||
return (TTY_ACS_IMAGE_SEXTANT_FIRST + mask - 2);
|
||||
return (TTY_ACS_IMAGE_SEXTANT_FIRST + mask - 3);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Table mapping UTF-8 to ACS entries. */
|
||||
struct tty_acs_reverse_entry {
|
||||
const char *string;
|
||||
@@ -295,15 +239,6 @@ tty_acs_get(struct tty *tty, u_char ch)
|
||||
return (&tty->term->acs[ch][0]);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (ch >= TTY_ACS_IMAGE_SEXTANT_FIRST &&
|
||||
ch <= TTY_ACS_IMAGE_SEXTANT_LAST) {
|
||||
tty_acs_image_sextants_init();
|
||||
return (tty_acs_image_sextants[
|
||||
ch - TTY_ACS_IMAGE_SEXTANT_FIRST]);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Otherwise look up the UTF-8 translation. */
|
||||
entry = bsearch(&ch, tty_acs_table, nitems(tty_acs_table),
|
||||
sizeof tty_acs_table[0], tty_acs_cmp);
|
||||
|
||||
29
tty-draw.c
29
tty-draw.c
@@ -123,10 +123,6 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
||||
struct grid *gd = s->grid;
|
||||
const struct grid_cell *gcp;
|
||||
struct grid_cell gc, ngc, last;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell image_gc;
|
||||
int image_status;
|
||||
#endif
|
||||
struct grid_line *gl;
|
||||
u_int i, j, last_i, cx, ex, width;
|
||||
u_int cellsize, bg;
|
||||
@@ -251,35 +247,20 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
||||
|
||||
if (px >= ex || i >= ex - px) {
|
||||
/* Outside the area being drawn. */
|
||||
empty = nx - i;
|
||||
gcp = &grid_default_cell;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_status = image_get_fallback_at(tty, s, px + i,
|
||||
py, gcp, &image_gc, style_ctx);
|
||||
if (image_status == 1) {
|
||||
gcp = &image_gc;
|
||||
empty = 0;
|
||||
} else if (image_status == -1)
|
||||
empty = 1;
|
||||
else
|
||||
#endif
|
||||
empty = nx - i;
|
||||
} else {
|
||||
/* Get the current cell. */
|
||||
grid_view_get_cell(gd, px + i, py, &gc);
|
||||
gcp = &gc;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_get_fallback_at(tty, s, px + i, py, &gc,
|
||||
&image_gc, style_ctx) == 1)
|
||||
gcp = &image_gc;
|
||||
#endif
|
||||
|
||||
/* Work out empty cells. */
|
||||
empty = tty_draw_line_get_empty(gcp, &last, nx - i);
|
||||
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, gcp);
|
||||
gcp = tty_check_codeset(tty, &gc);
|
||||
|
||||
/* And for selection. */
|
||||
if (gcp->flags & GRID_FLAG_SELECTED) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tty-features.c,v 1.42 2026/08/17 14:47:41 nicm Exp $ */
|
||||
/* $OpenBSD: tty-features.c,v 1.43 2026/08/31 12:41:03 kirill Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -224,11 +224,9 @@ static const struct tty_feature tty_feature_strikethrough = {
|
||||
0
|
||||
};
|
||||
|
||||
#define TTY_FEATURE_SYNC "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;"
|
||||
|
||||
/* Terminal supports synchronized updates. */
|
||||
static const char *const tty_feature_sync_capabilities[] = {
|
||||
TTY_FEATURE_SYNC,
|
||||
"Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;",
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_sync = {
|
||||
@@ -359,34 +357,6 @@ static const struct tty_feature tty_feature_sixel = {
|
||||
TERM_SIXEL
|
||||
};
|
||||
|
||||
/* Terminal has Kitty graphics protocol Unicode placeholder capability. */
|
||||
static const char *const tty_feature_kitty_capabilities[] = {
|
||||
"Kty",
|
||||
TTY_FEATURE_SYNC,
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_kitty = {
|
||||
"kitty",
|
||||
tty_feature_kitty_capabilities,
|
||||
TERM_KITTY
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
static const char *const tty_feature_image_capabilities[] = {
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_image_quadrants = {
|
||||
"image-quadrants",
|
||||
tty_feature_image_capabilities,
|
||||
TERM_IMAGE_QUADRANTS
|
||||
};
|
||||
static const struct tty_feature tty_feature_image_sextants = {
|
||||
"image-sextants",
|
||||
tty_feature_image_capabilities,
|
||||
TERM_IMAGE_SEXTANTS
|
||||
};
|
||||
#endif
|
||||
|
||||
/* Terminal supports the OSC 9;4 progress bar. */
|
||||
static const char *const tty_feature_progressbar_capabilities[] = {
|
||||
"Spb=\\E]9;4;%p1%d;%p2%d\\E\\\\",
|
||||
@@ -398,6 +368,13 @@ static const struct tty_feature tty_feature_progressbar = {
|
||||
0
|
||||
};
|
||||
|
||||
/* Terminal supports UTF-8. */
|
||||
static const struct tty_feature tty_feature_utf8 = {
|
||||
"utf8",
|
||||
NULL,
|
||||
0
|
||||
};
|
||||
|
||||
/* Available terminal features. */
|
||||
static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_256,
|
||||
@@ -409,11 +386,6 @@ static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_extkeys,
|
||||
&tty_feature_focus,
|
||||
&tty_feature_ignorefkeys,
|
||||
&tty_feature_kitty,
|
||||
#ifdef ENABLE_IMAGES
|
||||
&tty_feature_image_quadrants,
|
||||
&tty_feature_image_sextants,
|
||||
#endif
|
||||
&tty_feature_margins,
|
||||
&tty_feature_mouse,
|
||||
&tty_feature_osc7,
|
||||
@@ -425,7 +397,8 @@ static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_strikethrough,
|
||||
&tty_feature_sync,
|
||||
&tty_feature_title,
|
||||
&tty_feature_usstyle
|
||||
&tty_feature_usstyle,
|
||||
&tty_feature_utf8
|
||||
};
|
||||
|
||||
/* Parse features for client. */
|
||||
@@ -508,6 +481,9 @@ tty_feature_present(struct tty_term *term, const char *name)
|
||||
u_int i;
|
||||
char *copy;
|
||||
|
||||
if (strcmp(name, "utf8") == 0)
|
||||
return ((term->tty->client->flags & CLIENT_UTF8) != 0);
|
||||
|
||||
for (i = 0; i < nitems(tty_features); i++) {
|
||||
tf = tty_features[i];
|
||||
if (strcmp(tf->name, name) == 0) {
|
||||
@@ -521,7 +497,8 @@ tty_feature_present(struct tty_term *term, const char *name)
|
||||
* We don't just have the feature flag set. Check if the capabilities
|
||||
* supported by the client are actual set instead.
|
||||
*/
|
||||
if (tf == NULL || strcmp(name, "ignorefkeys") == 0)
|
||||
if (tf == NULL || tf->capabilities == NULL ||
|
||||
strcmp(name, "ignorefkeys") == 0)
|
||||
return (0);
|
||||
if (tf->flags != 0 && (term->flags & tf->flags) != tf->flags)
|
||||
return (0);
|
||||
@@ -569,6 +546,8 @@ tty_apply_features(struct tty_term *term)
|
||||
}
|
||||
}
|
||||
term->flags |= tf->flags;
|
||||
if (tf == &tty_feature_utf8)
|
||||
c->flags |= CLIENT_UTF8;
|
||||
}
|
||||
if ((term->applied_features|feat) == term->applied_features)
|
||||
return (0);
|
||||
@@ -644,7 +623,6 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
||||
"extkeys,"
|
||||
"focus,"
|
||||
"hyperlinks,"
|
||||
"sixel,"
|
||||
"usstyle"
|
||||
},
|
||||
{ .name = "ghostty",
|
||||
@@ -658,20 +636,7 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
||||
"osc7,"
|
||||
"sync,"
|
||||
"usstyle,"
|
||||
"progressbar,"
|
||||
"kitty"
|
||||
},
|
||||
{ .name = "kitty",
|
||||
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
|
||||
"ccolour,"
|
||||
"cstyle,"
|
||||
"extkeys,"
|
||||
"focus,"
|
||||
"hyperlinks,"
|
||||
"osc7,"
|
||||
"sync,"
|
||||
"usstyle,"
|
||||
"kitty"
|
||||
"progressbar"
|
||||
},
|
||||
{ .name = "Rio",
|
||||
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
|
||||
|
||||
@@ -729,7 +729,6 @@ tty_keys_winsz(struct tty *tty, const char *buf, size_t len, size_t *size)
|
||||
char_y = (ypixel && tty->sy) ? ypixel / tty->sy : 0;
|
||||
tty_set_size(tty, tty->sx, tty->sy, char_x, char_y);
|
||||
tty_invalidate(tty);
|
||||
recalculate_sizes();
|
||||
|
||||
tty->flags &= ~TTY_WINSIZEQUERY;
|
||||
*size = end + 1;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user