Compare commits

...
Author SHA1 Message Date
Nicholas Marriott 17d32ae40d Update CHANGES and tests. 2026-09-24 20:49:01 +01:00
Nicholas Marriott e4129d92c2 Test for newp -A and resize. 2026-09-24 20:13:35 +01:00
Nicholas Marriott 6a5de0d8e3 Test for resizing floating panes, from Noam Stolero. 2026-09-24 20:13:29 +01:00
Nicholas Marriott ff7efbe37c Some tests. 2026-09-24 20:13:22 +01:00
nicm ff7d45ad05 Do not cancel mark when click on border. 2026-09-24 20:12:52 +01:00
nicm 15a7944576 Add a feature for mintty's application escape to avoid dodgy terminals
whinging about not supporting it, GitHub issue 5626 from Pete Dietl.
2026-09-24 20:12:40 +01:00
nicm de69122034 Some terminals have extended DECSCUSR to allow it to reset to the
default style and put that as the Se capability in their terminfo entry.
Change so that if Ss and Se exist, tmux does not overwrite them with its
own. GitHub issue 5476.
2026-09-24 20:10:36 +01:00
nicm f38558ee12 Rather than allowing floating panes to remain outside the window and
invisible after resize, move them and resize them so they are fully
inside the window. GitHub issue 5582 from Noam Stolero.
2026-09-24 20:10:12 +01:00
nicm 38c7eb7413 Correctly write Z indexes to v2 layouts if zoomed, GitHub issue 5624. 2026-09-24 20:09:36 +01:00
nicm ad785736e9 Only rely on the previous line wrapping when the cursor is actually on
it, GitHub issue 5625 from Ben Maurer.
2026-09-24 20:09:25 +01:00
nicm 61687f2a5b Fix typo (too few 0s) in CLIENT_CONTROL_DISCARD, from someone in GitHub issue
5622.
2026-09-24 20:08:53 +01:00
nicm ed2b349c3e Do not leak waiting clients (in wait-for) if they are killed, GitHub
issue 5614.
2026-09-24 20:08:30 +01:00
nicm 92d29e54f4 Get x and y the right way round, fixes crash when opening a mode prompt
when the client is offset into the window; may be the crash reported by
sthen@.
2026-09-24 20:07:58 +01:00
nicm a34d2b6b21 Do not trim all lines to make a zero line grid which reflow does not like, from
Kaixuan Li.
2026-09-21 21:06:07 +01:00
nicm 550231e83a Do not do anything in session_detach and rely on the caller destroying
the session, otherwise grouped sessions can reuse a session with no
windows. From Jeong, Heon in GitHub issue 5620.
2026-09-21 21:05:52 +01:00
nicm 102d5185c1 Do not unzoom when resizing a floating pane that was created with -A.
Similarly, skip hidden floating panes when changing Z order. Reported by Clark
Wang.
2026-09-21 21:05:19 +01:00
Nicholas Marriott 1d9029c8d7 Update tests in 3.8 branch. 2026-09-21 08:38:11 +01:00
nicm a23ff880a2 If no floating panes, reset default starting position, and reset if any part of
the pane goes outside the window.
2026-09-21 08:30:53 +01:00
nicm 32dc7d5095 Empty string for invalid old-style formats causes old iTerm2 versions to crash,
so emit "0000," instead.
2026-09-21 08:30:29 +01:00
nicm 06ef15955c Fix session_*_flag format variables which loop over the windows (they should
only be false if all windows do not have the flag, not the first one). GitHub
issue 5599.
2026-09-21 08:30:17 +01:00
nicm ce9bdf6734 Do not loop forever if someone tries to give WCHAR_MAX a width, GitHub issue
5602.
2026-09-21 08:30:14 +01:00
nicm 757b15f2ca Expand -c for run-shell like the other -c flags, reported by Saúl Nogueras. 2026-09-21 08:30:11 +01:00
nicm fcb4e9760a Reset layout manually instead of calling window_unzoom which can go down the
notification path and end up double freeing the pane (this was previously
removed in 2015 but added back to fix a problem with late destroy - this is a
better fix). GitHub issue 5591 from Romain Francoise.
2026-09-21 08:30:08 +01:00
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
Nicholas Marriott 5aeacf1cae Update CHANGES. 2026-09-09 13:06:56 +01:00
tmux update bot 7774731b95 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Draw bottom border with pane status line at the top.
  Bound the memory used by buffered control mode command replies (to 64 MB), GitHub issue 5565 from kagari dot shusei at proton dot me.
  Do not adjust prompt position on invalid Unicode, from Kaixuan Li.
2026-09-09 10:12:52 +00:00
nicm 0544f0b210 Draw bottom border with pane status line at the top. 2026-09-09 10:12:50 +00:00
nicm a3129249d1 Bound the memory used by buffered control mode command replies (to 64
MB), GitHub issue 5565 from kagari dot shusei at proton dot me.
2026-09-09 10:12:50 +00:00
nicm 930c81b819 Do not adjust prompt position on invalid Unicode, from Kaixuan Li. 2026-09-09 10:12:50 +00:00
Nicholas Marriott fc695b4e86 Test for v1 nesting limit, based on a changed from Afonso Januário. 2026-09-09 10:01:32 +01:00
Nicholas Marriott 36fb7ab084 Tests of border. 2026-09-09 09:32:14 +01:00
tmux update bot 90a7dd4654 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add new layout format which includes floating panes. The new format is now a JSON subset which is less fragile and easier to handle than the old custom format. The old (version 1) format is still supported for control mode clients for now - they must set the new-layouts flag to receive the new format. From Dane Jensen.
2026-09-09 08:27:51 +00:00
nicm bf43fdc0c7 Add new layout format which includes floating panes. The new format is
now a JSON subset which is less fragile and easier to handle than the
old custom format. The old (version 1) format is still supported for
control mode clients for now - they must set the new-layouts flag to
receive the new format. From Dane Jensen.
2026-09-09 08:27:49 +00:00
Nicholas Marriott 077d6e53cd Test for invalid prompt. 2026-09-09 08:53:51 +01:00
Nicholas Marriott 4e19f7a8dc New layout format regress test, from Dane Jensen. 2026-09-09 08:04:23 +01:00
tmux update bot 7941f7b863 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  The client for the layout format is the client we are sending it to, not the target client.
2026-09-08 21:40:59 +00:00
nicm c7102ebbe5 The client for the layout format is the client we are sending it to, not
the target client.
2026-09-08 21:40:57 +00:00
tmux update bot 1b87cb752e Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add new-layouts flag for control clients so they can default to old layouts for backwards compatibility.
2026-09-08 11:38:06 +00:00
nicm d9692f7ecf Add new-layouts flag for control clients so they can default to old
layouts for backwards compatibility.
2026-09-08 11:38:04 +00:00
Nicholas Marriott 98637576bd Test for new-layouts flag. 2026-09-08 11:27:25 +01:00
Nicholas Marriott d8bdb23108 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a function to find last pane index, from Dane Jensen.
  Add a parser for a subset of JSON, will be used for new layout strings (and maybe some other stuff), from Dane Jensen.
  Add capture-pane -I to show times.
2026-09-08 11:25:17 +01:00
nicm 2191c9b172 Add a function to find last pane index, from Dane Jensen. 2026-09-08 10:22:40 +00:00
nicm 01d1d6580a Add a parser for a subset of JSON, will be used for new layout strings
(and maybe some other stuff), from Dane Jensen.
2026-09-08 10:22:40 +00:00
nicm 96f4c7b9fa Add capture-pane -I to show times. 2026-09-08 10:22:40 +00:00
Nicholas Marriott 6a24d5437b JSON test. 2026-09-08 09:36:26 +01:00
Nicholas Marriott 8396a63128 capture-pane -I test. 2026-09-08 08:32:05 +01:00
tmux update bot d44bfda26d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Error on invalid relative targets, such as +foo or -0. GitHub issue 5576 from imcusg at gmail dot com.
  Do not silently make a session monitor if the target is unknown, GitHub issue 5575 from zzchun12826 at gmail dot com.
2026-09-07 17:56:25 +00:00
nicm 00f3899aea Error on invalid relative targets, such as +foo or -0. GitHub issue 5576
from imcusg at gmail dot com.
2026-09-07 17:56:23 +00:00
nicm 557967c36c Do not silently make a session monitor if the target is unknown, GitHub
issue 5575 from zzchun12826 at gmail dot com.
2026-09-07 17:56:23 +00:00
Nicholas Marriott 73db0a54e5 Tests from GitHub issue 5576. 2026-09-07 13:05:38 +01:00
tmux update bot 578e07fcbc Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not attempt to write to control clients which have been destroyed, reported by Artur Penttinen in GitHub issue 5570.
2026-09-04 06:00:37 +00:00
nicm 93169e2e62 Do not attempt to write to control clients which have been destroyed,
reported by Artur Penttinen in GitHub issue 5570.
2026-09-04 06:00:36 +00:00
tmux update bot db24c029be Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Extend neww -S to work with -t as well as -t, GitHub issue 5571 from Mazunki Hoksaas.
2026-09-03 23:28:11 +00:00
nicm a6f9a206b7 Extend neww -S to work with -t as well as -t, GitHub issue 5571 from
Mazunki Hoksaas.
2026-09-03 23:28:09 +00:00
Nicholas Marriott ce253864f8 Test for late run-shell on a client that has exited. 2026-09-03 22:39:09 +01:00
tmux update bot 79f1f43830 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Wait for pane exit status in all cases not just remain-on-exit since events need it.
2026-09-03 21:24:20 +00:00
nicm 7b666e62be Wait for pane exit status in all cases not just remain-on-exit since
events need it.
2026-09-03 21:24:19 +00:00
Nicholas Marriott 5d5f66239a Test for -S from Mazunki Hoksaas. 2026-09-03 22:04:34 +01:00
Nicholas Marriott 4b1586fa81 Test for exit status. 2026-09-03 20:12:44 +01:00
Nicholas Marriott 9fa390aee6 Fix error in test. 2026-09-02 12:32:29 +01:00
Nicholas Marriott ad632726bd Some more regress tests. 2026-09-02 11:16:47 +01:00
Nicholas Marriott 1dd1717462 Fix tests for macOS. 2026-09-02 08:43:21 +01:00
tmux update bot 73db721dab Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Create screen before zoom may need to use it.
2026-09-02 06:15:13 +00:00
nicm 6bb6c286a9 Create screen before zoom may need to use it. 2026-09-02 06:15:11 +00:00
tmux update bot ca34d190b5 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not allow cursor on/off to escape synchronized updates.
2026-09-01 23:20:22 +00:00
nicm 57a13664cc Do not allow cursor on/off to escape synchronized updates. 2026-09-01 23:20:20 +00:00
Nicholas Marriott 59dc0e75c4 Add a couple more tests. 2026-09-01 22:07:08 +01:00
Nicholas Marriott 2d6aed1d92 Add some more tests for recent stuff. 2026-09-01 21:55:50 +01:00
Nicholas Marriott 3dd575ce55 Update CHANGES. 2026-09-01 21:48:30 +01:00
Nicholas Marriott 60e871658f Remove +x. 2026-09-01 20:52:44 +01:00
Nicholas Marriott c49dfdf496 Regress test for cursor updates. 2026-09-01 20:51:20 +01:00
tmux update bot 9b0ad73a4b Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  When changing selection-mode to line, set up the selection start and end correctly, GitHub issue 5545.
  Extend word commands to use any Unicode whitespace character, GitHub issue 5562 from Hongyi Zhao.
2026-09-01 14:55:38 +00:00
nicm bd8218e85d When changing selection-mode to line, set up the selection start and end
correctly, GitHub issue 5545.
2026-09-01 14:55:35 +00:00
nicm 8f3101d820 Extend word commands to use any Unicode whitespace character, GitHub
issue 5562 from Hongyi Zhao.
2026-09-01 14:55:35 +00:00
Nicholas Marriott 77309c07b0 Whitespace regress test from Hongyi Zhao. 2026-09-01 13:50:56 +01:00
tmux update bot c8e889694d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a generation counter to pane output, from Michael K Darling.
  Add format variables for history change counters, from Michael K Darling.
2026-09-01 00:07:34 +00:00
nicm b402687fcd Add a generation counter to pane output, from Michael K Darling. 2026-09-01 00:07:32 +00:00
nicm 0530a7d89a Add format variables for history change counters, from Michael K Darling. 2026-09-01 00:07:32 +00:00
tmux update bot 6bdb9ba54c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  tmux: add utf8 terminal-features
2026-08-31 20:43:13 +00:00
kirill f2bfd611e2 tmux: add utf8 terminal-features
OK: nicm@
2026-08-31 20:43:11 +00:00
Nicholas Marriott 9c1d5a080b Use hash for actions, from Andreas Fehlner. 2026-08-31 20:52:54 +01:00
tmux update bot b28244e2fe Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not leak temporary commands when expanding aliases, from Jeong, Heon.
  Do not allow popups for control clients, from Noam Stolero.
  Layout cell can be replaced by unzoom, fixes use after free. From Uzair Aftab.
  Do not try to append zero size cells, from Uzair Aftab.
  Clear stale marks when a search regex fails to compile, GitHub issue 5547 from Uzair Aftab.
2026-08-31 14:56:14 +00:00
nicm 8b3834c34f Do not leak temporary commands when expanding aliases, from Jeong, Heon. 2026-08-31 14:56:12 +00:00
nicm af3e4d2e5b Do not allow popups for control clients, from Noam Stolero. 2026-08-31 14:56:12 +00:00
nicm c552d1bf16 Layout cell can be replaced by unzoom, fixes use after free. From Uzair
Aftab.
2026-08-31 14:56:12 +00:00
nicm 6a1ecdcce3 Do not try to append zero size cells, from Uzair Aftab. 2026-08-31 14:56:12 +00:00
nicm 39357c829c Clear stale marks when a search regex fails to compile, GitHub issue
5547 from Uzair Aftab.
2026-08-31 14:56:12 +00:00
tmux update bot 40381bdcd9 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not walk off end of grid lines if last line is wrapped, reported by Moe Khalilov.
  Do no free input context if it is NULL when popup create fails.
2026-08-28 10:32:01 +00:00
nicm 7bad4f7722 Do not walk off end of grid lines if last line is wrapped, reported by
Moe Khalilov.
2026-08-28 10:32:00 +00:00
nicm 65e811570b Do no free input context if it is NULL when popup create fails. 2026-08-28 10:32:00 +00:00
tmux update bot dd31900f80 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Ignore focus events earlier to avoid them interfering with prefix, GitHub issue 5539.
2026-08-28 09:02:23 +00:00
nicm 013ada58c8 Ignore focus events earlier to avoid them interfering with prefix,
GitHub issue 5539.
2026-08-28 09:02:22 +00:00
Nicholas Marriott 78dad45f3b Add timeout for lock and don't keep credentials for regress, from Andreas
Fehlner.
2026-08-28 09:07:16 +01:00
Nicholas Marriott 52b452948f Check for buffer write error. 2026-08-28 08:56:12 +01:00
tmux update bot 267746603a Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Fix tiled pane resizing with floating layout cells, reported by Kevin Hovsäter.
2026-08-25 21:31:27 +00:00
nicm b26b240613 Fix tiled pane resizing with floating layout cells, reported by Kevin
Hovsäter.
2026-08-25 21:31:25 +00:00
Nicholas Marriott 8e48c889dd Add a resize test. 2026-08-25 19:36:06 +01:00
tmux update bot f4445a310a Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a clear-on-attach option which when turned off scrolls away the existing terminal state instead of entering the alternate screen. This is useful in environments where tmux is started as a login shell but it is important that any pre-tmux messages are still visible. GitHub issue 5508 from Josef Řídký.
2026-08-25 11:30:47 +00:00
nicm 4157b64754 Add a clear-on-attach option which when turned off scrolls away the
existing terminal state instead of entering the alternate screen. This
is useful in environments where tmux is started as a login shell but it
is important that any pre-tmux messages are still visible. GitHub issue
5508 from Josef Řídký.
2026-08-25 11:30:45 +00:00
tmux update bot 0d2aafe7b7 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not pass NULL to qsort, and add checks for invalid session or window after swap.
2026-08-25 09:38:42 +00:00
nicm 6f4c9b32af Do not pass NULL to qsort, and add checks for invalid session or window
after swap.
2026-08-25 09:38:40 +00:00
tmux update bot eb77a75ab7 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not leak exit message on client free, from Jeong, Heon.
  Tidy up args_make_commands reference counting to fix a memory leak, from Jeong, Heon.
2026-08-25 07:59:42 +00:00
nicm 0851650c6b Do not leak exit message on client free, from Jeong, Heon. 2026-08-25 07:59:40 +00:00
nicm 1459c90a7f Tidy up args_make_commands reference counting to fix a memory leak, from
Jeong, Heon.
2026-08-25 07:59:40 +00:00
tmux update bot af29c080ba Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Correct a comparison ('\0' should be 0).
  If a pane has no scrollbar, don't bother to redraw it, and if redraw is deferred change scrollbar redraws into redraw-all-scrollbars not redraw entire window. GitHub issue 5529.
2026-08-25 01:46:56 +00:00
nicm 50ea32162f Correct a comparison ('\0' should be 0). 2026-08-25 01:46:55 +00:00
nicm 2d5328a860 If a pane has no scrollbar, don't bother to redraw it, and if redraw is
deferred change scrollbar redraws into redraw-all-scrollbars not redraw
entire window. GitHub issue 5529.
2026-08-25 01:46:55 +00:00
tmux update bot e5db7598b2 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not add messages to cfg_causes after config has finished, GitHub issue 5528.
2026-08-24 22:03:29 +00:00
nicm dc78d8f9ea Do not add messages to cfg_causes after config has finished, GitHub
issue 5528.
2026-08-24 22:03:27 +00:00
tmux update bot 022b8451e3 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Always use synchronized updates in copy mode (like when not in the active pane), since copy mode will almost always moved the cursor. From Artem Goldenberg in GitHub issue 5525.
2026-08-24 16:44:37 +00:00
nicm 5b54c7d41a Always use synchronized updates in copy mode (like when not in the
active pane), since copy mode will almost always moved the cursor. From
Artem Goldenberg in GitHub issue 5525.
2026-08-24 16:44:34 +00:00
tmux update bot f94036b2b0 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Free any remaining items when destroying screen, from Jeong, Heon.
2026-08-24 15:57:10 +00:00
nicm 7d358208b9 Free any remaining items when destroying screen, from Jeong, Heon. 2026-08-24 15:57:09 +00:00
tmux update bot c8ff8ea96f Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add {} to shell escape characters (special in bash/ksh/csh), GitHub issue 5514.
2026-08-24 15:02:17 +00:00
nicm 402d366bba Add {} to shell escape characters (special in bash/ksh/csh), GitHub
issue 5514.
2026-08-24 15:02:15 +00:00
Nicholas Marriott 41e701ad84 Return early if image is zero height when generating fallback, otherwise we can
allocated too little space. Reported by Vivek Parikh.
2026-08-24 15:28:17 +01:00
tmux update bot f763f94556 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not leak cached status line, from Jeong, Heon.
  Free pane fallback range when freeing pane, from Jeong, Heon.
  Do not leak path when destroying client, from Jeong, Heon.
  Do not leak format when drawing preview, from Jeong, Heon.
  Do not leak buffer name on failure, from Jeong, Heon.
2026-08-24 08:28:15 +00:00
nicm c9f5ecb4b5 Do not leak cached status line, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
nicm 7792a74c76 Free pane fallback range when freeing pane, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
nicm 4cabc2ae85 Do not leak path when destroying client, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
nicm 995b395795 Do not leak format when drawing preview, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
nicm 3d44f75d09 Do not leak buffer name on failure, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
tmux update bot c1f947a3c5 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Use correct X position when combining characters, GitHub issue 5511 from Francesc Rosas.
2026-08-21 11:56:01 +00:00
nicm 696a16cca2 Use correct X position when combining characters, GitHub issue 5511 from
Francesc Rosas.
2026-08-21 11:55:59 +00:00
Nicholas Marriott 936bb6e7cd Regress for GitHub issue 5511 from Francesc Rosas. 2026-08-21 10:54:29 +01:00
tmux update bot 8dfa903346 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a way for floating panes to stay above zoom (-A) flag. Use by default for modal panes to match popups.
2026-08-20 12:57:29 +00:00
nicm 95a7170709 Add a way for floating panes to stay above zoom (-A) flag. Use by
default for modal panes to match popups.
2026-08-20 12:57:26 +00:00
Nicholas Marriott 554c4bbde4 Tests for modal panes on top. 2026-08-20 10:19:19 +01:00
Nicholas Marriott ac23f21a2c More regress tests for zoom. 2026-08-20 09:18:58 +01:00
tmux update bot 6696151008 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Stop at the right end line when walking wrapped lines, GitHub issue 5479.
2026-08-19 21:05:08 +00:00
nicm 50d0348b64 Stop at the right end line when walking wrapped lines, GitHub issue 5479. 2026-08-19 21:05:06 +00:00
tmux update bot bf7bbba7ea Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a -K flag to new-pane to make a modal pane capture all keys like popups used to.
2026-08-19 13:10:09 +00:00
nicm b28b1b7b9a Add a -K flag to new-pane to make a modal pane capture all keys like
popups used to.
2026-08-19 13:10:08 +00:00
Nicholas Marriott 8499d8007e new-pane -K test bits. 2026-08-19 11:56:27 +01:00
tmux update bot e5a2058c7c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Use DECRQM to detect mode 2026, from Michael Grant.
2026-08-18 12:03:17 +00:00
nicm e94fcd96f9 Use DECRQM to detect mode 2026, from Michael Grant. 2026-08-18 12:03:15 +00:00
Nicholas Marriott c5017e6da8 Bump time limit. 2026-08-18 11:49:34 +01:00
tmux update bot 0169f9e18c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Change correct depth counter for source-file when used from hooks, GitHub issue 5437.
2026-08-18 09:49:28 +00:00
nicm b170c19274 Change correct depth counter for source-file when used from hooks,
GitHub issue 5437.
2026-08-18 09:49:26 +00:00
tmux update bot 22b3679752 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  When a pane is respawned, reset the control mode offsets. GitHub issue 5498 from Alex Rattray.
  Correctly clear clients referring to a session with destroy-unattached, GitHub issue 5497 from Jeong, Heon.
  Restore umask if bind fails, from nachalsa at naver dot com.
2026-08-18 09:04:02 +00:00
nicm 4714f478fe When a pane is respawned, reset the control mode offsets. GitHub issue
5498 from Alex Rattray.
2026-08-18 09:04:00 +00:00
nicm 72c5037f06 Correctly clear clients referring to a session with destroy-unattached,
GitHub issue 5497 from Jeong, Heon.
2026-08-18 09:04:00 +00:00
nicm 6238793ad7 Restore umask if bind fails, from nachalsa at naver dot com. 2026-08-18 09:04:00 +00:00
Nicholas Marriott da641e4e8d Add test for GitHub issue 5498 from Alex Rattray. 2026-08-18 08:45:11 +01:00
Nicholas Marriott 19a085a672 Fix socket names for macOS. 2026-08-18 08:23:16 +01:00
tmux update bot f672a0f677 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Log unknown message types.
2026-08-17 23:38:12 +00:00
nicm db194ced5b Log unknown message types. 2026-08-17 23:38:11 +00:00
tmux update bot 11ed661e2c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Flush output before ending sync. GitHub issue 5495 from xiangzhedev at gmail dot com.
2026-08-17 21:11:33 +00:00
nicm b2de803871 Flush output before ending sync. GitHub issue 5495 from xiangzhedev at
gmail dot com.
2026-08-17 21:11:31 +00:00
Nicholas Marriott 30abbe36a8 Regress for sync, from xiangzhedev@gmail.com. 2026-08-17 21:04:26 +01:00
tmux update bot 62044f02df Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Allow features to be disabled using @ suffix in terminal-features, from Michael Grant.
2026-08-17 16:39:40 +00:00
nicm a18d4e0067 Allow features to be disabled using @ suffix in terminal-features, from
Michael Grant.
2026-08-17 16:39:38 +00:00
Nicholas Marriott 0f241b7447 Update CHANGES. 2026-08-17 13:59:10 +01:00
tmux update bot efaa1fe31c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  If writing a file fails, propagate the error to the server via a new message. Use a client flag rather than bumping the protocol version. GitHub issue 5451.
  Add default terminal features for Rio, GitHub issue 5489 from Raphael Amorim.
2026-08-17 09:45:11 +00:00
nicm 1729bb8e8a If writing a file fails, propagate the error to the server via a new
message. Use a client flag rather than bumping the protocol version.
GitHub issue 5451.
2026-08-17 09:45:10 +00:00
nicm 9fdd04ae61 Add default terminal features for Rio, GitHub issue 5489 from Raphael
Amorim.
2026-08-17 09:45:10 +00:00
tmux update bot 87a8bda023 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Remove flags from both keys for menus, GitHub issue 5484.
2026-08-17 08:54:28 +00:00
nicm d384557b63 Remove flags from both keys for menus, GitHub issue 5484. 2026-08-17 08:54:26 +00:00
tmux update bot 534a4f81fc Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Attach window to session earlier so resize cannot cause customize mode to blow up. Reported by Marcel Partap.
  Do not copy too many positions when deleting, GitHub issue 5478 from Uzair Aftab.
2026-08-17 07:58:26 +00:00
nicm a0d3585c6c Attach window to session earlier so resize cannot cause customize mode
to blow up. Reported by Marcel Partap.
2026-08-17 07:58:24 +00:00
nicm ec34ff52cc Do not copy too many positions when deleting, GitHub issue 5478 from
Uzair Aftab.
2026-08-17 07:58:24 +00:00
Nicholas Marriott 0ac8138ce3 Add a break-pane test for customize mode. 2026-08-17 08:06:31 +01:00
Nicholas Marriott c41cad4c5f Merge pull request #5481 from r1w1s1/fix-getpeereid-socklen
compat: use socklen_t for getsockopt length
2026-08-17 07:52:02 +01:00
r1w1s1 f466d6289e compat: use socklen_t for getsockopt length 2026-08-10 15:38:55 -03:00
tmux update bot 851c5a933d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Only unzoom in the floating-split case, modal panes take care of themselves.
2026-08-07 09:33:41 +00:00
nicm 10048f6579 Only unzoom in the floating-split case, modal panes take care of themselves. 2026-08-07 09:33:39 +00:00
tmux update bot a598b2a910 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Always unzoom before splitting window, for floating panes also. We already unzoomed for tiled panes and this does the same for floating panes (until we support having them float over a zoomed pane). From Éric NICOLAS.
2026-08-07 03:40:47 +00:00
nicm 2ec1038ffd Always unzoom before splitting window, for floating panes also. We
already unzoomed for tiled panes and this does the same for floating
panes (until we support having them float over a zoomed pane). From Éric
NICOLAS.
2026-08-07 03:40:45 +00:00
Nicholas Marriott cbb1da66c3 Merge pull request #5471 from mathstuf/actions-only-on-main-repo
ci: only run `lock` and `regress` workflows on the main repo
2026-08-06 15:44:32 +01:00
Ben Boeckel 35ab660042 ci: only run lock and regress workflows on the main repo
Forks are just wasting CI cycles spinning actions which are probably not
looked at. Also avoid locking issues and PRs on any forks with
upstream's policies.
2026-08-06 10:23:46 -04:00
tmux update bot 40a485db9c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add T key to change pane title, from Fouad Wahabi in GitHub issue 5461.
2026-08-06 11:42:00 +00:00
nicm 641e75e6e2 Add T key to change pane title, from Fouad Wahabi in GitHub issue 5461. 2026-08-06 11:41:58 +00:00
tmux update bot 2e394bef11 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a refresh-now command and change the r binding back to it in copy mode.
2026-08-05 15:40:06 +00:00
nicm 4a9dbac435 Add a refresh-now command and change the r binding back to it in copy mode. 2026-08-05 15:40:04 +00:00
tmux update bot d60dbcf91c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Ignore Ms if it is invalid (for example no %p1) and report in client mode, GitHub issue 5460.
2026-08-05 11:31:10 +00:00
nicm bd483a63cf Ignore Ms if it is invalid (for example no %p1) and report in client
mode, GitHub issue 5460.
2026-08-05 11:31:08 +00:00
tmux update bot d5dc0ba855 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Use ACS arrows for tree mode.
  Do not crash when synchronizing groups and there is no current window. GitHub issue 5467 from Ju-an Zhang.
  Add pane_private_modes format with list of DEC private modes, from George Nachman.
  Do not log when logging is off, from Michael Grant.
