Compare commits

..

7 Commits

Author SHA1 Message Date
Nicholas Marriott
03436d273a Bump version. 2026-09-11 09:06:39 +01:00
Nicholas Marriott
7fdb1c90b7 Fix tests. 2026-09-11 09:06:20 +01:00
nicm
55012ded8d Add a nesting limit for v1 layouts, reported by M Khalilov, fix based on
issue 5572 from Afonso Januário. Also tweak some language while here.
2026-09-11 09:05:55 +01:00
nicm
de04ae2c65 Add remain-on-exit failed-key and a -D flag to new-pane to have a modal
pane wait for Escape/C-c. Both to allow better compatibility with
popups.
2026-09-11 09:05:41 +01:00
Nicholas Marriott
3b931d9c20 Note changes from Heon Jeong. 2026-09-11 09:05:39 +01:00
Nicholas Marriott
6f387eec2b Tweak changes. 2026-09-09 13:50:14 +01:00
Nicholas Marriott
80f522f577 Bump version. 2026-09-09 13:27:07 +01:00
17 changed files with 24 additions and 933 deletions

View File

@@ -1,6 +1,6 @@
# configure.ac
AC_INIT([tmux], next-3.9)
AC_INIT([tmux], 3.8-rc2)
AC_PREREQ([2.60])
AC_CONFIG_AUX_DIR(etc)

View File