2026-08-05 09:10:55 +00:00
nicm e56a042153 Use ACS arrows for tree mode. 2026-08-05 09:10:54 +00:00
nicm 1995b5559d Do not crash when synchronizing groups and there is no current window.
GitHub issue 5467 from Ju-an Zhang.
2026-08-05 09:10:54 +00:00
nicm fe384e06bc Add pane_private_modes format with list of DEC private modes, from
George Nachman.
2026-08-05 09:10:54 +00:00
nicm 0198ee9e51 Do not log when logging is off, from Michael Grant. 2026-08-05 09:10:54 +00:00
Nicholas Marriott 72e705cbd6 Tweak times for macOS. 2026-08-05 08:26:10 +01:00
tmux update bot d5afb67a81 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Switch from imsg_get to imsgbuf_get and switch type of n to int.
2026-08-04 17:23:12 +00:00
claudio 3894397b07 Switch from imsg_get to imsgbuf_get and switch type of n to int.
OK tb@
2026-08-04 17:23:10 +00:00
tmux update bot 36381f0884 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not attempt to drain write buffer on stuck clients, since it requires a weird dance to make libevent do it. Instead, just ignore the client and destroy the buffer normally if not drained in 10 seconds.
2026-08-04 15:37:15 +00:00
nicm 5cd1976a4a Do not attempt to drain write buffer on stuck clients, since it requires
a weird dance to make libevent do it. Instead, just ignore the client
and destroy the buffer normally if not drained in 10 seconds.
2026-08-04 15:37:14 +00:00
tmux update bot c9ae3f6a02 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Check for floating only for flags that require a floating pane, reported by Ilya Grigoriev.
2026-08-03 23:50:33 +00:00
nicm d2322d0114 Check for floating only for flags that require a floating pane, reported
by Ilya Grigoriev.
2026-08-03 23:50:32 +00:00
tmux update bot 7a431ec7b9 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not let a stuck client prevent the server from exiting - give up after 10 seconds. GitHub issue 5444 from Ben Maurer.
2026-08-03 21:33:23 +00:00
nicm 114aa68082 Do not let a stuck client prevent the server from exiting - give up
after 10 seconds. GitHub issue 5444 from Ben Maurer.
2026-08-03 21:33:21 +00:00
Nicholas Marriott f07a76d959 Use move-pane for a test. 2026-08-03 21:30:44 +01:00
Nicholas Marriott ca0d77855f Client exit test from Ben Maurer. 2026-08-03 21:19:10 +01:00
Nicholas Marriott cd3f577c85 Add test for control mode events and guards. 2026-08-03 20:27:10 +01:00
tmux update bot 9228f97d7d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Queue control mode notifications rather than emitting them inside %begin/%end, GitHub issue 5458 from George Nachman.
2026-08-03 16:29:11 +00:00
nicm 6db5175e4e Queue control mode notifications rather than emitting them inside
%begin/%end, GitHub issue 5458 from George Nachman.
2026-08-03 16:29:09 +00:00
Nicholas Marriott fba5ece05c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Store background colour in padding cells and correctly clear adjacent cells when tabs are overwritten, GitHu issue 5441 from Ayman Bagabas.
  Exit failure on socket failure, from Rayan Salhab.
  Pessimize compiler flags for screen-redraw.c to prevent tmux from dumping core upon startup on landisk.
2026-08-03 17:27:11 +01:00
nicm 01a775a46c Store background colour in padding cells and correctly clear adjacent
cells when tabs are overwritten, GitHu issue 5441 from Ayman Bagabas.
2026-08-03 13:59:13 +00:00
nicm 4918360ea1 Exit failure on socket failure, from Rayan Salhab. 2026-08-03 13:59:13 +00:00
Nicholas Marriott 433a5cf580 Add regress test from Ayman Bagabas. 2026-08-03 13:59:27 +01:00
Nicholas Marriott fc9270c943 Test for socket error from Rayan Salhab. 2026-08-03 11:09:04 +01:00
Nicholas Marriott fa8c5789a6 Sleep after kill. 2026-08-03 09:53:50 +01:00
miod cce56fa19b Pessimize compiler flags for screen-redraw.c to prevent tmux from dumping core
upon startup on landisk.
2026-08-02 23:13:19 +00:00
151 changed files with 9964 additions and 894 deletions
+3 -1
View File
@@ -16,8 +16,10 @@ concurrency:
jobs: jobs:
action: action:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'tmux/tmux'
timeout-minutes: 10
steps: steps:
- uses: dessant/lock-threads@v6 - uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
issue-inactive-days: '30' issue-inactive-days: '30'
+7 -4
View File
@@ -16,7 +16,8 @@ jobs:
regress: regress:
name: ${{ matrix.name }} name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
timeout-minutes: 45 if: github.repository == 'tmux/tmux'
timeout-minutes: 300
strategy: strategy:
fail-fast: false fail-fast: false
@@ -37,7 +38,9 @@ jobs:
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: dependencies - name: dependencies
if: runner.os == 'Linux' if: runner.os == 'Linux'
@@ -76,11 +79,11 @@ jobs:
- name: test - name: test
run: | run: |
cd regress cd regress
${{ matrix.make }} ${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
- name: logs - name: logs
if: failure() if: failure()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with: with:
name: regress-logs-${{ matrix.name }} name: regress-logs-${{ matrix.name }}
path: regress/logs/*.log path: regress/logs/*.log
+97 -18
View File
@@ -1,7 +1,4 @@
CHANGES FROM 3.7b TO 3.8 CHANGES FROM 3.7c TO 3.8
* Build with jemalloc on macOS to avoid what appears to be a bug in the system
calloc(3) (issue 5385).
* Many improvements to floating panes: * Many improvements to floating panes:
@@ -12,7 +9,12 @@ CHANGES FROM 3.7b TO 3.8
- modal panes may be created with new-pane -O; a window can have one modal - 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. 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. -D closes the modal pane with
Escape or C-c. 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 - move-pane and resize-pane can now move and resize floating panes, including
mouse dragging; mouse dragging;
@@ -27,10 +29,18 @@ CHANGES FROM 3.7b TO 3.8
-k for choose commands kills the pane when the mode exits, which is useful -k for choose commands kills the pane when the mode exits, which is useful
for modes in floating panes; for modes in floating panes;
- pane-border-status has top-floating and bottom-floating, and there are new - command-prompt -P opens a prompt inside a pane rather than on the status
default bindings under C-b g for common move and resize operations; line;
- floating panes are unzoomed before creation to avoid a crash. - 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.
* Layout strings now use a JSON subset format which includes floating panes.
The old format is still accepted; control mode clients receive old layouts
unless they set the new-layouts flag.
* Add remain-on-exit failed-key to keep a pane open after a failed command
until a key is pressed.
* Add support for themes and improve default colours: * Add support for themes and improve default colours:
@@ -77,7 +87,7 @@ CHANGES FROM 3.7b TO 3.8
- new hooks to cover areas including client create and destroy, pane - new hooks to cover areas including client create and destroy, pane
activity, pane mode and prompt changes, pane movement and resizing, session 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, - OSC 133 escape sequences now trigger events: pane-command-started,
pane-command-finished and pane-shell-prompt. pane-command-finished and pane-shell-prompt.
@@ -90,10 +100,29 @@ CHANGES FROM 3.7b TO 3.8
while scrolling or while hovered and disappears after pane-scrollbars-timeout while scrolling or while hovered and disappears after pane-scrollbars-timeout
(Michael Grant). (Michael Grant).
* Extend the fill-character option so both inside and outside the window can be * Add a clear-on-attach server option. When disabled, tmux does not enter the
changed. 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 an appesc terminal feature for mintty's application escape key mode,
avoiding unsupported sequences on other terminals (Pete Dietl, issue 5626).
* Preserve existing Ss and Se terminfo capabilities for cursor styles and
restoring the default cursor (issue 5476).
* Add a #{A/count:frames} modifier to show a series of frames as an animation * Add a #{A/count:frames} modifier to show a series of frames as an animation
in the status line (issue 5412 from Fernando Daciuk). in the status line (issue 5412 from Fernando Daciuk).
@@ -108,6 +137,12 @@ CHANGES FROM 3.7b TO 3.8
* Add new-window -E, respawn-pane -E and respawn-window -E as more convenient * 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). methods to create an empty pane (rather than using '' for the command).
* Add capture-pane -I to include the time each line entered history.
* Various memory leak fixes from Heon Jeong.
* Add a default C-b T binding to change the current pane title.
* Menus now belong to the window, so appear on all clients. * Menus now belong to the window, so appear on all clients.
* Dragging over an existing copy mode selection now adjusts it (Michael Grant). * Dragging over an existing copy mode selection now adjusts it (Michael Grant).
@@ -121,7 +156,12 @@ CHANGES FROM 3.7b TO 3.8
* Copy mode no longer exits at the bottom while a selection is in progress * Copy mode no longer exits at the bottom while a selection is in progress
(issue 5349). (issue 5349).
* Fix scrollbar initial state so scrollbars appear on new windows (issue 5339). * 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 * Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
nolink) (issues 4842 and 4280 from Moritz Angermann). nolink) (issues 4842 and 4280 from Moritz Angermann).
@@ -133,7 +173,9 @@ CHANGES FROM 3.7b TO 3.8
* Add or improve format variables including client_colours, * Add or improve format variables including client_colours,
pane_start_command_list, window_manual_width, window_manual_height, 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 * Add additional pane sort orders, and a z sort order for choose-tree so
floating panes can be sorted by z-index. floating panes can be sorted by z-index.
@@ -145,15 +187,34 @@ CHANGES FROM 3.7b TO 3.8
* The mouse option now defaults to on. * The mouse option now defaults to on.
* The active-pane client flag has been removed.
* Fix send-keys -K so keys are inserted in the correct place in the input * Fix send-keys -K so keys are inserted in the correct place in the input
queue, like keys from key bindings (issue 3476). queue, like keys from key bindings (issue 3476).
* Fix control mode clients hanging on exit if pty data was still queued (Ben * 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 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). Bound buffered command replies so a control mode
client that stops reading cannot consume unlimited memory (issue 5565), and
fix a collision between the output discard and bracketed paste flags (issue
5622).
* Fix grouped sessions sometimes being left as unusable command targets while * Fix grouped sessions sometimes being left as unusable command targets while
they are being killed (Bryce Miller, issue 5180). they are being killed (Bryce Miller, issue 5180), and prevent reuse of a
session with no windows while detaching (Heon Jeong, issue 5620).
* Free wait-for clients when they disconnect and do not run their remaining
queued commands (issue 5614).
* Expand formats in run-shell -c like other start-directory flags (reported by
Saul Nogueras).
* Fix session alert flag formats to check all windows (issue 5599).
* Do not clear the marked pane when clicking on a pane border.
* Fix choose mode filtering when more than one pane exists (issue 5326), and * Fix choose mode filtering when more than one pane exists (issue 5326), and
fix an infinite loop in customize mode when a filter does not match. fix an infinite loop in customize mode when a filter does not match.
@@ -164,12 +225,30 @@ CHANGES FROM 3.7b TO 3.8
status lines, menus, popups, wide characters and floating panes. This status lines, menus, popups, wide characters and floating panes. This
includes fixes for flicker with alternate screen applications and scrollbars includes fixes for flicker with alternate screen applications and scrollbars
(Michael Grant, issue 5351; Noam Stolero, issue 5350; Aung Myo Kyaw, issue (Michael Grant, issue 5351; Noam Stolero, issue 5350; Aung Myo Kyaw, issue
5098). 5098), and misplaced wrapped lines during partial redraws (Ben Maurer, issue
5625).
* Do not crash looking for the next or previous session (issue 5344), or when * Do not crash looking for the next or previous session (issue 5344), or when
no client is available. no client is available.
* Check time periodically in loops rather than for every item (issue 5367). * Fix crashes when destroying a zoomed pane (Romain Francoise, issue 5591),
copying from a blank source pane (Kaixuan Li), or opening a mode prompt when
the client is offset into the window.
* Avoid an infinite loop when assigning a width to WCHAR_MAX (issue 5602).
CHANGES FROM 3.7b TO 3.7c
* Build with jemalloc on macOS to avoid what appears to be a bug in calloc
(issue 5385).
* Fix scrollbar initial state so they appear on new windows (issue 5339).
* Check time periodically in loops rather than every one (issue 5367).
* Use message-style again as default for message-format.
* Unzoom before creating floating panes to avoid a crash.
CHANGES FROM 3.7a TO 3.7b CHANGES FROM 3.7a TO 3.7b
+1
View File
@@ -174,6 +174,7 @@ dist_tmux_SOURCES = \
input-keys.c \ input-keys.c \
input.c \ input.c \
job.c \ job.c \
json.c \
key-bindings.c \ key-bindings.c \
key-string.c \ key-string.c \
layout-custom.c \ layout-custom.c \
+4 -4
View File
@@ -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> * 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); cmdq_error(item, "%s", error);
free(error); free(error);
} }
else
cmdlist->references++;
args_make_commands_free(state); args_make_commands_free(state);
return (cmdlist); return (cmdlist);
} }
@@ -827,8 +825,10 @@ args_make_commands(struct args_command_state *state, int argc, char **argv,
int i; int i;
if (state->cmdlist != NULL) { if (state->cmdlist != NULL) {
if (argc == 0) if (argc == 0) {
state->cmdlist->references++;
return (state->cmdlist); return (state->cmdlist);
}
return (cmd_list_copy(state->cmdlist, argc, argv)); return (cmd_list_copy(state->cmdlist, argc, argv));
} }
+10 -6
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cfg.c,v 1.90 2026/07/14 17:17:17 nicm Exp $ */ /* $OpenBSD: cfg.c,v 1.91 2026/08/03 13:38:42 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -223,13 +223,15 @@ cfg_print_causes(struct cmdq_item *item)
{ {
struct client *c = cmdq_get_client(item); struct client *c = cmdq_get_client(item);
u_int i; u_int i;
char *cause;
for (i = 0; i < cfg_ncauses; i++) { for (i = 0; i < cfg_ncauses; i++) {
cause = cfg_causes[i];
if (c != NULL && (c->flags & CLIENT_CONTROL)) if (c != NULL && (c->flags & CLIENT_CONTROL))
control_write(c, "%%config-error %s", cfg_causes[i]); control_notify_write(c, "%%config-error %s", cause);
else else
cmdq_print(item, "%s", cfg_causes[i]); cmdq_print(item, "%s", cause);
free(cfg_causes[i]); free(cause);
} }
free(cfg_causes); free(cfg_causes);
@@ -244,14 +246,16 @@ cfg_show_causes(struct session *s)
struct window_pane *wp; struct window_pane *wp;
struct window_mode_entry *wme; struct window_mode_entry *wme;
u_int i; u_int i;
char *cause;
if (cfg_ncauses == 0) if (cfg_ncauses == 0)
return; return;
if (c != NULL && (c->flags & CLIENT_CONTROL)) { if (c != NULL && (c->flags & CLIENT_CONTROL)) {
for (i = 0; i < cfg_ncauses; i++) { for (i = 0; i < cfg_ncauses; i++) {
control_write(c, "%%config-error %s", cfg_causes[i]); cause = cfg_causes[i];
free(cfg_causes[i]); control_notify_write(c, "%%config-error %s", cause);
free(cause);
} }
goto out; goto out;
} }
+8 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: client.c,v 1.166 2026/07/10 15:45:11 nicm Exp $ */ /* $OpenBSD: client.c,v 1.168 2026/08/17 20:14:31 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -276,7 +276,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
proc_set_signals(client_proc, client_signal); proc_set_signals(client_proc, client_signal);
/* Save the flags. */ /* Save the flags. */
client_flags = flags; client_flags = flags|CLIENT_WRITE_ACK;
log_debug("flags are %#llx", (unsigned long long)client_flags); log_debug("flags are %#llx", (unsigned long long)client_flags);
/* Initialize the client socket and start the server. */ /* Initialize the client socket and start the server. */
@@ -712,6 +712,9 @@ client_dispatch_wait(struct imsg *imsg)
fprintf(stderr, "server version is too old for client\n"); fprintf(stderr, "server version is too old for client\n");
proc_exit(client_proc); proc_exit(client_proc);
break; break;
default:
log_debug("unknown message type %u", imsg->hdr.type);
break;
} }
} }
@@ -798,5 +801,8 @@ client_dispatch_attached(struct imsg *imsg)
system(data); system(data);
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0); proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
break; break;
default:
log_debug("unknown message type %u", imsg->hdr.type);
break;
} }
} }
+5 -4
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-break-pane.c,v 1.75 2026/07/23 09:38:27 nicm Exp $ */ /* $OpenBSD: cmd-break-pane.c,v 1.76 2026/08/17 07:04:45 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -174,13 +174,14 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
} }
window_set_fill_cells(w); window_set_fill_cells(w);
if (idx == -1)
idx = -1 - options_get_number(dst_s->options, "base-index");
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
layout_init(w, wp); layout_init(w, wp);
wp->flags |= PANE_CHANGED; wp->flags |= PANE_CHANGED;
colour_palette_from_option(&wp->palette, wp->options); colour_palette_from_option(&wp->palette, wp->options);
if (idx == -1)
idx = -1 - options_get_number(dst_s->options, "base-index");
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
window_remove_ref(w, __func__); window_remove_ref(w, __func__);
events_fire_window("window-created", w); events_fire_window("window-created", w);
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx); window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
+13 -7
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-capture-pane.c,v 1.68 2026/07/20 11:16:33 nicm Exp $ */ /* $OpenBSD: cmd-capture-pane.c,v 1.69 2026/09/08 07:31:59 nicm Exp $ */
/* /*
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net> * Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
.name = "capture-pane", .name = "capture-pane",
.alias = "capturep", .alias = "capturep",
.args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL }, .args = { "ab:CeE:FHIJLMNpPqRS:Tt:", 0, 0, NULL },
.usage = "[-aCeFHJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] " .usage = "[-aCeFHIJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
"[-S start-line] " CMD_TARGET_PANE_USAGE, "[-S start-line] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -200,7 +200,7 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
tmp[0] = line[i]; tmp[0] = line[i];
tmp[1] = '\0'; tmp[1] = '\0';
} else } else
xsnprintf(tmp, sizeof tmp, "\\%03hho", line[i]); snprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
buf = cmd_capture_pane_append(buf, len, tmp, buf = cmd_capture_pane_append(buf, len, tmp,
strlen(tmp)); strlen(tmp));
} }
@@ -259,7 +259,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
struct grid_cell *gc = NULL; struct grid_cell *gc = NULL;
struct window_mode_entry *wme; struct window_mode_entry *wme;
int n, join_lines, number_lines, flags = 0; int n, join_lines, number_lines, flags = 0;
int show_flags, hyperlinks; int show_flags, show_time, hyperlinks;
u_int *links = NULL, nlinks = 0; u_int *links = NULL, nlinks = 0;
u_int i, sx, top, bottom, tmp; u_int i, sx, top, bottom, tmp;
char *cause, *buf = NULL, *line, b[64], *cp; char *cause, *buf = NULL, *line, b[64], *cp;
@@ -342,6 +342,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
flags |= GRID_STRING_TRIM_SPACES; flags |= GRID_STRING_TRIM_SPACES;
number_lines = args_has(args, 'L'); number_lines = args_has(args, 'L');
show_flags = args_has(args, 'F'); show_flags = args_has(args, 'F');
show_time = args_has(args, 'I');
hyperlinks = args_has(args, 'H'); hyperlinks = args_has(args, 'H');
if (hyperlinks) if (hyperlinks)
links = xreallocarray(NULL, gd->sx, sizeof *links); links = xreallocarray(NULL, gd->sx, sizeof *links);
@@ -358,6 +359,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
free(line); free(line);
continue; continue;
} }
gl = grid_peek_line(gd, i);
if (number_lines) { if (number_lines) {
if (i >= gd->hsize) if (i >= gd->hsize)
@@ -368,11 +370,16 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
if (n >= 0) if (n >= 0)
buf = cmd_capture_pane_append(buf, len, b, n); buf = cmd_capture_pane_append(buf, len, b, n);
} }
if (show_time) {
n = snprintf(b, sizeof b, "%llu ",
(unsigned long long)grid_line_time(gl));
if (n >= 0)
buf = cmd_capture_pane_append(buf, len, b, n);
}
if (show_flags) { if (show_flags) {
cp = b; cp = b;
*cp = '\0'; *cp = '\0';
gl = grid_peek_line(gd, i);
if (gl->flags & GRID_LINE_DEAD) if (gl->flags & GRID_LINE_DEAD)
*cp++ = 'D'; *cp++ = 'D';
if (gl->flags & GRID_LINE_HYPERLINK) if (gl->flags & GRID_LINE_HYPERLINK)
@@ -393,7 +400,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
} }
buf = cmd_capture_pane_append(buf, len, line, linelen); buf = cmd_capture_pane_append(buf, len, line, linelen);
gl = grid_peek_line(gd, i);
if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED)) if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
buf[(*len)++] = '\n'; buf[(*len)++] = '\n';
+3 -1
View File
@@ -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> * 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) { } else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL); new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item); cmdq_append(c, new_item);
cmd_list_free(cmdlist);
} else { } else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item); cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
} }
cmd_free_argv(argc, argv); cmd_free_argv(argc, argv);
+3 -1
View File
@@ -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> * 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); server_client_clear_overlay(tc);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (tc->flags & CLIENT_CONTROL)
return (CMD_RETURN_NORMAL);
if (!modify && tc->overlay_draw != NULL) if (!modify && tc->overlay_draw != NULL)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
+23 -7
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-display-message.c,v 1.65 2026/02/23 08:46:57 nicm Exp $ */ /* $OpenBSD: cmd-display-message.c,v 1.66 2026/09/08 08:33:10 nicm Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = {
.name = "display-message", .name = "display-message",
.alias = "display", .alias = "display",
.args = { "aCc:d:lINpt:F:v", 0, 1, NULL }, .args = { "aCc:d:jlINpt:F:v", 0, 1, NULL },
.usage = "[-aCIlNpv] [-c target-client] [-d delay] [-F format] " .usage = "[-aCIjlNpv] [-c target-client] [-d delay] [-F format] "
CMD_TARGET_PANE_USAGE " [message]", CMD_TARGET_PANE_USAGE " [message]",
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
@@ -67,14 +67,15 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
struct winlink *wl = target->wl; struct winlink *wl = target->wl;
struct window_pane *wp = target->wp; struct window_pane *wp = target->wp;
const char *template; const char *template;
char *msg, *cause; char *msg, *cause = NULL;
int delay = -1, flags, Nflag = args_has(args, 'N'); int delay = -1, flags, Nflag = args_has(args, 'N');
int Cflag = args_has(args, 'C'); int Cflag = args_has(args, 'C');
struct format_tree *ft; struct format_tree *ft;
u_int count = args_count(args); u_int count = args_count(args);
struct evbuffer *evb; struct evbuffer *evb;
struct json_node *jn;
if (args_has(args, 'I')) { if (args_has(args, 'I') && !args_has(args, 'j')) {
if (wp == NULL) if (wp == NULL)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
switch (window_pane_start_input(wp, item, &cause)) { switch (window_pane_start_input(wp, item, &cause)) {
@@ -107,7 +108,9 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
template = args_string(args, 0); template = args_string(args, 0);
else else
template = args_get(args, 'F'); template = args_get(args, 'F');
if (template == NULL) if (args_has(args, 'j') && template == NULL)
template = "";
else if (template == NULL)
template = DISPLAY_MESSAGE_TEMPLATE; template = DISPLAY_MESSAGE_TEMPLATE;
/* /*
@@ -129,7 +132,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
format_defaults(ft, c, s, wl, wp); format_defaults(ft, c, s, wl, wp);
if (args_has(args, 'a')) { if (args_has(args, 'a') && !args_has(args, 'j')) {
format_each(ft, cmd_display_message_each, item); format_each(ft, cmd_display_message_each, item);
format_free(ft); format_free(ft);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
@@ -139,6 +142,19 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
msg = xstrdup(template); msg = xstrdup(template);
else else
msg = format_expand_time(ft, template); msg = format_expand_time(ft, template);
if (args_has(args, 'j')) {
jn = json_parse(msg, &cause);
if (jn == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
free(msg);
format_free(ft);
return (CMD_RETURN_ERROR);
}
free(msg);
msg = json_to_string(jn);
json_destroy_node(jn);
}
if (cmdq_get_client(item) == NULL) if (cmdq_get_client(item) == NULL)
cmdq_error(item, "%s", msg); cmdq_error(item, "%s", msg);
+11 -7
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-find.c,v 1.87 2026/07/17 12:42:51 nicm Exp $ */ /* $OpenBSD: cmd-find.c,v 1.88 2026/09/07 12:05:12 nicm Exp $ */
/* /*
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -388,9 +388,11 @@ cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window)
/* Try as an offset. */ /* Try as an offset. */
if (!exact && (window[0] == '+' || window[0] == '-')) { if (!exact && (window[0] == '+' || window[0] == '-')) {
if (window[1] != '\0') if (window[1] != '\0') {
n = strtonum(window + 1, 1, INT_MAX, NULL); n = strtonum(window + 1, 1, INT_MAX, &errstr);
else if (errstr != NULL)
return (-1);
} else
n = 1; n = 1;
s = fs->s; s = fs->s;
if (fs->flags & CMD_FIND_WINDOW_INDEX) { if (fs->flags & CMD_FIND_WINDOW_INDEX) {
@@ -626,9 +628,11 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane)
/* Try as an offset. */ /* Try as an offset. */
if (pane[0] == '+' || pane[0] == '-') { if (pane[0] == '+' || pane[0] == '-') {
if (pane[1] != '\0') if (pane[1] != '\0') {
n = strtonum(pane + 1, 1, INT_MAX, NULL); n = strtonum(pane + 1, 1, INT_MAX, &errstr);
else if (errstr != NULL)
return (-1);
} else
n = 1; n = 1;
wp = fs->w->active; wp = fs->w->active;
if (pane[0] == '+') if (pane[0] == '+')
+4 -1
View File
@@ -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> * 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); return (CMD_RETURN_ERROR);
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item); cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
@@ -164,9 +165,11 @@ cmd_if_shell_callback(struct job *job)
} else if (item == NULL) { } else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL); new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item); cmdq_append(c, new_item);
cmd_list_free(cmdlist);
} else { } else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item); cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
} }
out: out:
+25 -18
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-join-pane.c,v 1.73 2026/07/17 15:24:30 nicm Exp $ */ /* $OpenBSD: cmd-join-pane.c,v 1.75 2026/09/21 10:33:16 nicm Exp $ */
/* /*
* Copyright (c) 2011 George Nachman <tmux@georgester.com> * Copyright (c) 2011 George Nachman <tmux@georgester.com>
@@ -133,7 +133,7 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
} else if (strcmp(position, "back") == 0) { } else if (strcmp(position, "back") == 0) {
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_FOREACH(owp, &w->z_index, zentry) { TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp)) if (!window_pane_is_floating_with_hidden(owp))
break; break;
} }
if (owp != NULL) if (owp != NULL)
@@ -142,24 +142,30 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
} else if (strcmp(position, "forward") == 0) { } else if (strcmp(position, "forward") == 0) {
owp = TAILQ_PREV(wp, window_panes_zindex, zentry); owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
while (owp != NULL && owp->layout_cell == NULL)
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
if (owp != NULL) { if (owp != NULL) {
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_BEFORE(owp, wp, zentry); TAILQ_INSERT_BEFORE(owp, wp, zentry);
} }
} else if (strcmp(position, "backward") == 0) { } else if (strcmp(position, "backward") == 0) {
owp = TAILQ_NEXT(wp, zentry); owp = TAILQ_NEXT(wp, zentry);
while (owp != NULL && owp->layout_cell == NULL)
owp = TAILQ_NEXT(owp, zentry);
if (owp != NULL && window_pane_is_floating(owp)) { if (owp != NULL && window_pane_is_floating(owp)) {
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry); TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
} }
} else if (strcmp(position, "forward-loop") == 0) { } else if (strcmp(position, "forward-loop") == 0) {
owp = TAILQ_PREV(wp, window_panes_zindex, zentry); owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
while (owp != NULL && owp->layout_cell == NULL)
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
if (owp != NULL) if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry); TAILQ_INSERT_BEFORE(owp, wp, zentry);
else { else {
TAILQ_FOREACH(owp, &w->z_index, zentry) { TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp)) if (!window_pane_is_floating_with_hidden(owp))
break; break;
} }
if (owp != NULL) if (owp != NULL)
@@ -169,6 +175,8 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
} }
} else if (strcmp(position, "backward-loop") == 0) { } else if (strcmp(position, "backward-loop") == 0) {
owp = TAILQ_NEXT(wp, zentry); owp = TAILQ_NEXT(wp, zentry);
while (owp != NULL && owp->layout_cell == NULL)
owp = TAILQ_NEXT(owp, zentry);
if (owp != NULL && window_pane_is_floating(owp)) { if (owp != NULL && window_pane_is_floating(owp)) {
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry); TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
@@ -347,8 +355,10 @@ cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl,
n = 0; n = 0;
TAILQ_FOREACH(owp, &w->z_index, zentry) { TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp)) if (!window_pane_is_floating_with_hidden(owp))
break; break;
if (owp->layout_cell == NULL)
continue;
if (n >= z) if (n >= z)
break; break;
n++; n++;
@@ -431,25 +441,22 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
if (cmd_get_entry(self) == &cmd_move_pane_entry) { if (cmd_get_entry(self) == &cmd_move_pane_entry) {
if (args_has(args, 'M')) if (args_has(args, 'M'))
return (cmd_join_pane_mouse_update(item)); return (cmd_join_pane_mouse_update(item));
if (!window_pane_is_floating(dst_wp)) { if (args_has(args, 'P') ||
cmdq_error(item, "pane is not floating"); args_has(args, 'z') ||
return (CMD_RETURN_ERROR); args_has(args, 'X') ||
}
if ((s = args_get(args, 'P')) != NULL) {
server_unzoom_window(dst_w);
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
}
if ((s = args_get(args, 'z')) != NULL) {
server_unzoom_window(dst_w);
return (cmd_join_pane_zindex(item, dst_wl, dst_wp, s));
}
if (args_has(args, 'X') ||
args_has(args, 'Y') || args_has(args, 'Y') ||
args_has(args, 'U') || args_has(args, 'U') ||
args_has(args, 'D') || args_has(args, 'D') ||
args_has(args, 'L') || args_has(args, 'L') ||
args_has(args, 'R')) { args_has(args, 'R')) {
server_unzoom_window(dst_w); if (!window_pane_is_floating(dst_wp)) {
cmdq_error(item, "pane is not floating");
return (CMD_RETURN_ERROR);
}
if ((s = args_get(args, 'P')) != NULL)
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
if ((s = args_get(args, 'z')) != NULL)
return (cmd_join_pane_zindex(item, dst_wl, dst_wp, s));
return (cmd_join_pane_move(item, args, dst_wl, dst_wp)); return (cmd_join_pane_move(item, args, dst_wl, dst_wp));
} }
} }
+3 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-list-panes.c,v 1.40 2026/06/01 14:01:09 nicm Exp $ */ /* $OpenBSD: cmd-list-panes.c,v 1.41 2026/09/08 10:20:08 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -97,6 +97,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
struct cmdq_item *item, int type) struct cmdq_item *item, int type)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_client(item);
struct window_pane *wp, **l; struct window_pane *wp, **l;
u_int i, n; u_int i, n;
struct format_tree *ft; struct format_tree *ft;
@@ -147,7 +148,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
wp = l[i]; wp = l[i];
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", n); format_add(ft, "line", "%u", n);
format_defaults(ft, NULL, s, wl, wp); format_defaults(ft, c, s, wl, wp);
if (filter != NULL) { if (filter != NULL) {
expanded = format_expand(ft, filter); expanded = format_expand(ft, filter);
+3 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-list-sessions.c,v 1.36 2026/02/27 08:25:12 nicm Exp $ */ /* $OpenBSD: cmd-list-sessions.c,v 1.37 2026/09/08 10:20:08 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -53,6 +53,7 @@ static enum cmd_retval
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item) cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_client(item);
struct session **l; struct session **l;
u_int n, i; u_int n, i;
struct format_tree *ft; struct format_tree *ft;
@@ -76,7 +77,7 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", i); format_add(ft, "line", "%u", i);
format_defaults(ft, NULL, l[i], NULL, NULL); format_defaults(ft, c, l[i], NULL, NULL);
if (filter != NULL) { if (filter != NULL) {
expanded = format_expand(ft, filter); expanded = format_expand(ft, filter);
+3 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-list-windows.c,v 1.50 2026/02/27 08:25:12 nicm Exp $ */ /* $OpenBSD: cmd-list-windows.c,v 1.51 2026/09/08 10:20:08 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -60,6 +60,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct client *c = cmdq_get_client(item);
struct winlink *wl, **l; struct winlink *wl, **l;
struct session *s; struct session *s;
u_int i, n; u_int i, n;
@@ -94,7 +95,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
s = wl->session; s = wl->session;
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", n); format_add(ft, "line", "%u", n);
format_defaults(ft, NULL, s, wl, NULL); format_defaults(ft, c, s, wl, NULL);
if (filter != NULL) { if (filter != NULL) {
expanded = format_expand(ft, filter); expanded = format_expand(ft, filter);
+17 -7
View File
@@ -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> * 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 client *tc = cmdq_get_target_client(item);
struct session *s = target->s; struct session *s = target->s;
struct winlink *wl = target->wl, *new_wl = NULL; 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; char *cause = NULL, *cp, *expanded, *wname = NULL;
const char *template, *name; const char *template, *name;
struct cmd_find_state fs; 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 * If -S is given, select an existing window instead of creating a
* name already exists, select it. * 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'); name = args_get(args, 'n');
if (name != NULL) { if (name != NULL) {
@@ -88,7 +91,10 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
wname = clean_name(expanded, 0); wname = clean_name(expanded, 0);
free(expanded); free(expanded);
} }
if (args_has(args, 'S') && wname != NULL && target->idx == -1) { 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); expanded = format_single(item, wname, c, s, NULL, NULL);
RB_FOREACH(wl, winlinks, &s->windows) { RB_FOREACH(wl, winlinks, &s->windows) {
if (strcmp(wl->window->name, expanded) != 0) if (strcmp(wl->window->name, expanded) != 0)
@@ -97,12 +103,17 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
new_wl = wl; new_wl = wl;
continue; continue;
} }
cmdq_error(item, "multiple windows named %s", wname); cmdq_error(item, "multiple windows named %s",
wname);
free(wname); free(wname);
free(expanded); free(expanded);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
free(expanded); free(expanded);
}
}
/* Found an existing window to select instead of creating a new one. */
if (new_wl != NULL) { if (new_wl != NULL) {
free(wname); free(wname);
if (args_has(args, 'd')) if (args_has(args, 'd'))
@@ -114,7 +125,6 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
recalculate_sizes(); recalculate_sizes();
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
}
before = args_has(args, 'b'); before = args_has(args, 'b');
if (args_has(args, 'a') || before) { if (args_has(args, 'a') || before) {
+4 -2
View File
@@ -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.60 2026/09/08 10:20:08 nicm Exp $ */
/* /*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -210,7 +210,7 @@ expanded : format
cmd_find_from_client(&fs, c, 0); cmd_find_from_client(&fs, c, 0);
fsp = &fs; fsp = &fs;
} }
ft = format_create(NULL, pi->item, FORMAT_NONE, flags); ft = format_create(c, pi->item, FORMAT_NONE, flags);
format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp); format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
$$ = format_expand(ft, $1); $$ = format_expand(ft, $1);
@@ -802,6 +802,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
if (last == NULL) { if (last == NULL) {
pr->status = CMD_PARSE_SUCCESS; pr->status = CMD_PARSE_SUCCESS;
pr->cmdlist = cmd_list_new(); pr->cmdlist = cmd_list_new();
cmd_parse_free_commands(cmds);
return (1); return (1);
} }
@@ -814,6 +815,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
pi->flags |= CMD_PARSE_NOALIAS; pi->flags |= CMD_PARSE_NOALIAS;
cmd_parse_build_commands(cmds, pi, pr); cmd_parse_build_commands(cmds, pi, pr);
pi->flags &= ~CMD_PARSE_NOALIAS; pi->flags &= ~CMD_PARSE_NOALIAS;
cmd_parse_free_commands(cmds);
return (1); return (1);
} }
+16 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-queue.c,v 1.121 2026/07/14 17:17:17 nicm Exp $ */ /* $OpenBSD: cmd-queue.c,v 1.124 2026/09/22 06:46:50 nicm Exp $ */
/* /*
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -580,6 +580,9 @@ cmdq_fire_command(struct cmdq_item *item)
int flags, quiet = 0; int flags, quiet = 0;
char *tmp; char *tmp;
if (item->client != NULL && (item->client->flags & CLIENT_DEAD))
return (CMD_RETURN_ERROR);
if (cfg_finished) if (cfg_finished)
cmdq_add_message(item); cmdq_add_message(item);
if (log_get_level() > 1) { if (log_get_level() > 1) {
@@ -802,7 +805,7 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
u_int number = item->number; u_int number = item->number;
if (c != NULL && (c->flags & CLIENT_CONTROL)) if (c != NULL && (c->flags & CLIENT_CONTROL))
control_write(c, "%%%s %ld %u %d", guard, t, number, flags); control_write_guard(c, guard, t, number, flags);
} }
/* Show message from command. */ /* Show message from command. */
@@ -850,7 +853,18 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...)
if (c == NULL) { if (c == NULL) {
cmd_get_source(cmd, &file, &line); cmd_get_source(cmd, &file, &line);
if (!cfg_finished) {
if (file != NULL)
cfg_add_cause("%s:%u: %s", file, line, msg); cfg_add_cause("%s:%u: %s", file, line, msg);
else
cfg_add_cause("%s", msg);
} else {
if (file != NULL) {
server_add_message("message: %s:%u: %s", file,
line, msg);
} else
server_add_message("message: %s", msg);
}
} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
server_add_message("%s message: %s", c->name, msg); server_add_message("%s message: %s", c->name, msg);
if (~c->flags & CLIENT_UTF8) { if (~c->flags & CLIENT_UTF8) {
+3 -1
View File
@@ -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.69 2026/09/21 10:33:16 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -91,7 +91,9 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
server_redraw_window(w); server_redraw_window(w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (!window_pane_is_floating(wp))
server_unzoom_window(w); server_unzoom_window(w);
lc = wp->layout_cell; /* may have been replaced by unzoom */
if (args_has(args, 'x')) { if (args_has(args, 'x')) {
x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause); x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);
+4 -2
View File
@@ -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.95 2026/09/20 07:59:55 nicm Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -164,7 +164,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
if (cdata->client != NULL) if (cdata->client != NULL)
cdata->client->references++; cdata->client->references++;
if (args_has(args, 'c')) if (args_has(args, 'c'))
cdata->cwd = xstrdup(args_get(args, 'c')); cdata->cwd = format_single_from_target(item, args_get(args, 'c'));
else else
cdata->cwd = xstrdup(server_client_get_cwd(c, s)); cdata->cwd = xstrdup(server_client_get_cwd(c, s));
@@ -233,9 +233,11 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
} else if (item == NULL) { } else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL); new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item); cmdq_append(c, new_item);
cmd_list_free(cmdlist);
} else { } else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item); cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
} }
if (cdata->item != NULL) if (cdata->item != NULL)
+9 -4
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-select-layout.c,v 1.43 2026/07/10 13:38:45 nicm Exp $ */ /* $OpenBSD: cmd-select-layout.c,v 1.45 2026/09/09 07:03:39 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -73,12 +73,13 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct client *c = cmdq_get_target_client(item);
struct winlink *wl = target->wl; struct winlink *wl = target->wl;
struct window *w = wl->window; struct window *w = wl->window;
struct window_pane *wp = target->wp; struct window_pane *wp = target->wp;
const char *layoutname; const char *layoutname;
char *oldlayout, *cause; char *oldlayout, *cause = NULL;
int next, previous, layout; int next, previous, layout, flags = 0;
server_unzoom_window(w); server_unzoom_window(w);
@@ -89,8 +90,12 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'p')) if (args_has(args, 'p'))
previous = 1; previous = 1;
if (c != NULL &&
(c->flags & CLIENT_CONTROL) &&
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
oldlayout = w->old_layout; oldlayout = w->old_layout;
w->old_layout = layout_dump(w, w->layout_root); w->old_layout = layout_dump(w, w->layout_root, flags);
if (next || previous) { if (next || previous) {
if (next) if (next)
+14 -5
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-select-pane.c,v 1.77 2026/07/17 12:42:51 nicm Exp $ */ /* $OpenBSD: cmd-select-pane.c,v 1.78 2026/08/20 09:19:24 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -160,6 +160,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
char *title; char *title;
const char *style; const char *style;
struct options_entry *o; struct options_entry *o;
int visible, Zflag = args_has(args, 'Z');
if (entry == &cmd_last_pane_entry || args_has(args, 'l')) { if (entry == &cmd_last_pane_entry || args_has(args, 'l')) {
/* /*
@@ -185,14 +186,18 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
server_redraw_window_borders(lastwp->window); server_redraw_window_borders(lastwp->window);
server_status_window(lastwp->window); server_status_window(lastwp->window);
} else { } else {
if (window_push_zoom(w, 0, args_has(args, 'Z'))) if (w->modal != NULL && lastwp != w->modal)
visible = 1;
else
visible = window_pane_is_visible(lastwp);
if (!visible && window_push_zoom(w, 0, Zflag))
server_redraw_window(w); server_redraw_window(w);
window_redraw_active_switch(w, lastwp); window_redraw_active_switch(w, lastwp);
if (window_set_active_pane(w, lastwp, 1)) { if (window_set_active_pane(w, lastwp, 1)) {
cmd_find_from_winlink(current, wl, 0); cmd_find_from_winlink(current, wl, 0);
cmd_select_pane_redraw(w); cmd_select_pane_redraw(w);
} }
if (window_pop_zoom(w)) if (!visible && window_pop_zoom(w))
server_redraw_window(w); server_redraw_window(w);
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
@@ -268,14 +273,18 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
if (wp == w->active) if (wp == w->active)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (window_push_zoom(w, 0, args_has(args, 'Z'))) if (w->modal != NULL && wp != w->modal)
visible = 1;
else
visible = window_pane_is_visible(wp);
if (!visible && window_push_zoom(w, 0, Zflag))
server_redraw_window(w); server_redraw_window(w);
window_redraw_active_switch(w, wp); window_redraw_active_switch(w, wp);
if (window_set_active_pane(w, wp, 1)) if (window_set_active_pane(w, wp, 1))
cmd_find_from_winlink_pane(current, wl, wp, 0); cmd_find_from_winlink_pane(current, wl, wp, 0);
cmdq_insert_hook(s, item, current, "after-select-pane"); cmdq_insert_hook(s, item, current, "after-select-pane");
cmd_select_pane_redraw(w); cmd_select_pane_redraw(w);
if (window_pop_zoom(w)) if (!visible && window_pop_zoom(w))
server_redraw_window(w); server_redraw_window(w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
+6 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-set-buffer.c,v 1.37 2026/02/15 17:43:26 nicm Exp $ */ /* $OpenBSD: cmd-set-buffer.c,v 1.38 2026/08/24 07:05:23 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -100,6 +100,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
goto fail; goto fail;
} }
free(bufname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
@@ -107,8 +108,10 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
cmdq_error(item, "no data specified"); cmdq_error(item, "no data specified");
goto fail; goto fail;
} }
if ((newsize = strlen(args_string(args, 0))) == 0) if ((newsize = strlen(args_string(args, 0))) == 0) {
free(bufname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'a') && pb != NULL) { if (args_has(args, 'a') && pb != NULL) {
olddata = paste_buffer_data(pb, &bufsize); olddata = paste_buffer_data(pb, &bufsize);
@@ -127,6 +130,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'w') && tc != NULL) if (args_has(args, 'w') && tc != NULL)
tty_set_selection(&tc->tty, "", bufdata, bufsize); tty_set_selection(&tc->tty, "", bufdata, bufsize);
free(bufname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
fail: fail:
+36 -18
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-source-file.c,v 1.62 2025/11/18 08:42:09 nicm Exp $ */ /* $OpenBSD: cmd-source-file.c,v 1.63 2026/08/18 08:05:05 nicm Exp $ */
/* /*
* Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org>
@@ -50,6 +50,7 @@ const struct cmd_entry cmd_source_file_entry = {
struct cmd_source_file_data { struct cmd_source_file_data {
struct cmdq_item *item; struct cmdq_item *item;
struct client *client;
int flags; int flags;
struct cmdq_item *after; struct cmdq_item *after;
@@ -60,10 +61,24 @@ struct cmd_source_file_data {
u_int nfiles; u_int nfiles;
}; };
static enum cmd_retval static void
cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data) cmd_source_file_free_data(struct cmd_source_file_data *cdata)
{ {
struct client *c = cmdq_get_client(item); u_int i;
for (i = 0; i < cdata->nfiles; i++)
free(cdata->files[i]);
free(cdata->files);
if (cdata->client != NULL)
server_client_unref(cdata->client);
free(cdata);
}
static enum cmd_retval
cmd_source_file_complete_cb(struct cmdq_item *item, void *data)
{
struct cmd_source_file_data *cdata = data;
struct client *c = cdata->client;
if (c == NULL) { if (c == NULL) {
cmd_source_file_depth--; cmd_source_file_depth--;
@@ -74,36 +89,36 @@ cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
} }
cfg_print_causes(item); cfg_print_causes(item);
cmd_source_file_free_data(cdata);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
static void static void
cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata) cmd_source_file_complete(struct cmd_source_file_data *cdata)
{ {
struct client *c = cdata->client;
struct cmdq_item *new_item; struct cmdq_item *new_item;
u_int i;
if (cfg_finished) { if (!cfg_finished) {
cmd_source_file_free_data(cdata);
return;
}
if (cdata->retval == CMD_RETURN_ERROR && if (cdata->retval == CMD_RETURN_ERROR &&
c != NULL && c != NULL &&
c->session == NULL) c->session == NULL)
c->retval = 1; c->retval = 1;
new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL); new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
cmdq_insert_after(cdata->after, new_item); cmdq_insert_after(cdata->after, new_item);
} }
for (i = 0; i < cdata->nfiles; i++)
free(cdata->files[i]);
free(cdata->files);
free(cdata);
}
static void static void
cmd_source_file_done(struct client *c, const char *path, int error, cmd_source_file_done(__unused struct client *oc, const char *path,
int closed, struct evbuffer *buffer, void *data) int error, int closed, struct evbuffer *buffer, void *data)
{ {
struct cmd_source_file_data *cdata = data; struct cmd_source_file_data *cdata = data;
struct cmdq_item *item = cdata->item; struct cmdq_item *item = cdata->item;
struct client *c = cdata->client;
void *bdata = EVBUFFER_DATA(buffer); void *bdata = EVBUFFER_DATA(buffer);
size_t bsize = EVBUFFER_LENGTH(buffer); size_t bsize = EVBUFFER_LENGTH(buffer);
u_int n; u_int n;
@@ -127,7 +142,7 @@ cmd_source_file_done(struct client *c, const char *path, int error,
if (n < cdata->nfiles) if (n < cdata->nfiles)
file_read(c, cdata->files[n], cmd_source_file_done, cdata); file_read(c, cdata->files[n], cmd_source_file_done, cdata);
else { else {
cmd_source_file_complete(c, cdata); cmd_source_file_complete(cdata);
cmdq_continue(item); cmdq_continue(item);
} }
} }
@@ -187,6 +202,9 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
cdata = xcalloc(1, sizeof *cdata); cdata = xcalloc(1, sizeof *cdata);
cdata->item = item; cdata->item = item;
cdata->client = c;
if (c != NULL)
c->references++;
if (args_has(args, 'q')) if (args_has(args, 'q'))
cdata->flags |= CMD_PARSE_QUIET; cdata->flags |= CMD_PARSE_QUIET;
@@ -249,7 +267,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
file_read(c, cdata->files[0], cmd_source_file_done, cdata); file_read(c, cdata->files[0], cmd_source_file_done, cdata);
retval = CMD_RETURN_WAIT; retval = CMD_RETURN_WAIT;
} else } else
cmd_source_file_complete(c, cdata); cmd_source_file_complete(cdata);
free(cwd); free(cwd);
return (retval); return (retval);
+29 -7
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-split-window.c,v 1.146 2026/07/21 12:28:43 nicm Exp $ */ /* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
.name = "new-pane", .name = "new-pane",
.alias = "newp", .alias = "newp",
.args = { "bB:Cc:de:EfF:hIkl:LMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, .args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
.usage = "[-bCdefhIklMOPvWZ] [-B border-lines] " .usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] "
"[-c start-directory] [-e environment] " "[-c start-directory] [-e environment] "
"[-F format] [-l size] [-m message] [-p percentage] " "[-F format] [-l size] [-m message] [-p percentage] "
"[-s style] [-S active-border-style] " "[-s style] [-S active-border-style] "
@@ -89,6 +89,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state fs; struct cmd_find_state fs;
struct key_event *event = cmdq_get_event(item); struct key_event *event = cmdq_get_event(item);
int input, empty, is_floating, flags = 0; int input, empty, is_floating, flags = 0;
int restore_zoom = 0;
const char *template, *style, *value; const char *template, *style, *value;
char *cause = NULL, *cp, *title; char *cause = NULL, *cp, *title;
const struct options_table_entry *oe; const struct options_table_entry *oe;
@@ -96,9 +97,16 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
enum pane_lines lines; enum pane_lines lines;
u_int count = args_count(args); u_int count = args_count(args);
if (window_active_pane_is_over_zoom(w))
restore_zoom = 1;
if (cmd_get_entry(self) == &cmd_new_pane_entry) if (cmd_get_entry(self) == &cmd_new_pane_entry)
is_floating = !args_has(args, 'L'); is_floating = !args_has(args, 'L');
else { else {
if (!window_pane_is_visible(wp))
restore_zoom = 0;
if (!restore_zoom)
window_unzoom(w, 1);
is_floating = window_pane_is_floating(wp); is_floating = window_pane_is_floating(wp);
flags |= SPAWN_SPLIT; flags |= SPAWN_SPLIT;
} }
@@ -132,7 +140,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'Z')) if (args_has(args, 'Z'))
flags |= SPAWN_ZOOM; flags |= SPAWN_ZOOM;
if (args_has(args, 'O')) if (args_has(args, 'O'))
flags |= SPAWN_MODAL; flags |= SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
if (is_floating && args_has(args, 'A'))
flags |= SPAWN_FLOATOVERZOOM;
if ((w->flags & WINDOW_ZOOMED) && (flags & SPAWN_FLOATOVERZOOM))
restore_zoom = 1;
input = args_has(args, 'I'); input = args_has(args, 'I');
if (input || (count == 1 && *args_string(args, 0) == '\0')) if (input || (count == 1 && *args_string(args, 0) == '\0'))
@@ -168,6 +180,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
free(cause); free(cause);
if (restore_zoom)
window_pop_zoom(w);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -201,8 +215,12 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
*/ */
goto fail; goto fail;
} }
if (args_has(args, 'K') && args_has(args, 'O'))
new_wp->flags |= PANE_CAPTUREALLKEYS;
if (args_has(args, 'C') && args_has(args, 'O')) if (args_has(args, 'C') && args_has(args, 'O'))
new_wp->flags |= PANE_CLOSEONCLICK; new_wp->flags |= PANE_CLOSEONCLICK;
if (args_has(args, 'D') && args_has(args, 'O'))
new_wp->flags |= PANE_CLOSEONCANCEL;
style = args_get(args, 's'); style = args_get(args, 's');
if (style != NULL) { if (style != NULL) {
@@ -270,7 +288,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (~flags & SPAWN_DETACHED) if (~flags & SPAWN_DETACHED)
cmd_find_from_winlink_pane(current, wl, new_wp, 0); cmd_find_from_winlink_pane(current, wl, new_wp, 0);
if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) { if (restore_zoom) {
window_pop_zoom(wp->window);
server_redraw_window(wp->window);
} else if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) {
window_pop_zoom(wp->window); window_pop_zoom(wp->window);
server_redraw_window(wp->window); server_redraw_window(wp->window);
} }
@@ -321,8 +342,9 @@ fail:
if (!is_floating) if (!is_floating)
layout_close_pane(new_wp); layout_close_pane(new_wp);
window_remove_pane(wp->window, new_wp); window_remove_pane(wp->window, new_wp);
} else if (args_has(args, 'O')) }
window_pop_modal_zoom(wp->window); if (restore_zoom || (~flags & SPAWN_FLOATING))
window_pop_zoom(wp->window);
if (sc.argv != NULL) if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv); cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ); environ_free(sc.environ);
+8 -4
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-switch-client.c,v 1.74 2026/05/22 15:22:43 nicm Exp $ */ /* $OpenBSD: cmd-switch-client.c,v 1.75 2026/08/20 09:19:24 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -53,7 +53,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state target; struct cmd_find_state target;
const char *tflag = args_get(args, 't'); const char *tflag = args_get(args, 't');
enum cmd_find_type type; enum cmd_find_type type;
int flags; int flags, visible, Zflag = args_has(args, 'Z');
struct client *c = cmdq_get_client(item); struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item); struct client *tc = cmdq_get_target_client(item);
struct session *s; struct session *s;
@@ -139,11 +139,15 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (wl != NULL && wp != NULL && wp != wl->window->active) { if (wl != NULL && wp != NULL && wp != wl->window->active) {
w = wl->window; w = wl->window;
if (window_push_zoom(w, 0, args_has(args, 'Z'))) if (w->modal != NULL && wp != w->modal)
visible = 1;
else
visible = window_pane_is_visible(wp);
if (!visible && window_push_zoom(w, 0, Zflag))
server_redraw_window(w); server_redraw_window(w);
window_redraw_active_switch(w, wp); window_redraw_active_switch(w, wp);
window_set_active_pane(w, wp, 1); window_set_active_pane(w, wp, 1);
if (window_pop_zoom(w)) if (!visible && window_pop_zoom(w))
server_redraw_window(w); server_redraw_window(w);
} }
if (wl != NULL) { if (wl != NULL) {
+35 -1
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-wait-for.c,v 1.23 2026/07/10 13:38:45 nicm Exp $ */ /* $OpenBSD: cmd-wait-for.c,v 1.24 2026/09/22 06:46:50 nicm Exp $ */
/* /*
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -480,6 +480,40 @@ cmd_wait_for_unlock(struct cmdq_item *item, const char *name,
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
void
cmd_wait_for_client_lost(struct client *c)
{
struct wait_channel *wc, *wc1;
struct wait_item *wi, *wi1;
struct wait_event_item *wei, *wei1;
TAILQ_FOREACH_SAFE(wei, &wait_event_items, entry, wei1) {
if (cmdq_get_client(wei->item) == c) {
TAILQ_REMOVE(&wait_event_items, wei, entry);
cmdq_continue(wei->item);
cmd_wait_for_event_free(wei);
}
}
RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
if (cmdq_get_client(wi->item) == c) {
cmdq_continue(wi->item);
TAILQ_REMOVE(&wc->waiters, wi, entry);
free(wi);
}
}
TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
if (cmdq_get_client(wi->item) == c) {
cmdq_continue(wi->item);
TAILQ_REMOVE(&wc->lockers, wi, entry);
free(wi);
}
}
cmd_wait_for_remove_empty(wc);
}
}
void void
cmd_wait_for_flush(void) cmd_wait_for_flush(void)
{ {
+1 -1
View File
@@ -31,7 +31,7 @@ getpeereid(int s, uid_t *uid, gid_t *gid)
{ {
#ifdef HAVE_SO_PEERCRED #ifdef HAVE_SO_PEERCRED
struct ucred uc; struct ucred uc;
int len = sizeof uc; socklen_t len = sizeof uc;
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1) if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1)
return (-1); return (-1);
+1 -1
View File
@@ -1,6 +1,6 @@
# configure.ac # configure.ac
AC_INIT([tmux], next-3.8) AC_INIT([tmux], 3.8-rc2)
AC_PREREQ([2.60]) AC_PREREQ([2.60])
AC_CONFIG_AUX_DIR(etc) AC_CONFIG_AUX_DIR(etc)
+50 -40
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: control-notify.c,v 1.37 2026/07/10 13:38:45 nicm Exp $ */ /* $OpenBSD: control-notify.c,v 1.39 2026/09/08 10:20:08 nicm Exp $ */
/* /*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -44,7 +44,8 @@ control_pane_mode_changed_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_write(c, "%%pane-mode-changed %%%u", wp->id); control_notify_write(c, "%%pane-mode-changed %%%u",
wp->id);
} }
return; return;
} }
@@ -53,8 +54,9 @@ control_pane_mode_changed_cb(__unused const char *name,
if (value == NULL) if (value == NULL)
return; return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
control_write(c, "%%pane-mode-changed %s", value); continue;
control_notify_write(c, "%%pane-mode-changed %s", value);
} }
free(value); free(value);
} }
@@ -68,6 +70,7 @@ control_window_layout_changed_cb(__unused const char *name,
struct session *s; struct session *s;
struct winlink *wl; struct winlink *wl;
struct window *w = event_payload_get_window(ep, "window"); struct window *w = event_payload_get_window(ep, "window");
struct format_tree *ft;
const char *template; const char *template;
char *cp; char *cp;
@@ -82,20 +85,26 @@ control_window_layout_changed_cb(__unused const char *name,
* and we don't need to inform the client about the layout change * and we don't need to inform the client about the layout change
* because the whole window will go away soon. * because the whole window will go away soon.
*/ */
wl = TAILQ_FIRST(&w->winlinks); if (TAILQ_FIRST(&w->winlinks) == NULL || w->layout_root == NULL)
if (wl == NULL || w->layout_root == NULL)
return; return;
cp = format_single(NULL, template, NULL, NULL, wl, NULL);
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue; continue;
s = c->session; s = c->session;
if (winlink_find_by_window_id(&s->windows, w->id) != NULL) wl = winlink_find_by_window_id(&s->windows, w->id);
control_write(c, "%s", cp); if (wl == NULL)
} continue;
ft = format_create(c, NULL, FORMAT_NONE, 0);
format_defaults(ft, c, s, wl, NULL);
cp = format_expand(ft, template);
format_free(ft);
control_notify_write(c, "%s", cp);
free(cp); free(cp);
} }
}
/* Notify control clients that window pane changed. */ /* Notify control clients that window pane changed. */
static void static void
@@ -111,7 +120,7 @@ control_window_pane_changed_cb(__unused const char *name,
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_write(c, "%%window-pane-changed @%u %%%u", w->id, control_notify_write(c, "%%window-pane-changed @%u %%%u", w->id,
w->active->id); w->active->id);
} }
} }
@@ -133,9 +142,11 @@ control_window_unlinked_cb(__unused const char *name, struct event_payload *ep,
cs = c->session; cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
control_write(c, "%%window-close @%u", w->id); control_notify_write(c, "%%window-close @%u", w->id);
else else {
control_write(c, "%%unlinked-window-close @%u", w->id); control_notify_write(c, "%%unlinked-window-close @%u",
w->id);
}
} }
} }
@@ -156,9 +167,11 @@ control_window_linked_cb(__unused const char *name, struct event_payload *ep,
cs = c->session; cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
control_write(c, "%%window-add @%u", w->id); control_notify_write(c, "%%window-add @%u", w->id);
else else {
control_write(c, "%%unlinked-window-add @%u", w->id); control_notify_write(c, "%%unlinked-window-add @%u",
w->id);
}
} }
} }
@@ -179,11 +192,11 @@ control_window_renamed_cb(__unused const char *name, struct event_payload *ep,
cs = c->session; cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) { if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) {
control_write(c, "%%window-renamed @%u %s", w->id, control_notify_write(c, "%%window-renamed @%u %s",
w->name);
} else {
control_write(c, "%%unlinked-window-renamed @%u %s",
w->id, w->name); w->id, w->name);
} else {
control_notify_write(c,
"%%unlinked-window-renamed @%u %s", w->id, w->name);
} }
} }
} }
@@ -206,11 +219,12 @@ control_client_session_changed_cb(__unused const char *name,
continue; continue;
if (cc == c) { if (cc == c) {
control_write(c, "%%session-changed $%u %s", s->id, control_notify_write(c, "%%session-changed $%u %s",
s->name); s->id, s->name);
} else { } else {
control_write(c, "%%client-session-changed %s $%u %s", control_notify_write(c,
cc->name, s->id, s->name); "%%client-session-changed %s $%u %s", cc->name,
s->id, s->name);
} }
} }
} }
@@ -226,8 +240,9 @@ control_client_detached_cb(__unused const char *name, struct event_payload *ep,
if (cc == NULL) if (cc == NULL)
return; return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
control_write(c, "%%client-detached %s", cc->name); continue;
control_notify_write(c, "%%client-detached %s", cc->name);
} }
} }
@@ -244,8 +259,8 @@ control_session_renamed_cb(__unused const char *name, struct event_payload *ep,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%session-renamed $%u %s", s->id,
control_write(c, "%%session-renamed $%u %s", s->id, s->name); s->name);
} }
} }
@@ -259,8 +274,7 @@ control_session_created_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%sessions-changed");
control_write(c, "%%sessions-changed");
} }
} }
@@ -274,8 +288,7 @@ control_session_closed_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%sessions-changed");
control_write(c, "%%sessions-changed");
} }
} }
@@ -298,9 +311,8 @@ control_session_window_changed_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%session-window-changed $%u @%u",
control_write(c, "%%session-window-changed $%u @%u", s->id, s->id, s->curw->window->id);
s->curw->window->id);
} }
} }
@@ -317,8 +329,7 @@ control_paste_buffer_changed_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%paste-buffer-changed %s", pbname);
control_write(c, "%%paste-buffer-changed %s", pbname);
} }
} }
@@ -335,8 +346,7 @@ control_paste_buffer_deleted_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%paste-buffer-deleted %s", pbname);
control_write(c, "%%paste-buffer-deleted %s", pbname);
} }
} }
+216 -26
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: control.c,v 1.62 2026/07/17 08:37:29 nicm Exp $ */ /* $OpenBSD: control.c,v 1.68 2026/09/09 08:30:05 nicm Exp $ */
/* /*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -53,6 +53,17 @@ struct control_block {
TAILQ_ENTRY(control_block) all_entry; TAILQ_ENTRY(control_block) all_entry;
}; };
/*
* A notification line deferred because it was generated while a command's
* %begin/%end guard block was open. Notifications must never appear inside a
* guard block, so they are held here and flushed once the block closes.
*/
struct control_line {
char *line;
TAILQ_ENTRY(control_line) entry;
};
/* Control client pane. */ /* Control client pane. */
struct control_pane { struct control_pane {
u_int pane; u_int pane;
@@ -97,11 +108,19 @@ struct control_state {
u_int pending_count; u_int pending_count;
TAILQ_HEAD(, control_block) all_blocks; TAILQ_HEAD(, control_block) all_blocks;
size_t queued_reply_bytes;
struct bufferevent *read_event; struct bufferevent *read_event;
struct bufferevent *write_event; struct bufferevent *write_event;
struct monitor_set *subs; struct monitor_set *subs;
/*
* Depth of open %begin/%end guard blocks and notifications deferred
* until the outermost block closes.
*/
int guard_depth;
TAILQ_HEAD(, control_line) deferred;
}; };
/* Low and high watermarks. */ /* Low and high watermarks. */
@@ -114,6 +133,9 @@ struct control_state {
/* Maximum age for clients that are not using pause mode. */ /* Maximum age for clients that are not using pause mode. */
#define CONTROL_MAXIMUM_AGE 300000 #define CONTROL_MAXIMUM_AGE 300000
/* Maximum buffered command replies for a client that is not reading. */
#define CONTROL_MAXIMUM_REPLY_BUFFER (64 * 1024 * 1024)
/* Flags to ignore client. */ /* Flags to ignore client. */
#define CONTROL_IGNORE_FLAGS \ #define CONTROL_IGNORE_FLAGS \
(CLIENT_CONTROL_NOOUTPUT| \ (CLIENT_CONTROL_NOOUTPUT| \
@@ -147,6 +169,15 @@ RB_GENERATE_STATIC(control_windows, control_window, entry, control_window_cmp);
static void static void
control_free_block(struct control_state *cs, struct control_block *cb) control_free_block(struct control_state *cs, struct control_block *cb)
{ {
size_t size;
if (cb->size == 0 && cb->line != NULL) {
size = strlen(cb->line) + 1;
if (cs->queued_reply_bytes > size)
cs->queued_reply_bytes -= size;
else
cs->queued_reply_bytes = 0;
}
free(cb->line); free(cb->line);
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry); TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
free(cb); free(cb);
@@ -352,7 +383,7 @@ control_continue_pane(struct client *c, struct window_pane *wp)
cp->flags &= ~CONTROL_PANE_PAUSED; cp->flags &= ~CONTROL_PANE_PAUSED;
memcpy(&cp->offset, &wp->offset, sizeof cp->offset); memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued); memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
control_write(c, "%%continue %%%u", wp->id); control_notify_write(c, "%%continue %%%u", wp->id);
} }
} }
@@ -366,52 +397,186 @@ control_pause_pane(struct client *c, struct window_pane *wp)
if (~cp->flags & CONTROL_PANE_PAUSED) { if (~cp->flags & CONTROL_PANE_PAUSED) {
cp->flags |= CONTROL_PANE_PAUSED; cp->flags |= CONTROL_PANE_PAUSED;
control_discard_pane(c, cp); control_discard_pane(c, cp);
control_write(c, "%%pause %%%u", wp->id); control_notify_write(c, "%%pause %%%u", wp->id);
} }
} }
/* Write a line. */ /*
static void printflike(2, 0) * Reset a pane after its buffer has been replaced: drop any output still
control_vwrite(struct client *c, const char *fmt, va_list ap) * queued from the old buffer and start again from the pane's own offset.
*/
void
control_reset_pane(struct client *c, struct window_pane *wp)
{
struct control_pane *cp;
if (c->control_state == NULL)
return;
cp = control_get_pane(c, wp);
if (cp == NULL)
return;
control_discard_pane(c, cp);
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
}
/*
* Check if the replies buffered for a client, including one about to be
* added, have grown too large and kill it if so. Returns 1 if further output
* for the client should be dropped.
*/
static int
control_check_reply_buffer(struct client *c, size_t added)
{ {
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
char *s; size_t size;
xvasprintf(&s, fmt, ap); if (c->flags & CLIENT_CONTROL_DISCARD)
log_debug("%s: %s: writing line: %s", __func__, c->name, s); return (1);
size = EVBUFFER_LENGTH(cs->write_event->output);
size += cs->queued_reply_bytes;
size += added;
if (size < CONTROL_MAXIMUM_REPLY_BUFFER)
return (0);
bufferevent_write(cs->write_event, s, strlen(s)); log_debug("%s: %s: %zu bytes of replies buffered", __func__, c->name,
bufferevent_write(cs->write_event, "\n", 1); size);
if (~c->flags & CLIENT_EXIT) {
bufferevent_enable(cs->write_event, EV_WRITE); c->exit_message = xstrdup("too far behind");
free(s); c->flags |= CLIENT_EXIT;
control_discard(c);
}
c->flags |= CLIENT_CONTROL_DISCARD;
return (1);
} }
/* Write a line. */ /* Write an already-formatted line, queueing it behind %output if needed. */
void static void
control_write(struct client *c, const char *fmt, ...) control_write_line(struct client *c, char *line)
{ {
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
struct control_block *cb; struct control_block *cb;
va_list ap; size_t size = strlen(line) + 1;
va_start(ap, fmt); if (control_check_reply_buffer(c, size)) {
free(line);
return;
}
if (TAILQ_EMPTY(&cs->all_blocks)) { if (TAILQ_EMPTY(&cs->all_blocks)) {
control_vwrite(c, fmt, ap); log_debug("%s: %s: writing line: %s", __func__, c->name, line);
va_end(ap); bufferevent_write(cs->write_event, line, size - 1);
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
free(line);
return; return;
} }
cb = xcalloc(1, sizeof *cb); cb = xcalloc(1, sizeof *cb);
xvasprintf(&cb->line, fmt, ap); cb->line = line;
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
cs->queued_reply_bytes += size;
cb->t = get_timer(); cb->t = get_timer();
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line); log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
bufferevent_enable(cs->write_event, EV_WRITE); bufferevent_enable(cs->write_event, EV_WRITE);
}
/* Flush notifications that were deferred while a guard block was open. */
static void
control_flush_deferred(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_line *cl, *cl1;
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
TAILQ_REMOVE(&cs->deferred, cl, entry);
control_write_line(c, cl->line);
free(cl);
}
}
/*
* Write a line of command output or error text. This is a sink for arbitrary
* user-controlled text (command output, capture-pane, error messages), so it
* must never try to interpret the content: guard tracking is done only in
* control_write_guard.
*/
void
control_write(struct client *c, const char *fmt, ...)
{
struct control_state *cs = c->control_state;
char *line;
va_list ap;
if (cs == NULL)
return;
va_start(ap, fmt);
xvasprintf(&line, fmt, ap);
va_end(ap); va_end(ap);
control_write_line(c, line);
}
/*
* Write a %begin, %end or %error guard around a command's output. This is the
* only place guard lines are produced, so the block depth is maintained here;
* when the outermost block closes any deferred notifications are flushed after
* it. "guard" is always one of the fixed strings from cmdq_guard, never user
* text.
*/
void
control_write_guard(struct client *c, const char *guard, long t, u_int number,
int flags)
{
struct control_state *cs = c->control_state;
char *line;
if (cs == NULL)
return;
if (strcmp(guard, "begin") == 0)
cs->guard_depth++;
xasprintf(&line, "%%%s %ld %u %d", guard, t, number, flags);
control_write_line(c, line);
if (strcmp(guard, "begin") != 0 && cs->guard_depth > 0 &&
--cs->guard_depth == 0)
control_flush_deferred(c);
}
/*
* Write a notification line. Notifications must never appear inside a command's
* %begin/%end guard block, so if one is open the line is deferred until it
* closes.
*/
void
control_notify_write(struct client *c, const char *fmt, ...)
{
struct control_state *cs = c->control_state;
struct control_line *cl;
va_list ap;
char *line;
if (cs == NULL)
return;
va_start(ap, fmt);
xvasprintf(&line, fmt, ap);
va_end(ap);
if (cs->guard_depth == 0) {
control_write_line(c, line);
return;
}
log_debug("%s: %s: deferring notification: %s", __func__, c->name,
line);
cl = xcalloc(1, sizeof *cl);
cl->line = line;
TAILQ_INSERT_TAIL(&cs->deferred, cl, entry);
} }
/* Check age for this pane. */ /* Check age for this pane. */
@@ -438,7 +603,7 @@ control_check_age(struct client *c, struct window_pane *wp,
return (0); return (0);
cp->flags |= CONTROL_PANE_PAUSED; cp->flags |= CONTROL_PANE_PAUSED;
control_discard_pane(c, cp); control_discard_pane(c, cp);
control_write(c, "%%pause %%%u", wp->id); control_notify_write(c, "%%pause %%%u", wp->id);
} else { } else {
if (age < CONTROL_MAXIMUM_AGE) if (age < CONTROL_MAXIMUM_AGE)
return (0); return (0);
@@ -805,14 +970,17 @@ control_sub_change(struct monitor_change *change, __unused void *data)
if (wp != NULL) { if (wp != NULL) {
w = wp->window; w = wp->window;
control_write(c, "%%subscription-changed %s $%u @%u %u %%%u : %s", control_notify_write(c,
"%%subscription-changed %s $%u @%u %u %%%u : %s",
change->name, s->id, w->id, wl->idx, wp->id, change->value); change->name, s->id, w->id, wl->idx, wp->id, change->value);
} else if (wl != NULL) { } else if (wl != NULL) {
w = wl->window; w = wl->window;
control_write(c, "%%subscription-changed %s $%u @%u %u - : %s", control_notify_write(c,
"%%subscription-changed %s $%u @%u %u - : %s",
change->name, s->id, w->id, wl->idx, change->value); change->name, s->id, w->id, wl->idx, change->value);
} else { } else {
control_write(c, "%%subscription-changed %s $%u - - - : %s", control_notify_write(c,
"%%subscription-changed %s $%u - - - : %s",
change->name, s->id, change->value); change->name, s->id, change->value);
} }
} }
@@ -835,6 +1003,7 @@ control_start(struct client *c)
RB_INIT(&cs->windows); RB_INIT(&cs->windows);
TAILQ_INIT(&cs->pending_list); TAILQ_INIT(&cs->pending_list);
TAILQ_INIT(&cs->all_blocks); TAILQ_INIT(&cs->all_blocks);
TAILQ_INIT(&cs->deferred);
cs->subs = monitor_create_client(c, control_sub_change, NULL); cs->subs = monitor_create_client(c, control_sub_change, NULL);
cs->read_event = bufferevent_new(c->fd, control_read_callback, cs->read_event = bufferevent_new(c->fd, control_read_callback,
@@ -878,6 +1047,20 @@ control_discard(struct client *c)
bufferevent_disable(cs->read_event, EV_READ); bufferevent_disable(cs->read_event, EV_READ);
} }
/* Discard all tmux-owned queued control blocks and stop writing. */
void
control_discard_all(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_block *cb, *cb1;
control_discard(c);
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
control_free_block(cs, cb);
cs->queued_reply_bytes = 0;
bufferevent_disable(cs->write_event, EV_WRITE);
}
/* Stop control mode. */ /* Stop control mode. */
void void
control_stop(struct client *c) control_stop(struct client *c)
@@ -885,12 +1068,19 @@ control_stop(struct client *c)
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
struct control_block *cb, *cb1; struct control_block *cb, *cb1;
struct control_window *cw, *cw1; struct control_window *cw, *cw1;
struct control_line *cl, *cl1;
if (cs == NULL) if (cs == NULL)
return; return;
monitor_destroy(cs->subs); monitor_destroy(cs->subs);
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
TAILQ_REMOVE(&cs->deferred, cl, entry);
free(cl->line);
free(cl);
}
if (~c->flags & CLIENT_CONTROLCONTROL) if (~c->flags & CLIENT_CONTROLCONTROL)
bufferevent_free(cs->write_event); bufferevent_free(cs->write_event);
bufferevent_free(cs->read_event); bufferevent_free(cs->read_event);
+67 -17
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: file.c,v 1.21 2026/07/26 15:08:15 nicm Exp $ */ /* $OpenBSD: file.c,v 1.22 2026/08/17 07:56:56 nicm Exp $ */
/* /*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -502,6 +502,7 @@ file_push(struct client_file *cf)
} else if (cf->stream > 2) { } else if (cf->stream > 2) {
close.stream = cf->stream; close.stream = cf->stream;
proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close); proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
file_fire_done(cf); file_fire_done(cf);
} }
free(msg); free(msg);
@@ -527,14 +528,48 @@ file_write_left(struct client_files *files)
return (waiting != 0); return (waiting != 0);
} }
/* Finish writing a client file. */
static void
file_write_finished(struct client_file *cf)
{
struct msg_write_done msg;
if (cf->event != NULL) {
bufferevent_free(cf->event);
cf->event = NULL;
}
if (cf->fd != -1) {
if (close(cf->fd) != 0 && cf->error == 0)
cf->error = errno;
cf->fd = -1;
}
msg.stream = cf->stream;
msg.error = cf->error;
proc_send(cf->peer, MSG_WRITE_DONE, -1, &msg, sizeof msg);
if (cf->cb != NULL)
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
file_free(cf);
}
/* Client file write error callback. */ /* Client file write error callback. */
static void static void
file_write_error_callback(__unused struct bufferevent *bev, __unused short what, file_write_error_callback(__unused struct bufferevent *bev, short what,
void *arg) void *arg)
{ {
struct client_file *cf = arg; struct client_file *cf = arg;
int error;
if (what & EVBUFFER_ERROR)
error = errno;
else
error = EIO;
if (error == 0)
error = EIO;
log_debug("write error file %d", cf->stream); log_debug("write error file %d", cf->stream);
cf->error = error;
bufferevent_free(cf->event); bufferevent_free(cf->event);
cf->event = NULL; cf->event = NULL;
@@ -542,7 +577,9 @@ file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
close(cf->fd); close(cf->fd);
cf->fd = -1; cf->fd = -1;
if (cf->cb != NULL) if (cf->closed)
file_write_finished(cf);
else if (cf->cb != NULL)
cf->cb(NULL, NULL, 0, -1, NULL, cf->data); cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
} }
@@ -554,15 +591,10 @@ file_write_callback(__unused struct bufferevent *bev, void *arg)
log_debug("write check file %d", cf->stream); log_debug("write check file %d", cf->stream);
if (cf->cb != NULL) if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0)
file_write_finished(cf);
else if (cf->cb != NULL)
cf->cb(NULL, NULL, 0, -1, NULL, cf->data); cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) {
bufferevent_free(cf->event);
close(cf->fd);
RB_REMOVE(client_files, cf->tree, cf);
file_free(cf);
}
} }
/* Handle a file write open message (client). */ /* Handle a file write open message (client). */
@@ -663,14 +695,10 @@ file_write_close(struct client_files *files, struct imsg *imsg)
if ((cf = RB_FIND(client_files, files, &find)) == NULL) if ((cf = RB_FIND(client_files, files, &find)) == NULL)
fatalx("unknown stream number"); fatalx("unknown stream number");
log_debug("close file %d", cf->stream); log_debug("close file %d", cf->stream);
cf->closed = 1;
if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) { if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) {
if (cf->event != NULL) file_write_finished(cf);
bufferevent_free(cf->event);
if (cf->fd != -1)
close(cf->fd);
RB_REMOVE(client_files, files, cf);
file_free(cf);
} }
} }
@@ -829,6 +857,28 @@ file_write_ready(struct client_files *files, struct imsg *imsg)
return (0); return (0);
} }
/* Handle a write done message (server). */
int
file_write_done(struct client_files *files, struct imsg *imsg)
{
struct msg_write_done *msg = imsg->data;
size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
struct client_file find, *cf;
if (msglen != sizeof *msg)
return (-1);
find.stream = msg->stream;
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
return (0);
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
return (0);
log_debug("file %d write done", cf->stream);
cf->error = msg->error;
file_fire_done(cf);
return (0);
}
/* Handle read data message (server). */ /* Handle read data message (server). */
int int
file_read_data(struct client_files *files, struct imsg *imsg) file_read_data(struct client_files *files, struct imsg *imsg)
+142 -10
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: format.c,v 1.410 2026/07/29 20:43:20 nicm Exp $ */ /* $OpenBSD: format.c,v 1.418 2026/09/20 08:19:31 nicm Exp $ */
/* /*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -855,26 +855,42 @@ format_cb_window_active_clients_list(struct format_tree *ft)
static void * static void *
format_cb_window_layout(struct format_tree *ft) format_cb_window_layout(struct format_tree *ft)
{ {
struct client *c = ft->client;
struct window *w = ft->w; struct window *w = ft->w;
struct layout_cell *lcroot;
int flags = 0;
if (w == NULL) if (w == NULL)
return (NULL); return (NULL);
if (w->saved_layout_root != NULL) if (w->saved_layout_root != NULL)
return (layout_dump(w, w->saved_layout_root)); lcroot = w->saved_layout_root;
return (layout_dump(w, w->layout_root)); else
lcroot = w->layout_root;
if (c != NULL &&
(c->flags & CLIENT_CONTROL) &&
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
return (layout_dump(w, lcroot, flags));
} }
/* Callback for window_visible_layout. */ /* Callback for window_visible_layout. */
static void * static void *
format_cb_window_visible_layout(struct format_tree *ft) format_cb_window_visible_layout(struct format_tree *ft)
{ {
struct client *c = ft->client;
struct window *w = ft->w; struct window *w = ft->w;
int flags = 0;
if (w == NULL) if (w == NULL)
return (NULL); return (NULL);
return (layout_dump(w, w->layout_root)); if (c != NULL &&
(c->flags & CLIENT_CONTROL) &&
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
return (layout_dump(w, w->layout_root, flags));
} }
/* Callback for pane_start_command. */ /* Callback for pane_start_command. */
@@ -1887,6 +1903,37 @@ format_cb_cursor_blinking(struct format_tree *ft)
return (NULL); 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. */ /* Callback for history_limit. */
static void * static void *
format_cb_history_limit(struct format_tree *ft) format_cb_history_limit(struct format_tree *ft)
@@ -2101,6 +2148,60 @@ format_cb_synchronized_output_flag(struct format_tree *ft)
return (NULL); return (NULL);
} }
/* Callback for pane_private_modes. */
static void *
format_cb_pane_private_modes(struct format_tree *ft)
{
static const struct {
int mode;
int number;
} table[] = {
{ MODE_KCURSOR, 1 }, /* DECCKM */
{ MODE_ORIGIN, 6 }, /* DECOM */
{ MODE_WRAP, 7 }, /* DECAWM */
{ MODE_CURSOR_BLINKING, 12 }, /* cursor blinking */
{ MODE_CURSOR, 25 }, /* DECTCEM */
{ MODE_MOUSE_STANDARD, 1000 }, /* mouse normal tracking */
{ MODE_MOUSE_BUTTON, 1002 }, /* mouse button tracking */
{ MODE_MOUSE_ALL, 1003 }, /* mouse any tracking */
{ MODE_FOCUSON, 1004 }, /* focus reporting */
{ MODE_MOUSE_UTF8, 1005 }, /* mouse: UTF-8 */
{ MODE_MOUSE_SGR, 1006 }, /* mouse: SGR */
{ MODE_BRACKETPASTE, 2004 }, /* bracketed paste */
{ MODE_SYNC, 2026 }, /* synchronized output */
{ MODE_THEME_UPDATES, 2031 }, /* theme update notifications */
};
int mode;
char *value = NULL, *tmp;
u_int i;
if (ft->wp == NULL)
return (NULL);
mode = ft->wp->base.mode;
for (i = 0; i < nitems(table); i++) {
if (~mode & table[i].mode)
continue;
/*
* Only report cursor blinking when set by the application, not
* when it comes from the cursor-style option.
*/
if (table[i].mode == MODE_CURSOR_BLINKING &&
(~mode & MODE_CURSOR_BLINKING_SET))
continue;
if (value == NULL)
xasprintf(&value, "%d", table[i].number);
else {
xasprintf(&tmp, "%s,%d", value, table[i].number);
free(value);
value = tmp;
}
}
if (value == NULL)
return (xstrdup(""));
return (value);
}
/* Callback for pane_active. */ /* Callback for pane_active. */
static void * static void *
format_cb_pane_active(struct format_tree *ft) format_cb_pane_active(struct format_tree *ft)
@@ -2222,6 +2323,19 @@ format_cb_pane_last_output_time(struct format_tree *ft)
return (NULL); 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. */ /* Callback for pane_last_prompt_time. */
static void * static void *
format_cb_pane_last_prompt_time(struct format_tree *ft) format_cb_pane_last_prompt_time(struct format_tree *ft)
@@ -2762,10 +2876,10 @@ format_cb_session_activity_flag(struct format_tree *ft)
if (ft->s != NULL) { if (ft->s != NULL) {
RB_FOREACH(wl, winlinks, &ft->s->windows) { RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (ft->wl->flags & WINLINK_ACTIVITY) if (wl->flags & WINLINK_ACTIVITY)
return (xstrdup("1")); return (xstrdup("1"));
return (xstrdup("0"));
} }
return (xstrdup("0"));
} }
return (NULL); return (NULL);
} }
@@ -2780,8 +2894,8 @@ format_cb_session_bell_flag(struct format_tree *ft)
RB_FOREACH(wl, winlinks, &ft->s->windows) { RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (wl->flags & WINLINK_BELL) if (wl->flags & WINLINK_BELL)
return (xstrdup("1")); return (xstrdup("1"));
return (xstrdup("0"));
} }
return (xstrdup("0"));
} }
return (NULL); return (NULL);
} }
@@ -2794,10 +2908,10 @@ format_cb_session_silence_flag(struct format_tree *ft)
if (ft->s != NULL) { if (ft->s != NULL) {
RB_FOREACH(wl, winlinks, &ft->s->windows) { RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (ft->wl->flags & WINLINK_SILENCE) if (wl->flags & WINLINK_SILENCE)
return (xstrdup("1")); return (xstrdup("1"));
return (xstrdup("0"));
} }
return (xstrdup("0"));
} }
return (NULL); return (NULL);
} }
@@ -3609,12 +3723,21 @@ static const struct format_table_entry format_table[] = {
{ "cursor_y", FORMAT_TABLE_STRING, { "cursor_y", FORMAT_TABLE_STRING,
format_cb_cursor_y format_cb_cursor_y
}, },
{ "history_added", FORMAT_TABLE_STRING,
format_cb_history_added
},
{ "history_all_bytes", FORMAT_TABLE_STRING, { "history_all_bytes", FORMAT_TABLE_STRING,
format_cb_history_all_bytes format_cb_history_all_bytes
}, },
{ "history_bytes", FORMAT_TABLE_STRING, { "history_bytes", FORMAT_TABLE_STRING,
format_cb_history_bytes 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, { "history_limit", FORMAT_TABLE_STRING,
format_cb_history_limit format_cb_history_limit
}, },
@@ -3795,6 +3918,9 @@ static const struct format_table_entry format_table[] = {
{ "pane_mode", FORMAT_TABLE_STRING, { "pane_mode", FORMAT_TABLE_STRING,
format_cb_pane_mode format_cb_pane_mode
}, },
{ "pane_output_generation", FORMAT_TABLE_STRING,
format_cb_pane_output_generation
},
{ "pane_path", FORMAT_TABLE_STRING, { "pane_path", FORMAT_TABLE_STRING,
format_cb_pane_path format_cb_pane_path
}, },
@@ -3813,6 +3939,9 @@ static const struct format_table_entry format_table[] = {
{ "pane_pipe_pid", FORMAT_TABLE_STRING, { "pane_pipe_pid", FORMAT_TABLE_STRING,
format_cb_pane_pipe_pid format_cb_pane_pipe_pid
}, },
{ "pane_private_modes", FORMAT_TABLE_STRING,
format_cb_pane_private_modes
},
{ "pane_right", FORMAT_TABLE_STRING, { "pane_right", FORMAT_TABLE_STRING,
format_cb_pane_right format_cb_pane_right
}, },
@@ -4200,6 +4329,8 @@ format_log_debug_cb(const char *key, const char *value, void *arg)
void void
format_log_debug(struct format_tree *ft, const char *prefix) format_log_debug(struct format_tree *ft, const char *prefix)
{ {
if (log_get_level() == 0)
return;
format_each(ft, format_log_debug_cb, (void *)prefix); format_each(ft, format_log_debug_cb, (void *)prefix);
} }
@@ -4328,7 +4459,7 @@ format_quote_shell(const char *s)
at = out = xmalloc(strlen(s) * 2 + 1); at = out = xmalloc(strlen(s) * 2 + 1);
for (cp = s; *cp != '\0'; cp++) { for (cp = s; *cp != '\0'; cp++) {
if (strchr("|&;<>()$`\\\"'*?[# =%\n\t", *cp) != NULL) if (strchr("|&;<>(){}$`\\\"'*?[# =%\n\t", *cp) != NULL)
*at++ = '\\'; *at++ = '\\';
*at++ = *cp; *at++ = *cp;
} }
@@ -5119,6 +5250,7 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
format_log(es, "session loop: $%u", s->id); format_log(es, "session loop: $%u", s->id);
if (active != NULL && if (active != NULL &&
ft->c != NULL && ft->c != NULL &&
ft->c->session != NULL &&
s->id == ft->c->session->id) s->id == ft->c->session->id)
use = active; use = active;
else else
+3 -3
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: grid-view.c,v 1.38 2026/01/22 08:55:01 nicm Exp $ */ /* $OpenBSD: grid-view.c,v 1.39 2026/08/03 12:58:53 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -47,9 +47,9 @@ grid_view_set_cell(struct grid *gd, u_int px, u_int py,
/* Set padding. */ /* Set padding. */
void void
grid_view_set_padding(struct grid *gd, u_int px, u_int py) grid_view_set_padding(struct grid *gd, u_int px, u_int py, int bg)
{ {
grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py)); grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py), bg);
} }
/* Set cells. */ /* Set cells. */
+23 -11
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: grid.c,v 1.155 2026/07/29 17:42:56 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> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -682,9 +682,13 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
/* Set padding at position. */ /* Set padding at position. */
void void
grid_set_padding(struct grid *gd, u_int px, u_int py) grid_set_padding(struct grid *gd, u_int px, u_int py, int bg)
{ {
grid_set_cell(gd, px, py, &grid_padding_cell); struct grid_cell gc;
memcpy(&gc, &grid_padding_cell, sizeof gc);
gc.bg = bg;
grid_set_cell(gd, px, py, &gc);
} }
/* Set cells at position. */ /* Set cells at position. */
@@ -1631,7 +1635,7 @@ grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy)
void void
grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy) 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++) { for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) {
if (ay == wy) if (ay == wy)
@@ -1645,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. * until we find the end or the line now containing wx.
*/ */
if (wx == UINT_MAX) { if (wx == UINT_MAX) {
while (gd->linedata[yy].flags & GRID_LINE_WRAPPED) while (yy < ey && gd->linedata[yy].flags & GRID_LINE_WRAPPED)
yy++; yy++;
wx = gd->linedata[yy].cellused; wx = gd->linedata[yy].cellused;
} else { } else {
@@ -1707,21 +1711,29 @@ grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
{ {
struct grid_cell gc, tmp_gc; struct grid_cell gc, tmp_gc;
u_int pxx; 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); grid_get_cell(gd, px, py, &gc);
if (strchr(set, '\t')) {
if (gc.flags & GRID_FLAG_PADDING) { if (gc.flags & GRID_FLAG_PADDING) {
if (!has_tab && !has_space)
return (0);
pxx = px; pxx = px;
do do
grid_get_cell(gd, --pxx, py, &tmp_gc); grid_get_cell(gd, --pxx, py, &tmp_gc);
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING); while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
if (tmp_gc.flags & GRID_FLAG_TAB) 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 (tmp_gc.data.width - (px - pxx));
} else if (gc.flags & GRID_FLAG_TAB)
return (gc.data.width);
}
if (gc.flags & GRID_FLAG_PADDING)
return (0); 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)); return (utf8_cstrhas(set, &gc.data));
} }
+5
View File
@@ -84,6 +84,11 @@ image_fallback(char **ret, u_int sx, u_int sy)
char *buf, *label; char *buf, *label;
u_int py, size, lsize; u_int py, size, lsize;
if (sy == 0) {
*ret = xstrdup("");
return;
}
/* Allocate first line. */ /* Allocate first line. */
lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1; lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1;
if (sx < lsize - 3) if (sx < lsize - 3)
+3 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: input.c,v 1.269 2026/07/20 11:16:33 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> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1049,6 +1049,7 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
if (len == 0) if (len == 0)
return; return;
wp->output_generation++;
window_update_activity(wp->window); window_update_activity(wp->window);
if (~wp->flags & PANE_ACTIVITY) { if (~wp->flags & PANE_ACTIVITY) {
wp->flags |= PANE_ACTIVITY; wp->flags |= PANE_ACTIVITY;
@@ -1978,7 +1979,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx)
screen_write_mode_clear(sctx, MODE_BRACKETPASTE); screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
break; break;
case 2026: case 2026:
screen_write_stop_sync(ictx->wp); screen_write_end_sync(sctx);
break; break;
case 2031: case 2031:
screen_write_mode_clear(sctx, MODE_THEME_UPDATES); screen_write_mode_clear(sctx, MODE_THEME_UPDATES);
+1001
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: key-bindings.c,v 1.190 2026/07/22 19:23:59 nicm Exp $ */ /* $OpenBSD: key-bindings.c,v 1.193 2026/09/21 20:45:48 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -421,6 +421,7 @@ key_bindings_init(void)
"bind -N 'Spread panes out evenly' E { select-layout -E }", "bind -N 'Spread panes out evenly' E { select-layout -E }",
"bind -N 'Switch to the last client' L { switch-client -l }", "bind -N 'Switch to the last client' L { switch-client -l }",
"bind -N 'Clear the marked pane' M { select-pane -M }", "bind -N 'Clear the marked pane' M { select-pane -M }",
"bind -N 'Change the pane title' T { command-prompt -I'#T' { select-pane -T '%%' } }",
"bind -N 'Enter copy mode' [ { copy-mode }", "bind -N 'Enter copy mode' [ { copy-mode }",
"bind -N 'Paste the most recent paste buffer' ] { paste-buffer -p }", "bind -N 'Paste the most recent paste buffer' ] { paste-buffer -p }",
"bind -N 'Create a new window' c { new-window }", "bind -N 'Create a new window' c { new-window }",
@@ -522,7 +523,7 @@ key_bindings_init(void)
"bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }", "bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }",
/* Mouse button 1 on border. */ /* Mouse button 1 on border. */
"bind -n MouseDown1Border { select-pane -M }", "bind -n MouseDown1Border { select-pane -t= }",
/* Mouse button 1 drag on border. */ /* Mouse button 1 drag on border. */
"bind -n MouseDrag1Border { resize-pane -M }", "bind -n MouseDrag1Border { resize-pane -M }",
@@ -595,7 +596,7 @@ key_bindings_init(void)
"bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }", "bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }",
"bind -Tcopy-mode n { send -X search-again }", "bind -Tcopy-mode n { send -X search-again }",
"bind -Tcopy-mode q { send -X cancel }", "bind -Tcopy-mode q { send -X cancel }",
"bind -Tcopy-mode r { send -X refresh-toggle }", "bind -Tcopy-mode r { send -X refresh-now }",
"bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", "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 Home { send -X start-of-line }",
"bind -Tcopy-mode End { send -X end-of-line }", "bind -Tcopy-mode End { send -X end-of-line }",
@@ -705,7 +706,7 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi n { send -X search-again }", "bind -Tcopy-mode-vi n { send -X search-again }",
"bind -Tcopy-mode-vi o { send -X other-end }", "bind -Tcopy-mode-vi o { send -X other-end }",
"bind -Tcopy-mode-vi q { send -X cancel }", "bind -Tcopy-mode-vi q { send -X cancel }",
"bind -Tcopy-mode-vi r { send -X refresh-toggle }", "bind -Tcopy-mode-vi r { send -X refresh-now }",
"bind -Tcopy-mode-vi t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }", "bind -Tcopy-mode-vi t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
"bind -Tcopy-mode-vi v { send -X rectangle-toggle }", "bind -Tcopy-mode-vi v { send -X rectangle-toggle }",
"bind -Tcopy-mode-vi w { send -X next-word }", "bind -Tcopy-mode-vi w { send -X next-word }",
+962 -138
View File
File diff suppressed because it is too large Load Diff
+101 -51
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: layout.c,v 1.96 2026/07/15 13:02:33 nicm Exp $ */ /* $OpenBSD: layout.c,v 1.101 2026/09/20 08:42:46 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -106,7 +106,7 @@ layout_free_cell(struct layout_cell *lc, int only_nodes)
} }
break; break;
case LAYOUT_WINDOWPANE: case LAYOUT_WINDOWPANE:
if (lc->wp != NULL) { if (lc->wp != NULL && lc->wp->layout_cell != NULL) {
lc->wp->layout_cell->parent = NULL; lc->wp->layout_cell->parent = NULL;
lc->wp->layout_cell = NULL; lc->wp->layout_cell = NULL;
} }
@@ -233,29 +233,6 @@ layout_make_node(struct layout_cell *lc, enum layout_type type)
lc->wp = NULL; lc->wp = NULL;
} }
/* Fix z-indexes. */
void
layout_fix_zindexes(struct window *w, struct layout_cell *lc)
{
struct layout_cell *lcchild;
if (lc == NULL)
return;
switch (lc->type) {
case LAYOUT_WINDOWPANE:
TAILQ_INSERT_TAIL(&w->z_index, lc->wp, zentry);
break;
case LAYOUT_LEFTRIGHT:
case LAYOUT_TOPBOTTOM:
TAILQ_FOREACH(lcchild, &lc->cells, entry)
layout_fix_zindexes(w, lcchild);
return;
default:
fatalx("bad layout type");
}
}
int int
layout_cell_is_tiled(struct layout_cell *lc) layout_cell_is_tiled(struct layout_cell *lc)
{ {
@@ -265,7 +242,7 @@ layout_cell_is_tiled(struct layout_cell *lc)
return is_leaf && !is_floating; return is_leaf && !is_floating;
} }
static int int
layout_cell_has_tiled_child(struct layout_cell *lc) layout_cell_has_tiled_child(struct layout_cell *lc)
{ {
struct layout_cell *lcchild; struct layout_cell *lcchild;
@@ -509,18 +486,20 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
/* Count the number of available cells in a layout. */ /* Count the number of available cells in a layout. */
u_int u_int
layout_count_cells(struct layout_cell *lc) layout_count_cells(struct layout_cell *lc, int with_floating)
{ {
struct layout_cell *lcchild; struct layout_cell *lcchild;
u_int count = 0; u_int count = 0;
switch (lc->type) { switch (lc->type) {
case LAYOUT_WINDOWPANE: case LAYOUT_WINDOWPANE:
if (lc->flags & LAYOUT_CELL_FLOATING && !with_floating)
return 0;
return (1); return (1);
case LAYOUT_LEFTRIGHT: case LAYOUT_LEFTRIGHT:
case LAYOUT_TOPBOTTOM: case LAYOUT_TOPBOTTOM:
TAILQ_FOREACH(lcchild, &lc->cells, entry) TAILQ_FOREACH(lcchild, &lc->cells, entry)
count += layout_count_cells(lcchild); count += layout_count_cells(lcchild, with_floating);
return (count); return (count);
default: default:
fatalx("bad layout type"); fatalx("bad layout type");
@@ -539,6 +518,10 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
status = window_get_pane_status(w); 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) { if (lc->type == LAYOUT_WINDOWPANE) {
/* Space available in this cell only. */ /* Space available in this cell only. */
if (type == LAYOUT_LEFTRIGHT) { if (type == LAYOUT_LEFTRIGHT) {
@@ -568,6 +551,9 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
/* Different type: minimum of available space in child cells. */ /* Different type: minimum of available space in child cells. */
minimum = UINT_MAX; minimum = UINT_MAX;
TAILQ_FOREACH(lcchild, &lc->cells, entry) { 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); available = layout_resize_check(w, lcchild, type);
if (available < minimum) if (available < minimum)
minimum = available; minimum = available;
@@ -661,7 +647,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. */ /* Find and return the nearest neighbour to a cell in a specific direction. */
static struct layout_cell * 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; struct layout_cell *lcn = lc;
@@ -695,9 +681,9 @@ layout_cell_get_neighbour(struct layout_cell *lc)
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
direction = !direction; direction = !direction;
lcother = layout_cell_get_neighbour_direction(lc, direction); lcother = layout_cell_get_neighbour_dir(lc, direction);
if (lcother == NULL) if (lcother == NULL)
lcother = layout_cell_get_neighbour_direction(lc, !direction); lcother = layout_cell_get_neighbour_dir(lc, !direction);
return (lcother); return (lcother);
} }
@@ -714,7 +700,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
/* If no parent, this is the last pane in a window. */ /* If no parent, this is the last pane in a window. */
lcparent = lc->parent; lcparent = lc->parent;
if (lcparent == NULL) { if (lcparent == NULL) {
if (lc->wp != NULL) if (*lcroot == lc)
*lcroot = NULL; *lcroot = NULL;
layout_free_cell(lc, 0); layout_free_cell(lc, 0);
return; return;
@@ -782,6 +768,49 @@ layout_free(struct window *w, int only_nodes)
layout_free_cell(w->layout_root, 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. */ /* Resize the entire layout after window resize. */
void void
layout_resize(struct window *w, u_int sx, u_int sy) layout_resize(struct window *w, u_int sx, u_int sy)
@@ -802,8 +831,11 @@ layout_resize(struct window *w, u_int sx, u_int sy)
* out proportionately - this should leave the layout fitting the new * out proportionately - this should leave the layout fitting the new
* window size. * window size.
*/ */
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) {
layout_clamp_floating_panes(w, sx, sy);
layout_fix_panes(w, NULL);
return; return;
}
xchange = sx - lc->g.sx; xchange = sx - lc->g.sx;
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT); xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
if (xchange < 0 && xchange < -xlimit) if (xchange < 0 && xchange < -xlimit)
@@ -833,6 +865,7 @@ layout_resize(struct window *w, u_int sx, u_int sy)
/* Fix cell offsets. */ /* Fix cell offsets. */
layout_fix_offsets(w); layout_fix_offsets(w);
layout_clamp_floating_panes(w, sx, sy);
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);
} }
@@ -860,7 +893,7 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
size = lc->g.sx; size = lc->g.sx;
else else
size = lc->g.sy; size = lc->g.sy;
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) if (layout_cell_is_last_tiled(lc))
change = size - new_size; change = size - new_size;
else else
change = new_size - size; change = new_size - size;
@@ -985,11 +1018,11 @@ layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
if (lcparent == NULL) if (lcparent == NULL)
return; return;
/* If this is the last cell, move back one. */ /* If this is the last tiled cell, move back one. */
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) { if (layout_cell_is_last_tiled(lc)) {
do lc = layout_cell_get_neighbour_dir(lc, 0);
lc = TAILQ_PREV(lc, layout_cells, entry); if (lc == NULL)
while (lc->flags & LAYOUT_CELL_FLOATING); return;
} }
layout_resize_layout(wp->window, lc, type, change, opposite); layout_resize_layout(wp->window, lc, type, change, opposite);
@@ -1007,22 +1040,22 @@ layout_resize_pane_grow(struct window *w, struct layout_cell *lc,
lcadd = lc; lcadd = lc;
/* Look towards the tail for a suitable cell for reduction. */ /* 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) { while (lcremove != NULL) {
size = layout_resize_check(w, lcremove, type); size = layout_resize_check(w, lcremove, type);
if (size > 0) if (size > 0)
break; break;
lcremove = TAILQ_NEXT(lcremove, entry); lcremove = layout_cell_get_neighbour_dir(lcremove, 1);
} }
/* If none found, look towards the head. */ /* If none found, look towards the head. */
if (opposite && lcremove == NULL) { if (opposite && lcremove == NULL) {
lcremove = TAILQ_PREV(lc, layout_cells, entry); lcremove = layout_cell_get_neighbour_dir(lc, 0);
while (lcremove != NULL) { while (lcremove != NULL) {
size = layout_resize_check(w, lcremove, type); size = layout_resize_check(w, lcremove, type);
if (size > 0) if (size > 0)
break; break;
lcremove = TAILQ_PREV(lcremove, layout_cells, entry); lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
} }
} }
if (lcremove == NULL) if (lcremove == NULL)
@@ -1050,13 +1083,13 @@ layout_resize_pane_shrink(struct window *w, struct layout_cell *lc,
size = layout_resize_check(w, lcremove, type); size = layout_resize_check(w, lcremove, type);
if (size != 0) if (size != 0)
break; break;
lcremove = TAILQ_PREV(lcremove, layout_cells, entry); lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
} while (lcremove != NULL); } while (lcremove != NULL);
if (lcremove == NULL) if (lcremove == NULL)
return (0); return (0);
/* And add onto the next cell (from the original cell). */ /* 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) if (lcadd == NULL)
return (0); return (0);
@@ -1650,7 +1683,10 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args,
return (NULL); return (NULL);
} }
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); if (window_active_pane_is_over_zoom(w))
window_push_zoom(w, 0, 1);
else
window_push_zoom(w, 1, (flags & SPAWN_ZOOM));
lc = layout_split_pane(wp, type, size, flags); lc = layout_split_pane(wp, type, size, flags);
if (lc == NULL) if (lc == NULL)
*cause = xstrdup("no space for a new pane"); *cause = xstrdup("no space for a new pane");
@@ -1677,8 +1713,10 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args,
return (NULL); return (NULL);
} }
if (flags & SPAWN_MODAL) if (flags & SPAWN_FLOATOVERZOOM)
window_push_modal_zoom(w); window_push_zoom(wp->window, 0, 1);
else if (window_active_pane_is_over_zoom(w))
window_push_zoom(wp->window, 0, 1);
else else
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM));
lcnew = layout_floating_pane(w, wp, &fg); lcnew = layout_floating_pane(w, wp, &fg);
@@ -1690,7 +1728,7 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
enum pane_lines lines, struct window *w, struct layout_geometry *lg, enum pane_lines lines, struct window *w, struct layout_geometry *lg,
char **cause) char **cause)
{ {
int sx, sy, ox, oy; int sx, sy, ox, oy, pad;
char *error = NULL; char *error = NULL;
sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx; sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx;
@@ -1739,12 +1777,20 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
} }
} }
if (!window_has_floating_panes(w)) {
w->last_new_pane_x = 0;
w->last_new_pane_y = 0;
}
if (ox == INT_MAX) { if (ox == INT_MAX) {
if (w->last_new_pane_x == 0) if (w->last_new_pane_x == 0)
ox = 4; ox = 4;
else { else {
if (lines != PANE_LINES_NONE)
pad = 1;
else
pad = 0;
ox = w->last_new_pane_x + 4; ox = w->last_new_pane_x + 4;
if (w->last_new_pane_x > w->sx) if (ox + sx + pad > (int)w->sx)
ox = 4; ox = 4;
} }
w->last_new_pane_x = ox; w->last_new_pane_x = ox;
@@ -1755,8 +1801,12 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
if (w->last_new_pane_y == 0) if (w->last_new_pane_y == 0)
oy = 2; oy = 2;
else { else {
if (lines != PANE_LINES_NONE)
pad = 1;
else
pad = 0;
oy = w->last_new_pane_y + 2; oy = w->last_new_pane_y + 2;
if (w->last_new_pane_y > w->sy) if (oy + sy + pad > (int)w->sy)
oy = 2; oy = 2;
} }
w->last_new_pane_y = oy; w->last_new_pane_y = oy;
+4 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: menu.c,v 1.69 2026/07/14 19:07:03 nicm Exp $ */ /* $OpenBSD: menu.c,v 1.70 2026/08/17 07:33:55 nicm Exp $ */
/* /*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -337,6 +337,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
struct cmdq_state *state; struct cmdq_state *state;
enum cmd_parse_status status; enum cmd_parse_status status;
char *error; char *error;
key_code key;
if (KEYC_IS_MOUSE(event->key)) { if (KEYC_IS_MOUSE(event->key)) {
/* /*
@@ -387,7 +388,8 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
name = menu->items[i].name; name = menu->items[i].name;
if (name == NULL || *name == '-') if (name == NULL || *name == '-')
continue; continue;
if ((event->key & ~KEYC_MASK_FLAGS) == menu->items[i].key) { key = (event->key & ~KEYC_MASK_FLAGS);
if (key == (menu->items[i].key & ~KEYC_MASK_FLAGS)) {
md->choice = i; md->choice = i;
goto chosen; goto chosen;
} }
+2 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: mode-tree.c,v 1.100 2026/07/14 19:07:03 nicm Exp $ */ /* $OpenBSD: mode-tree.c,v 1.101 2026/08/05 07:50:21 nicm Exp $ */
/* /*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -47,7 +47,7 @@ enum mode_tree_preview {
"#[acs]x" MODE_TREE_PREFIX_STYLE " }," \ "#[acs]x" MODE_TREE_PREFIX_STYLE " }," \
"#{mode_tree_repeat}}" \ "#{mode_tree_repeat}}" \
"#{?mode_tree_branch," \ "#{?mode_tree_branch," \
"#[acs]#{?mode_tree_last,mq,tq}" MODE_TREE_PREFIX_STYLE "> ,}" \ "#[acs]#{?mode_tree_last,mq,tq}+" MODE_TREE_PREFIX_STYLE " ,}" \
"#{?mode_tree_has_children," \ "#{?mode_tree_has_children," \
"#{?mode_tree_expanded,#[fg=themered]-" MODE_TREE_PREFIX_STYLE " ," \ "#{?mode_tree_expanded,#[fg=themered]-" MODE_TREE_PREFIX_STYLE " ," \
"#[fg=themegreen]+" MODE_TREE_PREFIX_STYLE " }," \ "#[fg=themegreen]+" MODE_TREE_PREFIX_STYLE " }," \
+4 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: monitor.c,v 1.7 2026/07/27 19:15:58 nicm Exp $ */ /* $OpenBSD: monitor.c,v 1.8 2026/09/07 10:15:25 nicm Exp $ */
/* /*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -614,8 +614,10 @@ monitor_parse(const char *value, char **name, enum monitor_type *type, int *id,
*type = MONITOR_ALL_WINDOWS; *type = MONITOR_ALL_WINDOWS;
else if (sscanf(what, "@%d", id) == 1 && *id >= 0) else if (sscanf(what, "@%d", id) == 1 && *id >= 0)
*type = MONITOR_WINDOW; *type = MONITOR_WINDOW;
else else if (*what == '\0')
*type = MONITOR_SESSION; *type = MONITOR_SESSION;
else
goto fail;
*name = xstrdup(copy); *name = xstrdup(copy);
*format = xstrdup(split); *format = xstrdup(split);
+16 -4
View File
@@ -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.245 2026/09/10 11:02:18 nicm Exp $ */
/* /*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -91,7 +91,7 @@ static const char *options_table_window_size_list[] = {
"largest", "smallest", "manual", "latest", NULL "largest", "smallest", "manual", "latest", NULL
}; };
static const char *options_table_remain_on_exit_list[] = { static const char *options_table_remain_on_exit_list[] = {
"off", "on", "failed", "key", NULL "off", "on", "failed", "key", "failed-key", NULL
}; };
static const char *options_table_destroy_unattached_list[] = { static const char *options_table_destroy_unattached_list[] = {
"off", "on", "keep-last", "keep-group", NULL "off", "on", "keep-last", "keep-group", NULL
@@ -305,6 +305,16 @@ const struct options_table_entry options_table[] = {
"When this is reached, the oldest buffer is deleted." "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", { .name = "command-alias",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER, .scope = OPTIONS_TABLE_SERVER,
@@ -1240,7 +1250,8 @@ const struct options_table_entry options_table[] = {
* underscore. * underscore.
*/ */
.default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", .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. */ /* Window options. */
@@ -1687,7 +1698,8 @@ const struct options_table_entry options_table[] = {
.choices = options_table_remain_on_exit_list, .choices = options_table_remain_on_exit_list,
.default_num = 0, .default_num = 0,
.text = "Whether panes should remain ('on'), remain until a key is " .text = "Whether panes should remain ('on'), remain until a key is "
"pressed ('key') or be automatically killed ('off' or " "pressed after any exit ('key') or after a failure "
"('failed-key'), or be automatically killed ('off' or "
"'failed') when the program inside exits." "'failed') when the program inside exits."
}, },
+2 -1
View File
@@ -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> * Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -79,6 +79,7 @@ popup_free(struct popup_data *pd)
if (pd->job != NULL) if (pd->job != NULL)
job_free(pd->job); job_free(pd->job);
if (pd->ictx != NULL)
input_free(pd->ictx); input_free(pd->ictx);
free(pd->r.ranges); free(pd->r.ranges);
+3 -3
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: proc.c,v 1.31 2026/06/08 21:38:19 nicm Exp $ */ /* $OpenBSD: proc.c,v 1.32 2026/08/04 13:16:03 claudio Exp $ */
/* /*
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -79,7 +79,7 @@ static void
proc_event_cb(__unused int fd, short events, void *arg) proc_event_cb(__unused int fd, short events, void *arg)
{ {
struct tmuxpeer *peer = arg; struct tmuxpeer *peer = arg;
ssize_t n; int n;
struct imsg imsg; struct imsg imsg;
if (!(peer->flags & PEER_BAD) && (events & EV_READ)) { if (!(peer->flags & PEER_BAD) && (events & EV_READ)) {
@@ -88,7 +88,7 @@ proc_event_cb(__unused int fd, short events, void *arg)
return; return;
} }
for (;;) { for (;;) {
if ((n = imsg_get(&peer->ibuf, &imsg)) == -1) { if ((n = imsgbuf_get(&peer->ibuf, &imsg)) == -1) {
peer->dispatchcb(NULL, peer->arg); peer->dispatchcb(NULL, peer->arg);
return; return;
} }
+6 -4
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: prompt.c,v 1.5 2026/07/13 10:29:17 nicm Exp $ */ /* $OpenBSD: prompt.c,v 1.7 2026/09/09 07:53:03 nicm Exp $ */
/* /*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1334,7 +1334,7 @@ process_key:
if (pr->index != size) { if (pr->index != size) {
memmove(pr->buffer + pr->index, memmove(pr->buffer + pr->index,
pr->buffer + pr->index + 1, pr->buffer + pr->index + 1,
(size + 1 - pr->index) * (size - pr->index) *
sizeof *pr->buffer); sizeof *pr->buffer);
goto changed; goto changed;
} }
@@ -1493,9 +1493,11 @@ append_key:
utf8_set(&tmp, key); utf8_set(&tmp, key);
if (key <= 0x1f || key == 0x7f) if (key <= 0x1f || key == 0x7f)
tmp.width = 2; tmp.width = 2;
} else if (KEYC_IS_UNICODE(key)) } else if (KEYC_IS_UNICODE(key)) {
utf8_to_data(key, &tmp); utf8_to_data(key, &tmp);
else if (tmp.size == 0)
return (PROMPT_KEY_HANDLED);
} else
return (PROMPT_KEY_HANDLED); return (PROMPT_KEY_HANDLED);
pr->buffer = xreallocarray(pr->buffer, size + 2, pr->buffer = xreallocarray(pr->buffer, size + 2,
+23 -20
View File
@@ -1,33 +1,17 @@
TESTS!= echo *.sh TESTS!= echo *.sh
LOGDIR=logs LOGDIR=logs
.PHONY: all .PHONY: all prepare $(TESTS)
.NOTPARALLEL: all
all: all: prepare $(TESTS)
@mkdir -p "$(LOGDIR)"; \ @failed=0; failures=; \
rm -f "$(LOGDIR)"/*.log; \
failed=0; failures=; \
for test in $(TESTS); do \ for test in $(TESTS); do \
base=$${test##*/}; \ base=$${test##*/}; \
log="$(LOGDIR)/$${base%.sh}.log"; \ log="$(LOGDIR)/$${base%.sh}.log"; \
rm -f "$$log"; \ if [ -f "$$log" ]; then \
printf '%-40s ' "$$test"; \
start=$$(date +%s); \
env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
sh -x "$$test" >"$$log" 2>&1; \
if [ $$? -eq 0 ]; then \
end=$$(date +%s); \
rm -f "$$log"; \
echo "PASS ($$((end - start))s)"; \
else \
end=$$(date +%s); \
echo "FAIL ($$((end - start))s)"; \
echo " log: $$log"; \
failed=1; \ failed=1; \
failures="$$failures $$test"; \ failures="$$failures $$test"; \
fi; \ fi; \
sleep 1; \
done; \ done; \
if [ "$$failed" -ne 0 ]; then \ if [ "$$failed" -ne 0 ]; then \
echo; \ echo; \
@@ -39,3 +23,22 @@ all:
rmdir "$(LOGDIR)" 2>/dev/null || true; \ rmdir "$(LOGDIR)" 2>/dev/null || true; \
fi; \ fi; \
exit $$failed exit $$failed
prepare:
@mkdir -p "$(LOGDIR)"
@rm -f "$(LOGDIR)"/*.log
$(TESTS): prepare
@base="$@"; base=$${base##*/}; \
log="$(LOGDIR)/$${base%.sh}.log"; \
start=$$(date +%s); \
if env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
sh -x "$@" >"$$log" 2>&1; then \
end=$$(date +%s); \
rm -f "$$log"; \
printf '%-40s PASS (%ss)\n' "$@" "$$((end - start))"; \
else \
end=$$(date +%s); \
printf '%-40s FAIL (%ss)\n log: %s\n' \
"$@" "$$((end - start))" "$$log"; \
fi
+32 -2
View File
@@ -2,8 +2,9 @@
# monitor-activity, monitor-bell and monitor-silence: both the # monitor-activity, monitor-bell and monitor-silence: both the
# alert-activity, alert-bell and alert-silence hooks and the winlink alert # alert-activity, alert-bell and alert-silence hooks and the winlink alert
# flags (window_activity_flag, window_bell_flag, window_silence_flag and # flags (window_activity_flag, window_bell_flag, window_silence_flag,
# the #, !, ~ characters in window_flags). The sessions are detached so # their session equivalents and the #, !, ~ characters in window_flags).
# The sessions are detached so
# alert flags are set even on the current window; the *-action options # alert flags are set even on the current window; the *-action options
# still decide whether the hooks fire. Panes run cat: activity is # still decide whether the hooks fire. Panes run cat: activity is
# generated by the tty echo of send-keys and a bell by sending a BEL and # generated by the tty echo of send-keys and a bell by sending a BEL and
@@ -111,6 +112,24 @@ flags_have()
esac esac
} }
assert_session_flag()
{
flag="#{session_${1}_flag}"
expected=$2
# Only mon exists here. Check both window contexts and list-sessions.
for target in mon:w0 "mon:$3"; do
value=$($TMUX display -pt "$target" "$flag") ||
fail "display $flag failed"
[ "$value" = "$expected" ] ||
fail "expected $flag for $target to be '$expected' but got '$value'"
done
value=$($TMUX list-sessions -F "$flag") ||
fail "list-sessions $flag failed"
[ "$value" = "$expected" ] ||
fail "expected list-sessions $flag to be '$expected' but got '$value'"
}
flags_lack() flags_lack()
{ {
target=$1 target=$1
@@ -150,10 +169,12 @@ $TMUX set-hook -g alert-silence \
# alert-bell and sets the bell flag, shown as ! in window_flags. # alert-bell and sets the bell flag, shown as ! in window_flags.
$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed" $TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed"
assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0 assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0
assert_session_flag bell 0 bellw
flags_lack mon:bellw '!' flags_lack mon:bellw '!'
bell mon:bellw bell mon:bellw
wait_for @log '|alert-bell:mon:bellw' wait_for @log '|alert-bell:mon:bellw'
wait_for_fmt mon:bellw '#{window_bell_flag}' 1 wait_for_fmt mon:bellw '#{window_bell_flag}' 1
assert_session_flag bell 1 bellw
flags_have mon:bellw '!' flags_have mon:bellw '!'
# Bells are not deduplicated: a second bell fires the hook again even # Bells are not deduplicated: a second bell fires the hook again even
@@ -164,6 +185,7 @@ wait_for @log '|alert-bell:mon:bellw|alert-bell:mon:bellw'
# Selecting the window clears the alert flags. # Selecting the window clears the alert flags.
$TMUX selectw -t mon:bellw || fail "select-window bellw failed" $TMUX selectw -t mon:bellw || fail "select-window bellw failed"
wait_for_fmt mon:bellw '#{window_bell_flag}' 0 wait_for_fmt mon:bellw '#{window_bell_flag}' 0
assert_session_flag bell 0 bellw
flags_lack mon:bellw '!' flags_lack mon:bellw '!'
$TMUX selectw -t mon:w0 || fail "select-window w0 failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
@@ -173,6 +195,7 @@ $TMUX set -g @log '' || fail "reset @log failed"
bell mon:w0 bell mon:w0
wait_for @log '|alert-bell:mon:w0' wait_for @log '|alert-bell:mon:w0'
wait_for_fmt mon:w0 '#{window_bell_flag}' 1 wait_for_fmt mon:w0 '#{window_bell_flag}' 1
assert_session_flag bell 1 bellw
$TMUX selectw -t mon:bellw || fail "select-window bellw failed" $TMUX selectw -t mon:bellw || fail "select-window bellw failed"
$TMUX selectw -t mon:w0 || fail "select-window w0 failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
wait_for_fmt mon:w0 '#{window_bell_flag}' 0 wait_for_fmt mon:w0 '#{window_bell_flag}' 0
@@ -199,6 +222,7 @@ $TMUX neww -d -t mon: -n actw cat || fail "new-window actw failed"
$TMUX set -g @log '' || fail "reset @log failed" $TMUX set -g @log '' || fail "reset @log failed"
activity mon:actw activity mon:actw
assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0 assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0
assert_session_flag activity 0 actw
assert_unchanged @log '' assert_unchanged @log ''
# With monitor-activity on, output fires alert-activity and sets the # With monitor-activity on, output fires alert-activity and sets the
@@ -208,6 +232,7 @@ $TMUX set -wt mon:actw monitor-activity on ||
activity mon:actw activity mon:actw
wait_for @log '|alert-activity:mon:actw' wait_for @log '|alert-activity:mon:actw'
wait_for_fmt mon:actw '#{window_activity_flag}' 1 wait_for_fmt mon:actw '#{window_activity_flag}' 1
assert_session_flag activity 1 actw
flags_have mon:actw '#' flags_have mon:actw '#'
# While the flag is set further activity does not fire the hook again. # While the flag is set further activity does not fire the hook again.
@@ -234,6 +259,7 @@ exec 3>"$OUT/fifo"
wait_for_fmt mon: '#{session_attached}' 1 wait_for_fmt mon: '#{session_attached}' 1
$TMUX selectw -t mon:actw || fail "select-window actw failed" $TMUX selectw -t mon:actw || fail "select-window actw failed"
wait_for_fmt mon:actw '#{window_activity_flag}' 0 wait_for_fmt mon:actw '#{window_activity_flag}' 0
assert_session_flag activity 0 actw
flags_lack mon:actw '#' flags_lack mon:actw '#'
$TMUX selectw -t mon:w0 || fail "select-window w0 failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
exec 3>&- exec 3>&-
@@ -250,6 +276,7 @@ $TMUX set -wt mon:w0 monitor-activity on ||
$TMUX set -g @log '' || fail "reset @log failed" $TMUX set -g @log '' || fail "reset @log failed"
activity mon:w0 activity mon:w0
wait_for_fmt mon:w0 '#{window_activity_flag}' 1 wait_for_fmt mon:w0 '#{window_activity_flag}' 1
assert_session_flag activity 1 actw
assert_unchanged @log '' assert_unchanged @log ''
$TMUX set -wut mon:w0 monitor-activity || $TMUX set -wut mon:w0 monitor-activity ||
fail "unset monitor-activity w0 failed" fail "unset monitor-activity w0 failed"
@@ -273,16 +300,19 @@ $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
# running. # running.
$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed" $TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed"
assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0 assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0
assert_session_flag silence 0 silw
$TMUX set -g @log '' || fail "reset @log failed" $TMUX set -g @log '' || fail "reset @log failed"
$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed" $TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed"
wait_for @log '|alert-silence:mon:silw' wait_for @log '|alert-silence:mon:silw'
wait_for_fmt mon:silw '#{window_silence_flag}' 1 wait_for_fmt mon:silw '#{window_silence_flag}' 1
assert_session_flag silence 1 silw
flags_have mon:silw '~' flags_have mon:silw '~'
assert_unchanged @log '|alert-silence:mon:silw' assert_unchanged @log '|alert-silence:mon:silw'
$TMUX set -wt mon:silw monitor-silence 0 || $TMUX set -wt mon:silw monitor-silence 0 ||
fail "reset monitor-silence failed" fail "reset monitor-silence failed"
$TMUX selectw -t mon:silw || fail "select-window silw failed" $TMUX selectw -t mon:silw || fail "select-window silw failed"
wait_for_fmt mon:silw '#{window_silence_flag}' 0 wait_for_fmt mon:silw '#{window_silence_flag}' 0
assert_session_flag silence 0 silw
flags_lack mon:silw '~' flags_lack mon:silw '~'
$TMUX selectw -t mon:w0 || fail "select-window w0 failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
+22 -1
View File
@@ -28,7 +28,7 @@ $TMUX kill-server 2>/dev/null
TMP=$(mktemp) TMP=$(mktemp)
TMP2=$(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... # check_ok $cmd...
# #
@@ -270,6 +270,27 @@ if [ $? -eq 0 ]; then
exit 1 exit 1
fi 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. # save-buffer - writes to stdout and load-buffer - reads from stdin.
out=$($TMUX save-buffer -b sb -) out=$($TMUX save-buffer -b sb -)
if [ "$out" != "data" ]; then if [ "$out" != "data" ]; then
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
# capture-pane -I line timestamps
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMP=$(mktemp)
trap '$TMUX kill-server 2>/dev/null; rm -f "$TMP"' 0 1 15
$TMUX kill-server 2>/dev/null
before=$(date +%s)
$TMUX new-session -d -x 40 -y 5 'seq 1 12; sleep 10' || exit 1
sleep 1
after=$(date +%s)
$TMUX capture-pane -pILF -S - -E - >"$TMP" || exit 1
awk -v before="$before" -v after="$after" '
$1 !~ /^-?[0-9]+$/ || $2 !~ /^[0-9]+$/ { exit 1 }
$4 == "1" {
if ($2 < before || $2 > after)
exit 1
history = 1
}
$4 == "9" {
if ($2 != 0)
exit 1
visible = 1
}
END { if (!history || !visible) exit 1 }
' "$TMP" || {
cat "$TMP"
exit 1
}
exit 0
Executable → Regular
View File
+88
View File
@@ -290,4 +290,92 @@ wait_count ': G4' 7
fail "window 1 of zzz not killed" fail "window 1 of zzz not killed"
exit_mode q 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 exit 0
+96
View 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
View 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
View 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
+53
View File
@@ -0,0 +1,53 @@
#!/bin/sh
# Width cache ranges must include their endpoint without overflowing wchar_t.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
SERVER=
WATCHDOG=
cleanup()
{
if [ -n "$WATCHDOG" ]; then
kill "$WATCHDOG" 2>/dev/null
wait "$WATCHDOG" 2>/dev/null
fi
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
}
trap cleanup 0
trap 'exit 1' 1 2 15
$TMUX new-session -d 'exec sleep 60' || exit 1
SERVER=$($TMUX display-message -p '#{pid}') || exit 1
# kill-server cannot stop a server stuck rebuilding the width cache.
(
sleep 5
kill -9 "$SERVER" 2>/dev/null
) &
WATCHDOG=$!
$TMUX set -g codepoint-widths 'U+1F600=2' || exit 1
# Cover both signed and unsigned 32-bit wchar_t. Values above WCHAR_MAX
# are ignored by the parser on platforms where they cannot be represented.
for value in U+7FFFFFFF=1 U+7FFFFFFE-U+7FFFFFFF=2 \
U+FFFFFFFF=1 U+FFFFFFFE-U+FFFFFFFF=2; do
$TMUX set -g codepoint-widths "$value" || exit 1
[ "$($TMUX display-message -p alive)" = alive ] || exit 1
done
# Check that an ordinary range still includes both endpoints and stops there.
$TMUX set -g codepoint-widths 'U+03B1-U+03B3=2' || exit 1
$TMUX set -g @text 'αβγδ' || exit 1
[ "$($TMUX display-message -p '#{w:@text}')" = 7 ] || exit 1
$TMUX set -gu codepoint-widths || exit 1
[ "$($TMUX display-message -p '#{w:@text}')" = 4 ] || exit 1
$TMUX kill-server || exit 1
SERVER=
exit 0
+150
View 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
+77
View File
@@ -0,0 +1,77 @@
#!/bin/sh
# A control client whose terminal has stopped accepting output (for example
# its ssh connection or terminal emulator died) never drains the output that
# server_client_check_exit waits on before sending MSG_EXIT, and may then
# never close its socket either. This must not prevent the server from
# exiting: after kill-server the server previously stayed alive forever with
# server_exit set, closing every new connection immediately, so every new
# client failed with "server exited unexpectedly" until the server was killed
# by hand. The server now bounds the exit handshake: it discards output it
# can never deliver and drops the client if it still does not close.
#
# The stalled terminal is simulated by a fifo whose read end is held open but
# never read.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null
DIR=$(mktemp -d)
FIFO=$DIR/fifo
SERVER=
mkfifo "$FIFO" || exit 1
cleanup() {
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
$TMUX kill-server 2>/dev/null
exec 8<&- 2>/dev/null
rm -rf "$DIR"
}
trap cleanup 0 1 15
# A detached session whose pane floods printable output forever.
$TMUX -f/dev/null new -d -x 80 -y 24 -s rt 'cat /dev/zero | tr "\000" x' || exit 1
SERVER=$($TMUX display -pt rt '#{pid}')
# Attach a control client with output down the fifo; stdin held open by sleep
# so it stays attached. The fifo fills and is never read, so a backlog forms
# in the server that can never be delivered.
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >"$FIFO" 2>&1 &
exec 8<"$FIFO"
n=0
while [ $n -lt 50 ]; do
$TMUX lsc -F '#{client_name}' 2>/dev/null | grep -q . && break
sleep 0.1
n=$((n + 1))
done
$TMUX lsc -F '#{client_name}' 2>/dev/null | grep -q . ||
{ echo "control client did not attach"; exit 1; }
# Let the pane flood fill the fifo so the client's output is stuck.
sleep 2
# Ask the server to exit. This sets CLIENT_EXIT on the stalled client, whose
# output can never drain.
$TMUX kill-server 2>/dev/null
# The server must exit within the exit timeout (10 seconds) plus slack,
# rather than staying in limbo forever rejecting new clients.
n=0
while [ $n -lt 100 ]; do
if ! kill -0 "$SERVER" 2>/dev/null; then
SERVER=
exit 0
fi
sleep 0.2
n=$((n + 1))
done
echo "server did not exit after kill-server; new clients see:"
$TMUX ls 2>&1
exit 1
+1 -1
View File
@@ -19,7 +19,7 @@ PATH=/bin:/usr/bin
TERM=screen TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest" TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
DIR=$(mktemp -d) DIR=$(mktemp -d)
+123
View File
@@ -0,0 +1,123 @@
#!/bin/sh
# Check that control client flags are available in every format expansion path
# used to produce layouts. The layout strings themselves are tested separately.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
LANG=C.UTF-8
export PATH TERM LC_ALL LANG
[ -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
CFG=$DIR/flags.conf
PID=
fail()
{
echo "$*" >&2
exit 1
}
cleanup()
{
exec 3>&-
[ -n "$PID" ] && kill "$PID" 2>/dev/null
$TMUX kill-server 2>/dev/null
rm -rf "$DIR"
}
trap cleanup EXIT
wait_for()
{
pattern=$1
i=0
while [ "$i" -lt 50 ]; do
grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0
if [ -n "$PID" ] && ! kill -0 "$PID" 2>/dev/null; then
fail "control client exited waiting for: $pattern"
fi
sleep 0.1
i=$((i + 1))
done
fail "missing: $pattern"
}
send()
{
printf '%s\n' "$*" >&3
}
check_commands()
{
name=$1
expected=$2
match='#{m:*new-layouts*,#{client_flags}}'
send "display-message -p 'DISPLAY-$name $match'"
wait_for "DISPLAY-$name $expected"
send "list-panes -F 'PANES-$name $match'"
wait_for "PANES-$name $expected"
send "list-windows -F 'WINDOWS-$name $match'"
wait_for "WINDOWS-$name $expected"
send "list-sessions -F 'SESSIONS-$name $match'"
wait_for "SESSIONS-$name $expected"
send "list-clients -F 'CLIENTS-$name $match'"
wait_for "CLIENTS-$name $expected"
}
check_config()
{
name=$1
expected=$2
send "source-file '$CFG'"
send "display-message -p 'CONFIG-$name #{@config-new-layouts}'"
wait_for "CONFIG-$name $expected"
}
cat >"$CFG" <<'EOF'
%if #{m:*new-layouts*,#{client_flags}}
set-option -g @config-new-layouts 1
%else
set-option -g @config-new-layouts 0
%endif
EOF
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -s layouts -x 80 -y 24 || exit 1
$TMUX split-window -h -t layouts: || exit 1
mkfifo "$FIFO" || exit 1
: >"$OUT"
$TMUX -C attach-session -t layouts <"$FIFO" >"$OUT" 2>&1 &
PID=$!
exec 3>"$FIFO"
send 'display-message -p READY'
wait_for READY
check_commands OFF 0
check_config OFF 0
send 'refresh-client -f new-layouts'
check_commands ON 1
check_config ON 1
send 'refresh-client -f !new-layouts'
check_commands OFF-AGAIN 0
check_config OFF-AGAIN 0
# The list commands also run for unattached command clients. Supplying that
# client to formats must not make the session loop dereference a NULL session.
$TMUX list-sessions -F '#{S:all,active}' >/dev/null ||
fail "session loop failed for unattached command client"
$TMUX has-session -t layouts || fail "server exited"
exit 0
+63
View 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
View 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
+5 -2
View File
@@ -30,8 +30,11 @@ killw
EOF EOF
sleep 1 sleep 1
$TMUX has || exit 1 $TMUX has || exit 1
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1 # Use a control client to request legacy layouts, keeping only pane lines
cat <<EOF|cmp -s $TMP - || exit 1 # from the control protocol output.
$TMUX -C lsp -aF '#{pane_id} #{window_layout}' |
grep '^%[0-9]' >$TMP || exit 1
cat <<EOF|cmp $TMP - || exit 1
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3] %0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3] %3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4] %2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
+1 -1
View File
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
TERM=screen TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest" TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
FIFO=$(mktemp -u) FIFO=$(mktemp -u)
+158
View File
@@ -0,0 +1,158 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
LANG=C.UTF-8
export TERM LC_ALL LANG
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMPDIR=$(mktemp -d)
IN="$TMPDIR/in"
OUT="$TMPDIR/out"
BADCFG="$TMPDIR/bad.conf"
PID=
cleanup()
{
exec 3>&- 2>/dev/null
[ -n "$PID" ] && kill "$PID" 2>/dev/null
$TMUX kill-server 2>/dev/null
rm -rf "$TMPDIR"
}
trap cleanup EXIT
fail()
{
echo "$1" >&2
[ -s "$OUT" ] && cat "$OUT" >&2
exit 1
}
wait_for()
{
pattern=$1
timeout=${2:-60}
i=0
while [ "$i" -lt "$timeout" ]; do
grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0
sleep 0.1
i=$((i + 1))
done
fail "missing: $pattern"
}
wait_for_count()
{
pattern=$1
expected=$2
timeout=${3:-60}
i=0
while [ "$i" -lt "$timeout" ]; do
count=$(grep -F -c -- "$pattern" "$OUT" 2>/dev/null)
[ "$count" -ge "$expected" ] && return 0
sleep 0.1
i=$((i + 1))
done
fail "missing count $expected for: $pattern"
}
send()
{
printf '%s\n' "$1" >&3 || fail "failed to send: $1"
}
# A notification is a standalone protocol message. It must never be part of
# the response between the real %begin and matching %end or %error for a
# command. Ignore guard-looking command output while a real guard is open.
check_guards()
{
awk '
function notification(line) {
return line ~ /^%(sessions-changed|session-changed |unlinked-window-add )/ ||
line ~ /^%(pause |continue |config-error )/
}
!open && $0 ~ /^%begin [0-9]+ [0-9]+ [0-9]+$/ {
open = 1
time = $2
number = $3
flags = $4
next
}
open && ($0 == "%end " time " " number " " flags ||
$0 == "%error " time " " number " " flags) {
open = 0
next
}
notification($0) {
seen[$1]++
if (open) {
print "notification inside guard: " $0 > "/dev/stderr"
bad = 1
}
}
END {
if (open) {
print "unterminated command guard" > "/dev/stderr"
bad = 1
}
if (seen["%sessions-changed"] < 2 ||
seen["%session-changed"] < 1 ||
seen["%unlinked-window-add"] < 2 ||
seen["%pause"] < 1 || seen["%continue"] < 1 ||
seen["%config-error"] < 1) {
print "missing expected notification" > "/dev/stderr"
bad = 1
}
exit bad
}' "$OUT" || fail "bad control protocol notification ordering"
}
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -s guard -x 80 -y 24 || exit 1
pane=$($TMUX display-message -p -t guard:0.0 '#{pane_id}') || exit 1
mkfifo "$IN" || exit 1
: >"$OUT"
$TMUX -C attach-session -t guard <"$IN" >"$OUT" 2>&1 &
PID=$!
exec 3>"$IN"
# Attaching generates a session notification synchronously from inside the
# attach command. It must follow the closing guard.
wait_for '%session-changed '
# Guard-looking output is arbitrary command output, not protocol state. Make
# new-session both generate notifications and print an unmatched fake %begin;
# it must not leave those notifications permanently deferred.
send "set-option -g @fake-begin '%begin 1 2 3'"
send "new-session -d -P -F '#{@fake-begin}' -s fake-begin"
wait_for_count '%sessions-changed' 1
# Likewise, fake %end output from a command that generates multiple
# notifications must not flush them before the command actually ends.
send "set-option -g @fake-end '%end 1 2 3'"
send "new-session -d -P -F '#{@fake-end}' -s fake-end"
wait_for_count '%sessions-changed' 2
# Exercise notification paths outside control-notify.c.
send "refresh-client -A '$pane:pause'"
wait_for "%pause $pane"
send "refresh-client -A '$pane:continue'"
wait_for "%continue $pane"
printf '%s\n' 'not-a-command' >"$BADCFG"
send "source-file '$BADCFG'"
wait_for '%config-error '
# A final command guarantees all earlier command guards have completed before
# the transcript is checked.
send "display-message -p 'guard-check-done'"
wait_for 'guard-check-done'
check_guards
exit 0
+253
View 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
+81
View 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
+1
View File
@@ -117,6 +117,7 @@ $TMUX send-keys -X copy-selection
# Test that vi cursor movement does not stop on the padding cell of a wide # Test that vi cursor movement does not stop on the padding cell of a wide
# character at the end of a line. # character at the end of a line.
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
sleep 1
$TMUX new -d -x20 -y5 \ $TMUX new -d -x20 -y5 \
"printf 'abc中\nxyz\n'; exec cat" || exit 1 "printf 'abc中\nxyz\n'; exec cat" || exit 1
$TMUX set-window-option -g mode-keys vi $TMUX set-window-option -g mode-keys vi
+163
View 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
+5 -3
View File
@@ -153,11 +153,13 @@ wait_format "$p0" '#{pane_in_mode}' '0'
wait_option @picked "$p1" wait_option @picked "$p1"
# Commands after display-panes run immediately while the mode remains. # Commands after display-panes run immediately while the mode remains.
# Record the mode in the same command queue so client startup cannot race
# the 500ms timeout.
$TMUX set -g @after none || fail "set @after failed" $TMUX set -g @after none || fail "set @after failed"
$TMUX display-panes -Nd 500 -t "$p0" \; set -g @after fast || $TMUX display-panes -Nd 500 -t "$p0" \; \
set -gF -t "$p0" @after '#{pane_mode}' ||
fail "display-panes immediate command failed" fail "display-panes immediate command failed"
wait_option @after fast wait_option @after 'panes-mode'
wait_format "$p0" '#{pane_mode}' 'panes-mode'
wait_format "$p0" '#{pane_in_mode}' '0' wait_format "$p0" '#{pane_in_mode}' '0'
# Existing zoom is restored on exit. # Existing zoom is restored on exit.
+118
View 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
+213
View File
@@ -199,5 +199,218 @@ must_fail $TMUX resize-pane -t "$id" -x 0
must_fail $TMUX resize-pane -t "$id" -y 0 must_fail $TMUX resize-pane -t "$id" -y 0
$TMUX kill-pane -t "$id" || exit 1 $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
# --- 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 $TMUX kill-server 2>/dev/null
exit 0 exit 0
+158
View File
@@ -0,0 +1,158 @@
#!/bin/sh
# Moving, resizing and reordering an over-zoom float must leave the tiled
# pane zoomed, and the changes must survive an explicit unzoom.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
trap '$TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
fail()
{
echo "$*" >&2
exit 1
}
run()
{
$TMUX "$@" || fail "failed: $*"
}
check()
{
got=$(run display-message -p -t "$1" "$2") || exit 1
[ "$got" = "$3" ] || fail "$1 $2: got '$got', expected '$3'"
}
check_zoom()
{
check "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
check "$float" '#{pane_floating_flag}:#{pane_active}' "1:$active"
check "$base" '#{@unzoomed}' ''
}
run new-session -d -x 80 -y 24
base=$(run display-message -p '#{pane_id}') || exit 1
other=$(run split-window -dPF '#{pane_id}') || exit 1
layout=$(run display-message -p '#{window_layout}') || exit 1
run set-hook -g window-unzoomed 'set -g @unzoomed 1'
# Cover creation before and after zoom, with both the float and the tiled
# pane active. Modal panes use the same over-zoom flag.
for mode in before after detached modal; do
if [ "$mode" != before ]; then
run resize-pane -Z -t "$base"
fi
case "$mode" in
detached) flags=-Ad; active=0 ;;
modal) flags=-O; active=1 ;;
*) flags=-A; active=1 ;;
esac
float=$(run new-pane "$flags" -PF '#{pane_id}' -t "$base" \
-x 20 -y 8 -X 8 -Y 3 '') || exit 1
if [ "$mode" = before ]; then
run resize-pane -Z -t "$base"
fi
run set -gu @unzoomed
check_zoom
run move-pane -t "$float" -D
check "$float" '#{pane_top}' 5
check_zoom
run move-pane -t "$float" -U 2 -R 3 -L 1
check "$float" '#{pane_left}:#{pane_top}' '11:3'
check_zoom
run move-pane -t "$float" -X 25% -Y 25%
check "$float" '#{pane_left}:#{pane_top}' '21:7'
check_zoom
run move-pane -t "$float" -P bottom-right
check "$float" '#{pane_left}:#{pane_top}' '61:17'
check_zoom
run resize-pane -t "$float" -x 30 -y 10
check "$float" '#{pane_width}:#{pane_height}' '28:8'
check_zoom
run resize-pane -t "$float" -U 1 -L 2
check "$float" '#{pane_left}:#{pane_top}:#{pane_width}:#{pane_height}' \
'59:16:30:9'
check_zoom
run move-pane -t "$float" -P centre
check "$float" '#{pane_left}:#{pane_top}' '25:7'
check_zoom
# Failed commands must not change zoom either.
for command in 'move-pane -D invalid' 'move-pane -P invalid' \
'move-pane -z invalid' 'resize-pane -x invalid' \
'resize-pane -D invalid'; do
$TMUX $command -t "$float" 2>/dev/null &&
fail "unexpected success: $command"
check_zoom
done
run resize-pane -Z -t "$base"
check "$float" '#{pane_left}:#{pane_top}:#{pane_width}:#{pane_height}' \
'25:7:30:9'
run kill-pane -t "$float"
check "$base" '#{window_layout}' "$layout"
done
# Put an ordinary (hidden while zoomed) float between two over-zoom floats
# in the stacking order. Reordering must skip it when counting visible
# positions, but keep all floats ahead of the tiled panes after unzoom.
back=$(run new-pane -AdPF '#{pane_id}' -t "$base" '') || exit 1
hidden=$(run new-pane -dPF '#{pane_id}' -t "$base" '') || exit 1
float=$(run new-pane -AdPF '#{pane_id}' -t "$base" '') || exit 1
active=0
run resize-pane -Z -t "$base"
run set -gu @unzoomed
for position in backward back forward-loop; do
run move-pane -t "$float" -P front
run move-pane -t "$float" -P "$position"
check "$float" '#{pane_z}' 1
check "$back" '#{pane_z}' 0
check_zoom
done
for position in forward front backward-loop; do
run move-pane -t "$float" -P back
run move-pane -t "$float" -P "$position"
check "$float" '#{pane_z}' 0
check "$back" '#{pane_z}' 1
check_zoom
done
for z in 1 0 99; do
run move-pane -t "$float" -z "$z"
want=$z
[ "$z" = 99 ] && want=1
check "$float" '#{pane_z}' "$want"
check_zoom
done
run resize-pane -Z -t "$base"
check "$float" '#{pane_z}' 2
check "$base" '#{pane_z}' 4
run kill-pane -t "$hidden"
run kill-pane -t "$back"
# Explicit zoom toggling and operations on tiled panes retain their
# existing behaviour. The over-zoom flag alone does not make a pane a float:
# zooming the float itself gives it a tiled cell until unzoom.
run resize-pane -Z -t "$float"
check "$float" '#{pane_zoomed_flag}:#{pane_floating_flag}' '1:0'
run resize-pane -t "$float" -x 25
check "$float" '#{window_zoomed_flag}:#{pane_floating_flag}:#{pane_width}' \
'0:1:23'
run resize-pane -Z -t "$base"
run resize-pane -t "$other" -D 1
check "$base" '#{window_zoomed_flag}' 0
# A hidden ordinary float must still be unzoomed before resizing it.
hidden=$(run new-pane -dPF '#{pane_id}' -t "$base" '') || exit 1
run resize-pane -Z -t "$base"
run resize-pane -t "$hidden" -x 25
check "$hidden" '#{window_zoomed_flag}:#{pane_floating_flag}:#{pane_width}' \
'0:1:23'
exit 0
+2
View File
@@ -7,6 +7,8 @@ TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
trap '$TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
# test_format $format $expected_result # test_format $format $expected_result
test_format() test_format()
+20 -4
View File
@@ -18,6 +18,24 @@ export TZ LANG LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
HOLD1=
HOLD2=
CC1=
CC2=
cleanup()
{
for pid in $HOLD1 $HOLD2 $CC1 $CC2; do
kill "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
done
$TMUX kill-server 2>/dev/null
rm -f "$FIFO1" "$FIFO2"
}
trap cleanup 0
trap 'exit 1' 1 2 15
ESC=$(printf '\033') ESC=$(printf '\033')
@@ -579,8 +597,6 @@ assert_alive "verbose loop expansion"
# L loops over attached clients. Attach two control-mode clients, each held # L loops over attached clients. Attach two control-mode clients, each held
# open by a background process keeping a FIFO's write end open. # open by a background process keeping a FIFO's write end open.
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
rm -f "$FIFO1" "$FIFO2" rm -f "$FIFO1" "$FIFO2"
mkfifo "$FIFO1" "$FIFO2" || exit 1 mkfifo "$FIFO1" "$FIFO2" || exit 1
# Hold the write ends open so the control clients stay attached. # Hold the write ends open so the control clients stay attached.
@@ -605,9 +621,9 @@ test_format "#{L/nr:x}" "xx"
test_format "#{L/r:x}" "xx" test_format "#{L/r:x}" "xx"
# Now detach one and confirm the count drops to one. # Now detach one and confirm the count drops to one.
kill $HOLD2 2>/dev/null kill $HOLD2 2>/dev/null
wait $HOLD2 2>/dev/null
HOLD2=
sleep 1 sleep 1
test_format "#{L:x}" "x" test_format "#{L:x}" "x"
kill $HOLD1 $CC1 $CC2 2>/dev/null
rm -f "$FIFO1" "$FIFO2"
exit 0 exit 0
+2
View File
@@ -7,6 +7,8 @@ TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
trap '$TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
# test_format $format $expected_result # test_format $format $expected_result
test_format() test_format()
+18
View File
@@ -71,7 +71,10 @@ cursor_very_visible
cursor_x cursor_x
cursor_y cursor_y
history_all_bytes history_all_bytes
history_added
history_bytes history_bytes
history_collected
history_generation
history_limit history_limit
history_size history_size
host host
@@ -103,6 +106,11 @@ pane_at_right
pane_at_top pane_at_top
pane_bg pane_bg
pane_bottom pane_bottom
pane_command_duration
pane_command_end_time
pane_command_running
pane_command_start_time
pane_command_status
pane_current_command pane_current_command
pane_current_path pane_current_path
pane_dead pane_dead
@@ -120,16 +128,21 @@ pane_index
pane_input_off pane_input_off
pane_key_mode pane_key_mode
pane_last pane_last
pane_last_output_time
pane_last_prompt_time
pane_left pane_left
pane_marked pane_marked
pane_marked_set pane_marked_set
pane_modal_flag
pane_mode pane_mode
pane_output_generation
pane_path pane_path
pane_pb_progress pane_pb_progress
pane_pb_state pane_pb_state
pane_pid pane_pid
pane_pipe pane_pipe
pane_pipe_pid pane_pipe_pid
pane_private_modes
pane_right pane_right
pane_search_string pane_search_string
pane_start_command pane_start_command
@@ -141,6 +154,8 @@ pane_title
pane_top pane_top
pane_tty pane_tty
pane_unseen_changes pane_unseen_changes
pane_unzoomed_height
pane_unzoomed_width
pane_width pane_width
pane_x pane_x
pane_y pane_y
@@ -206,7 +221,10 @@ window_layout
window_linked window_linked
window_linked_sessions window_linked_sessions
window_linked_sessions_list window_linked_sessions_list
window_manual_height
window_manual_width
window_marked_flag window_marked_flag
window_modal_pane
window_name window_name
window_offset_x window_offset_x
window_offset_y window_offset_y
+6 -6
View File
@@ -76,14 +76,14 @@ $TMUX set-hook -g session-closed \
fail "set-hook session-closed failed" fail "set-hook session-closed failed"
# The only pane of the only window of a session exits: pane-exited, then # The only pane of the only window of a session exits: pane-exited, then
# window-unlinked, then session-closed, each seeing the dead object in the # session-closed, then window-unlinked, each seeing the dead object in the
# hook formats. # hook formats.
pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') || pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') ||
fail "new-session doomed failed" fail "new-session doomed failed"
wait_for @log \ wait_for @log \
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed" "|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
assert_unchanged @log \ assert_unchanged @log \
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed" "|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
# The dead pane, window and session cannot be used as targets but the # The dead pane, window and session cannot be used as targets but the
# server survives. # server survives.
@@ -110,14 +110,14 @@ $TMUX set-hook -g pane-exited \
'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' || 'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' ||
fail "restore pane-exited hook failed" fail "restore pane-exited hook failed"
# kill-window on the last window: window-unlinked then session-closed but # kill-window on the last window: session-closed then window-unlinked but
# no pane-exited for the panes in the killed window. # no pane-exited for the panes in the killed window.
$TMUX set -g @log '' || fail "reset @log failed" $TMUX set -g @log '' || fail "reset @log failed"
$TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed" $TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed"
$TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed" $TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed"
$TMUX kill-window -t doomed2:0 || fail "kill-window failed" $TMUX kill-window -t doomed2:0 || fail "kill-window failed"
wait_for @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2' wait_for @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
assert_unchanged @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2' assert_unchanged @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
$TMUX has -t main || fail "server died after kill-window chain" $TMUX has -t main || fail "server died after kill-window chain"
# kill-session: session-closed fires first, then window-unlinked for its # kill-session: session-closed fires first, then window-unlinked for its
+6 -4
View File
@@ -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. # pane-exited when a pane's command exits with remain-on-exit off.
$TMUX set -g @x 0 || fail "set @x failed" $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" fail "set-hook pane-exited failed"
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'true') || pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \
fail "split-window true failed" 'trap "" HUP; exec </dev/null >/dev/null 2>&1; sleep 1; exit 42') ||
wait_for @x "pane-exited:$pane" 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" $TMUX set-hook -gu pane-exited || fail "unset pane-exited failed"
# pane-died when a pane's command exits with remain-on-exit on. # pane-died when a pane's command exits with remain-on-exit on.
+2 -1
View File
@@ -12,7 +12,8 @@ $TMUX kill-server 2>/dev/null
TMP=$(mktemp) TMP=$(mktemp)
OUT=$(mktemp) OUT=$(mktemp)
trap "rm -f $TMP $OUT" 0 1 15 trap 'rm -f "$TMP" "$OUT"; $TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
cat <<EOF >$TMP cat <<EOF >$TMP
if 'true' 'wibble wobble' if 'true' 'wibble wobble'
+5 -2
View File
@@ -4,6 +4,9 @@ TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
# Keep panes alive until the next case or cleanup, without reading replies.
INPUT_HOLD='while :; do sleep 3600; done'
TMP=$(mktemp) TMP=$(mktemp)
EXP=$(mktemp) EXP=$(mktemp)
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15 trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
@@ -37,10 +40,10 @@ start_pane_hlimit()
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
sleep 0.1 sleep 0.1
$TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1 $TMUX new-session -d -x 1 -y 1 -s test-setup "$INPUT_HOLD" || exit 1
$TMUX set-option -g history-limit "$hlimit" || exit 1 $TMUX set-option -g history-limit "$hlimit" || exit 1
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \ $TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \
"printf '$seq'; sleep 2" || exit 1 "printf '$seq'; $INPUT_HOLD" || exit 1
$TMUX kill-session -t test-setup $TMUX kill-session -t test-setup
sleep 0.3 sleep 0.3
} }
+13 -1
View File
@@ -22,7 +22,19 @@ assert_key () {
clear-history -t$W \; \ clear-history -t$W \; \
send-keys -t$W "$key" 'EOL' || exit 1 send-keys -t$W "$key" 'EOL' || exit 1
actual_code=$($TMUX capturep -pt$W | \ # cat echoes the keys back asynchronously, so wait for the EOL marker
# to reach the pane instead of capturing straight away.
i=0
while [ $i -lt 50 ]; do
screen=$($TMUX capturep -pt$W)
case "$screen" in
*EOL*) break ;;
esac
i=$((i + 1))
sleep 0.1
done
actual_code=$(printf '%s\n' "$screen" | \
head -1 | \ head -1 | \
sed -e 's/EOL.*$//') sed -e 's/EOL.*$//')
+34 -7
View File
@@ -2,6 +2,38 @@
. ./input-common.inc . ./input-common.inc
# Wait for the terminator and following text to reach the parser.
check_discard()
{
name=$1
printf 'OK\n' >"$EXP"
i=0
while [ "$i" -lt 300 ]; do
capture_grid "$name" >"$TMP"
cmp -s "$TMP" "$EXP" && return 0
sleep 0.05
i=$((i + 1))
done
fail "$name (timed out waiting for discard)"
}
# The missing-terminator timer may expire before the string limit is reached
# on a slow build. Clear any payload printed after the timeout, then check
# that the string did not change the title and normal output has resumed.
test_discard()
{
name=$1
prefix=$2
start_cmd "$name" 8 3 "$INPUT_HOLD"
$TMUX select-pane -T discard-test || exit 1
$TMUX respawn-pane -k \
"perl -e 'print qq{$prefix}, q{x} x 1100000, qq{\e\\\\\e[H\e[2JOK}'; $INPUT_HOLD" || exit 1
check_discard "$name"
$TMUX display-message -p -t "$name:" '#{pane_title}' >"$TMP" || exit 1
printf 'discard-test\n' >"$EXP"
cmp "$TMP" "$EXP" || fail "$name title"
}
start_cmd csi-param-discard 8 3 \ start_cmd csi-param-discard 8 3 \
"perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2" "perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2"
check_capture csi-param-discard 'OK' check_capture csi-param-discard 'OK'
@@ -10,13 +42,8 @@ start_cmd csi-interm-discard 8 3 \
"perl -e 'print qq{\e[ \030OK}'; sleep 2" "perl -e 'print qq{\e[ \030OK}'; sleep 2"
check_capture csi-interm-discard 'OK' check_capture csi-interm-discard 'OK'
start_cmd osc-discard 8 3 \ test_discard osc-discard '\e]2;'
"perl -e 'print qq{\e]2;}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2" test_discard apc-discard '\e_'
check_capture osc-discard 'OK'
start_cmd apc-discard 8 3 \
"perl -e 'print qq{\e_}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
check_capture apc-discard 'OK'
start_pane unknown-csi 8 3 '\033[?9999zOK' start_pane unknown-csi 8 3 '\033[?9999zOK'
check_capture unknown-csi 'OK' check_capture unknown-csi 'OK'
+1 -1
View File
@@ -51,7 +51,7 @@ $TMUX kill-server 2>/dev/null
sleep 0.1 sleep 0.1
$TMUX new-session -d -x 5 -y 3 -s history \; \ $TMUX new-session -d -x 5 -y 3 -s history \; \
set-option -g history-limit 3 \; \ set-option -g history-limit 3 \; \
respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; sleep 2" || exit 1 respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; $INPUT_HOLD" || exit 1
sleep 0.3 sleep 0.3
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP" $TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
printf "%s\n" '01 printf "%s\n" '01
+137
View File
@@ -0,0 +1,137 @@
#!/bin/sh
# Test parsing and printing JSON with display-message -j.
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
fail()
{
echo "$1"
$TMUX kill-server 2>/dev/null
exit 1
}
check()
{
actual=$($TMUX display-message -plj "$1" 2>&1) ||
fail "JSON was rejected: $1: $actual"
[ "$actual" = "$2" ] ||
fail "JSON output mismatch: $1: expected $2, got $actual"
}
check_fail()
{
if $TMUX display-message -plj "$1" >/dev/null 2>&1; then
fail "invalid JSON was accepted: $1"
fi
}
$TMUX new-session -d || exit 1
check '{}' '{}'
check ' { "z":true, "a":-9223372036854775808, "m":[{}, {"x":false}] } ' \
'{"a":-9223372036854775808,"m":[{},{"x":false}],"z":true}'
check '{"max":9223372036854775807,"min":-9223372036854775808}' \
'{"max":9223372036854775807,"min":-9223372036854775808}'
check '{" key":" value","{key":"[value"}' \
'{" key":" value","{key":"[value"}'
check '{"brace":"{value","bracket":"[value","colon":":value",'\
'"comma":",value","space":" value"}' \
'{"brace":"{value","bracket":"[value","colon":":value",'\
'"comma":",value","space":" value"}'
check '{"esc":"\"\\\/\b\f\n\r\t\u0041"}' \
'{"esc":"\"\\\/\b\f\n\r\t\u0041"}'
check '{"unicode":"\u0123\uabcd\uABCD"}' \
'{"unicode":"\u0123\uabcd\uABCD"}'
actual=$($TMUX display-message -palj '{}' 2>&1) ||
fail "display-message -aj rejected JSON: $actual"
[ "$actual" = '{}' ] ||
fail "display-message -aj did not ignore -a: got $actual"
actual=$($TMUX display-message -pIlj '{}' 2>&1) ||
fail "display-message -Ij rejected JSON: $actual"
[ "$actual" = '{}' ] ||
fail "display-message -Ij did not ignore -I: got $actual"
check_fail ''
check_fail ' '
check_fail '[]'
check_fail 'true'
check_fail '1'
check_fail '"string"'
check_fail '{}{}'
check_fail '{'
check_fail '{x:1}'
check_fail '{"":1}'
check_fail '{"x" 1}'
check_fail '{"x",1}'
check_fail '{"x":}'
check_fail '{"x":,}'
check_fail '{"x":1'
check_fail '{"x":1 "y":2}'
check_fail '{"x":1,,"y":2}'
check_fail '{"x":1,}'
check_fail '{"x":1,"x":2}'
check_fail '{"x":{}}{}'
check_fail '{"x":{}} trailing'
check_fail '{"x":{'
check_fail '{"x":{}'
check_fail '{"x":['
check_fail '{"x":[}'
check_fail '{"x":[{}'
check_fail '{"x":[{},}'
check_fail '{"x":[{},]}'
check_fail '{"x":[{}{}]}'
check_fail '{"x":[{},,{}]}'
check_fail '{"x":[{"y":}]}'
check_fail '{"x":""}'
check_fail '{"x":null}'
check_fail '{"x":[null]}'
check_fail '{"x":[true]}'
check_fail '{"x":["string"]}'
check_fail '{"x":[[]]}'
check_fail '{"x":1.0}'
check_fail '{"x":1e2}'
check_fail '{"x":+1}'
check_fail '{"x":-}'
check_fail '{"x":0x10}'
check_fail '{"x":01}'
check_fail '{"x":-01}'
check_fail '{"x":[1]}'
check_fail '{"x":9223372036854775808}'
check_fail '{"x":-9223372036854775809}'
check_fail '{"x":tru}'
check_fail '{"x":True}'
check_fail '{"x":falsee}'
check_fail '{"x":"bad\q"}'
check_fail '{"x":"bad\x20"}'
check_fail '{"x":"bad\u123"}'
check_fail '{"x":"bad\u12x4"}'
check_fail '{"x":"unterminated}'
check_fail '{"x":"escaped quote\"}'
check_fail '{"x":"line
break"}'
# The maximum object nesting depth is 200.
json='{}'
n=1
while [ "$n" -lt 200 ]; do
json='{"x":'"$json"'}'
n=$((n + 1))
done
check "$json" "$json"
check_fail '{"x":'"$json"'}'
if $TMUX display-message -pj >/dev/null 2>&1; then
fail "display-message -j accepted a missing message"
fi
$TMUX kill-server 2>/dev/null
exit 0
+28
View File
@@ -0,0 +1,28 @@
#!/bin/sh
# Killing a session whose window is zoomed must not crash the server:
# window_destroy used to unzoom the window, which resized the panes and
# fired pane-resized with the window in the payload, and releasing that
# reference destroyed the window a second time.
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 -f/dev/null new -d -sfoo || exit 1
$TMUX split-window -d -h -tfoo:0 || exit 1
$TMUX resize-pane -Z -tfoo:0 || exit 1
$TMUX new -d -sbar || exit 1
$TMUX kill-session -tfoo || exit 1
$TMUX has-session -tbar || exit 1
# The same with the zoomed window in a session killed by kill-server.
$TMUX new -d -sbaz || exit 1
$TMUX split-window -d -v -tbaz:0 || exit 1
$TMUX resize-pane -Z -tbaz:0 || exit 1
$TMUX kill-server || exit 1
exit 0
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
#!/bin/sh
# Version 1 layouts must reject excessive nesting without losing the session or
# changing its layout. Check both split types and the depth limit boundary.
PATH=/bin:/usr/bin
TERM=screen
export PATH TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
DIR=$(mktemp -d) || exit 1
TMUX="$TEST_TMUX -S$DIR/socket -f/dev/null"
fail()
{
echo "$*" >&2
exit 1
}
cleanup()
{
$TMUX kill-server 2>/dev/null
rm -rf "$DIR"
}
trap cleanup 0
trap 'exit 1' 1 2 15
# Generate a checksum-valid v1 layout with one child in every nested group.
layout()
{
awk -v depth="$1" -v open="$2" 'BEGIN {
closing = (open == "{" ? "}" : "]")
body = ""
for (i = 0; i < depth; i++)
body = body "1x1,0,0" open
body = body "1x1,0,0"
for (i = 0; i < depth; i++)
body = body closing
ord["0"] = 48; ord["1"] = 49; ord["x"] = 120
ord[","] = 44; ord["{"] = 123; ord["}"] = 125
ord["["] = 91; ord["]"] = 93
csum = 0
for (i = 1; i <= length(body); i++) {
c = substr(body, i, 1)
bit = csum % 2
csum = int(csum / 2) + bit * 32768
csum = (csum + ord[c]) % 65536
}
printf "%04x,%s", csum, body
}'
}
$TMUX new-session -d -s deep -x80 -y24 'exec sleep 100' || exit 1
for open in '{' '['; do
# A valid checksum and nesting up to the limit must still be accepted.
value=$(layout 1000 "$open") || fail "could not generate layout"
$TMUX select-layout -t deep "$value" ||
fail "layout at the depth limit was rejected ($open)"
before=$($TMUX display-message -p -t deep \
'#{pane_id} #{pane_width} #{pane_height} #{window_layout}') || exit 1
for depth in 1001 1500; do
value=$(layout "$depth" "$open") || fail "could not generate layout"
$TMUX select-layout -t deep "$value" >/dev/null 2>&1 &&
fail "excessive nesting was accepted ($open, $depth)"
$TMUX has-session -t deep ||
fail "server died on excessive nesting ($open, $depth)"
after=$($TMUX display-message -p -t deep \
'#{pane_id} #{pane_width} #{pane_height} #{window_layout}') || exit 1
[ "$before" = "$after" ] ||
fail "pane or layout changed after rejection ($open, $depth)"
done
done
exit 0
+126
View 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
View 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
+224 -1
View File
@@ -105,6 +105,24 @@ drag()
sleep 1 sleep 1
} }
meta_drag()
{
scol="$1"
srow="$2"
ecol="$3"
erow="$4"
seq=$(printf '\033[<8;%s;%sM' "$scol" "$srow")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 0.2
seq=$(printf '\033[<40;%s;%sM' "$ecol" "$erow")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 0.2
seq=$(printf '\033[<8;%s;%sm' "$ecol" "$erow")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 1
}
cleanup cleanup
check_ok new-session -d -s modal -x 80 -y 24 'cat' check_ok new-session -d -s modal -x 80 -y 24 'cat'
@@ -127,12 +145,16 @@ case "$(fmt "$modal" '#{pane_flags}')" in
*O*) ;; *O*) ;;
*) fail "modal pane flags do not include O" ;; *) fail "modal pane flags do not include O" ;;
esac esac
case "$(fmt "$modal" '#{pane_flags}')" in
*A*) ;;
*) fail "modal pane flags do not include A" ;;
esac
case "$(fmt modal:0 '#{window_flags}')" in case "$(fmt modal:0 '#{window_flags}')" in
*O*) ;; *O*) ;;
*) fail "modal window flags do not include O" ;; *) fail "modal window flags do not include O" ;;
esac esac
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal" must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 0 must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
check_fail "window already has a modal pane" \ check_fail "window already has a modal pane" \
new-pane -O -x 10 -y 4 'cat' new-pane -O -x 10 -y 4 'cat'
@@ -159,12 +181,14 @@ under=$($TMUX split-window -PF '#{pane_id}' -t "$p0" 'cat') ||
sleep 1 sleep 1
must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" must_equal "$(fmt modal:0 '#{pane_id}')" "$modal"
must_equal "$(fmt "$under" '#{pane_active}')" 0 must_equal "$(fmt "$under" '#{pane_active}')" 0
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
float=$($TMUX new-pane -PF '#{pane_id}' -x 10 -y 4 -X 5 -Y 3 'cat') || float=$($TMUX new-pane -PF '#{pane_id}' -x 10 -y 4 -X 5 -Y 3 'cat') ||
fail "new floating pane under modal failed" fail "new floating pane under modal failed"
sleep 1 sleep 1
must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" must_equal "$(fmt modal:0 '#{pane_id}')" "$modal"
must_equal "$(fmt "$float" '#{pane_active}')" 0 must_equal "$(fmt "$float" '#{pane_active}')" 0
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
check_ok new-window -d -t modal: -n other 'cat' check_ok new-window -d -t modal: -n other 'cat'
check_ok select-window -t modal:other check_ok select-window -t modal:other
@@ -303,6 +327,205 @@ check_ok kill-pane -t "$modal"
sleep 1 sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
must_equal "$(fmt modal:0 '#{pane_id}')" "$p0" must_equal "$(fmt modal:0 '#{pane_id}')" "$p0"
must_equal "$(fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 0:0
ignored=$($TMUX new-pane -KdPF '#{pane_id}' -t "$p0" 'cat') ||
fail "new-pane -K without -O failed"
check_ok kill-pane -t "$ignored"
$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" \
-x 20 -y 5 -X 20 -Y 10 'cat') ||
fail "new-pane -ODK failed"
sleep 1
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
sleep 1
must_equal "$($TMUX show -gv @modal-prefix)" no
must_equal "$($TMUX show -gv @modal-root)" no
case "$($TMUX capture-pane -pt "$modal")" in
*xz*) ;;
*) fail "keys did not reach key-capturing modal pane" ;;
esac
left=$(fmt "$modal" '#{pane_left}')
top=$(fmt "$modal" '#{pane_top}')
meta_drag $((left + 2)) $((top + 2)) $((left + 7)) $((top + 4))
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'
base=$(fmt modal:float-over-zoom '#{pane_id}')
check_ok split-window -dh -t "$base" 'cat'
check_ok resize-pane -Z -t "$base"
over=$($TMUX new-pane -APF '#{pane_id}' -t "$base" \
-x 20 -y 5 -X 20 -Y 10 'cat') ||
fail "new-pane -A failed"
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_active}')" 1:1
case "$(fmt "$over" '#{pane_flags}')" in
*A*) ;;
*) fail "float-over-zoom pane flags do not include A" ;;
esac
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
check_ok select-pane -t "$base"
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_active}')" 1:1
check_ok select-pane -t "$over"
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
client=$($TMUX list-clients -F '#{client_name}' | head -1)
[ -n "$client" ] || fail "no client for switch-client test"
check_ok switch-client -c "$client" -t "$base"
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_active}')" 1:1
check_ok switch-client -c "$client" -t "$over"
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
check_ok kill-pane -t "$over"
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
check_ok resize-pane -Z -t "$base"
ignored=$($TMUX new-pane -ALdPF '#{pane_id}' -t "$base" 'cat') ||
fail "new-pane -A -L failed"
case "$(fmt "$ignored" '#{pane_flags}')" in
*A*) fail "tiled pane flags include A" ;;
*) ;;
esac
# Existing floating panes are filtered when zoom begins: -A panes remain in the
# visible layout and ordinary floating panes do not.
check_ok new-window -d -t modal: -n existing-over-zoom 'cat'
base=$(fmt modal:existing-over-zoom '#{pane_id}')
check_ok split-window -dh -t "$base" 'cat'
over=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
-x 20 -y 5 -X 20 -Y 10 'cat') ||
fail "pre-existing new-pane -A failed"
under=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
-x 15 -y 4 -X 2 -Y 2 'cat') ||
fail "pre-existing ordinary new-pane failed"
check_ok select-window -t modal:existing-over-zoom
check_ok select-pane -t "$base"
check_ok resize-pane -Z -t "$base"
must_equal "$(fmt "$over" '#{pane_floating_flag}')" 1
must_equal "$(fmt "$under" '#{pane_floating_flag}')" 0
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
# Geometry changed in the visible zoom layout is copied back when unzooming.
check_ok select-pane -t "$over"
left=$(fmt "$over" '#{pane_left}')
top=$(fmt "$over" '#{pane_top}')
meta_drag $((left + 2)) $((top + 2)) $((left + 7)) $((top + 4))
new_left=$(fmt "$over" '#{pane_left}')
new_top=$(fmt "$over" '#{pane_top}')
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
fail "float-over-zoom pane did not move"
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
check_ok resize-pane -Z -t "$base"
must_equal "$(fmt "$over" '#{pane_left}:#{pane_top}')" \
"$new_left:$new_top"
must_equal "$(fmt "$under" '#{pane_floating_flag}')" 1
# Resizing with the over-zoom pane active restores the original zoom target.
check_ok resize-pane -Z -t "$base"
check_ok select-pane -t "$over"
check_ok resize-window -t modal:existing-over-zoom -x 90 -y 30
must_equal "$(fmt "$base" \
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}')" \
90x30:1:1
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_active}')" 1:1
# Natural pane exit uses a different removal path from kill-pane and must also
# preserve zoom.
dying=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
-x 12 -y 4 -X 4 -Y 3 'true') ||
fail "short-lived new-pane -A failed"
i=0
while $TMUX list-panes -a -F '#{pane_id}' | grep -qx "$dying"; do
i=$((i + 1))
[ $i -gt 50 ] && fail "short-lived float-over-zoom pane did not exit"
sleep 0.1
done
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
# A pane with -A is also above a zoom target which was itself floating. The
# temporary tiled target must sit behind retained floating panes, then return
# to the normal floating z order when unzoomed.
check_ok new-window -d -t modal: -n floating-zoom-target 'cat'
base=$(fmt modal:floating-zoom-target '#{pane_id}')
check_ok split-window -dh -t "$base" 'cat'
target=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
-x 30 -y 10 -X 10 -Y 5 'cat') ||
fail "floating zoom target creation failed"
over=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
-x 15 -y 5 -X 15 -Y 8 'cat') ||
fail "float-over-zoom pane creation failed"
check_ok select-window -t modal:floating-zoom-target
check_ok select-pane -t "$target"
must_equal "$(fmt "$target" '#{pane_z}')" 0
must_equal "$(fmt "$over" '#{pane_z}')" 1
check_ok resize-pane -Z -t "$target"
must_equal "$(fmt "$target" \
'#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_floating_flag}:#{pane_z}')" \
1:1:0:2
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_z}')" 1:0
check_ok select-pane -t "$over"
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
check_ok resize-pane -Z -t "$target"
must_equal "$(fmt "$over" '#{pane_active}:#{pane_z}')" 1:0
must_equal "$(fmt "$target" '#{pane_floating_flag}:#{pane_z}')" 1:1
# If the target remains active, it returns to the front on unzoom.
check_ok select-pane -t "$target"
check_ok resize-pane -Z -t "$target"
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_z}')" 1:0
check_ok resize-pane -Z -t "$target"
must_equal "$(fmt "$target" '#{pane_active}:#{pane_z}')" 1:0
must_equal "$(fmt "$over" '#{pane_z}')" 1
cleanup cleanup
exit 0 exit 0
Executable → Regular
View File
+17
View File
@@ -269,6 +269,22 @@ test_customize_mode()
assert_alive "customize-mode exit" assert_alive "customize-mode exit"
} }
test_customize_break_pane()
{
start_client option-break
side=$($TMUX split-window -d -P -F '#{pane_id}' \
-t option-break:0 'cat') || fail "customize split failed"
$TMUX customize-mode -t "$side" || fail "customize-mode failed"
wait_mode "$side" 1
$TMUX break-pane -d -s "$side" || fail "customize break-pane failed"
assert_alive "customize-mode break-pane"
wait_mode "$side" 1
$TMUX send-keys -t "$side" q || fail "customize-mode quit failed"
wait_mode "$side" 0
}
test_copy_mode() test_copy_mode()
{ {
start_client copy-a 'i=0; while [ $i -lt 200 ]; do echo "copy mutation line $i"; i=$((i + 1)); done; cat' start_client copy-a 'i=0; while [ $i -lt 200 ]; do echo "copy mutation line $i"; i=$((i + 1)); done; cat'
@@ -302,6 +318,7 @@ test_choose_tree
test_choose_buffer test_choose_buffer
test_choose_client test_choose_client
test_customize_mode test_customize_mode
test_customize_break_pane
test_copy_mode test_copy_mode
cleanup cleanup
exit 0 exit 0
+184 -7
View File
@@ -19,7 +19,10 @@
# - respawn-pane/respawn-window refusing a live pane without -k, working on a # - respawn-pane/respawn-window refusing a live pane without -k, working on a
# dead pane (remain-on-exit) and killing with -k; # dead pane (remain-on-exit) and killing with -k;
# - resize-pane -x/-y in cells and percent, -L/-R/-U/-D adjustments and -Z # - resize-pane -x/-y in cells and percent, -L/-R/-U/-D adjustments and -Z
# zoom/unzoom (including implicit unzoom on split). # zoom/unzoom, layout round trips, zoom target selection, pane removal,
# window resizing and implicit unzoom on layout changes;
# - swap-pane preserving one or two zoomed windows, including cross-window
# swaps of hidden panes and zoom targets.
# #
# window-ops.sh covers window-level commands and buffers.sh paste buffers. # window-ops.sh covers window-level commands and buffers.sh paste buffers.
@@ -101,6 +104,25 @@ assert_alive()
fi fi
} }
# Compare layout geometry and pane ordering, ignoring the active pane and
# last-pane history in the JSON layout. Selection is checked separately.
pane_layout()
{
$TMUX display-message -p -t "$1" '#{window_layout}' |
sed 's/,"a":true//g; s/,"l":[0-9][0-9]*//g'
}
check_layout()
{
out=$(pane_layout "$1")
if [ "$out" != "$2" ]; then
echo "Layout for '$1' wrong."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# split-window geometry. # split-window geometry.
@@ -144,7 +166,8 @@ check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
# break-pane and join-pane. # break-pane and join-pane.
# break-pane moves a pane to a new window; -P -F prints where it went and -n # break-pane moves a pane to a new window; -P -F prints where it went and -n
# names the new window. # names the new window. Moving a pane out of a zoomed window unzooms it first.
check_ok resize-pane -Z -t "$p0"
out=$($TMUX break-pane -d -P -F '#{window_index}:#{pane_id}' -n broken \ out=$($TMUX break-pane -d -P -F '#{window_index}:#{pane_id}' -n broken \
-s "$p1" -t P:) -s "$p1" -t P:)
if [ "$out" != "1:$p1" ]; then if [ "$out" != "1:$p1" ]; then
@@ -153,10 +176,14 @@ if [ "$out" != "1:$p1" ]; then
fi fi
check_fmt 'P:1' '#{window_name}:#{window_panes}' 'broken:1' check_fmt 'P:1' '#{window_name}:#{window_panes}' 'broken:1'
check_fmt 'P:0' '#{window_panes}' '3' check_fmt 'P:0' '#{window_panes}' '3'
check_fmt 'P:0' '#{window_zoomed_flag}' '0'
# join-pane -v moves it back (the source window, left empty, is destroyed). # join-pane -v moves it back (the source window, left empty, is destroyed) and
# also unzooms the destination before changing its layout.
check_ok resize-pane -Z -t "$p0"
check_ok join-pane -d -v -s P:broken.0 -t "$p2" check_ok join-pane -d -v -s P:broken.0 -t "$p2"
check_fmt 'P:0' '#{window_panes}' '4' check_fmt 'P:0' '#{window_panes}' '4'
check_fmt 'P:0' '#{window_zoomed_flag}' '0'
if $TMUX has-session -t P:broken 2>/dev/null; then if $TMUX has-session -t P:broken 2>/dev/null; then
echo "Window 'broken' still exists after join-pane." echo "Window 'broken' still exists after join-pane."
exit 1 exit 1
@@ -192,13 +219,13 @@ check_fail "invalid window name: $(printf 'a\377b')" \
break-pane -d -n "$(printf 'a\377b')" -s "$p1" -t P: break-pane -d -n "$(printf 'a\377b')" -s "$p1" -t P:
# join-pane can move a pane from one window to another without destroying # join-pane can move a pane from one window to another without destroying
# the source window if other panes remain. (On this branch move-pane is # the source window if other panes remain. move-pane does the same when no
# reserved for floating panes, covered by floating-pane-geometry.sh.) # floating-pane movement flags are given.
check_ok new-window -d -t P:5 -n other check_ok new-window -d -t P:5 -n other
check_ok join-pane -d -s "$p1" -t P:5.0 check_ok join-pane -d -s "$p1" -t P:5.0
check_fmt 'P:5' '#{window_panes}' '2' check_fmt 'P:5' '#{window_panes}' '2'
check_fmt 'P:0' '#{window_panes}' '3' check_fmt 'P:0' '#{window_panes}' '3'
check_ok join-pane -d -v -s "$p1" -t "$p2" check_ok move-pane -d -v -s "$p1" -t "$p2"
check_fmt 'P:0' '#{window_panes}' '4' check_fmt 'P:0' '#{window_panes}' '4'
check_fmt 'P:5' '#{window_panes}' '1' check_fmt 'P:5' '#{window_panes}' '1'
@@ -299,6 +326,122 @@ check_fmt 'P:0' '#{window_panes}' '5'
p6=$($TMUX display-message -p -t P:0.2 '#{pane_id}') p6=$($TMUX display-message -p -t P:0.2 '#{pane_id}')
check_ok kill-pane -t "$p6" check_ok kill-pane -t "$p6"
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
# without -Z unzooms, while -Z transfers zoom to the selected pane.
layout=$(pane_layout P:0)
check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0"
check_ok select-pane -t "$p2"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
check_layout P:0 "$layout"
check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0"
check_ok select-pane -Z -t "$p2"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1'
check_ok resize-pane -Z -t "$p2"
check_layout P:0 "$layout"
# Directional selection temporarily restores the full layout to find its
# neighbour, then follows the same unzoom or -Z transfer rules.
check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0"
check_ok select-pane -t "$p0"
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1'
check_ok select-pane -D -t "$p0"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0"
check_ok select-pane -D -Z -t "$p0"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1'
check_ok resize-pane -Z -t "$p2"
check_layout P:0 "$layout"
# The last-pane path has separate zoom handling, both with and without -Z.
check_ok select-pane -t "$p0"
check_ok select-pane -t "$p2"
check_ok resize-pane -Z -t "$p2"
check_ok select-pane -l -t P:0
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'0:0:1'
check_ok select-pane -t "$p0"
check_ok select-pane -t "$p2"
check_ok resize-pane -Z -t "$p2"
check_ok select-pane -l -Z -t P:0
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1'
check_ok resize-pane -Z -t "$p0"
check_layout P:0 "$layout"
# Killing either a hidden ordinary pane or the zoom target unzooms.
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
zk0=$($TMUX display-message -p -t P:12.0 '#{pane_id}')
zk1=$($TMUX split-window -d -P -F '#{pane_id}' -t P:12.0 'cat')
zk2=$($TMUX split-window -d -P -F '#{pane_id}' -t P:12.0 'cat')
check_ok resize-pane -Z -t "$zk0"
check_ok kill-pane -t "$zk1"
check_fmt "$zk0" '#{window_panes}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
'2:0:0'
check_ok resize-pane -Z -t "$zk0"
check_ok kill-pane -t "$zk0"
check_fmt "$zk2" '#{window_panes}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
'1:0:0'
check_ok kill-window -t P:12
# The same cases through natural process exit exercise server_destroy_pane.
check_ok new-window -d -t P:13 -n zoom-exit 'cat'
ze0=$($TMUX display-message -p -t P:13.0 '#{pane_id}')
ze1=$($TMUX split-window -d -P -F '#{pane_id}' -t P:13.0 'cat')
ze2=$($TMUX split-window -d -P -F '#{pane_id}' -t P:13.0 'cat')
check_ok resize-pane -Z -t "$ze0"
check_ok send-keys -t "$ze1" C-d
i=0
while [ "$($TMUX display-message -p -t P:13 '#{window_panes}')" != 2 ]; do
i=$((i + 1))
[ $i -gt 50 ] && { echo "Hidden pane did not exit."; exit 1; }
sleep 0.1
done
check_fmt "$ze0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
check_ok resize-pane -Z -t "$ze2"
check_ok send-keys -t "$ze2" C-d
i=0
while [ "$($TMUX display-message -p -t P:13 '#{window_panes}')" != 1 ]; do
i=$((i + 1))
[ $i -gt 50 ] && { echo "Zoomed pane did not exit."; exit 1; }
sleep 0.1
done
check_fmt "$ze0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
check_ok kill-window -t P:13
# resize-window restores both tiled and floating zoom targets after rebuilding
# the layout at the new size.
check_ok new-window -d -t P:14 -n zoom-resize 'cat'
zr0=$($TMUX display-message -p -t P:14.0 '#{pane_id}')
check_ok split-window -d -t P:14.0 'cat'
check_ok resize-pane -Z -t "$zr0"
check_ok resize-window -t P:14 -x 90 -y 30
check_fmt "$zr0" \
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
'90x30:1:1'
check_ok resize-pane -Z -t "$zr0"
zrf=$($TMUX new-pane -dP -F '#{pane_id}' -t P:14 -x 20 -y 6 'cat')
zrf_size=$($TMUX display-message -p -t "$zrf" \
'#{pane_width}x#{pane_height}')
check_ok resize-pane -Z -t "$zrf"
check_ok resize-window -t P:14 -x 100 -y 32
check_fmt "$zrf" \
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
'100x32:1:1'
check_ok resize-pane -Z -t "$zrf"
check_fmt "$zrf" '#{pane_floating_flag}:#{pane_width}x#{pane_height}' \
"1:$zrf_size"
check_ok kill-window -t P:14
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# kill-pane. # kill-pane.
@@ -307,8 +450,10 @@ check_ok kill-pane -t "$p3"
check_panes P:0 "0:$p0 1:$p2 2:$p1" check_panes P:0 "0:$p0 1:$p2 2:$p1"
# -a kills every pane except the target. # -a kills every pane except the target.
check_ok resize-pane -Z -t "$p0"
check_ok kill-pane -a -t "$p0" check_ok kill-pane -a -t "$p0"
check_panes P:0 "0:$p0" check_panes P:0 "0:$p0"
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
# Killing the last pane in a window kills the window. # Killing the last pane in a window kills the window.
check_ok new-window -d -t P:7 -n goner check_ok new-window -d -t P:7 -n goner
@@ -516,6 +661,10 @@ check_panes P:3 "0:$r0 1:$r1 2:$r2"
# Swapping a pane with itself quietly does nothing. # Swapping a pane with itself quietly does nothing.
check_ok swap-pane -d -s "$r1" -t "$r1" check_ok swap-pane -d -s "$r1" -t "$r1"
check_panes P:3 "0:$r0 1:$r1 2:$r2" check_panes P:3 "0:$r0 1:$r1 2:$r2"
check_ok resize-pane -Z -t "$r1"
check_ok swap-pane -d -Z -s "$r1" -t "$r1"
check_fmt "$r1" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
check_ok resize-pane -Z -t "$r1"
# Panes can be swapped between different windows. # Panes can be swapped between different windows.
check_ok swap-pane -d -s "$o0" -t "$r1" check_ok swap-pane -d -s "$o0" -t "$r1"
@@ -525,10 +674,38 @@ check_ok swap-pane -d -s "$r1" -t "$o0"
check_panes P:3 "0:$r0 1:$r1 2:$r2" check_panes P:3 "0:$r0 1:$r1 2:$r2"
check_panes P:5 "0:$o0" check_panes P:5 "0:$o0"
# With both windows zoomed, -Z preserves each window's zoom when hidden panes
# are exchanged and when the zoom targets themselves are exchanged.
o1=$($TMUX split-window -dP -F '#{pane_id}' -t P:5.0)
check_ok resize-pane -Z -t "$r0"
check_ok resize-pane -Z -t "$o0"
check_ok swap-pane -d -Z -s "$r1" -t "$o1"
check_fmt "$r0" \
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'swaps:1:1'
check_fmt "$o0" \
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'other:1:1'
check_fmt "$r1" '#{window_name}' 'other'
check_fmt "$o1" '#{window_name}' 'swaps'
check_ok swap-pane -d -Z -s "$r0" -t "$o0"
check_fmt "$o0" \
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'swaps:1:1'
check_fmt "$r0" \
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'other:1:1'
check_ok resize-pane -Z -t "$o0"
check_ok resize-pane -Z -t "$r0"
# Restore the original panes before the remaining swap tests.
check_ok swap-pane -d -s "$r0" -t "$o0"
check_ok swap-pane -d -s "$r1" -t "$o1"
check_ok kill-pane -t "$o1"
check_panes P:3 "0:$r0 1:$r1 2:$r2"
check_panes P:5 "0:$o0"
# -Z keeps the window zoomed across the swap. # -Z keeps the window zoomed across the swap.
check_ok resize-pane -Z -t "$r0" check_ok resize-pane -Z -t "$r0"
check_ok swap-pane -d -Z -s "$r0" -t "$r1" check_ok swap-pane -d -Z -s "$r0" -t "$r1"
check_fmt 'P:3' '#{window_zoomed_flag}' '1' check_fmt "$r0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
check_ok resize-pane -Z -t P:3 check_ok resize-pane -Z -t P:3
check_panes P:3 "0:$r1 1:$r0 2:$r2" check_panes P:3 "0:$r1 1:$r0 2:$r2"
+3 -3
View File
@@ -35,9 +35,9 @@ check_alive()
# A pipe-pane -I child may write after the pane process has exited. With # A pipe-pane -I child may write after the pane process has exited. With
# remain-on-exit, the pane stays around but its bufferevent has been freed. # remain-on-exit, the pane stays around but its bufferevent has been freed.
check_ok new-session -d -s pipe -x 80 -y 24 'sleep 0.2' check_ok new-session -d -s pipe -x 80 -y 24 'sleep 1'
check_ok set-option -t pipe:0 remain-on-exit on check_ok set-option -t pipe:0 remain-on-exit on
check_ok pipe-pane -t pipe:0.0 -I 'sleep 0.6; printf x' check_ok pipe-pane -t pipe:0.0 -I 'sleep 2; printf x'
i=0 i=0
while [ "$($TMUX display-message -p -t pipe:0.0 '#{pane_dead}')" != "1" ]; do while [ "$($TMUX display-message -p -t pipe:0.0 '#{pane_dead}')" != "1" ]; do
@@ -46,7 +46,7 @@ while [ "$($TMUX display-message -p -t pipe:0.0 '#{pane_dead}')" != "1" ]; do
sleep 0.1 sleep 0.1
done done
sleep 0.7 sleep 2
check_alive check_alive
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null

Some files were not shown because too many files have changed in this diff Show More