@@ -53,7 +53,6 @@
" '#{?#{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},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}" \
@@ -576,7 +575,6 @@ 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 -- '%%' } }",
@@ -594,7 +592,6 @@ key_bindings_init(void)
"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 }",
@@ -690,8 +687,6 @@ 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 P { send -X toggle-position }",
"bind -Tcopy-mode-vi T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: layout.c,v 1.100 2026/09/11 08:16:14 nicm Exp $ */
/* $OpenBSD: layout.c,v 1.99 2026/09/09 07:03:39 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -768,49 +768,6 @@ layout_free(struct window *w, int only_nodes)
layout_free_cell(w->layout_root, only_nodes);
}
/* Move and resize floating panes so they stay inside the window. */
static void
layout_clamp_floating_panes(struct window *w, u_int sx, u_int sy)
{
struct window_pane *wp;
struct layout_cell *lc;
u_int pad, avail, csx, csy;
TAILQ_FOREACH(wp, &w->z_index, zentry) {
lc = wp->layout_cell;
if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING))
continue;
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
pad = 0;
else
pad = 1;
csx = lc->g.sx;
avail = (sx > 2 * pad) ? sx - 2 * pad : 0;
if (csx > avail)
csx = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
csy = lc->g.sy;
avail = (sy > 2 * pad) ? sy - 2 * pad : 0;
if (csy > avail)
csy = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
if (csx != lc->g.sx || csy != lc->g.sy)
layout_set_size(lc, csx, csy, lc->g.xoff, lc->g.yoff);
if (lc->g.xoff + lc->g.sx + pad > sx) {
if (lc->g.sx + 2 * pad >= sx)
lc->g.xoff = pad;
else
lc->g.xoff = sx - lc->g.sx - pad;
}
if (lc->g.yoff + lc->g.sy + pad > sy) {
if (lc->g.sy + 2 * pad >= sy)
lc->g.yoff = pad;
else
lc->g.yoff = sy - lc->g.sy - pad;
}
}
}
/* Resize the entire layout after window resize. */
void
layout_resize(struct window *w, u_int sx, u_int sy)
@@ -831,11 +788,8 @@ layout_resize(struct window *w, u_int sx, u_int sy)
* out proportionately - this should leave the layout fitting the new
* window size.
*/
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) {
layout_clamp_floating_panes(w, sx, sy);
layout_fix_panes(w, NULL);
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING))
return;
}
xchange = sx - lc->g.sx;
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
if (xchange < 0 && xchange < -xlimit)
@@ -865,7 +819,6 @@ layout_resize(struct window *w, u_int sx, u_int sy)
/* Fix cell offsets. */
layout_fix_offsets(w);
layout_clamp_floating_panes(w, sx, sy);
layout_fix_panes(w, NULL);
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: options-table.c,v 1.246 2026/09/11 10:17:16 nicm Exp $ */
/* $OpenBSD: options-table.c,v 1.245 2026/09/10 11:02:18 nicm Exp $ */
/*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -76,8 +76,7 @@ static const char *options_table_pane_border_indicators_list[] = {
"off", "colour", "arrows", "both", NULL
};
static const char *options_table_pane_border_lines_list[] = {
"single", "double", "heavy", "simple", "number", "spaces", "none",
"rounded", NULL
"single", "double", "heavy", "simple", "number", "spaces", "none", NULL
};
static const char *options_table_popup_border_lines_list[] = {
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL

View File

@@ -268,149 +268,5 @@ $TMUX kill-pane -t "$floating" || exit 1
$TMUX kill-pane -t "$right" || exit 1
$TMUX kill-pane -t "$lower" || exit 1
# --- Floating panes clamped when the window shrinks (issue #5581, PR #5582) ---
#
# layout_resize clamps floating panes back inside the window when it shrinks:
# move them and, only if they cannot fit, shrink them (never below
# PANE_MINIMUM). A floating cell is the pane's content, so with the default
# single-line border the clamp counts 1 cell of border per side, exactly as
# layout_floating_args_parse does on creation; with no border it counts 0.
# Each case below gets its own window, since resize-window fixes a window at
# a manual size for the rest of its life.
# Case 1: a lone floating pane -- break-pane -W on a window's only pane --
# takes the early-return path in layout_resize, added during PR #5582's
# review round, since there is no tiled tree to walk.
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for lone float failed"
# -x 20 -y 6 -> pane 18x4; -X 60 -Y 18 -> pane_left 61, pane_top 19: with the
# border, the footprint is columns 60-79, rows 18-23, flush with the 80x24
# window's right and bottom edges, so the float fits before it is shrunk.
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
fail "break-pane -W for lone float failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
# Shrink to 40x16. The float still fits at its own size (18 <= 40-2, 4 <=
# 16-2) so only its position moves, flush to the new right/bottom edges:
# xoff = 40 - 18 - 1 = 21; yoff = 16 - 4 - 1 = 11.
$TMUX resize-window -t "$win" -x 40 -y 16 ||
fail "resize-window (lone float) failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
# Case 5: grow the window back. The clamp must not chase the window back
# outward -- it only ever pulls a float in, never restores where it was.
$TMUX resize-window -t "$win" -x 80 -y 24 ||
fail "resize-window grow (lone float) failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
$TMUX kill-window -t "$win" || exit 1
# Case 2: the same float, but with a tiled sibling surviving alongside it, so
# the window's root cell stays tiled and layout_resize takes the normal path
# (the clamp call after layout_fix_offsets) instead of the early return
# above. Same geometry as case 1, so the same numbers should come out.
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for tiled sibling failed"
$TMUX split-window -t "$win" 'sleep 100' ||
fail "split-window for tiled sibling failed"
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
fail "break-pane -W for tiled sibling failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
$TMUX resize-window -t "$win" -x 40 -y 16 ||
fail "resize-window (tiled sibling) failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
$TMUX kill-window -t "$win" || exit 1
# Case 3: new-pane float, the original repro from issue #5581 and the PR
# body -- also the normal path, via the tiled base pane new-window creates.
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for new-pane repro failed"
$TMUX resize-window -t "$win" -x 120 -y 40 ||
fail "resize-window to 120x40 failed"
# -x 30 -y 10 -> pane 28x8; -X 85 -Y 25 -> pane_left 86, pane_top 26.
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
-x 30 -y 10 -X 85 -Y 25 'sleep 100') ||
fail "new-pane for new-pane repro failed"
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 86
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 26
# Shrink to 60x20: xoff = 60 - 28 - 1 = 31; yoff = 20 - 8 - 1 = 11.
$TMUX resize-window -t "$win" -x 60 -y 20 ||
fail "resize-window (new-pane repro) failed"
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 31
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 11
$TMUX kill-window -t "$win" || exit 1
# Case 4: a float that already fits inside the shrunk window is left alone --
# assert position and size are both unchanged, not just one of them.
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for untouched float failed"
# -x 20 -y 6 -> pane 18x4; -X 8 -Y 3 -> pane_left 9, pane_top 4 (as at the top
# of this file). Footprint columns 8-27, rows 3-8: well inside 60x20 too.
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
-x 20 -y 6 -X 8 -Y 3 'sleep 100') ||
fail "new-pane for untouched float failed"
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
$TMUX resize-window -t "$win" -x 60 -y 20 ||
fail "resize-window (untouched float) failed"
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
$TMUX kill-window -t "$win" || exit 1
# Case 6: pad = 0, via the pane-border-lines window option set to none. The
# border arithmetic differs here (no -2/+1 adjustment either on creation or
# in the clamp).
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for pad=0 float failed"
$TMUX set-option -w -t "$win" pane-border-lines none ||
fail "set pane-border-lines none failed"
# No border: -x 20 -y 6 -> pane 20x6 directly; -X 60 -Y 18 -> pane_left 60,
# pane_top 18 directly. Footprint (== content, no border) is columns 60-79,
# rows 18-23: flush right/bottom of the 80x24 window, same as case 1.
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
fail "break-pane -W for pad=0 float failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 60
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 18
# Shrink to 40x16 with pad=0: xoff = 40 - 20 - 0 = 20; yoff = 16 - 6 - 0 = 10.
$TMUX resize-window -t "$win" -x 40 -y 16 ||
fail "resize-window (pad=0 float) failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 20
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 10
$TMUX kill-window -t "$win" || exit 1
$TMUX kill-server 2>/dev/null
exit 0

View File

@@ -337,9 +337,9 @@ $TMUX set -g @modal-prefix no
$TMUX set -g @modal-root no
$TMUX bind -n z set -g @modal-root yes
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'cat') ||
fail "new-pane -ODK failed"
fail "new-pane -OK failed"
sleep 1
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
sleep 1
@@ -356,50 +356,9 @@ new_left=$(fmt "$modal" '#{pane_left}')
new_top=$(fmt "$modal" '#{pane_top}')
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
fail "key-capturing modal pane did not move"
$TMUX2 send-keys -t "$OUTER" Escape
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'trap "" INT; exec cat') ||
fail "new-pane -ODK failed"
sleep 1
$TMUX2 send-keys -t "$OUTER" C-c
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
# A dead modal does not close on Escape or C-c without -D.
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
fail "new-pane -O failed"
check_ok set-option -p -t "$modal" remain-on-exit on
sleep 2
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
$TMUX2 send-keys -t "$OUTER" Escape
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
check_ok kill-pane -t "$modal"
sleep 1
# failed-key closes successful panes and retains failed panes until a key.
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
fail "new-pane -O failed"
check_ok set-option -p -t "$modal" remain-on-exit failed-key
sleep 2
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'sleep 1; exit 1') ||
fail "new-pane -O failed"
check_ok set-option -p -t "$modal" remain-on-exit failed-key
sleep 2
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
must_equal "$($TMUX show-options -pv -t "$modal" remain-on-exit)" failed-key
$TMUX2 send-keys -t "$OUTER" a
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
# A nonmodal floating pane may remain above zoom, and switching between it and
# the zoomed tiled pane must not unzoom the window.
check_ok new-window -d -t modal: -n float-over-zoom 'cat'

View File

@@ -86,11 +86,6 @@ check_value "-gv status-keys" "vi"
check_fail "unknown value: bogus" set -g status-keys bogus
check_value "-gv status-keys" "vi"
# pane-border-lines accepts rounded as a pane border style.
check_ok set -gw pane-border-lines rounded
check_value "-gwv pane-border-lines" "rounded"
check_ok set -gw pane-border-lines single
# --- flag options ---------------------------------------------------------
#
# focus-events is an on/off flag. Setting with no value toggles it; explicit

View File

@@ -1,115 +0,0 @@
#!/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 || 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 || exit 1
$TMUX send-keys -X select-output || exit 1
$TMUX send-keys -X pipe-selection "wc -c >$OUT" || exit 1
sleep 1
[ "$(cat "$OUT")" = 10 ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX copy-mode || exit 1
$TMUX send-keys -X search-backward separator || exit 1
$TMUX send-keys -X copy-output || exit 1
[ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX copy-mode || exit 1
$TMUX send-keys -X search-backward one || exit 1
$TMUX send-keys -X copy-output || exit 1
[ "$($TMUX show-buffer)" = "$EXPECTED" ] || 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 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 -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 -t :prompt || exit 1
$TMUX send-keys -t :prompt.0 -X -N 100 cursor-down || exit 1
$TMUX send-keys -t :prompt.0 C-o || 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 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 -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 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
exit 0

View File

@@ -107,12 +107,6 @@ $TMUX2 new-pane -x28 -y8 -X4 -Y1 -B double \
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
compare floating-border-double
# Larger floating pane with rounded border lines.
new_scene 40 12
$TMUX2 new-pane -x28 -y8 -X4 -Y1 -B rounded \
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
compare floating-border-rounded
# Floating pane with no border lines: redraw_mark_pane_borders returns early so
# the float has no border at all, only its (clipped) content over the base pane.
new_scene 40 12

View File

@@ -1,12 +0,0 @@
base
╭──────────────────────────╮
│FLOAT │
│ │
│ │
│ │
│ │
│ │
╰──────────────────────────╯

View File

@@ -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,
const char *editor, spawn_finish_edit_cb cb, void *arg)
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);
if (editor == NULL)
editor = options_get_string(global_options, "editor");
editor = options_get_string(global_options, "editor");
fd = mkstemp(path);
if (fd == -1)
return (NULL);

89
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1170 2026/09/11 10:17:16 nicm Exp $
.\" $OpenBSD: tmux.1,v 1.1169 2026/09/10 11:02:18 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: September 11 2026 $
.Dd $Mdocdate: September 10 2026 $
.Dt TMUX 1
.Os
.Sh NAME
@@ -2006,25 +2006,6 @@ 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
@@ -2243,32 +2224,6 @@ but treat all non-whitespace characters as part of a word.
.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)
@@ -2309,21 +2264,6 @@ 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 pipe\-selection
.Op Ar command
.Xc
Pipe the selected text to
.Ar command
and clear the selection.
.It Xo
.Ic previous\-matching\-bracket
(emacs: M\-C\-b)
.Xc
@@ -2513,15 +2453,6 @@ Search forward for the specified plain text.
Repeat the last search in the reverse direction (forward becomes backward and
backward becomes forward).
.It Xo
.Ic select\-output
.Op Fl a
.Xc
Select the current OSC 133 command output.
The default emacs binding is
.Ql C\-o
and the default vi binding is
.Ql M\-o .
.It Xo
.Ic select\-line
(vi: V)
.Xc
@@ -2565,20 +2496,6 @@ 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
.Ql C
marker up to its matching
.Ql D
marker.
If there is no
.Ql D
marker, the output ends at the end of the copy-mode buffer.
At the following prompt or below it, they use the most recent nonempty output.
With
.Fl a ,
they use the entire copy-mode buffer instead.
If no output range can be found, they do nothing.
.Pp
The search commands come in several varieties:
.Ql search\-forward
and
@@ -6063,8 +5980,6 @@ single lines using ACS or UTF\-8 characters
double lines using UTF\-8 characters
.It heavy
heavy lines using UTF\-8 characters
.It rounded
single lines with rounded corners using UTF\-8 characters
.It simple
simple ASCII characters
.It number

7
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1440 2026/09/11 10:17:16 nicm Exp $ */
/* $OpenBSD: tmux.h,v 1.1439 2026/09/10 11:02:18 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1150,8 +1150,7 @@ enum pane_lines {
PANE_LINES_SIMPLE,
PANE_LINES_NUMBER,
PANE_LINES_SPACES,
PANE_LINES_NONE,
PANE_LINES_ROUNDED
PANE_LINES_NONE
};
/* Pane border indicator option. */
@@ -4227,7 +4226,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,
const char *, spawn_finish_edit_cb, void *);
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 *);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-border.c,v 1.4 2026/09/11 10:17:16 nicm Exp $ */
/* $OpenBSD: window-border.c,v 1.3 2026/07/23 09:38:27 nicm Exp $ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -120,10 +120,6 @@ window_get_border_cell(struct window_pane *wp, enum pane_lines pane_lines,
gc->attr &= ~GRID_ATTR_CHARSET;
utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
break;
case PANE_LINES_ROUNDED:
gc->attr &= ~GRID_ATTR_CHARSET;
utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type));
break;
case PANE_LINES_SIMPLE:
gc->attr &= ~GRID_ATTR_CHARSET;
utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);

View File

@@ -606,8 +606,7 @@ window_buffer_start_edit(struct window_buffer_modedata *data,
ed->name = xstrdup(paste_buffer_name(pb));
ed->pb = pb;
ed->editor = spawn_editor(c, buf, len, NULL, window_buffer_edit_close_cb,
ed);
ed->editor = spawn_editor(c, buf, len, window_buffer_edit_close_cb, ed);
if (ed->editor == NULL)
window_buffer_finish_edit(ed);
else {

View File

@@ -117,20 +117,9 @@ static int window_copy_set_selection(struct window_mode_entry *, int, int);
static int window_copy_update_selection(struct window_mode_entry *, int,
int);
static void window_copy_synchronize_cursor(struct window_mode_entry *, int);
static int window_copy_find_output_range(struct window_mode_entry *,
u_int *, u_int *, u_int *, u_int *);
static int window_copy_find_previous_output_range(struct window_mode_entry *,
u_int, u_int, u_int *, u_int *, u_int *, u_int *);
static void window_copy_output_end(struct screen *, u_int *, u_int *);
static void *window_copy_get_selection(struct window_mode_entry *, size_t *);
static void *window_copy_get_output(struct window_mode_entry *, size_t *,
int);
static void *window_copy_get_grid_range(struct window_mode_entry *, u_int,
u_int, u_int, u_int, size_t *);
static void window_copy_copy_buffer(struct window_mode_entry *,
const char *, void *, size_t, int, int);
static void window_copy_pipe_buffer(struct session *, const char *, void *,
size_t);
static void window_copy_pipe(struct window_mode_entry *,
struct session *, const char *);
static void window_copy_copy_pipe(struct window_mode_entry *,
@@ -1598,80 +1587,6 @@ window_copy_cmd_copy_selection_and_cancel(struct window_copy_cmd_state *cs)
return (WINDOW_COPY_CMD_CANCEL);
}
static enum window_copy_cmd_action
window_copy_cmd_select_output(struct window_copy_cmd_state *cs)
{
struct window_mode_entry *wme = cs->wme;
struct window_copy_mode_data *data = wme->data;
struct grid_reader gr;
void *buf;
size_t len;
u_int sx, sy, ex, ey, total;
int all = args_has(cs->wargs, 'a');
if (all) {
sx = sy = 0;
total = data->backing->grid->hsize + data->backing->grid->sy;
ey = total - 1;
ex = grid_get_line(data->backing->grid, ey)->cellused;
} else if (!window_copy_find_output_range(wme, &sx, &sy, &ex, &ey))
return (WINDOW_COPY_CMD_NOTHING);
buf = window_copy_get_output(wme, &len, all);
if (buf == NULL)
return (WINDOW_COPY_CMD_NOTHING);
free(buf);
window_copy_clear_selection(wme);
data->lineflag = LINE_SEL_NONE;
data->rectflag = 0;
data->selflag = SEL_CHAR;
window_copy_scroll_to(wme, sx, sy, 1);
window_copy_start_selection(wme);
if (options_get_number(wme->wp->window->options, "mode-keys") ==
MODEKEY_VI) {
grid_reader_start(&gr, data->backing->grid, ex, ey);
grid_reader_cursor_left(&gr, 1);
grid_reader_get_cursor(&gr, &ex, &ey);
}
window_copy_scroll_to(wme, ex, ey, 1);
return (WINDOW_COPY_CMD_REDRAW);
}
static enum window_copy_cmd_action
window_copy_cmd_copy_output_no_clear(struct window_copy_cmd_state *cs)
{
struct window_mode_entry *wme = cs->wme;
struct client *c = cs->c;
struct session *s = cs->s;
struct winlink *wl = cs->wl;
struct window_pane *wp = wme->wp;
void *buf;
char *prefix = NULL;
const char *arg0 = args_string(cs->wargs, 0);
size_t len;
int set_paste = !args_has(cs->wargs, 'P');
int set_clip = !args_has(cs->wargs, 'C');
if (arg0 != NULL)
prefix = format_single(NULL, arg0, c, s, wl, wp);
if (s != NULL) {
buf = window_copy_get_output(wme, &len, args_has(cs->wargs, 'a'));
if (buf != NULL)
window_copy_copy_buffer(wme, prefix, buf, len, set_paste,
set_clip);
}
free(prefix);
return (WINDOW_COPY_CMD_NOTHING);
}
static enum window_copy_cmd_action
window_copy_cmd_copy_output(struct window_copy_cmd_state *cs)
{
window_copy_cmd_copy_output_no_clear(cs);
window_copy_clear_selection(cs->wme);
return (WINDOW_COPY_CMD_REDRAW);
}
static enum window_copy_cmd_action
window_copy_cmd_cursor_down(struct window_copy_cmd_state *cs)
{
@@ -2827,133 +2742,6 @@ window_copy_cmd_pipe_and_cancel(struct window_copy_cmd_state *cs)
return (WINDOW_COPY_CMD_CANCEL);
}
static enum window_copy_cmd_action
window_copy_cmd_copy_pipe_output_no_clear(struct window_copy_cmd_state *cs)
{
struct window_mode_entry *wme = cs->wme;
struct client *c = cs->c;
struct session *s = cs->s;
struct winlink *wl = cs->wl;
struct window_pane *wp = wme->wp;
void *buf;
char *command = NULL, *prefix = NULL;
const char *arg0 = args_string(cs->wargs, 0);
const char *arg1 = args_string(cs->wargs, 1);
size_t len;
int set_paste = !args_has(cs->wargs, 'P');
int set_clip = !args_has(cs->wargs, 'C');
if (arg1 != NULL)
prefix = format_single(NULL, arg1, c, s, wl, wp);
if (s != NULL && arg0 != NULL && *arg0 != '\0')
command = format_single(NULL, arg0, c, s, wl, wp);
if (s != NULL) {
buf = window_copy_get_output(wme, &len, args_has(cs->wargs, 'a'));
if (buf != NULL) {
window_copy_pipe_buffer(s, command, buf, len);
window_copy_copy_buffer(wme, prefix, buf, len, set_paste,
set_clip);
}
}
free(command);
free(prefix);
return (WINDOW_COPY_CMD_NOTHING);
}
static enum window_copy_cmd_action
window_copy_cmd_copy_pipe_output(struct window_copy_cmd_state *cs)
{
window_copy_cmd_copy_pipe_output_no_clear(cs);
window_copy_clear_selection(cs->wme);
return (WINDOW_COPY_CMD_REDRAW);
}
static enum window_copy_cmd_action
window_copy_cmd_pipe_output_no_clear(struct window_copy_cmd_state *cs)
{
struct window_mode_entry *wme = cs->wme;
struct client *c = cs->c;
struct session *s = cs->s;
struct winlink *wl = cs->wl;
struct window_pane *wp = wme->wp;
void *buf;
char *command = NULL;
const char *arg0 = args_string(cs->wargs, 0);
size_t len;
if (s != NULL && arg0 != NULL && *arg0 != '\0')
command = format_single(NULL, arg0, c, s, wl, wp);
if (s != NULL) {
buf = window_copy_get_output(wme, &len, args_has(cs->wargs, 'a'));
if (buf != NULL) {
window_copy_pipe_buffer(s, command, buf, len);
free(buf);
}
}
free(command);
return (WINDOW_COPY_CMD_NOTHING);
}
static enum window_copy_cmd_action
window_copy_cmd_pipe_output(struct window_copy_cmd_state *cs)
{
window_copy_cmd_pipe_output_no_clear(cs);
window_copy_clear_selection(cs->wme);
return (WINDOW_COPY_CMD_REDRAW);
}
static void
window_copy_open_done(char *buf, __unused size_t len, __unused void *arg)
{
free(buf);
}
static enum window_copy_cmd_action
window_copy_cmd_open_selection(struct window_copy_cmd_state *cs)
{
struct window_mode_entry *wme = cs->wme;
struct window_copy_mode_data *data = wme->data;
void *buf;
char *editor = NULL;
const char *arg0 = args_string(cs->wargs, 0);
size_t len;
if (cs->c == NULL ||
(data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE))
return (WINDOW_COPY_CMD_NOTHING);
if (arg0 != NULL && *arg0 != '\0')
editor = format_single(NULL, arg0, cs->c, cs->s, cs->wl, wme->wp);
buf = window_copy_get_selection(wme, &len);
if (buf != NULL) {
spawn_editor(cs->c, buf, len, editor, window_copy_open_done, NULL);
free(buf);
}
free(editor);
return (WINDOW_COPY_CMD_NOTHING);
}
static enum window_copy_cmd_action
window_copy_cmd_open_output(struct window_copy_cmd_state *cs)
{
struct window_mode_entry *wme = cs->wme;
void *buf;
char *editor = NULL;
const char *arg0 = args_string(cs->wargs, 0);
size_t len;
if (cs->c == NULL)
return (WINDOW_COPY_CMD_NOTHING);
if (arg0 != NULL && *arg0 != '\0')
editor = format_single(NULL, arg0, cs->c, cs->s, cs->wl, wme->wp);
buf = window_copy_get_output(wme, &len, args_has(cs->wargs, 'a'));
if (buf != NULL) {
spawn_editor(cs->c, buf, len, editor, window_copy_open_done, NULL);
free(buf);
}
free(editor);
return (WINDOW_COPY_CMD_NOTHING);
}
static enum window_copy_cmd_action
window_copy_cmd_goto_line(struct window_copy_cmd_state *cs)
{
@@ -3616,18 +3404,6 @@ static const struct {
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_copy_pipe_and_cancel
},
{ .command = "copy-pipe-output",
.args = { "aCP", 0, 2, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_copy_pipe_output
},
{ .command = "copy-output",
.args = { "aCP", 0, 1, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_copy_output
},
{ .command = "copy-selection-no-clear",
.args = { "CP", 0, 1, NULL },
.flags = 0,
@@ -3850,18 +3626,6 @@ static const struct {
.clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
.f = window_copy_cmd_other_end
},
{ .command = "open-output",
.args = { "a", 0, 1, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_NEVER,
.f = window_copy_cmd_open_output
},
{ .command = "open-selection",
.args = { "", 0, 1, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_NEVER,
.f = window_copy_cmd_open_selection
},
{ .command = "page-down",
.args = { "", 0, 0, NULL },
.flags = WINDOW_COPY_CMD_FLAG_READONLY,
@@ -3898,18 +3662,6 @@ static const struct {
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_pipe_and_cancel
},
{ .command = "pipe-output",
.args = { "a", 0, 1, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_pipe_output
},
{ .command = "pipe-selection",
.args = { "", 0, 1, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_pipe
},
{ .command = "previous-matching-bracket",
.args = { "", 0, 0, NULL },
.flags = WINDOW_COPY_CMD_FLAG_READONLY,
@@ -4087,12 +3839,6 @@ static const struct {
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_search_reverse
},
{ .command = "select-output",
.args = { "a", 0, 0, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_select_output
},
{ .command = "select-line",
.args = { "", 0, 0, NULL },
.flags = 0,
@@ -6224,185 +5970,6 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw,
return (1);
}
/* Find the output range for the command at the cursor. */
static int
window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx,
u_int *sy, u_int *ex, u_int *ey)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
struct grid_line *gl;
u_int cursor_x, cursor_y, prompt_x = 0;
u_int prompt_y = UINT_MAX, y, total;
int found_start = 0, found_end = 0;
cursor_x = data->cx;
cursor_y = screen_hsize(data->backing) + data->cy - data->oy;
log_debug("%s: cursor at %u,%u", __func__, cursor_x, cursor_y);
total = gd->hsize + gd->sy;
for (y = 0; y < total; y++) {
gl = grid_get_line(gd, y);
if (~gl->flags & GRID_LINE_START_PROMPT)
continue;
if (y > cursor_y ||
(y == cursor_y && gl->osc133_data.prompt_col > cursor_x))
break;
prompt_y = y;
prompt_x = gl->osc133_data.prompt_col;
}
if (prompt_y == UINT_MAX) {
log_debug("%s: no osc133 prompt before cursor", __func__);
return (0);
}
log_debug("%s: prompt at %u,%u", __func__, prompt_x, prompt_y);
for (y = prompt_y; y < total; y++) {
gl = grid_get_line(gd, y);
/* An output may end on the next prompt's line. */
if (found_start && gl->flags & GRID_LINE_END_OUTPUT &&
(y != prompt_y || gl->osc133_data.out_end_col >= prompt_x)) {
*ex = gl->osc133_data.out_end_col;
*ey = y;
found_end = 1;
break;
}
if (y != prompt_y && gl->flags & GRID_LINE_START_PROMPT)
break;
if (gl->flags & GRID_LINE_START_OUTPUT &&
(y != prompt_y || gl->osc133_data.out_start_col >= prompt_x)) {
*sx = gl->osc133_data.out_start_col;
*sy = y;
found_start = 1;
}
}
if (!found_start) {
log_debug("%s: no output after prompt", __func__);
return (window_copy_find_previous_output_range(wme, cursor_x,
cursor_y, sx, sy, ex, ey));
}
if (!found_end) {
if (y != total) {
log_debug("%s: output interrupted by next prompt", __func__);
return (0);
}
window_copy_output_end(data->backing, ex, ey);
}
log_debug("%s: output from %u,%u to %u,%u", __func__, *sx, *sy,
*ex, *ey);
return (1);
}
/* Find the most recent complete output at or before a position. */
static int
window_copy_find_previous_output_range(struct window_mode_entry *wme,
u_int cursor_x, u_int cursor_y, u_int *sx, u_int *sy, u_int *ex,
u_int *ey)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
struct grid_line *gl;
void *buf;
u_int start_x, start_y, end_x, end_y;
u_int y, total;
size_t len;
int found = 0, have_prompt = 0;
int pending = 0;
total = gd->hsize + gd->sy;
for (y = 0; y < total && y <= cursor_y; y++) {
gl = grid_get_line(gd, y);
if (gl->flags & GRID_LINE_START_OUTPUT &&
(y != cursor_y || gl->osc133_data.out_start_col <= cursor_x)) {
start_x = gl->osc133_data.out_start_col;
start_y = y;
pending = 1;
}
/* The output may have cleared its C marker from the screen. */
if (!pending && !have_prompt &&
gl->flags & GRID_LINE_END_OUTPUT &&
(~gl->flags & GRID_LINE_START_PROMPT ||
gl->osc133_data.out_end_col <= gl->osc133_data.prompt_col)) {
start_x = start_y = 0;
pending = 1;
}
if (pending && gl->flags & GRID_LINE_END_OUTPUT &&
(y != cursor_y || gl->osc133_data.out_end_col <= cursor_x)) {
end_x = gl->osc133_data.out_end_col;
end_y = y;
buf = window_copy_get_grid_range(wme, start_x, start_y,
end_x, end_y, &len);
if (buf != NULL) {
free(buf);
*sx = start_x;
*sy = start_y;
*ex = end_x;
*ey = end_y;
found = 1;
}
pending = 0;
}
if (gl->flags & GRID_LINE_START_PROMPT) {
pending = 0;
have_prompt = 1;
}
}
return (found);
}
static void
window_copy_output_end(struct screen *s, u_int *x, u_int *y)
{
*x = s->cx;
*y = screen_hsize(s) + s->cy;
}
static void *
window_copy_get_output(struct window_mode_entry *wme, size_t *len, int all)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
u_int sx, sy, ex, ey, total;
total = gd->hsize + gd->sy;
if (all) {
sx = sy = 0;
ey = total - 1;
ex = grid_get_line(gd, ey)->cellused;
} else if (!window_copy_find_output_range(wme, &sx, &sy, &ex, &ey)) {
*len = 0;
return (NULL);
}
return (window_copy_get_grid_range(wme, sx, sy, ex, ey, len));
}
static void *
window_copy_get_grid_range(struct window_mode_entry *wme, u_int sx, u_int sy,
u_int ex, u_int ey, size_t *len)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
char *buf;
size_t off;
u_int i;
buf = xmalloc(1);
off = 0;
for (i = sy; i <= ey; i++) {
window_copy_copy_line(wme, &buf, &off, i, i == sy ? sx : 0,
i == ey ? ex : gd->sx);
}
if (off != 0 && buf[off - 1] == '\n')
off--;
if (off == 0) {
free(buf);
*len = 0;
return (NULL);
}
*len = off;
return (buf);
}
static void *
window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
{
@@ -6535,7 +6102,8 @@ window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix,
if (set_clip &&
options_get_number(global_options, "set-clipboard") != 0) {
if (wp->flags & PANE_REDRAW) {
if (window_copy_line_numbers_active(wme) &&
(wp->flags & PANE_REDRAW)) {
/* Clear PANE_REDRAW so clipboard write not skipped. */
redraw = PANE_REDRAW;
wp->flags &= ~PANE_REDRAW;
@@ -6558,28 +6126,18 @@ window_copy_pipe_run(struct window_mode_entry *wme, struct session *s,
const char *cmd, size_t *len)
{
void *buf;
buf = window_copy_get_selection(wme, len);
window_copy_pipe_buffer(s, cmd, buf, *len);
return (buf);
}
static void
window_copy_pipe_buffer(struct session *s, const char *cmd, void *buf,
size_t len)
{
struct job *job;
if (buf == NULL)
return;
buf = window_copy_get_selection(wme, len);
if (cmd == NULL || *cmd == '\0')
cmd = options_get_string(global_options, "copy-command");
if (cmd != NULL && *cmd != '\0') {
job = job_run(cmd, 0, NULL, NULL, s, NULL, NULL, NULL, NULL,
NULL, JOB_NOWAIT, -1, -1);
if (job != NULL)
bufferevent_write(job_get_event(job), buf, len);
bufferevent_write(job_get_event(job), buf, *len);
}
return (buf);
}
static void

View File

@@ -2104,8 +2104,8 @@ window_customize_start_edit(struct window_customize_modedata *data,
buf = "\n";
len = 1;
}
ed->editor = spawn_editor(c, buf, len, NULL,
window_customize_edit_close_cb, ed);
ed->editor = spawn_editor(c, buf, len, window_customize_edit_close_cb,
ed);
free(value);
if (ed->editor == NULL)
window_customize_finish_edit(ed);