Compare commits

...

235 Commits
3.7 ... layouts

Author SHA1 Message Date
Thomas Adam
d176f1f89c Merge branch 'obsd-master' 2026-06-25 18:30:06 +01:00
nicm
2d0845860a Fix scrollbar with a format colour. 2026-06-25 17:17:25 +00:00
Thomas Adam
5ae701aab5 Merge branch 'obsd-master' 2026-06-25 18:00:06 +01:00
nicm
0d016195ac Expand colours as formats like styles. 2026-06-25 16:32:42 +00:00
Thomas Adam
fc2de240d2 Merge branch 'obsd-master' 2026-06-25 15:30:06 +01:00
nicm
7c1ef6a387 Format variable for client colours. 2026-06-25 14:27:58 +00:00
Thomas Adam
0b538b6f2a Merge branch 'obsd-master' 2026-06-25 13:29:53 +01:00
Your Name
3d26aa0dfb Prompt regress (will not pass yet). 2026-06-25 12:40:49 +01:00
nicm
51d037e881 Major rework of prompts. The basic prompt mechanics (draw, editing, etc)
are now wrapped up in prompt*.c and do not depend on a client. These
functions are used to provide the original client prompt but also to
allow panes to have their own prompts, which works much much better for
floating panes. The mode prompts for both the tree modes and copy mode
are switched over to be per pane.

There are some visible changes (some of these may be changed if they
don't seem to be working well):

- Prompts in modes now appear in the bottom line, covering whatever
  content was there.

- command-prompt has a -P flag to open a pane prompt.

- Because they cover the content, the default style for prompts in modes
  now does not fill the entire line; the main command prompt stays the
  same.

- The old completion menu has gone, and completions are now shown after
  the text. Builtin aliases are no longer completed.

- Clicking the mouse on the prompt now moves the cursor or selects a
  completion.
2026-06-25 11:39:11 +00:00
Nicholas Marriott
e5fb88fb85 Merge pull request #5273 from daneofmanythings/floating-geometry-regression-test
test(master): Added regression test for floating pane geometry.
2026-06-25 10:23:01 +01:00
Thomas Adam
787234eff4 Merge branch 'obsd-master' 2026-06-24 23:00:06 +01:00
nicm
6b1219b370 Ignore mouse when help is open. 2026-06-24 21:47:06 +00:00
Thomas Adam
ed48555bdd Merge branch 'obsd-master' 2026-06-24 22:30:06 +01:00
nicm
ba9faae8b6 Free modes rather than going through reset when pane is destroyed as the
reset path may try to destroy the pane again.
2026-06-24 21:10:05 +00:00
Dane Jensen
6935dee976 Added regression test for floating pane geometry and how it interacts
with pane borders.
2026-06-24 13:48:17 -07:00
Thomas Adam
12473bb6d3 Merge branch 'obsd-master' 2026-06-24 12:15:06 +01:00
nicm
280a962328 Replace two-value prompt_mode enum with a flag. 2026-06-24 11:05:58 +00:00
nicm
37633ff957 Add some enums for prompt return values to make them a bit less confusing. 2026-06-24 10:55:39 +00:00
Thomas Adam
1c2fc8c3c4 Merge branch 'obsd-master' 2026-06-24 10:15:06 +01:00
Thomas Adam
d3adecd819 Merge branch 'obsd-master' 2026-06-24 10:00:06 +01:00
nicm
0a71af41be Handle different borders in move-pane, from Dane Jensen. 2026-06-24 08:52:15 +00:00
nicm
8fb484da37 Remove target completion which never worked very well, leave only
command completion.
2026-06-24 08:35:46 +00:00
Nicholas Marriott
6e6a434a2d Remove no longer needed test bits. 2026-06-24 09:24:18 +01:00
Thomas Adam
f23ea9e977 Merge branch 'obsd-master' 2026-06-24 01:15:06 +01:00
nicm
55cfdbaa3f Remove fuzzy match operator again for now, this will be done differently. 2026-06-23 23:53:06 +00:00
nicm
1b5a07a740 Rename prompt-input to match the usual format names. 2026-06-23 23:52:39 +00:00
Thomas Adam
00b0af7021 Merge branch 'obsd-master' 2026-06-23 22:15:06 +01:00
nicm
89027bd22c Fix MOVE flag handling and remove key code (not actually needed). 2026-06-23 21:00:20 +00:00
Thomas Adam
ff8de1a106 Merge branch 'obsd-master' 2026-06-23 22:00:05 +01:00
nicm
0c5e6770df Turn off preview if no draw callback and add a function to move to top. 2026-06-23 20:30:10 +00:00
Thomas Adam
c2ad33a04c Merge branch 'obsd-master' 2026-06-23 21:15:06 +01:00
nicm
c0a5e10313 Use flags for input callback instead of a single int done so the
callback can be told about cursor movement in an incremental prompt.
2026-06-23 20:07:58 +00:00
Nicholas Marriott
1c394b8606 Tweak the test. 2026-06-23 20:31:42 +01:00
Nicholas Marriott
505d557fbb Fix SIXEL, from Marc Haesen. 2026-06-23 15:48:02 +01:00
Thomas Adam
a63e429d22 Merge branch 'obsd-master' 2026-06-23 13:00:09 +01:00
nicm
de086f9848 Fix an infinite loop in customize mode when a filter does not match, and
tweak a tmux.1 example.
2026-06-23 11:29:27 +00:00
Thomas Adam
6643acb375 Merge branch 'obsd-master' 2026-06-23 11:00:09 +01:00
Thomas Adam
c25cb107bb Merge branch 'obsd-master' 2026-06-23 10:30:05 +01:00
nicm
9dba08ac8b Allow -p more than 100%, and account for borders when sizing new panes.
From Dane Jensen.
2026-06-23 09:29:26 +00:00
nicm
3ec115115d Do not leak error message, use it instead. From Dane Jensen. 2026-06-23 09:13:30 +00:00
Thomas Adam
09c91b8931 Merge branch 'obsd-master' 2026-06-23 10:00:05 +01:00
Nicholas Marriott
de1940065a Update test. 2026-06-23 10:01:01 +01:00
nicm
748ef8b2ae Extend match to do multiple terms. 2026-06-23 08:35:28 +00:00
Thomas Adam
2ecf0a9053 Merge branch 'obsd-master' 2026-06-23 09:30:05 +01:00
nicm
b82048eb18 Add c key to clear filter. 2026-06-23 08:20:15 +00:00
Nicholas Marriott
9d58ff97fe Update format test. 2026-06-23 09:14:13 +01:00
Nicholas Marriott
51b787e449 Fix SIXEL build. 2026-06-23 09:06:43 +01:00
Nicholas Marriott
7654b518f1 Add to .gitignore. 2026-06-23 08:36:07 +01:00
Thomas Adam
52877c9af1 Merge branch 'obsd-master' 2026-06-23 07:35:51 +01:00
nicm
265f22be49 Make cursor movement exit incremental prompt (more like emacs). 2026-06-22 19:43:22 +00:00
nicm
2a26c738bd Add z modifier to m operator for fuzzy match. 2026-06-22 19:39:01 +00:00
nicm
02b37b3c69 Fix scrollbar pad to work even if not set in style. 2026-06-22 13:57:33 +00:00
nicm
d8370864f3 Memory leak from someone in GitHub issue 5259. 2026-06-22 12:39:56 +00:00
Nicholas Marriott
ffe48f00e3 Tests for screen-redraw.c (not all passing right now). 2026-06-22 12:09:44 +01:00
nicm
49dff83a00 Set check callback for menus so they aren't overwritten by sync. 2026-06-22 10:17:08 +00:00
nicm
95afd7549c Rewrite screen-redraw.c to make it tidier and more maintainable. A scene
is generated and cached in the client: it holds positions and sizes of
panes, borders and so on. The scene is invalidated when a pane is moved
or resized or relevant option is changed. This scene is then drawn to
the client as needed and text and colours are filled in. With Michael
Grant.
2026-06-22 08:47:45 +00:00
Thomas Adam
06ee890db3 Merge branch 'obsd-master' 2026-06-22 09:30:06 +01:00
nicm
e680420c89 Place floating cells meaningfully into the layouts, from Dane Jensen. 2026-06-22 08:10:16 +00:00
Thomas Adam
357ee692bd Merge branch 'obsd-master' 2026-06-21 19:00:05 +01:00
nicm
347baa6f3e Do not walk off end or beginning of menu item list if every item is
disabled, GitHub issue 5255 from aizumusheer2 at gmail dot com.
2026-06-21 17:43:47 +00:00
Thomas Adam
cc922de758 Merge branch 'obsd-master' 2026-06-20 10:15:05 +01:00
Thomas Adam
dbca906ef5 Merge branch 'obsd-master' 2026-06-20 10:00:05 +01:00
nicm
1fc4dcb76f Error if key is unknown rather than just carry on, GitHub issue 5248. 2026-06-20 08:51:28 +00:00
nicm
dcd7d1c8e6 Fix {} keys in tmux.1, pointed out by tb. 2026-06-20 08:46:08 +00:00
Thomas Adam
72e768ef14 Merge branch 'obsd-master' 2026-06-19 20:00:06 +01:00
nicm
047f61aad6 popup_write can be deleted now also. 2026-06-19 18:41:36 +00:00
nicm
d04b1ffca5 Use a floating pane for the buffer mode editor instead of a popup. 2026-06-19 18:37:10 +00:00
nicm
c93e2f2332 Do not loop forever if a pane ends up with borders the wrong way round. 2026-06-19 18:28:18 +00:00
Thomas Adam
e7c29d0ceb Merge branch 'obsd-master' 2026-06-19 19:00:05 +01:00
nicm
6c46358be8 Draw help directly instead of using a popup. 2026-06-19 17:37:26 +00:00
Thomas Adam
b0ff8af9ac Merge branch 'obsd-master' 2026-06-19 17:30:05 +01:00
nicm
624db256c6 Add a loop_index variable and use it to show last used 5 sessions on the
session menu instead of next/previous.
2026-06-19 16:25:43 +00:00
Thomas Adam
cfb15d158e Merge branch 'obsd-master' 2026-06-19 17:15:05 +01:00
Thomas Adam
34c7881ddd Merge branch 'obsd-master' 2026-06-19 17:00:05 +01:00
nicm
f06d930445 Correctly report theme changes to panes, based on a fix from someone in
GitHub issue 5246.
2026-06-19 16:00:03 +00:00
nicm
b29f5688bd Make default M-C-Up/Down bound to prompt movement commands in copy mode. 2026-06-19 15:28:26 +00:00
Thomas Adam
333e2f266a Merge branch 'obsd-master' 2026-06-19 12:00:07 +01:00
Nicholas Marriott
af94399473 Add some more bits to test. 2026-06-19 11:50:29 +01:00
Nicholas Marriott
3d35f26031 Add a test for tty_draw_line. 2026-06-19 11:43:43 +01:00
nicm
b1c72ec48c Use a helper and skip some setup for empty cells. 2026-06-19 10:38:29 +00:00
Thomas Adam
8dee34459b Merge branch 'obsd-master' 2026-06-19 11:15:06 +01:00
nicm
e7f649e40a Tidy up a bit in tty_draw_line. 2026-06-19 09:56:53 +00:00
Thomas Adam
5843fbc6ee Merge branch 'obsd-master' 2026-06-19 09:30:06 +01:00
Nicholas Marriott
c8eca3a69d Add command-alias.sh test, from tanushnry at gmail dot com. 2026-06-19 09:29:41 +01:00
Nicholas Marriott
12fdcea8b8 Get use of raster attributes in the right order, from aizumusheer2 at gmail dot
com in GitHub issue 5225.
2026-06-19 09:24:48 +01:00
nicm
035447b08f Do not dereference the current window if it is NULL (can be if the
notification fires after session is destroyed). GitHub issue 5244 from
Ben Maurer.
2026-06-19 08:21:55 +00:00
Thomas Adam
55b2e748e7 Merge branch 'obsd-master' 2026-06-18 13:15:05 +01:00
Thomas Adam
3246331876 Merge branch 'obsd-master' 2026-06-18 13:00:05 +01:00
Nicholas Marriott
6d9fbb0e97 Update regress for format changes. 2026-06-18 12:55:13 +01:00
nicm
14fc4a06a6 Unescape arguments before passing to format_expand1 so that escaping :s
etc actually works.
2026-06-18 11:54:37 +00:00
nicm
cba4ba9cdc Check time periodically in loops rather than every one. 2026-06-18 11:45:08 +00:00
Thomas Adam
937d99150c Merge branch 'obsd-master' 2026-06-18 12:15:05 +01:00
nicm
0a6c846091 Use evbuffer for building S, W, L lists, also add an xmemdup helper
instead of misusing asprintf.
2026-06-18 10:56:22 +00:00
Thomas Adam
c6b8ad6e7e Merge branch 'obsd-master' 2026-06-18 11:15:07 +01:00
nicm
d92a479d2f Add a dim= style attribute to dim the colours (as best as tmux is able
to). GitHub issue 4842.
2026-06-18 09:59:55 +00:00
Thomas Adam
f242182840 Merge branch 'obsd-master' 2026-06-18 10:30:05 +01:00
Thomas Adam
f5871369cc Merge branch 'obsd-master' 2026-06-18 10:15:06 +01:00
nicm
b1b184cdb8 Add functions to work out cell neighbours, and to remove a tiled cell.
From Dane Jensen.
2026-06-18 09:11:24 +00:00
nicm
1a6de01210 Check the panes again if deferred redraw to make sure the flag is always
set. Also add a helper for the loop.
2026-06-18 08:56:07 +00:00
Thomas Adam
1dd2589a42 Merge branch 'obsd-master' 2026-06-17 14:30:05 +01:00
nicm
3485e1c089 Tidy up server_client_check_redraw. Get rid of the bitmask tracking
panes which want redraw for deferred clients - if they are deferred then
they can just have a full redraw instead. Also return earlier if no
redraw is actually needed, and improve the comments.
2026-06-17 13:22:48 +00:00
Thomas Adam
6f7965e4f1 Merge branch 'obsd-master' 2026-06-17 14:00:05 +01:00
nicm
1ba8fa6f04 Fix shifts to 1ULL and check scrollbar even if pane is not being redrawn. 2026-06-17 12:32:54 +00:00
Thomas Adam
6eb67fcc5f Merge branch 'obsd-master' 2026-06-17 09:15:05 +01:00
Thomas Adam
a8abea8247 Merge branch 'obsd-master' 2026-06-17 09:00:07 +01:00
nicm
d4defd943a Add -M to move-pane and default bindings for M-drag. From Michael Grant. 2026-06-17 07:52:21 +00:00
nicm
eafd2bed5d FIx a couple of minor errors in use of layout_cell_is_tiled. 2026-06-17 07:43:55 +00:00
Thomas Adam
6549765930 Makefile: add window-visible.c 2026-06-16 12:55:41 +01:00
Thomas Adam
6cb2233cd2 Merge branch 'obsd-master' 2026-06-16 12:55:00 +01:00
nicm
9ad75e1dc3 Rename the visible ranges functions since they really relate to windows
and put them nto a new file.
2026-06-16 10:47:35 +00:00
Thomas Adam
bec0f7d975 Merge branch 'obsd-master' 2026-06-16 11:00:05 +01:00
nicm
4b772fd783 Allow rectangle selection to extend past end of current line to behave
the same as vi with virtualedit=block set. From Mark Kelly in GitHub
issue 5227.
2026-06-16 09:28:17 +00:00
Thomas Adam
1d026b921c Merge branch 'obsd-master' 2026-06-16 10:15:14 +01:00
nicm
fcbb6a7384 Initialize wname so it we are not freeing garbage if it is not used. 2026-06-16 09:00:25 +00:00
nicm
bdc01f44de Fix resizing floating pane with a left scrollbar, from Michael Grant. 2026-06-16 08:57:07 +00:00
nicm
d2c23773ff Fix missing border when drawing floating panes. From Michael Grant. 2026-06-16 08:53:14 +00:00
Nicholas Marriott
38dc3ec0da Prefer libtinfow if it is available, GitHub issue 5224 from Lars Wendler. 2026-06-16 09:48:04 +01:00
Thomas Adam
c03acc9572 Merge branch 'obsd-master' 2026-06-16 08:15:07 +01:00
nicm
30e133d36a Fix default window check in break-pane, from Dane Jensen. 2026-06-16 07:07:49 +00:00
nicm
0890df5891 Add layout_cell_is_tiled and layout_cell_has_tiled_child helper
functions, from Dane Jensen.
2026-06-16 07:06:32 +00:00
Thomas Adam
d223a49d18 Merge branch 'obsd-master' 2026-06-16 06:15:05 +01:00
jsg
26285e183e botton -> bottom 2026-06-16 05:01:56 +00:00
Thomas Adam
408e9f1481 Merge branch 'obsd-master' 2026-06-15 23:00:06 +01:00
Nicholas Marriott
7c8344930b Add a test for names. 2026-06-15 22:47:39 +01:00
nicm
482e254a49 Rename window_pane_visible to match other functions, from Dane Jensen. 2026-06-15 21:47:01 +00:00
nicm
eb65331403 Be more strict about what names and titles we allow and reject them
immediately when possible, but allow # again for those directly set by
commands (but not escape sequences). From Barrett Ruth in GitHub issue
5175.
2026-06-15 21:41:39 +00:00
Thomas Adam
9031f59f7b Merge branch 'obsd-master' 2026-06-15 19:00:06 +01:00
nicm
b86bd1fcd0 Add pane-border-lines none like popups (-B to new-pane). 2026-06-15 17:34:25 +00:00
Thomas Adam
c156e597dc Merge branch 'obsd-master' 2026-06-15 16:15:07 +01:00
nicm
bb750b07bc If the client is blocked, defer the redraw because it may end up
partially discarded leading to redraw artifacts.
2026-06-15 15:05:12 +00:00
nicm
0e6fe5a097 Convert cursor position back to pane coordinates for tty_cmd_cell. 2026-06-15 14:56:30 +00:00
Thomas Adam
13de7cafb2 Merge branch 'obsd-master' 2026-06-15 13:30:08 +01:00
nicm
057b688fc7 Tidy up screen_redraw_get_visible_ranges by using a couple of
temporaries for start and end of range.
2026-06-15 12:07:49 +00:00
Thomas Adam
45bb3e7ffc Merge branch 'obsd-master' 2026-06-15 13:00:05 +01:00
nicm
8ced9c332c Use correct x position when drawing clipped line. 2026-06-15 11:45:51 +00:00
Thomas Adam
9fcd5ce03d Merge branch 'obsd-master' 2026-06-15 10:30:05 +01:00
nicm
a41e6e2ed2 Add -B to new-pane to select the floating pane border. 2026-06-15 09:21:40 +00:00
Thomas Adam
e54855ef88 Merge branch 'obsd-master' 2026-06-15 09:30:05 +01:00
nicm
7a18fa281d Add top-floating and bottom-floating to pane-border-status to show
status line only on floating panes.
2026-06-15 08:16:05 +00:00
Thomas Adam
aed9018807 Merge branch 'obsd-master' 2026-06-15 09:00:05 +01:00
nicm
97b5f22a70 When redrawing a whole pane line, fall into tty_draw_line for any
characters that are not plain ASCII. Fixes redraw issues with partial
tabs and other wide characters when a pane is partly outside the window.
2026-06-15 07:40:45 +00:00
Thomas Adam
748dab2f38 Merge branch 'obsd-master' 2026-06-14 22:15:05 +01:00
Thomas Adam
3c11129b04 Merge branch 'obsd-master' 2026-06-14 22:00:05 +01:00
nicm
6e0e343f23 Fix various errors in redrawing:
- Fix the active pane colour when only two panes and scrollbars enabled.

- Clip left and right scrollbars the same for floating panes.

- Do not subtract scrollbar width twice when working out width of status
  line.

- Check if a character is inside a visible range correctly (do not
  include the next position outside the range).
2026-06-14 20:53:20 +00:00
nicm
cc27470cb6 Skip floating panes when working out the top or bottom cell. Fixes
missing bottom status pane status line when floating panes exist.
2026-06-14 20:37:57 +00:00
Thomas Adam
fb527fa7db Merge branch 'obsd-master' 2026-06-14 21:00:05 +01:00
nicm
87976aa48e Add a helper to get pane-border-status for a window for some other
changes to come.
2026-06-14 19:31:37 +00:00
Thomas Adam
863923472a Merge branch 'obsd-master' 2026-06-14 20:15:07 +01:00
nicm
778a15b64a Take account of borders when resizing floating panes. 2026-06-14 18:59:15 +00:00
Thomas Adam
5eaf5570c4 Merge branch 'obsd-master' 2026-06-14 10:00:06 +01:00
Nicholas Marriott
cbe0f1f1e9 Another const from Jere Viikari. 2026-06-14 09:50:50 +01:00
nicm
97472e374f Return early if connect construct cell, reported by Jere Viikari. 2026-06-14 08:47:44 +00:00
Thomas Adam
7a28aa0dd7 Merge branch 'obsd-master' 2026-06-13 22:00:06 +01:00
nicm
b1054ac227 With mode-keys vi, keep cursor in the same position relative to the text
when scrolling. GitHub issue 5216 from Arseniy Simonov.
2026-06-13 20:39:11 +00:00
Thomas Adam
1618b98533 Merge branch 'obsd-master' 2026-06-13 21:15:06 +01:00
nicm
5b6ed54817 Add some missing const, from Jere Viikari. 2026-06-13 20:07:30 +00:00
nicm
d23a2b7e97 Skip floating cells when moving to previous cell for resize of tiled cells. 2026-06-13 20:03:10 +00:00
nicm
ab92b27226 Make the resize keys always change right and bottom borders for floating
panes which is more intuitive.
2026-06-13 19:57:44 +00:00
Thomas Adam
eb6efdf17a Merge branch 'obsd-master' 2026-06-13 20:00:06 +01:00
nicm
b182791052 Tidy up error messages from split-window. 2026-06-13 18:30:16 +00:00
Thomas Adam
97a7f174ce Merge branch 'obsd-master' 2026-06-13 19:15:06 +01:00
nicm
654758e622 Add move-pane -z to move a pane to a particular the z-index. 2026-06-13 18:06:01 +00:00
Thomas Adam
c8887a5846 Merge branch 'obsd-master' 2026-06-13 19:00:06 +01:00
nicm
56779f9542 Add Z index positions to move-pane -P. 2026-06-13 17:43:20 +00:00
Thomas Adam
f474838e22 Merge branch 'obsd-master' 2026-06-13 18:30:06 +01:00
nicm
19393e13db Add -P to move-pane to move a floating pane to a specific place
(top-left, bottom-right). Get rid of the not-so-useful default { and }
swap-pane bindings and use the keys instead for moving to top-left,
top-right and add M-{ and M-} for bottom-left, bottom-right.
2026-06-13 17:12:02 +00:00
Thomas Adam
301b10de8d Merge branch 'obsd-master' 2026-06-13 17:30:06 +01:00
nicm
aa2fd3a151 Add flags to move-pane to move floating panes around (-U, -D, -L, -R
similar to resize-pane; -X, -Y similar to new-pane).
2026-06-13 16:16:18 +00:00
Thomas Adam
2dd604949f Merge branch 'obsd-master' 2026-06-13 14:15:06 +01:00
nicm
ec481ac82a Missed this one as well. 2026-06-13 13:07:12 +00:00
nicm
7afee45635 Use correct name for activity time. 2026-06-13 13:06:12 +00:00
Thomas Adam
385d18590a Merge branch 'obsd-master' 2026-06-13 13:00:06 +01:00
nicm
bc9252f6e3 Remove some stray code from an old floating panes implementation, from
Dane Jensen.
2026-06-13 11:37:46 +00:00
Thomas Adam
6c76f2a7c7 Merge branch 'obsd-master' 2026-06-13 12:00:06 +01:00
nicm
bf187170b1 Extend client mode so the preview can be changed to a view with a
summary of the client terminal and its features, intended to make
troubleshooting easier. "choose-client -i" or the "i" key in the mode.
2026-06-13 10:32:54 +00:00
Thomas Adam
14837e8e0f Merge branch 'obsd-master' 2026-06-13 10:30:06 +01:00
nicm
b44cdf1006 Add an I format modifier to get some bits of information about a client
(terminal features, capabilities and environment).
2026-06-13 09:17:29 +00:00
Thomas Adam
5d73f90133 Merge branch 'obsd-master' 2026-06-13 10:15:06 +01:00
nicm
34a6a9d3a1 Change relative time for now to only work in the past and not show a
sign which is more useful. Also tidy up some minor style nits.
2026-06-13 08:59:52 +00:00
Thomas Adam
f92a4e611b Merge branch 'obsd-master' 2026-06-12 14:30:06 +01:00
jsg
b3deb9ec86 fix tty_draw_line() indentation; ok nicm@ 2026-06-12 13:16:36 +00:00
Nicholas Marriott
a92653f2b2 base -> bash from tanushnry at gmail dot com. 2026-06-12 10:18:44 +01:00
Thomas Adam
29cdab650b Merge branch 'obsd-master' 2026-06-12 00:15:06 +01:00
nicm
574a4eccb5 Make buffer creation time sort oldest first like it used to, but change
windows to match sessions and panes as newest first.
2026-06-11 23:01:31 +00:00
Thomas Adam
42c0641daf Merge branch 'obsd-master' 2026-06-11 21:15:06 +01:00
nicm
11b6e7844a When MODE_SYNC is on, all terminal updates need to deferred, not just
scrolling. Check the flag before each one. GitHub issue 4983.
2026-06-11 19:50:23 +00:00
Thomas Adam
f1b67152af Merge branch 'obsd-master' 2026-06-11 20:30:06 +01:00
nicm
b6be437bab Change send-keys -K to insert keys in the right place in the queue, like
we already do for key bindings. GitHub issue 3476.
2026-06-11 19:13:34 +00:00
Thomas Adam
3d7f421809 Merge branch 'obsd-master' 2026-06-11 15:30:06 +01:00
nicm
fa6d33f742 Add a helper to free the list of pending resizes. 2026-06-11 14:19:59 +00:00
Thomas Adam
dc22196394 Merge branch 'obsd-master' 2026-06-11 11:30:06 +01:00
nicm
3ea458b9dc Add support for floating panes to resize-pane, from Dane Jensen. 2026-06-11 10:16:19 +00:00
Thomas Adam
86128a7f06 Merge branch 'obsd-master' 2026-06-11 09:15:09 +01:00
nicm
de60750506 Hide Swap Marked also since floating panes cannot be swapped yet. 2026-06-11 07:52:17 +00:00
Thomas Adam
f07ac3068b Merge branch 'obsd-master' 2026-06-10 20:00:06 +01:00
nicm
7ec94fda08 Make list-keys only use a message if -1 is given, otherwise behave like
other commands (stdout or mode).
2026-06-10 18:32:33 +00:00
Thomas Adam
9231c80cd8 Merge branch 'obsd-master' 2026-06-10 17:15:07 +01:00
nicm
4702e58ee5 Fix text to refer to status-keys not mode-keys, GitHub issue 5191. 2026-06-10 16:08:03 +00:00
nicm
2719549cf2 Rename new-pane -B to -W since -B is best kept to match -B to
display-popup.
2026-06-10 16:03:14 +00:00
Thomas Adam
cd26f29fb8 Merge branch 'obsd-master' 2026-06-10 16:15:11 +01:00
Thomas Adam
9c350af582 Merge branch 'obsd-master' 2026-06-10 16:00:11 +01:00
nicm
c019b3dad9 Use correct size when calculating status line right trim. GitHub issue
5098 from Aung Myo Kyaw.
2026-06-10 14:51:20 +00:00
nicm
8cb4aabb8b Replace refresh-from-pane in copy mode with a way to automatically
update as pane content changes. This is toggled by pressing r. GitHub
issue 5165 from Barrett Ruth.
2026-06-10 14:29:08 +00:00
Thomas Adam
7ad8b3366d Merge branch 'obsd-master' 2026-06-10 15:15:08 +01:00
nicm
c6c8f77bc0 While grouped sessions are being killed, it can leave session which are
not really useful as targets (no active pane or current window) in the
tree. Make cmd_find_best_session skip these. GitHub issue 5167.
2026-06-10 14:06:45 +00:00
Nicholas Marriott
f415a70c0b Merge two SIXEL blocks. 2026-06-10 14:17:10 +01:00
Thomas Adam
da31d94c2a Merge branch 'obsd-master' 2026-06-10 08:50:17 +01:00
nicm
5c0db5a293 Preserve the original text in the first line for display-panes. 2026-06-09 21:31:16 +00:00
nicm
d533d7c97c Add a context for cell/palette/hyperlinks when drawing to tty to avoid
passing so much in parameters.
2026-06-09 21:22:22 +00:00
Thomas Adam
490802099d Merge branch 'obsd-master' 2026-06-09 14:15:07 +01:00
nicm
34dd42dc7f Use name as marker for failure not type (which must be set to be in the error). 2026-06-09 12:58:40 +00:00
nicm
b374868750 Add missing headers. 2026-06-09 12:57:40 +00:00
nicm
538688486e Clarify -a for kill-window, kill-session. 2026-06-09 12:51:57 +00:00
Thomas Adam
27111bd76d Merge branch 'obsd-master' 2026-06-09 13:30:06 +01:00
nicm
4a3f380fd2 Add -f for filter with -a to kill-pane, kill-window, kill-session.
GitHub issue 4782.
2026-06-09 12:24:59 +00:00
Thomas Adam
4ec7e66e90 Merge branch 'obsd-master' 2026-06-09 13:15:06 +01:00
nicm
a42e425d44 Two fixes for RI codepoints. Firstly, do not combine more than two of
them - previously we were ending up with four codepoints in one cell
which tmux believed to be width 2, but terminals considered width 4.
Secondly, invalidate cursor position before redrawing the cell when the
second codepoint is received, terminals vary in how they manage
backspace and cursor movement across these characters, so it is better
to use absolute rather than relative positioning. GitHub issue 4853.
2026-06-09 11:49:36 +00:00
Thomas Adam
2179c3e420 Merge branch 'obsd-master' 2026-06-09 10:30:06 +01:00
nicm
d5c9196348 Add -B to new-pane/split-window to block until the command exits, like
run-shell -b.
2026-06-09 09:11:05 +00:00
Thomas Adam
97156f3382 Merge branch 'obsd-master' 2026-06-09 09:30:06 +01:00
nicm
27c58c764d Do not use NULL arguments, bug spotted by Dane Jensen. 2026-06-09 08:11:53 +00:00
Thomas Adam
1564584ba9 Merge branch 'obsd-master' 2026-06-09 00:15:06 +01:00
nicm
fd10db5a62 Fix mouse events on tiled pane status line - when panes share a border,
prefer the pane for which the border is the status line. With Dane
Jensen.
2026-06-08 23:06:21 +00:00
Thomas Adam
6f683da2e9 Merge branch 'obsd-master' 2026-06-08 23:00:06 +01:00
nicm
a6a06c5aa6 Fix squash-groups skipping every session, GitHub issue 5180 from Bryce
Miller.
2026-06-08 21:46:19 +00:00
nicm
4d1ab1ba2a Allow ACLs to use groups as well as users, GitHub issue 4917. 2026-06-08 21:38:19 +00:00
Thomas Adam
d7a6a8e35b Merge branch 'obsd-master' 2026-06-08 22:30:06 +01:00
nicm
bf2e078ecf Add a relative time option for time formatting, GitHub issue 5009. 2026-06-08 21:19:52 +00:00
Thomas Adam
a25ce36925 Merge branch 'obsd-master' 2026-06-08 22:15:06 +01:00
nicm
a0f4038df2 Add a -h flag to choose-tree and choose-client to hide the pane
containing the mode, intended for use with floating panes. From Michael
Grant, GitHub issue 5177.
2026-06-08 21:01:33 +00:00
Thomas Adam
7d8c1ae295 Merge branch 'obsd-master' 2026-06-08 22:00:07 +01:00
nicm
df7c2e605b Add -T to new-pane to set the title; GitHub issue 5176 from Michael Grant. 2026-06-08 20:44:57 +00:00
nicm
ea51cdb3f2 Add display-panes-format to change the top-of-pane text shown with
display-panes.
2026-06-08 20:42:39 +00:00
nicm
fe986a52d6 When entering or leaving the alternate screen, discard any pending
resizes. Improves flicking with scrollbars and programs that leave and
enter the alternate screen on every WINCH like nano. GitHub issue 4772.

Cvs: ----------------------------------------------------------------------
2026-06-08 20:41:21 +00:00
nicm
34fd261a4f Add a -k flag to choose commands to kill the pane when the mode is
exited (useful with floating panes).
2026-06-08 20:38:54 +00:00
205 changed files with 12952 additions and 4850 deletions

View File

@@ -63,7 +63,7 @@ tmux -vv -Ltest -f/dev/null new
Or if you need your configuration: Or if you need your configuration:
~~~base ~~~bash
tmux kill-server tmux kill-server
tmux -vv new tmux -vv new
~~~ ~~~

2
.gitignore vendored
View File

@@ -7,6 +7,8 @@
*~ *~
.deps/ .deps/
.dirstamp .dirstamp
.serena/
AGENTS.md
Makefile Makefile
Makefile.in Makefile.in
aclocal.m4 aclocal.m4

View File

@@ -184,6 +184,8 @@ dist_tmux_SOURCES = \
paste.c \ paste.c \
popup.c \ popup.c \
proc.c \ proc.c \
prompt.c \
prompt-history.c \
regsub.c \ regsub.c \
resize.c \ resize.c \
screen-redraw.c \ screen-redraw.c \
@@ -209,12 +211,14 @@ dist_tmux_SOURCES = \
tty.c \ tty.c \
utf8-combined.c \ utf8-combined.c \
utf8.c \ utf8.c \
window-border.c \
window-buffer.c \ window-buffer.c \
window-client.c \ window-client.c \
window-clock.c \ window-clock.c \
window-copy.c \ window-copy.c \
window-customize.c \ window-customize.c \
window-tree.c \ window-tree.c \
window-visible.c \
window.c \ window.c \
xmalloc.c \ xmalloc.c \
xmalloc.h xmalloc.h

View File

@@ -998,7 +998,7 @@ args_string_percentage(const char *value, long long minval, long long maxval,
copy = xstrdup(value); copy = xstrdup(value);
copy[valuelen - 1] = '\0'; copy[valuelen - 1] = '\0';
ll = strtonum(copy, 0, 100, &errstr); ll = strtonum(copy, 0, 1000, &errstr);
free(copy); free(copy);
if (errstr != NULL) { if (errstr != NULL) {
*cause = xstrdup(errstr); *cause = xstrdup(errstr);
@@ -1066,7 +1066,7 @@ args_string_percentage_and_expand(const char *value, long long minval,
copy[valuelen - 1] = '\0'; copy[valuelen - 1] = '\0';
f = format_single_from_target(item, copy); f = format_single_from_target(item, copy);
ll = strtonum(f, 0, 100, &errstr); ll = strtonum(f, 0, 1000, &errstr);
free(f); free(f);
free(copy); free(copy);
if (errstr != NULL) { if (errstr != NULL) {

2
cfg.c
View File

@@ -56,7 +56,7 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data)
if (cfg_item != NULL) if (cfg_item != NULL)
cmdq_continue(cfg_item); cmdq_continue(cfg_item);
status_prompt_load_history(); prompt_load_history();
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -58,9 +58,14 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
struct session *dst_s = target->s; struct session *dst_s = target->s;
struct window_pane *wp = source->wp; struct window_pane *wp = source->wp;
struct window *w = wl->window; struct window *w = wl->window;
char *name, *cause, *cp; char *newname, *cause, *cp;
int idx = target->idx, before; int idx = target->idx, before;
const char *template; const char *template, *name = args_get(args, 'n');
if (name != NULL && !check_name(name, WINDOW_NAME_FORBID)) {
cmdq_error(item, "invalid window name: %s", name);
return (CMD_RETURN_ERROR);
}
before = args_has(args, 'b'); before = args_has(args, 'b');
if (args_has(args, 'a') || before) { if (args_has(args, 'a') || before) {
@@ -80,8 +85,8 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
free(cause); free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (args_has(args, 'n')) { if (name != NULL) {
window_set_name(w, args_get(args, 'n')); window_set_name(w, name, WINDOW_NAME_FORBID);
options_set_number(w->options, "automatic-rename", 0); options_set_number(w->options, "automatic-rename", 0);
} }
server_unlink_window(src_s, wl); server_unlink_window(src_s, wl);
@@ -109,12 +114,12 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
w->active = wp; w->active = wp;
w->latest = tc; w->latest = tc;
if (!args_has(args, 'n')) { if (name == NULL) {
name = default_window_name(w); newname = default_window_name(w);
window_set_name(w, name); window_set_name(w, newname, WINDOW_NAME_FORBID);
free(name); free(newname);
} else { } else {
window_set_name(w, args_get(args, 'n')); window_set_name(w, name, 0);
options_set_number(w->options, "automatic-rename", 0); options_set_number(w->options, "automatic-rename", 0);
} }

View File

@@ -318,6 +318,7 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item)
grid_clear_history(wp->base.grid); grid_clear_history(wp->base.grid);
if (args_has(args, 'H')) if (args_has(args, 'H'))
screen_reset_hyperlinks(wp->screen); screen_reset_hyperlinks(wp->screen);
server_redraw_window(wp->window);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -33,8 +33,8 @@ const struct cmd_entry cmd_choose_tree_entry = {
.name = "choose-tree", .name = "choose-tree",
.alias = NULL, .alias = NULL,
.args = { "F:f:GK:NO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse }, .args = { "F:f:GhK:kNO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-GNrswZ] [-F format] [-f filter] [-K key-format] " .usage = "[-GhkNrswZ] [-F format] [-f filter] [-K key-format] "
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -47,8 +47,8 @@ const struct cmd_entry cmd_choose_client_entry = {
.name = "choose-client", .name = "choose-client",
.alias = NULL, .alias = NULL,
.args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, .args = { "F:f:hiK:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] " .usage = "[-hikNrZ] [-F format] [-f filter] [-K key-format] "
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -61,8 +61,8 @@ const struct cmd_entry cmd_choose_buffer_entry = {
.name = "choose-buffer", .name = "choose-buffer",
.alias = NULL, .alias = NULL,
.args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, .args = { "F:f:K:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] " .usage = "[-kNrZ] [-F format] [-f filter] [-K key-format] "
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -75,8 +75,8 @@ const struct cmd_entry cmd_customize_mode_entry = {
.name = "customize-mode", .name = "customize-mode",
.alias = NULL, .alias = NULL,
.args = { "F:f:Nt:yZ", 0, 0, NULL }, .args = { "F:f:kNt:yZ", 0, 0, NULL },
.usage = "[-NZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE, .usage = "[-kNZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },

View File

@@ -34,16 +34,16 @@ static enum args_parse_type cmd_command_prompt_args_parse(struct args *,
static enum cmd_retval cmd_command_prompt_exec(struct cmd *, static enum cmd_retval cmd_command_prompt_exec(struct cmd *,
struct cmdq_item *); struct cmdq_item *);
static int cmd_command_prompt_callback(struct client *, void *, static enum prompt_result cmd_command_prompt_callback(struct client *, void *,
const char *, int); const char *, enum prompt_key_result);
static void cmd_command_prompt_free(void *); static void cmd_command_prompt_free(void *);
const struct cmd_entry cmd_command_prompt_entry = { const struct cmd_entry cmd_command_prompt_entry = {
.name = "command-prompt", .name = "command-prompt",
.alias = NULL, .alias = NULL,
.args = { "1CbeFiklI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse }, .args = { "1CbeFiklI:NPp:t:T:", 0, 1, cmd_command_prompt_args_parse },
.usage = "[-1CbeFiklN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE .usage = "[-1CbeFiklNP] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE
" [-T prompt-type] [template]", " [-T prompt-type] [template]",
.flags = CMD_CLIENT_TFLAG, .flags = CMD_CLIENT_TFLAG,
@@ -62,6 +62,8 @@ struct cmd_command_prompt_cdata {
int flags; int flags;
enum prompt_type prompt_type; enum prompt_type prompt_type;
struct window_pane *wp;
struct cmd_command_prompt_prompt *prompts; struct cmd_command_prompt_prompt *prompts;
u_int count; u_int count;
u_int current; u_int current;
@@ -87,10 +89,15 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_command_prompt_cdata *cdata; struct cmd_command_prompt_cdata *cdata;
char *tmp, *prompts, *prompt, *next_prompt; char *tmp, *prompts, *prompt, *next_prompt;
char *inputs = NULL, *next_input; char *inputs = NULL, *next_input;
struct window_pane *wp = target->wp;
u_int count = args_count(args); u_int count = args_count(args);
int wait = !args_has(args, 'b'), space = 1; int wait = !args_has(args, 'b'), space = 1;
int pane = args_has(args, 'P');
if (tc->prompt_string != NULL) if (pane) {
if (wp == NULL || window_pane_has_prompt(wp))
return (CMD_RETURN_NORMAL);
} else if (tc->prompt != NULL)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (args_has(args, 'i')) if (args_has(args, 'i'))
wait = 0; wait = 0;
@@ -98,6 +105,8 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
cdata = xcalloc(1, sizeof *cdata); cdata = xcalloc(1, sizeof *cdata);
if (wait) if (wait)
cdata->item = item; cdata->item = item;
if (pane)
cdata->wp = wp;
cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait, cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait,
args_has(args, 'F')); args_has(args, 'F'));
@@ -146,7 +155,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
} }
if ((type = args_get(args, 'T')) != NULL) { if ((type = args_get(args, 'T')) != NULL) {
cdata->prompt_type = status_prompt_type(type); cdata->prompt_type = prompt_type(type);
if (cdata->prompt_type == PROMPT_TYPE_INVALID) { if (cdata->prompt_type == PROMPT_TYPE_INVALID) {
cmdq_error(item, "unknown type: %s", type); cmdq_error(item, "unknown type: %s", type);
cmd_command_prompt_free(cdata); cmd_command_prompt_free(cdata);
@@ -167,18 +176,27 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
cdata->flags |= PROMPT_BSPACE_EXIT; cdata->flags |= PROMPT_BSPACE_EXIT;
if (args_has(args, 'C')) if (args_has(args, 'C'))
cdata->flags |= PROMPT_NOFREEZE; cdata->flags |= PROMPT_NOFREEZE;
if (pane) {
cdata->flags |= PROMPT_ISPANE;
window_pane_set_prompt(wp, tc, target, cdata->prompts[0].prompt,
cdata->prompts[0].input, cmd_command_prompt_callback,
cmd_command_prompt_free, cdata, cdata->flags,
cdata->prompt_type);
} else {
status_prompt_set(tc, target, cdata->prompts[0].prompt, status_prompt_set(tc, target, cdata->prompts[0].prompt,
cdata->prompts[0].input, cmd_command_prompt_callback, cdata->prompts[0].input, cmd_command_prompt_callback,
cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type); cmd_command_prompt_free, cdata, cdata->flags,
cdata->prompt_type);
}
if (!wait) if (!wait)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
return (CMD_RETURN_WAIT); return (CMD_RETURN_WAIT);
} }
static int static enum prompt_result
cmd_command_prompt_callback(struct client *c, void *data, const char *s, cmd_command_prompt_callback(struct client *c, void *data, const char *s,
int done) enum prompt_key_result key)
{ {
struct cmd_command_prompt_cdata *cdata = data; struct cmd_command_prompt_cdata *cdata = data;
char *error; char *error;
@@ -188,26 +206,30 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
int argc = 0; int argc = 0;
char **argv = NULL; char **argv = NULL;
if (s == NULL) if (s == NULL || key == PROMPT_KEY_MOVE)
goto out; goto out;
if (done) { if (key == PROMPT_KEY_CLOSE) {
if (cdata->flags & PROMPT_INCREMENTAL) if (cdata->flags & PROMPT_INCREMENTAL)
goto out; goto out;
cmd_append_argv(&cdata->argc, &cdata->argv, s); cmd_append_argv(&cdata->argc, &cdata->argv, s);
if (++cdata->current != cdata->count) { if (++cdata->current != cdata->count) {
prompt = &cdata->prompts[cdata->current]; prompt = &cdata->prompts[cdata->current];
status_prompt_update(c, prompt->prompt, prompt->input); if (cdata->wp != NULL) {
return (1); window_pane_update_prompt(cdata->wp,
prompt->prompt, prompt->input);
} else
status_prompt_update(c, prompt->prompt,
prompt->input);
return (PROMPT_CONTINUE);
} }
} }
argc = cdata->argc; argc = cdata->argc;
argv = cmd_copy_argv(cdata->argc, cdata->argv); argv = cmd_copy_argv(cdata->argc, cdata->argv);
if (!done) if (key != PROMPT_KEY_CLOSE)
cmd_append_argv(&argc, &argv, s); cmd_append_argv(&argc, &argv, s);
else {
if (done) {
cmd_free_argv(cdata->argc, cdata->argv); cmd_free_argv(cdata->argc, cdata->argv);
cdata->argc = argc; cdata->argc = argc;
cdata->argv = cmd_copy_argv(argc, argv); cdata->argv = cmd_copy_argv(argc, argv);
@@ -226,13 +248,20 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
} }
cmd_free_argv(argc, argv); cmd_free_argv(argc, argv);
if (c->prompt_inputcb != cmd_command_prompt_callback) /*
return (1); * An incremental prompt fires its callback on every edit but must stay
* open for further typing; only an explicit close (handled above) ends
* it.
*/
if (cdata->flags & PROMPT_INCREMENTAL)
return (PROMPT_CONTINUE);
out: out:
if (item != NULL) if (item != NULL) {
cdata->item = NULL;
cmdq_continue(item); cmdq_continue(item);
return (0); }
return (PROMPT_CLOSE);
} }
static void static void
@@ -241,6 +270,11 @@ cmd_command_prompt_free(void *data)
struct cmd_command_prompt_cdata *cdata = data; struct cmd_command_prompt_cdata *cdata = data;
u_int i; u_int i;
if (cdata->item != NULL) {
cmdq_continue(cdata->item);
cdata->item = NULL;
}
for (i = 0; i < cdata->count; i++) { for (i = 0; i < cdata->count; i++) {
free(cdata->prompts[i].prompt); free(cdata->prompts[i].prompt);
free(cdata->prompts[i].input); free(cdata->prompts[i].input);

View File

@@ -33,8 +33,8 @@ static enum args_parse_type cmd_confirm_before_args_parse(struct args *,
static enum cmd_retval cmd_confirm_before_exec(struct cmd *, static enum cmd_retval cmd_confirm_before_exec(struct cmd *,
struct cmdq_item *); struct cmdq_item *);
static int cmd_confirm_before_callback(struct client *, void *, static enum prompt_result cmd_confirm_before_callback(struct client *, void *,
const char *, int); const char *, enum prompt_key_result);
static void cmd_confirm_before_free(void *); static void cmd_confirm_before_free(void *);
const struct cmd_entry cmd_confirm_before_entry = { const struct cmd_entry cmd_confirm_before_entry = {
@@ -118,9 +118,9 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_WAIT); return (CMD_RETURN_WAIT);
} }
static int static enum prompt_result
cmd_confirm_before_callback(struct client *c, void *data, const char *s, cmd_confirm_before_callback(struct client *c, void *data, const char *s,
__unused int done) __unused enum prompt_key_result key)
{ {
struct cmd_confirm_before_data *cdata = data; struct cmd_confirm_before_data *cdata = data;
struct cmdq_item *item = cdata->item, *new_item; struct cmdq_item *item = cdata->item, *new_item;
@@ -151,7 +151,7 @@ out:
cmdq_get_client(item)->retval = retcode; cmdq_get_client(item)->retval = retcode;
cmdq_continue(item); cmdq_continue(item);
} }
return (0); return (PROMPT_CLOSE);
} }
static void static void

View File

@@ -48,6 +48,16 @@ struct cmd_display_panes_data {
struct args_command_state *state; struct args_command_state *state;
}; };
struct cmd_display_panes_ctx {
struct client *c;
int ox;
int oy;
u_int sx;
u_int sy;
u_int statuslines;
int statustop;
};
static enum args_parse_type static enum args_parse_type
cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx, cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx,
__unused char **cause) __unused char **cause)
@@ -56,7 +66,7 @@ cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx,
} }
static void static void
cmd_display_panes_put(struct screen_redraw_ctx *ctx, cmd_display_panes_put(struct cmd_display_panes_ctx *ctx,
struct window_pane *wp, u_int cx, u_int cy, const char *buf, size_t len) struct window_pane *wp, u_int cx, u_int cy, const char *buf, size_t len)
{ {
struct client *c = ctx->c; struct client *c = ctx->c;
@@ -65,8 +75,7 @@ cmd_display_panes_put(struct screen_redraw_ctx *ctx,
struct visible_range *ri; struct visible_range *ri;
u_int i, j; u_int i, j;
r = screen_redraw_get_visible_ranges(wp, ctx->ox + cx, ctx->oy + cy, r = window_visible_ranges(wp, ctx->ox + cx, ctx->oy + cy, len, NULL);
len, NULL);
for (i = 0; i < r->used; i++) { for (i = 0; i < r->used; i++) {
ri = &r->ranges[i]; ri = &r->ranges[i];
for (j = ri->px; j < ri->px + ri->nx; j++) { for (j = ri->px; j < ri->px + ri->nx; j++) {
@@ -77,7 +86,43 @@ cmd_display_panes_put(struct screen_redraw_ctx *ctx,
} }
static void static void
cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, cmd_display_panes_draw_format(struct cmd_display_panes_ctx *ctx,
struct window_pane *wp, u_int xoff, u_int yoff, u_int sx,
const struct grid_cell *gc)
{
struct client *c = ctx->c;
struct tty *tty = &c->tty;
struct session *s = c->session;
struct screen *sc = wp->screen, screen;
struct screen_write_ctx sctx;
struct visible_ranges *r;
struct visible_range *ri;
const char *format;
char *expanded;
u_int i, px = ctx->ox + xoff;
format = options_get_string(s->options, "display-panes-format");
expanded = format_single(NULL, format, c, s, s->curw, wp);
screen_init(&screen, sx, 1, 0);
screen_write_start(&sctx, &screen);
screen_write_fast_copy(&sctx, sc, 0, sc->grid->hsize, sx, 1);
screen_write_cursormove(&sctx, 0, 0, 0);
format_draw(&sctx, gc, sx, expanded, NULL, 0);
screen_write_stop(&sctx);
free(expanded);
r = window_visible_ranges(wp, px, wp->yoff, sx, NULL);
for (i = 0; i < r->used; i++) {
ri = &r->ranges[i];
tty_draw_line(tty, &screen, ri->px - px, 0, ri->nx,
ri->px - ctx->ox, yoff, NULL);
}
screen_free(&screen);
}
static void
cmd_display_panes_draw_pane(struct cmd_display_panes_ctx *ctx,
struct window_pane *wp) struct window_pane *wp)
{ {
struct client *c = ctx->c; struct client *c = ctx->c;
@@ -88,9 +133,10 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
struct grid_cell fgc, bgc; struct grid_cell fgc, bgc;
u_int pane, idx, px, py, i, j, xoff, yoff, sx, sy; u_int pane, idx, px, py, i, j, xoff, yoff, sx, sy;
u_int cx, cy; u_int cx, cy;
int colour, active_colour; const char *name;
char buf[16], lbuf[16], rbuf[16], *ptr; struct format_tree *ft;
size_t len, llen, rlen; char buf[16], lbuf[16], *ptr;
size_t len, llen;
if (wp->xoff + (int)wp->sx <= ctx->ox || if (wp->xoff + (int)wp->sx <= ctx->ox ||
wp->xoff >= ctx->ox + (int)ctx->sx || wp->xoff >= ctx->ox + (int)ctx->sx ||
@@ -146,27 +192,24 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
if (sx < len) if (sx < len)
return; return;
colour = options_get_number(oo, "display-panes-colour"); if (w->active == wp)
active_colour = options_get_number(oo, "display-panes-active-colour"); name = "display-panes-active-colour";
else
name = "display-panes-colour";
ft = format_create_defaults(NULL, c, s, NULL, wp);
style_apply(&fgc, oo, name, ft);
format_free(ft);
memcpy(&fgc, &grid_default_cell, sizeof fgc);
memcpy(&bgc, &grid_default_cell, sizeof bgc); memcpy(&bgc, &grid_default_cell, sizeof bgc);
if (w->active == wp) { bgc.bg = fgc.fg;
fgc.fg = active_colour;
bgc.bg = active_colour;
} else {
fgc.fg = colour;
bgc.bg = colour;
}
rlen = xsnprintf(rbuf, sizeof rbuf, "%ux%u", wp->sx, wp->sy);
if (pane > 9 && pane < 35) if (pane > 9 && pane < 35)
llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10)); llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10));
else else
llen = 0; llen = 0;
if (sx < len * 6 || sy < 5) { if (sx < len * 6 || sy < 5) {
tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); tty_attributes(tty, &fgc, NULL);
if (sx >= len + llen + 1) { if (sx >= len + llen + 1) {
len += llen + 1; len += llen + 1;
cx = xoff + px - len / 2; cx = xoff + px - len / 2;
@@ -187,7 +230,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
px -= len * 3; px -= len * 3;
py -= 2; py -= 2;
tty_attributes(tty, &bgc, &grid_default_cell, NULL, NULL); tty_attributes(tty, &bgc, NULL);
for (ptr = buf; *ptr != '\0'; ptr++) { for (ptr = buf; *ptr != '\0'; ptr++) {
if (*ptr < '0' || *ptr > '9') if (*ptr < '0' || *ptr > '9')
continue; continue;
@@ -207,13 +250,9 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
if (sy <= 6) if (sy <= 6)
goto out; goto out;
tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); cmd_display_panes_draw_format(ctx, wp, xoff, yoff, sx, &fgc);
if (rlen != 0 && sx >= rlen) {
cx = xoff + sx - rlen;
cy = yoff;
cmd_display_panes_put(ctx, wp, cx, cy, rbuf, rlen);
}
if (llen != 0) { if (llen != 0) {
tty_attributes(tty, &fgc, NULL);
cx = xoff + sx / 2 + len * 3 - llen - 1; cx = xoff + sx / 2 + len * 3 - llen - 1;
cy = yoff + py + 5; cy = yoff + py + 5;
cmd_display_panes_put(ctx, wp, cx, cy, lbuf, llen); cmd_display_panes_put(ctx, wp, cx, cy, lbuf, llen);
@@ -224,17 +263,30 @@ out:
} }
static void static void
cmd_display_panes_draw(struct client *c, __unused void *data, cmd_display_panes_draw(struct client *c, __unused void *data)
struct screen_redraw_ctx *ctx)
{ {
struct window *w = c->session->curw->window; struct session *s = c->session;
struct window *w = s->curw->window;
struct window_pane *wp; struct window_pane *wp;
struct cmd_display_panes_ctx ctx;
u_int lines;
log_debug("%s: %s @%u", __func__, c->name, w->id); log_debug("%s: %s @%u", __func__, c->name, w->id);
memset(&ctx, 0, sizeof ctx);
ctx.c = c;
tty_window_offset(&c->tty, &ctx.ox, &ctx.oy, &ctx.sx, &ctx.sy);
if (options_get_number(s->options, "status-position") == 0) {
lines = status_line_size(c);
if (c->message_string != NULL || c->prompt != NULL)
lines = (lines == 0 ? 1 : lines);
ctx.statuslines = lines;
ctx.statustop = 1;
}
TAILQ_FOREACH(wp, &w->panes, entry) { TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_visible(wp)) if (window_pane_is_visible(wp))
cmd_display_panes_draw_pane(ctx, wp); cmd_display_panes_draw_pane(&ctx, wp);
} }
} }

View File

@@ -147,6 +147,18 @@ cmd_find_session_better(struct session *s, struct session *than, int flags)
return (timercmp(&s->activity_time, &than->activity_time, >)); return (timercmp(&s->activity_time, &than->activity_time, >));
} }
/* Can this session be usefully targeted? */
static int
cmd_find_session_valid(struct session *s)
{
if (!session_alive(s) ||
s->curw == NULL ||
s->curw->window == NULL ||
s->curw->window->active == NULL)
return (0);
return (1);
}
/* Find best session from a list, or all if list is NULL. */ /* Find best session from a list, or all if list is NULL. */
static struct session * static struct session *
cmd_find_best_session(struct session **slist, u_int ssize, int flags) cmd_find_best_session(struct session **slist, u_int ssize, int flags)
@@ -159,11 +171,15 @@ cmd_find_best_session(struct session **slist, u_int ssize, int flags)
s = NULL; s = NULL;
if (slist != NULL) { if (slist != NULL) {
for (i = 0; i < ssize; i++) { for (i = 0; i < ssize; i++) {
if (!cmd_find_session_valid(slist[i]))
continue;
if (cmd_find_session_better(slist[i], s, flags)) if (cmd_find_session_better(slist[i], s, flags))
s = slist[i]; s = slist[i];
} }
} else { } else {
RB_FOREACH(s_loop, sessions, &sessions) { RB_FOREACH(s_loop, sessions, &sessions) {
if (!cmd_find_session_valid(s_loop))
continue;
if (cmd_find_session_better(s_loop, s, flags)) if (cmd_find_session_better(s_loop, s, flags))
s = s_loop; s = s_loop;
} }

View File

@@ -30,6 +30,9 @@
*/ */
static enum cmd_retval cmd_join_pane_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_join_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_join_pane_mouse_update(struct cmdq_item *);
static void cmd_join_pane_mouse_move(struct client *,
struct mouse_event *);
const struct cmd_entry cmd_join_pane_entry = { const struct cmd_entry cmd_join_pane_entry = {
.name = "join-pane", .name = "join-pane",
@@ -49,8 +52,10 @@ const struct cmd_entry cmd_move_pane_entry = {
.name = "move-pane", .name = "move-pane",
.alias = "movep", .alias = "movep",
.args = { "bdfhvp:l:s:t:", 0, 0, NULL }, .args = { "bdfhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL },
.usage = "[-bdfhv] [-l size] " CMD_SRCDST_PANE_USAGE, .usage = "[-bdfhMv] [-D lines] [-l size] [-L columns] [-P position] "
"[-R columns] " CMD_SRCDST_PANE_USAGE " [-U lines] "
"[-X x-position] [-Y y-position] [-z z-index]",
.source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED }, .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED },
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -59,6 +64,305 @@ const struct cmd_entry cmd_move_pane_entry = {
.exec = cmd_join_pane_exec .exec = cmd_join_pane_exec
}; };
static enum cmd_retval
cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
struct window_pane *wp, const char *position)
{
struct window *w = wl->window;
struct layout_cell *lc = wp->layout_cell;
struct window_pane *owp;
int wx = w->sx, wy = w->sy, px = lc->sx;
int py = lc->sy, xoff = lc->xoff, yoff = lc->yoff;
int border = 1;
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
border = 0;
if (strcmp(position, "top-left") == 0) {
xoff = border;
yoff = border;
} else if (strcmp(position, "top-centre") == 0 ||
strcmp(position, "top-center") == 0) {
xoff = (wx - px) / 2;
yoff = border;
} else if (strcmp(position, "top-right") == 0) {
xoff = wx - px - border;
yoff = border;
} else if (strcmp(position, "centre-left") == 0 ||
strcmp(position, "center-left") == 0) {
xoff = border;
yoff = (wy - py) / 2;
} else if (strcmp(position, "centre") == 0 ||
strcmp(position, "center") == 0) {
xoff = (wx - px) / 2;
yoff = (wy - py) / 2;
} else if (strcmp(position, "centre-right") == 0 ||
strcmp(position, "center-right") == 0) {
xoff = wx - px - border;
yoff = (wy - py) / 2;
} else if (strcmp(position, "bottom-left") == 0) {
xoff = border;
yoff = wy - py - border;
} else if (strcmp(position, "bottom-centre") == 0 ||
strcmp(position, "bottom-center") == 0) {
xoff = (wx - px) / 2;
yoff = wy - py - border;
} else if (strcmp(position, "bottom-right") == 0) {
xoff = wx - px - border;
yoff = wy - py - border;
} else if (strcmp(position, "top-left-centre") == 0 ||
strcmp(position, "top-left-center") == 0) {
xoff = wx / 4 - px / 2;
yoff = wy / 4 - py / 2;
} else if (strcmp(position, "top-right-centre") == 0 ||
strcmp(position, "top-right-center") == 0) {
xoff = (3 * wx) / 4 - px / 2;
yoff = wy / 4 - py / 2;
} else if (strcmp(position, "bottom-left-centre") == 0 ||
strcmp(position, "bottom-left-center") == 0) {
xoff = wx / 4 - px / 2;
yoff = (3 * wy) / 4 - py / 2;
} else if (strcmp(position, "bottom-right-centre") == 0 ||
strcmp(position, "bottom-right-center") == 0) {
xoff = (3 * wx) / 4 - px / 2;
yoff = (3 * wy) / 4 - py / 2;
} else if (strcmp(position, "front") == 0) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
} else if (strcmp(position, "back") == 0) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp))
break;
}
if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry);
else
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
} else if (strcmp(position, "forward") == 0) {
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
if (owp != NULL) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_BEFORE(owp, wp, zentry);
}
} else if (strcmp(position, "backward") == 0) {
owp = TAILQ_NEXT(wp, zentry);
if (owp != NULL && window_pane_is_floating(owp)) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
}
} else if (strcmp(position, "forward-loop") == 0) {
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
TAILQ_REMOVE(&w->z_index, wp, zentry);
if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry);
else {
TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp))
break;
}
if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry);
else
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
}
} else if (strcmp(position, "backward-loop") == 0) {
owp = TAILQ_NEXT(wp, zentry);
if (owp != NULL && window_pane_is_floating(owp)) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
} else {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
}
} else {
cmdq_error(item, "unknown position: %s", position);
return (CMD_RETURN_ERROR);
}
if (xoff != lc->xoff || yoff != lc->yoff) {
lc->xoff = xoff;
lc->yoff = yoff;
layout_fix_panes(w, NULL);
}
notify_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_join_pane_move(struct cmdq_item *item, struct args *args,
struct winlink *wl, struct window_pane *wp)
{
struct window *w = wl->window;
struct layout_cell *lc = wp->layout_cell;
const char *errstr, *argval;
const char flags[] = { 'U', 'D', 'L', 'R' };
char *cause = NULL, flag;
int xoff = lc->xoff, yoff = lc->yoff, adjust;
u_int i;
enum pane_lines lines = window_pane_get_pane_lines(wp);
if (args_has(args, 'X')) {
xoff = args_percentage_and_expand(args, 'X', -(int)w->sx,
w->sx, w->sx, item, &cause);
if (cause != NULL) {
cmdq_error(item, "position %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
if (lines != PANE_LINES_NONE)
xoff += 1;
}
if (args_has(args, 'Y')) {
yoff = args_percentage_and_expand(args, 'Y', -(int)w->sy,
w->sy, w->sy, item, &cause);
if (cause != NULL) {
cmdq_error(item, "position %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
if (lines != PANE_LINES_NONE)
yoff += 1;
}
for (i = 0; i < nitems(flags); i++) {
flag = flags[i];
if (!args_has(args, flag))
continue;
argval = args_get(args, flag);
if (argval == NULL)
argval = "1";
adjust = strtonum(argval, INT_MIN, INT_MAX, &errstr);
if (errstr != NULL) {
cmdq_error(item, "offset %s", errstr);
return (CMD_RETURN_ERROR);
}
if (flag == 'U')
yoff -= adjust;
else if (flag == 'D')
yoff += adjust;
else if (flag == 'L')
xoff -= adjust;
else
xoff += adjust;
}
if (xoff != lc->xoff || yoff != lc->yoff) {
lc->xoff = xoff;
lc->yoff = yoff;
layout_fix_panes(w, NULL);
notify_window("window-layout-changed", w);
server_redraw_window(w);
}
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_join_pane_mouse_update(struct cmdq_item *item)
{
struct cmd_find_state *target = cmdq_get_target(item);
struct key_event *event = cmdq_get_event(item);
struct client *c = cmdq_get_client(item);
struct session *s = target->s;
struct winlink *wl;
struct window *w;
struct window_pane *wp;
if (!event->m.valid)
return (CMD_RETURN_NORMAL);
wp = cmd_mouse_pane(&event->m, &s, &wl);
if (wp == NULL || c == NULL || c->session != s)
return (CMD_RETURN_NORMAL);
if (!window_pane_is_floating(wp))
return (CMD_RETURN_NORMAL);
w = wl->window;
window_redraw_active_switch(w, wp);
window_set_active_pane(w, wp, 1);
c->tty.mouse_drag_update = cmd_join_pane_mouse_move;
cmd_join_pane_mouse_move(c, &event->m);
return (CMD_RETURN_NORMAL);
}
static void
cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
{
struct winlink *wl;
struct window *w;
struct window_pane *wp;
struct layout_cell *lc;
int y, ly, x, lx;
wp = cmd_mouse_pane(m, NULL, &wl);
if (wp == NULL) {
c->tty.mouse_drag_update = NULL;
return;
}
w = wl->window;
lc = wp->layout_cell;
y = m->y + m->oy; x = m->x + m->ox;
if (m->statusat == 0 && y >= (int)m->statuslines)
y -= m->statuslines;
else if (m->statusat > 0 && y >= m->statusat)
y = m->statusat - 1;
ly = m->ly + m->oy; lx = m->lx + m->ox;
if (m->statusat == 0 && ly >= (int)m->statuslines)
ly -= m->statuslines;
else if (m->statusat > 0 && ly >= m->statusat)
ly = m->statusat - 1;
if (x != lx || y != ly) {
lc->xoff += x - lx;
lc->yoff += y - ly;
layout_fix_panes(w, NULL);
server_redraw_window(w);
server_redraw_window_borders(w);
}
}
static enum cmd_retval
cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl,
struct window_pane *wp, const char *s)
{
struct window *w = wl->window;
struct window_pane *owp;
const char *errstr;
u_int n, z;
z = strtonum(s, 0, UINT_MAX, &errstr);
if (errstr != NULL) {
cmdq_error(item, "z-index %s", errstr);
return (CMD_RETURN_ERROR);
}
TAILQ_REMOVE(&w->z_index, wp, zentry);
n = 0;
TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp))
break;
if (n >= z)
break;
n++;
}
if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry);
else
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
notify_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval static enum cmd_retval
cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
{ {
@@ -70,6 +374,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
struct winlink *src_wl, *dst_wl; struct winlink *src_wl, *dst_wl;
struct window *src_w, *dst_w; struct window *src_w, *dst_w;
struct window_pane *src_wp, *dst_wp; struct window_pane *src_wp, *dst_wp;
const char *s;
char *cause = NULL; char *cause = NULL;
int flags = 0, dst_idx; int flags = 0, dst_idx;
struct layout_cell *lc; struct layout_cell *lc;
@@ -81,6 +386,26 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
dst_idx = dst_wl->idx; dst_idx = dst_wl->idx;
server_unzoom_window(dst_w); server_unzoom_window(dst_w);
if (cmd_get_entry(self) == &cmd_move_pane_entry) {
if (args_has(args, 'M'))
return (cmd_join_pane_mouse_update(item));
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));
if (args_has(args, 'X') ||
args_has(args, 'Y') ||
args_has(args, 'U') ||
args_has(args, 'D') ||
args_has(args, 'L') ||
args_has(args, 'R'))
return (cmd_join_pane_move(item, args, dst_wl, dst_wp));
}
src_wl = source->wl; src_wl = source->wl;
src_wp = source->wp; src_wp = source->wp;
src_w = src_wl->window; src_w = src_wl->window;

View File

@@ -27,13 +27,17 @@
*/ */
static enum cmd_retval cmd_kill_pane_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_kill_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_kill_pane_all(struct cmdq_item *, const char *);
static int cmd_kill_pane_filter(struct cmdq_item *,
struct session *, struct winlink *,
struct window_pane *, const char *);
const struct cmd_entry cmd_kill_pane_entry = { const struct cmd_entry cmd_kill_pane_entry = {
.name = "kill-pane", .name = "kill-pane",
.alias = "killp", .alias = "killp",
.args = { "at:", 0, 0, NULL }, .args = { "af:t:", 0, 0, NULL },
.usage = "[-a] " CMD_TARGET_PANE_USAGE, .usage = "[-a] [-f filter] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -46,14 +50,40 @@ cmd_kill_pane_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 winlink *wl = target->wl; struct window_pane *wp = target->wp;
struct window_pane *loopwp, *tmpwp, *wp = target->wp; const char *filter = args_get(args, 'f');
if (filter != NULL && !args_has(args, 'a')) {
cmdq_error(item, "-f only valid with -a");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'a'))
return (cmd_kill_pane_all(item, filter));
if (wp == NULL) {
cmdq_error(item, "no active pane to kill");
return (CMD_RETURN_ERROR);
}
server_kill_pane(wp);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_kill_pane_all(struct cmdq_item *item, const char *filter)
{
struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s;
struct winlink *wl = target->wl;
struct window_pane *wp = target->wp;
struct window_pane *loopwp, *tmpwp;
if (args_has(args, 'a')) {
server_unzoom_window(wl->window); server_unzoom_window(wl->window);
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) { TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
if (loopwp == wp) if (loopwp == wp)
continue; continue;
if (!cmd_kill_pane_filter(item, s, wl, loopwp, filter))
continue;
server_client_remove_pane(loopwp); server_client_remove_pane(loopwp);
layout_close_pane(loopwp); layout_close_pane(loopwp);
window_remove_pane(wl->window, loopwp); window_remove_pane(wl->window, loopwp);
@@ -62,10 +92,24 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (wp == NULL) { static int
cmdq_error(item, "no active pane to kill"); cmd_kill_pane_filter(struct cmdq_item *item, struct session *s,
return (CMD_RETURN_ERROR); struct winlink *wl, struct window_pane *wp, const char *filter)
} {
server_kill_pane(wp); struct format_tree *ft;
return (CMD_RETURN_NORMAL); char *expanded;
int flag;
if (filter == NULL)
return (1);
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, s, wl, wp);
expanded = format_expand(ft, filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
return (flag);
} }

View File

@@ -18,6 +18,8 @@
#include <sys/types.h> #include <sys/types.h>
#include <stdlib.h>
#include "tmux.h" #include "tmux.h"
/* /*
@@ -28,13 +30,16 @@
*/ */
static enum cmd_retval cmd_kill_session_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_kill_session_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_kill_session_all(struct cmdq_item *, const char *);
static int cmd_kill_session_filter(struct cmdq_item *,
struct session *, const char *);
const struct cmd_entry cmd_kill_session_entry = { const struct cmd_entry cmd_kill_session_entry = {
.name = "kill-session", .name = "kill-session",
.alias = NULL, .alias = NULL,
.args = { "aCgt:", 0, 0, NULL }, .args = { "aCgf:t:", 0, 0, NULL },
.usage = "[-aCg] " CMD_TARGET_SESSION_USAGE, .usage = "[-aCg] [-f filter] " CMD_TARGET_SESSION_USAGE,
.target = { 't', CMD_FIND_SESSION, 0 }, .target = { 't', CMD_FIND_SESSION, 0 },
@@ -50,6 +55,12 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
struct session *s = target->s, *sloop, *stmp; struct session *s = target->s, *sloop, *stmp;
struct session_group *sg; struct session_group *sg;
struct winlink *wl; struct winlink *wl;
const char *filter = args_get(args, 'f');
if (filter != NULL && (!args_has(args, 'a') || args_has(args, 'C'))) {
cmdq_error(item, "-f only valid with -a");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'C')) { if (args_has(args, 'C')) {
RB_FOREACH(wl, winlinks, &s->windows) { RB_FOREACH(wl, winlinks, &s->windows) {
@@ -57,14 +68,9 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
wl->flags &= ~WINLINK_ALERTFLAGS; wl->flags &= ~WINLINK_ALERTFLAGS;
} }
server_redraw_session(s); server_redraw_session(s);
} else if (args_has(args, 'a')) { } else if (args_has(args, 'a'))
RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) { return (cmd_kill_session_all(item, filter));
if (sloop != s) { else if (args_has(args, 'g') &&
server_destroy_session(sloop);
session_destroy(sloop, 1, __func__);
}
}
} else if (args_has(args, 'g') &&
(sg = session_group_contains(s)) != NULL) { (sg = session_group_contains(s)) != NULL) {
TAILQ_FOREACH_SAFE(sloop, &sg->sessions, gentry, stmp) { TAILQ_FOREACH_SAFE(sloop, &sg->sessions, gentry, stmp) {
server_destroy_session(sloop); server_destroy_session(sloop);
@@ -76,3 +82,42 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
static enum cmd_retval
cmd_kill_session_all(struct cmdq_item *item, const char *filter)
{
struct session *s = cmdq_get_target(item)->s;
struct session *sloop, *stmp;
RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) {
if (sloop == s)
continue;
if (!cmd_kill_session_filter(item, sloop, filter))
continue;
server_destroy_session(sloop);
session_destroy(sloop, 1, __func__);
}
return (CMD_RETURN_NORMAL);
}
static int
cmd_kill_session_filter(struct cmdq_item *item, struct session *s,
const char *filter)
{
struct format_tree *ft;
char *expanded;
int flag;
if (filter == NULL)
return (1);
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, s, NULL, NULL);
expanded = format_expand(ft, filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
return (flag);
}

View File

@@ -18,6 +18,8 @@
#include <sys/types.h> #include <sys/types.h>
#include <stdlib.h>
#include "tmux.h" #include "tmux.h"
/* /*
@@ -25,13 +27,16 @@
*/ */
static enum cmd_retval cmd_kill_window_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_kill_window_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_kill_window_all(struct cmdq_item *, const char *);
static int cmd_kill_window_filter(struct cmdq_item *,
struct session *, struct winlink *, const char *);
const struct cmd_entry cmd_kill_window_entry = { const struct cmd_entry cmd_kill_window_entry = {
.name = "kill-window", .name = "kill-window",
.alias = "killw", .alias = "killw",
.args = { "at:", 0, 0, NULL }, .args = { "af:t:", 0, 0, NULL },
.usage = "[-a] " CMD_TARGET_WINDOW_USAGE, .usage = "[-a] [-f filter] " CMD_TARGET_WINDOW_USAGE,
.target = { 't', CMD_FIND_WINDOW, 0 }, .target = { 't', CMD_FIND_WINDOW, 0 },
@@ -57,10 +62,15 @@ cmd_kill_window_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 winlink *wl = target->wl, *loop; struct winlink *wl = target->wl;
struct window *w = wl->window; struct window *w = wl->window;
struct session *s = target->s; struct session *s = target->s;
u_int found; const char *filter = args_get(args, 'f');
if (filter != NULL && !args_has(args, 'a')) {
cmdq_error(item, "-f only valid with -a");
return (CMD_RETURN_ERROR);
}
if (cmd_get_entry(self) == &cmd_unlink_window_entry) { if (cmd_get_entry(self) == &cmd_unlink_window_entry) {
if (!args_has(args, 'k') && !session_is_linked(s, w)) { if (!args_has(args, 'k') && !session_is_linked(s, w)) {
@@ -72,7 +82,22 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_has(args, 'a')) { if (args_has(args, 'a'))
return (cmd_kill_window_all(item, filter));
server_kill_window(wl->window, 1);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_kill_window_all(struct cmdq_item *item, const char *filter)
{
struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s;
struct winlink *wl = target->wl;
struct winlink *loop;
u_int found, kill_current;
if (RB_PREV(winlinks, &s->windows, wl) == NULL && if (RB_PREV(winlinks, &s->windows, wl) == NULL &&
RB_NEXT(winlinks, &s->windows, wl) == NULL) RB_NEXT(winlinks, &s->windows, wl) == NULL)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
@@ -81,7 +106,8 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
do { do {
found = 0; found = 0;
RB_FOREACH(loop, winlinks, &s->windows) { RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window != wl->window) { if (loop->window != wl->window &&
cmd_kill_window_filter(item, s, loop, filter)) {
server_kill_window(loop->window, 0); server_kill_window(loop->window, 0);
found++; found++;
break; break;
@@ -90,21 +116,42 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
} while (found != 0); } while (found != 0);
/* /*
* If the current window appears in the session more than once, * If the current window appears in the session more than once, kill it
* kill it as well. * as well if it matches the filter.
*/ */
found = 0; found = kill_current = 0;
RB_FOREACH(loop, winlinks, &s->windows) { RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window == wl->window) if (loop->window == wl->window) {
found++; found++;
if (cmd_kill_window_filter(item, s, loop, filter))
kill_current = 1;
} }
if (found > 1) }
if (kill_current && found > 1)
server_kill_window(wl->window, 0); server_kill_window(wl->window, 0);
server_renumber_all(); server_renumber_all();
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
server_kill_window(wl->window, 1); static int
return (CMD_RETURN_NORMAL); cmd_kill_window_filter(struct cmdq_item *item, struct session *s,
struct winlink *wl, const char *filter)
{
struct format_tree *ft;
char *expanded;
int flag;
if (filter == NULL)
return (1);
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, s, wl, NULL);
expanded = format_expand(ft, filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
return (flag);
} }

View File

@@ -223,7 +223,12 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
cmd_list_keys_filter_key_list(filter_notes, filter_key, only, l, cmd_list_keys_filter_key_list(filter_notes, filter_key, only, l,
&n); &n);
} }
if (single) if (filter_key && n == 0) {
cmdq_error(item, "unknown key: %s", keystr);
free(prefix);
return (CMD_RETURN_ERROR);
}
if (single && n > 1)
n = 1; n = 1;
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
@@ -237,7 +242,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
cmd_list_keys_format_add_key_binding(ft, l[i], prefix); cmd_list_keys_format_add_key_binding(ft, l[i], prefix);
line = format_expand(ft, template); line = format_expand(ft, template);
if ((single && tc != NULL) || n == 1) if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL))
status_message_set(tc, -1, 1, 0, 0, "%s", line); status_message_set(tc, -1, 1, 0, 0, "%s", line);
else if (*line != '\0') else if (*line != '\0')
cmdq_print(item, "%s", line); cmdq_print(item, "%s", line);

View File

@@ -77,8 +77,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
struct termios tio, *tiop; struct termios tio, *tiop;
struct session_group *sg = NULL; struct session_group *sg = NULL;
const char *errstr, *template, *group, *tmp; const char *errstr, *template, *group, *tmp;
char *cause, *cwd = NULL, *cp, *newname = NULL; char *cause, *cwd = NULL, *cp, *ename;
char *name, *prefix = NULL; char *wname = NULL, *sname = NULL, *prefix = NULL;
int detached, already_attached, is_control = 0; int detached, already_attached, is_control = 0;
u_int sx, sy, dsx, dsy, count = args_count(args); u_int sx, sy, dsx, dsy, count = args_count(args);
struct spawn_context sc = { 0 }; struct spawn_context sc = { 0 };
@@ -99,20 +99,29 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
tmp = args_get(args, 's'); if ((tmp = args_get(args, 'n')) != NULL) {
if (tmp != NULL) { ename = format_single(item, tmp, c, NULL, NULL, NULL);
name = format_single(item, tmp, c, NULL, NULL, NULL); if (!check_name(ename, WINDOW_NAME_FORBID)) {
newname = clean_name(name, "#:."); cmdq_error(item, "invalid window name: %s", ename);
if (newname == NULL) { free(ename);
cmdq_error(item, "invalid session: %s", name);
free(name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
free(name); wname = clean_name(ename, WINDOW_NAME_FORBID);
free(ename);
}
if ((tmp = args_get(args, 's')) != NULL) {
ename = format_single(item, tmp, c, NULL, NULL, NULL);
if (!check_name(ename, SESSION_NAME_FORBID)) {
cmdq_error(item, "invalid session name: %s", ename);
free(ename);
goto fail;
}
sname = clean_name(ename, SESSION_NAME_FORBID);
free(ename);
} }
if (args_has(args, 'A')) { if (args_has(args, 'A')) {
if (newname != NULL) if (sname != NULL)
as = session_find(newname); as = session_find(sname);
else else
as = target->s; as = target->s;
if (as != NULL) { if (as != NULL) {
@@ -120,12 +129,13 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
args_has(args, 'D'), args_has(args, 'X'), 0, args_has(args, 'D'), args_has(args, 'X'), 0,
args_get(args, 'c'), args_has(args, 'E'), args_get(args, 'c'), args_has(args, 'E'),
args_get(args, 'f')); args_get(args, 'f'));
free(newname); free(wname);
free(sname);
return (retval); return (retval);
} }
} }
if (newname != NULL && session_find(newname) != NULL) { if (sname != NULL && session_find(sname) != NULL) {
cmdq_error(item, "duplicate session: %s", newname); cmdq_error(item, "duplicate session: %s", sname);
goto fail; goto fail;
} }
@@ -142,12 +152,12 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
else if (groupwith != NULL) else if (groupwith != NULL)
prefix = xstrdup(groupwith->name); prefix = xstrdup(groupwith->name);
else { else {
prefix = clean_name(group, "#:."); if (!check_name(group, SESSION_NAME_FORBID)) {
if (prefix == NULL) { cmdq_error(item,
cmdq_error(item, "invalid session group: %s", "invalid session group name: %s", group);
group);
goto fail; goto fail;
} }
prefix = clean_name(group, SESSION_NAME_FORBID);
} }
} }
@@ -276,7 +286,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
environ_put(env, av->string, 0); environ_put(env, av->string, 0);
av = args_next_value(av); av = args_next_value(av);
} }
s = session_create(prefix, newname, cwd, env, oo, tiop); s = session_create(prefix, sname, cwd, env, oo, tiop);
/* Spawn the initial window. */ /* Spawn the initial window. */
sc.item = item; sc.item = item;
@@ -284,7 +294,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
if (!detached) if (!detached)
sc.tc = c; sc.tc = c;
sc.name = args_get(args, 'n'); sc.name = wname;
args_to_vector(args, &sc.argc, &sc.argv); args_to_vector(args, &sc.argc, &sc.argv);
sc.idx = -1; sc.idx = -1;
@@ -357,7 +367,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
if (sc.argv != NULL) if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv); cmd_free_argv(sc.argc, sc.argv);
free(cwd); free(cwd);
free(newname); free(wname);
free(sname);
free(prefix); free(prefix);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
@@ -365,7 +376,8 @@ fail:
if (sc.argv != NULL) if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv); cmd_free_argv(sc.argc, sc.argv);
free(cwd); free(cwd);
free(newname); free(wname);
free(sname);
free(prefix); free(prefix);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }

View File

@@ -61,7 +61,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *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; int idx = target->idx, before;
char *cause = NULL, *cp, *expanded; 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;
struct args_value *av; struct args_value *av;
@@ -71,8 +71,18 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
* name already exists, select it. * name already exists, select it.
*/ */
name = args_get(args, 'n'); name = args_get(args, 'n');
if (args_has(args, 'S') && name != NULL && target->idx == -1) { if (name != NULL) {
expanded = format_single(item, name, c, s, NULL, NULL); expanded = format_single(item, name, c, s, NULL, NULL);
if (!check_name(expanded, WINDOW_NAME_FORBID)) {
cmdq_error(item, "invalid window name: %s", expanded);
free(expanded);
return (CMD_RETURN_ERROR);
}
wname = clean_name(expanded, WINDOW_NAME_FORBID);
free(expanded);
}
if (args_has(args, 'S') && wname != NULL && target->idx == -1) {
expanded = format_single(item, wname, c, s, NULL, NULL);
RB_FOREACH(wl, winlinks, &s->windows) { RB_FOREACH(wl, winlinks, &s->windows) {
if (strcmp(wl->window->name, expanded) != 0) if (strcmp(wl->window->name, expanded) != 0)
continue; continue;
@@ -80,12 +90,14 @@ 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", name); cmdq_error(item, "multiple windows named %s", wname);
free(wname);
free(expanded); free(expanded);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
free(expanded); free(expanded);
if (new_wl != NULL) { if (new_wl != NULL) {
free(wname);
if (args_has(args, 'd')) if (args_has(args, 'd'))
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (session_set_current(s, new_wl) == 0) if (session_set_current(s, new_wl) == 0)
@@ -108,7 +120,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
sc.s = s; sc.s = s;
sc.tc = tc; sc.tc = tc;
sc.name = args_get(args, 'n'); sc.name = wname;
args_to_vector(args, &sc.argc, &sc.argv); args_to_vector(args, &sc.argc, &sc.argv);
sc.environ = environ_create(); sc.environ = environ_create();
@@ -130,10 +142,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
if ((new_wl = spawn_window(&sc, &cause)) == NULL) { if ((new_wl = spawn_window(&sc, &cause)) == NULL) {
cmdq_error(item, "create window failed: %s", cause); cmdq_error(item, "create window failed: %s", cause);
free(cause); free(cause);
if (sc.argv != NULL) goto fail;
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
return (CMD_RETURN_ERROR);
} }
if (!args_has(args, 'd') || new_wl == s->curw) { if (!args_has(args, 'd') || new_wl == s->curw) {
cmd_find_from_winlink(current, new_wl, 0); cmd_find_from_winlink(current, new_wl, 0);
@@ -156,5 +165,13 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
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);
free(wname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
fail:
if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
free(wname);
return (CMD_RETURN_ERROR);
} }

View File

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

View File

@@ -52,12 +52,12 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item)
char *newname, *tmp; char *newname, *tmp;
tmp = format_single_from_target(item, args_string(args, 0)); tmp = format_single_from_target(item, args_string(args, 0));
newname = clean_name(tmp, "#:."); if (!check_name(tmp, SESSION_NAME_FORBID)) {
if (newname == NULL) { cmdq_error(item, "invalid session name: %s", tmp);
cmdq_error(item, "invalid session: %s", tmp);
free(tmp); free(tmp);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
newname = clean_name(tmp, SESSION_NAME_FORBID);
free(tmp); free(tmp);
if (strcmp(newname, s->name) == 0) { if (strcmp(newname, s->name) == 0) {
free(newname); free(newname);

View File

@@ -48,15 +48,21 @@ cmd_rename_window_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 winlink *wl = target->wl; struct winlink *wl = target->wl;
char *newname; char *name;
newname = format_single_from_target(item, args_string(args, 0)); name = format_single_from_target(item, args_string(args, 0));
window_set_name(wl->window, newname); if (!check_name(name, WINDOW_NAME_FORBID)) {
cmdq_error(item, "invalid window name: %s", name);
free(name);
return (CMD_RETURN_ERROR);
}
window_set_name(wl->window, name, WINDOW_NAME_FORBID);
options_set_number(wl->window->options, "automatic-rename", 0); options_set_number(wl->window->options, "automatic-rename", 0);
free(name);
server_redraw_window_borders(wl->window); server_redraw_window_borders(wl->window);
server_status_window(wl->window); server_status_window(wl->window);
free(newname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -31,18 +31,18 @@ static enum cmd_retval cmd_resize_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_resize_pane_mouse_update(struct cmd *, static enum cmd_retval cmd_resize_pane_mouse_update(struct cmd *,
struct cmdq_item *); struct cmdq_item *);
static void cmd_resize_pane_mouse_update_floating(struct client *, static void cmd_resize_pane_mouse_resize_move_floating(
struct mouse_event *); struct client *, struct mouse_event *);
static void cmd_resize_pane_mouse_update_tiled(struct client *, static void cmd_resize_pane_mouse_resize_tiled(struct client *,
struct mouse_event *); struct mouse_event *);
const struct cmd_entry cmd_resize_pane_entry = { const struct cmd_entry cmd_resize_pane_entry = {
.name = "resize-pane", .name = "resize-pane",
.alias = "resizep", .alias = "resizep",
.args = { "DLMRTt:Ux:y:Z", 0, 1, NULL }, .args = { "D::L::MR::Tt:U::x:y:Z", 0, 1, NULL },
.usage = "[-DLMRTUZ] [-x width] [-y height] " CMD_TARGET_PANE_USAGE " " .usage = "[-MTZ] [-D lines] [-L columns] [-R columns] [-U lines] "
"[adjustment]", "[-x width] [-y height] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -58,17 +58,21 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
struct window_pane *wp = target->wp; struct window_pane *wp = target->wp;
struct winlink *wl = target->wl; struct winlink *wl = target->wl;
struct window *w = wl->window; struct window *w = wl->window;
const char *errstr; struct layout_cell *lc = wp->layout_cell;
char *cause; enum layout_type type;
u_int adjust; const char *errstr, *argval;
int x, y, status; const char flags[4] = { 'U', 'D', 'L', 'R' };
char *cause = NULL, flag;
u_int opposite = 0;
int adjust, x, y, status;
long unsigned i;
struct grid *gd = wp->base.grid; struct grid *gd = wp->base.grid;
if (args_has(args, 'T')) { if (args_has(args, 'T')) {
if (!TAILQ_EMPTY(&wp->modes)) if (!TAILQ_EMPTY(&wp->modes))
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
adjust = screen_size_y(&wp->base) - 1 - wp->base.cy; adjust = screen_size_y(&wp->base) - 1 - wp->base.cy;
if (adjust > gd->hsize) if (adjust > (int)gd->hsize)
adjust = gd->hsize; adjust = gd->hsize;
grid_remove_history(gd, adjust); grid_remove_history(gd, adjust);
wp->base.cy += adjust; wp->base.cy += adjust;
@@ -89,33 +93,32 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
} }
server_unzoom_window(w); server_unzoom_window(w);
if (args_count(args) == 0)
adjust = 1;
else {
adjust = strtonum(args_string(args, 0), 1, INT_MAX, &errstr);
if (errstr != NULL) {
cmdq_error(item, "adjustment %s", errstr);
return (CMD_RETURN_ERROR);
}
}
if (args_has(args, 'x')) { if (args_has(args, 'x')) {
x = args_percentage(args, 'x', 0, INT_MAX, w->sx, &cause); x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "width %s", cause); cmdq_error(item, "width %s", cause);
free(cause); free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (window_pane_is_floating(wp)) {
layout_resize_floating_pane_to(wp, LAYOUT_LEFTRIGHT, x,
&cause);
if (cause != NULL) {
cmdq_error(item, "size %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
} else
layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x); layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x);
} }
if (args_has(args, 'y')) { if (args_has(args, 'y')) {
y = args_percentage(args, 'y', 0, INT_MAX, w->sy, &cause); y = args_percentage(args, 'y', 0, PANE_MAXIMUM, w->sy, &cause);
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "height %s", cause); cmdq_error(item, "height %s", cause);
free(cause); free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
status = options_get_number(w->options, "pane-border-status"); status = window_get_pane_status(w);
switch (status) { switch (status) {
case PANE_STATUS_TOP: case PANE_STATUS_TOP:
if (y != INT_MAX && wp->yoff == 1) if (y != INT_MAX && wp->yoff == 1)
@@ -126,18 +129,60 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
y++; y++;
break; break;
} }
if (window_pane_is_floating(wp)) {
layout_resize_floating_pane_to(wp, LAYOUT_TOPBOTTOM, y,
&cause);
if (cause != NULL) {
cmdq_error(item, "size %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
} else
layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y); layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y);
} }
if (args_has(args, 'L')) for (i = 0; i < nitems(flags); i++) {
layout_resize_pane(wp, LAYOUT_LEFTRIGHT, -adjust, 1); flag = flags[i];
else if (args_has(args, 'R')) if (!args_has(args, flag))
layout_resize_pane(wp, LAYOUT_LEFTRIGHT, adjust, 1); continue;
else if (args_has(args, 'U'))
layout_resize_pane(wp, LAYOUT_TOPBOTTOM, -adjust, 1); argval = args_get(args, flag);
else if (args_has(args, 'D')) if (argval == NULL)
layout_resize_pane(wp, LAYOUT_TOPBOTTOM, adjust, 1); argval = "1";
server_redraw_window(wl->window);
adjust = strtonum(argval, INT_MIN, INT_MAX, &errstr);
if (errstr != NULL) {
cmdq_error(item, "adjustment %s", errstr);
return (CMD_RETURN_ERROR);
}
type = LAYOUT_TOPBOTTOM;
if (flag == 'L' || flag == 'R')
type = LAYOUT_LEFTRIGHT;
if (window_pane_is_floating(wp)) {
if (flag == 'L' || flag == 'U')
opposite = 1;
layout_resize_floating_pane(wp, type, adjust, opposite,
&cause);
if (cause != NULL) {
cmdq_error(item, "adjustment %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
} else {
if (flag == 'L' || flag == 'U')
adjust = -adjust;
layout_resize_pane(wp, type, adjust, 1);
}
}
if (lc->parent != NULL)
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
notify_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
@@ -160,27 +205,36 @@ cmd_resize_pane_mouse_update(__unused struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (!window_pane_is_floating(wp)) { if (!window_pane_is_floating(wp)) {
c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_tiled; c->tty.mouse_drag_update = cmd_resize_pane_mouse_resize_tiled;
cmd_resize_pane_mouse_update_tiled(c, &event->m); cmd_resize_pane_mouse_resize_tiled(c, &event->m);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
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);
c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_floating; c->tty.mouse_drag_update = cmd_resize_pane_mouse_resize_move_floating;
cmd_resize_pane_mouse_update_floating(c, &event->m); cmd_resize_pane_mouse_resize_move_floating(c, &event->m);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
/*
* Resizes or moves the pane by dragging. Resize a floating pane by dragging
* the borders or corners. Grabbing an edge only resizes that axis (special
* case). Moves the pane if dragging the top border. Since characters are
* generally rectangular, to make it easier to grab the corner, the character
* next to the corner is also considered the corner.
*/
static void static void
cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) cmd_resize_pane_mouse_resize_move_floating(struct client *c,
struct mouse_event *m)
{ {
struct winlink *wl; struct winlink *wl;
struct window *w; struct window *w;
struct window_pane *wp; struct window_pane *wp;
struct layout_cell *lc; struct layout_cell *lc;
int y, ly, x, lx, sx, sy, new_sx, new_sy; int y, ly, x, lx, sx, sy, new_sx, new_sy;
int scrollbars, sb_pos, left, right;
int new_xoff, new_yoff, resizes = 0; int new_xoff, new_yoff, resizes = 0;
wp = cmd_mouse_pane(m, NULL, &wl); wp = cmd_mouse_pane(m, NULL, &wl);
@@ -192,6 +246,17 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
lc = wp->layout_cell; lc = wp->layout_cell;
sx = wp->sx; sx = wp->sx;
sy = wp->sy; sy = wp->sy;
scrollbars = options_get_number(w->options, "pane-scrollbars");
sb_pos = options_get_number(w->options, "pane-scrollbars-position");
left = wp->xoff - 1;
right = wp->xoff + sx;
if (window_pane_show_scrollbar(wp, scrollbars) &&
sb_pos == PANE_SCROLLBARS_LEFT) {
left -= wp->scrollbar_style.width + wp->scrollbar_style.pad;
} else if (window_pane_show_scrollbar(wp, scrollbars) &&
sb_pos == PANE_SCROLLBARS_RIGHT) {
right += wp->scrollbar_style.width + wp->scrollbar_style.pad;
}
y = m->y + m->oy; x = m->x + m->ox; y = m->y + m->oy; x = m->x + m->ox;
if (m->statusat == 0 && y >= (int)m->statuslines) if (m->statusat == 0 && y >= (int)m->statuslines)
@@ -204,7 +269,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
else if (m->statusat > 0 && ly >= m->statusat) else if (m->statusat > 0 && ly >= m->statusat)
ly = m->statusat - 1; ly = m->statusat - 1;
if ((lx == wp->xoff - 1 || lx == wp->xoff) && ly == wp->yoff - 1) { if ((lx == left || lx == left + 1) && ly == wp->yoff - 1) {
/* Top left corner. */ /* Top left corner. */
new_sx = lc->sx + (lx - x); new_sx = lc->sx + (lx - x);
if (new_sx < PANE_MINIMUM) if (new_sx < PANE_MINIMUM)
@@ -216,7 +281,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
new_yoff = y + 1; new_yoff = y + 1;
layout_set_size(lc, new_sx, new_sy, new_xoff, new_yoff); layout_set_size(lc, new_sx, new_sy, new_xoff, new_yoff);
resizes++; resizes++;
} else if ((lx == wp->xoff + sx + 1 || lx == wp->xoff + sx) && } else if ((lx == right + 1 || lx == right) &&
ly == wp->yoff - 1) { ly == wp->yoff - 1) {
/* Top right corner. */ /* Top right corner. */
new_sx = x - lc->xoff; new_sx = x - lc->xoff;
@@ -228,7 +293,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
new_yoff = y + 1; new_yoff = y + 1;
layout_set_size(lc, new_sx, new_sy, lc->xoff, new_yoff); layout_set_size(lc, new_sx, new_sy, lc->xoff, new_yoff);
resizes++; resizes++;
} else if ((lx == wp->xoff - 1 || lx == wp->xoff) && } else if ((lx == left || lx == left + 1) &&
ly == wp->yoff + sy) { ly == wp->yoff + sy) {
/* Bottom left corner. */ /* Bottom left corner. */
new_sx = lc->sx + (lx - x); new_sx = lc->sx + (lx - x);
@@ -240,7 +305,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
new_xoff = x + 1; new_xoff = x + 1;
layout_set_size(lc, new_sx, new_sy, new_xoff, lc->yoff); layout_set_size(lc, new_sx, new_sy, new_xoff, lc->yoff);
resizes++; resizes++;
} else if ((lx == wp->xoff + sx + 1 || lx == wp->xoff + sx) && } else if ((lx == right + 1 || lx == right) &&
ly == wp->yoff + sy) { ly == wp->yoff + sy) {
/* Bottom right corner. */ /* Bottom right corner. */
new_sx = x - lc->xoff; new_sx = x - lc->xoff;
@@ -251,14 +316,14 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
new_sy = PANE_MINIMUM; new_sy = PANE_MINIMUM;
layout_set_size(lc, new_sx, new_sy, lc->xoff, lc->yoff); layout_set_size(lc, new_sx, new_sy, lc->xoff, lc->yoff);
resizes++; resizes++;
} else if (lx == wp->xoff + sx + 1) { } else if (lx == right) {
/* Right border. */ /* Right border. */
new_sx = x - lc->xoff; new_sx = x - lc->xoff;
if (new_sx < PANE_MINIMUM) if (new_sx < PANE_MINIMUM)
return; return;
layout_set_size(lc, new_sx, lc->sy, lc->xoff, lc->yoff); layout_set_size(lc, new_sx, lc->sy, lc->xoff, lc->yoff);
resizes++; resizes++;
} else if (lx == wp->xoff - 1) { } else if (lx == left) {
/* Left border. */ /* Left border. */
new_sx = lc->sx + (lx - x); new_sx = lc->sx + (lx - x);
if (new_sx < PANE_MINIMUM) if (new_sx < PANE_MINIMUM)
@@ -288,7 +353,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
} }
static void static void
cmd_resize_pane_mouse_update_tiled(struct client *c, struct mouse_event *m) cmd_resize_pane_mouse_resize_tiled(struct client *c, struct mouse_event *m)
{ {
struct winlink *wl; struct winlink *wl;
struct window *w; struct window *w;

View File

@@ -109,6 +109,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item)
window_set_active_pane(w, wp, 1); 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);
window_pop_zoom(w); window_pop_zoom(w);
redraw_invalidate_scene(w);
server_redraw_window(w); server_redraw_window(w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);

View File

@@ -135,7 +135,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
} }
if (args_has(args, 'm') || args_has(args, 'M')) { if (args_has(args, 'm') || args_has(args, 'M')) {
if (args_has(args, 'm') && !window_pane_visible(wp)) if (args_has(args, 'm') && !window_pane_is_visible(wp))
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (server_check_marked()) if (server_check_marked())
lastwp = marked_pane.wp; lastwp = marked_pane.wp;
@@ -217,7 +217,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'T')) { if (args_has(args, 'T')) {
title = format_single_from_target(item, args_get(args, 'T')); title = format_single_from_target(item, args_get(args, 'T'));
if (screen_set_title(&wp->base, title)) { if (screen_set_title(&wp->base, title, 0)) {
notify_pane("pane-title-changed", wp); notify_pane("pane-title-changed", wp);
server_redraw_window_borders(wp->window); server_redraw_window_borders(wp->window);
server_status_window(wp->window); server_status_window(wp->window);

View File

@@ -70,6 +70,7 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after,
struct key_table *table = NULL; struct key_table *table = NULL;
struct key_binding *bd; struct key_binding *bd;
struct key_event *event; struct key_event *event;
struct cmdq_item *new_after = after;
if (args_has(args, 'K')) { if (args_has(args, 'K')) {
if (tc == NULL) if (tc == NULL)
@@ -77,10 +78,16 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after,
event = xcalloc(1, sizeof *event); event = xcalloc(1, sizeof *event);
event->key = key|KEYC_SENT; event->key = key|KEYC_SENT;
memset(&event->m, 0, sizeof event->m); memset(&event->m, 0, sizeof event->m);
if (server_client_handle_key(tc, event) == 0) { if (after == NULL) {
if (server_client_handle_key(tc, event) != 0)
return (item);
} else {
if (server_client_handle_key_after(tc, event, after,
&new_after) != 0)
return (new_after);
}
free(event->buf); free(event->buf);
free(event); free(event);
}
return (item); return (item);
} }
@@ -229,8 +236,10 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item)
if (count == 0) { if (count == 0) {
if (args_has(args, 'N') || args_has(args, 'R')) if (args_has(args, 'N') || args_has(args, 'R'))
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
after = args_has(args, 'K') ? item : NULL;
for (; np != 0; np--) for (; np != 0; np--)
cmd_send_keys_inject_key(item, NULL, args, event->key); after = cmd_send_keys_inject_key(item, after, args,
event->key);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -19,6 +19,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <grp.h>
#include <pwd.h> #include <pwd.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -37,67 +38,71 @@ const struct cmd_entry cmd_server_access_entry = {
.name = "server-access", .name = "server-access",
.alias = NULL, .alias = NULL,
.args = { "adlrw", 0, 1, NULL }, .args = { "adglrw", 0, 1, NULL },
.usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]", .usage = "[-adglrw] " CMD_TARGET_PANE_USAGE " [user|group]",
.flags = CMD_CLIENT_CANFAIL, .flags = CMD_CLIENT_CANFAIL,
.exec = cmd_server_access_exec .exec = cmd_server_access_exec
}; };
static enum cmd_retval static enum cmd_retval
cmd_server_access_deny(struct cmdq_item *item, struct passwd *pw) cmd_server_access_deny(struct cmdq_item *item, id_t id, int flags,
const char *type, const char *name)
{ {
struct client *loop; if (!server_acl_find(id, flags)) {
struct server_acl_user *user; cmdq_error(item, "%s %s not found", type, name);
uid_t uid;
if ((user = server_acl_user_find(pw->pw_uid)) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
TAILQ_FOREACH(loop, &clients, entry) { server_acl_deny(id, flags);
uid = proc_get_peer_uid(loop->peer);
if (uid == server_acl_get_uid(user)) {
loop->exit_message = xstrdup("access not allowed");
loop->flags |= CLIENT_EXIT;
}
}
server_acl_user_deny(pw->pw_uid);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
static enum cmd_retval static enum cmd_retval
cmd_server_access_exec(struct cmd *self, struct cmdq_item *item) cmd_server_access_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_target_client(item); struct client *c = cmdq_get_target_client(item);
char *name; char *arg;
struct passwd *pw = NULL; const char *name = NULL, *type;
struct passwd *pw;
struct group *gr;
id_t id;
int flags = 0;
if (args_has(args, 'l')) { if (args_has(args, 'l')) {
server_acl_display(item); server_acl_display(item);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_count(args) == 0) { if (args_count(args) == 0) {
cmdq_error(item, "missing user argument"); cmdq_error(item, "missing user or group argument");
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL); arg = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
if (*name != '\0') if (args_has(args, 'g')) {
pw = getpwnam(name); type = "group";
if (pw == NULL) { if ((gr = getgrnam(arg)) != NULL) {
cmdq_error(item, "unknown user: %s", name); id = gr->gr_gid;
free(name); name = gr->gr_name;
flags |= SERVER_ACL_IS_GROUP;
}
} else {
type = "user";
if ((pw = getpwnam(arg)) != NULL) {
id = pw->pw_uid;
name = pw->pw_name;
}
}
if (name == NULL) {
cmdq_error(item, "unknown %s: %s", type, arg);
free(arg);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
free(name); free(arg);
if (pw->pw_uid == 0 || pw->pw_uid == getuid()) { if ((~flags & SERVER_ACL_IS_GROUP) && (id == 0 || id == getuid())) {
cmdq_error(item, "%s owns the server, can't change access", cmdq_error(item, "%s owns the server, can't change access",
pw->pw_name); name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -111,36 +116,35 @@ cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
} }
if (args_has(args, 'd')) if (args_has(args, 'd'))
return (cmd_server_access_deny(item, pw)); return (cmd_server_access_deny(item, id, flags, type, name));
if (args_has(args, 'a')) { if (args_has(args, 'a')) {
if (server_acl_user_find(pw->pw_uid) != NULL) { if (server_acl_find(id, flags)) {
cmdq_error(item, "user %s is already added", cmdq_error(item, "%s %s is already added", type, name);
pw->pw_name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
server_acl_user_allow(pw->pw_uid); server_acl_allow(id, flags);
/* Do not return - allow -r or -w with -a. */ /* Do not return - allow -r or -w with -a. */
} else if (args_has(args, 'r') || args_has(args, 'w')) { } else if (args_has(args, 'r') || args_has(args, 'w')) {
/* -r or -w implies -a if user does not exist. */ /* -r or -w implies -a if the entry does not exist. */
if (server_acl_user_find(pw->pw_uid) == NULL) if (!server_acl_find(id, flags))
server_acl_user_allow(pw->pw_uid); server_acl_allow(id, flags);
} }
if (args_has(args, 'w')) { if (args_has(args, 'w')) {
if (server_acl_user_find(pw->pw_uid) == NULL) { if (!server_acl_find(id, flags)) {
cmdq_error(item, "user %s not found", pw->pw_name); cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
server_acl_user_allow_write(pw->pw_uid); server_acl_allow_write(id, flags);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_has(args, 'r')) { if (args_has(args, 'r')) {
if (server_acl_user_find(pw->pw_uid) == NULL) { if (!server_acl_find(id, flags)) {
cmdq_error(item, "user %s not found", pw->pw_name); cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
server_acl_user_deny_write(pw->pw_uid); server_acl_deny_write(id, flags);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -55,56 +55,44 @@ cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
const char *typestr = args_get(args, 'T'); const char *typestr = args_get(args, 'T');
enum prompt_type type; enum prompt_type type;
u_int tidx, hidx; u_int t, h;
const char *v;
if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) { if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) {
if (typestr == NULL) { if (typestr == NULL) {
for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { for (t = 0; t < PROMPT_NTYPES; t++)
for (hidx = 0; hidx < status_prompt_hsize[tidx]; prompt_history_clear(t);
hidx++)
free(status_prompt_hlist[tidx][hidx]);
free(status_prompt_hlist[tidx]);
status_prompt_hlist[tidx] = NULL;
status_prompt_hsize[tidx] = 0;
}
} else { } else {
type = status_prompt_type(typestr); type = prompt_type(typestr);
if (type == PROMPT_TYPE_INVALID) { if (type == PROMPT_TYPE_INVALID) {
cmdq_error(item, "invalid type: %s", typestr); cmdq_error(item, "invalid type: %s", typestr);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) prompt_history_clear(type);
free(status_prompt_hlist[type][hidx]);
free(status_prompt_hlist[type]);
status_prompt_hlist[type] = NULL;
status_prompt_hsize[type] = 0;
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (typestr == NULL) { if (typestr == NULL) {
for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { for (t = 0; t < PROMPT_NTYPES; t++) {
cmdq_print(item, "History for %s:\n", typestr = prompt_type_string(t);
status_prompt_type_string(tidx)); cmdq_print(item, "History for %s:\n", typestr);
for (hidx = 0; hidx < status_prompt_hsize[tidx]; for (h = 0; h < prompt_history_size(t); h++) {
hidx++) { v = prompt_history_get(t, h);
cmdq_print(item, "%d: %s", hidx + 1, cmdq_print(item, "%d: %s", h + 1, v);
status_prompt_hlist[tidx][hidx]);
} }
cmdq_print(item, "%s", ""); cmdq_print(item, "%s", "");
} }
} else { } else {
type = status_prompt_type(typestr); type = prompt_type(typestr);
if (type == PROMPT_TYPE_INVALID) { if (type == PROMPT_TYPE_INVALID) {
cmdq_error(item, "invalid type: %s", typestr); cmdq_error(item, "invalid type: %s", typestr);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
cmdq_print(item, "History for %s:\n", cmdq_print(item, "History for %s:\n", prompt_type_string(type));
status_prompt_type_string(type)); for (h = 0; h < prompt_history_size(type); h++) {
for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) { v = prompt_history_get(type, h);
cmdq_print(item, "%d: %s", hidx + 1, cmdq_print(item, "%d: %s", h + 1, v);
status_prompt_hlist[type][hidx]);
} }
cmdq_print(item, "%s", ""); cmdq_print(item, "%s", "");
} }

View File

@@ -38,11 +38,12 @@ const struct cmd_entry cmd_new_pane_entry = {
.name = "new-pane", .name = "new-pane",
.alias = "newp", .alias = "newp",
.args = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL }, .args = { "bB:c:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
.usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " .usage = "[-bdefhIklPvWZ] [-B border-lines] "
"[-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] "
"[-R inactive-border-style] [-x width] [-y height] " "[-R inactive-border-style] [-T title] [-x width] [-y height] "
"[-X x-position] [-Y y-position] " CMD_TARGET_PANE_USAGE " " "[-X x-position] [-Y y-position] " CMD_TARGET_PANE_USAGE " "
"[shell-command [argument ...]]", "[shell-command [argument ...]]",
@@ -56,11 +57,11 @@ const struct cmd_entry cmd_split_window_entry = {
.name = "split-window", .name = "split-window",
.alias = "splitw", .alias = "splitw",
.args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:vZ", 0, -1, NULL }, .args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:T:vWZ", 0, -1, NULL },
.usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " .usage = "[-bdefhIklPvWZ] [-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] "
"[-R inactive-border-style] " CMD_TARGET_PANE_USAGE " " "[-R inactive-border-style] [-T title] " CMD_TARGET_PANE_USAGE " "
"[shell-command [argument ...]]", "[shell-command [argument ...]]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -84,9 +85,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
struct layout_cell *lc = NULL; struct layout_cell *lc = NULL;
struct cmd_find_state fs; struct cmd_find_state fs;
int input, empty, is_floating, flags = 0; int input, empty, is_floating, flags = 0;
const char *template, *style; const char *template, *style, *value;
char *cause = NULL, *cp; char *cause = NULL, *cp, *title;
const struct options_table_entry *oe;
struct args_value *av; struct args_value *av;
enum pane_lines lines;
u_int count = args_count(args); u_int count = args_count(args);
if (cmd_get_entry(self) == &cmd_new_pane_entry) if (cmd_get_entry(self) == &cmd_new_pane_entry)
@@ -114,12 +117,24 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (empty) if (empty)
flags |= SPAWN_EMPTY; flags |= SPAWN_EMPTY;
if ((value = args_get(args, 'B')) == NULL)
lines = window_get_pane_lines(w);
else {
oe = options_search("pane-border-lines");
lines = options_find_choice(oe, value, &cause);
if (cause != NULL) {
cmdq_error(item, "pane-border-lines %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
}
if (is_floating) if (is_floating)
lc = layout_get_floating_cell(item, args, w, wp, &cause); lc = layout_get_floating_cell(item, args, lines, w, wp, &cause);
else else
lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause); lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause);
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "size or position %s", cause); cmdq_error(item, "%s", cause);
free(cause); free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -152,10 +167,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { if ((new_wp = spawn_pane(&sc, &cause)) == NULL) {
cmdq_error(item, "create pane failed: %s", cause); cmdq_error(item, "create pane failed: %s", cause);
free(cause); free(cause);
if (sc.argv != NULL) goto fail;
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
return (CMD_RETURN_ERROR);
} }
style = args_get(args, 's'); style = args_get(args, 's');
@@ -163,7 +175,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (options_set_string(new_wp->options, "window-style", 0, if (options_set_string(new_wp->options, "window-style", 0,
"%s", style) == NULL) { "%s", style) == NULL) {
cmdq_error(item, "bad style: %s", style); cmdq_error(item, "bad style: %s", style);
return (CMD_RETURN_ERROR); goto fail;
} }
options_set_string(new_wp->options, "window-active-style", 0, options_set_string(new_wp->options, "window-active-style", 0,
"%s", style); "%s", style);
@@ -175,7 +187,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (options_set_string(new_wp->options, if (options_set_string(new_wp->options,
"pane-active-border-style", 0, "%s", style) == NULL) { "pane-active-border-style", 0, "%s", style) == NULL) {
cmdq_error(item, "bad active border style: %s", style); cmdq_error(item, "bad active border style: %s", style);
return (CMD_RETURN_ERROR); goto fail;
} }
} }
style = args_get(args, 'R'); style = args_get(args, 'R');
@@ -184,15 +196,24 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
"%s", style) == NULL) { "%s", style) == NULL) {
cmdq_error(item, "bad inactive border style: %s", cmdq_error(item, "bad inactive border style: %s",
style); style);
return (CMD_RETURN_ERROR); goto fail;
} }
} }
if (args_has(args, 'B'))
options_set_number(new_wp->options, "pane-border-lines", lines);
if (args_has(args, 'k') || args_has(args, 'm')) { if (args_has(args, 'k') || args_has(args, 'm')) {
options_set_number(new_wp->options, "remain-on-exit", 3); options_set_number(new_wp->options, "remain-on-exit", 3);
if (args_has(args, 'm')) if (args_has(args, 'm')) {
options_set_string(new_wp->options, options_set_string(new_wp->options,
"remain-on-exit-format", "remain-on-exit-format", 0, "%s",
0, "%s", args_get(args, 'm')); args_get(args, 'm'));
}
}
if (args_has(args, 'T')) {
title = format_single_from_target(item, args_get(args, 'T'));
screen_set_title(&new_wp->base, title, 0);
notify_pane("pane-title-changed", new_wp);
free(title);
} }
if (input) { if (input) {
@@ -204,10 +225,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
window_remove_pane(wp->window, new_wp); window_remove_pane(wp->window, new_wp);
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
free(cause); free(cause);
if (sc.argv != NULL) goto fail;
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
return (CMD_RETURN_ERROR);
case 1: case 1:
input = 0; input = 0;
break; break;
@@ -238,5 +256,24 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
environ_free(sc.environ); environ_free(sc.environ);
if (input) if (input)
return (CMD_RETURN_WAIT); return (CMD_RETURN_WAIT);
return (CMD_RETURN_NORMAL);
if (args_has(args, 'W')) {
/*
* With -W, block this command queue item until the pane's
* command exits; window_pane_wait_finish will be called to
* continue it.
*/
new_wp->wait_item = item;
return (CMD_RETURN_WAIT);
}
return (CMD_RETURN_NORMAL);
fail:
if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
layout_destroy_cell(w, lc, &w->layout_root);
return (CMD_RETURN_ERROR);
} }

View File

@@ -114,10 +114,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
dst_wp->layout_cell = src_lc; dst_wp->layout_cell = src_lc;
dst_lc->wp = src_wp; dst_lc->wp = src_wp;
src_wp->layout_cell = dst_lc; src_wp->layout_cell = dst_lc;
if (window_pane_is_floating(src_wp) != window_pane_is_floating(dst_wp)) {
src_wp->layout_cell->flags ^= LAYOUT_CELL_FLOATING;
dst_wp->layout_cell->flags ^= LAYOUT_CELL_FLOATING;
}
src_wp->window = dst_w; src_wp->window = dst_w;
options_set_parent(src_wp->options, dst_w->options); options_set_parent(src_wp->options, dst_w->options);
@@ -153,9 +149,11 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
colour_palette_from_option(&src_wp->palette, src_wp->options); colour_palette_from_option(&src_wp->palette, src_wp->options);
colour_palette_from_option(&dst_wp->palette, dst_wp->options); colour_palette_from_option(&dst_wp->palette, dst_wp->options);
layout_fix_panes(src_w, NULL); layout_fix_panes(src_w, NULL);
redraw_invalidate_scene(src_w);
server_redraw_window(src_w); server_redraw_window(src_w);
} }
layout_fix_panes(dst_w, NULL); layout_fix_panes(dst_w, NULL);
redraw_invalidate_scene(dst_w);
server_redraw_window(dst_w); server_redraw_window(dst_w);
notify_window("window-layout-changed", src_w); notify_window("window-layout-changed", src_w);

View File

@@ -120,6 +120,28 @@ colour_force_rgb(int c)
return (-1); return (-1);
} }
/* Dim colour by a percentage. */
int
colour_dim(int c, u_int dim)
{
u_char r, g, b;
if (dim == 0 || COLOUR_DEFAULT(c))
return (c);
if (dim >= 100)
return (colour_join_rgb(0, 0, 0));
c = colour_force_rgb(c);
if (c == -1)
return (-1);
colour_split_rgb(c, &r, &g, &b);
r = (r * (100 - dim)) / 100;
g = (g * (100 - dim)) / 100;
b = (b * (100 - dim)) / 100;
return (colour_join_rgb(r, g, b));
}
/* Convert colour to a string. */ /* Convert colour to a string. */
const char * const char *
colour_tostring(int c) colour_tostring(int c)

View File

@@ -339,7 +339,7 @@ static int
getopt_internal(int nargc, char * const *nargv, const char *options, getopt_internal(int nargc, char * const *nargv, const char *options,
const struct option *long_options, int *idx, int flags) const struct option *long_options, int *idx, int flags)
{ {
char *oli; /* option letter list index */ const char *oli; /* option letter list index */
int optchar, short_too; int optchar, short_too;
static int posixly_correct = -1; static int posixly_correct = -1;

View File

@@ -297,24 +297,24 @@ fi
# Look for ncurses or curses. Try pkg-config first then directly for the # Look for ncurses or curses. Try pkg-config first then directly for the
# library. # library.
PKG_CHECK_MODULES( PKG_CHECK_MODULES(
LIBTINFO, LIBTINFOW,
tinfo, tinfow,
[ [
AM_CPPFLAGS="$LIBTINFO_CFLAGS $AM_CPPFLAGS" AM_CPPFLAGS="$LIBTINFOW_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS" CPPFLAGS="$LIBTINFOW_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBTINFO_LIBS $LIBS" LIBS="$LIBTINFOW_LIBS $LIBS"
found_ncurses=yes found_ncurses=yes
], ],
found_ncurses=no found_ncurses=no
) )
if test "x$found_ncurses" = xno; then if test "x$found_ncurses" = xno; then
PKG_CHECK_MODULES( PKG_CHECK_MODULES(
LIBNCURSES, LIBTINFO,
ncurses, tinfo,
[ [
AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS" AM_CPPFLAGS="$LIBTINFO_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS" CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBNCURSES_LIBS $LIBS" LIBS="$LIBTINFO_LIBS $LIBS"
found_ncurses=yes found_ncurses=yes
], ],
found_ncurses=no found_ncurses=no
@@ -333,10 +333,23 @@ if test "x$found_ncurses" = xno; then
found_ncurses=no found_ncurses=no
) )
fi fi
if test "x$found_ncurses" = xno; then
PKG_CHECK_MODULES(
LIBNCURSES,
ncurses,
[
AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBNCURSES_LIBS $LIBS"
found_ncurses=yes
],
found_ncurses=no
)
fi
if test "x$found_ncurses" = xno; then if test "x$found_ncurses" = xno; then
AC_SEARCH_LIBS( AC_SEARCH_LIBS(
setupterm, setupterm,
[tinfo terminfo ncurses ncursesw], [tinfow tinfo terminfo ncursesw ncurses],
found_ncurses=yes, found_ncurses=yes,
found_ncurses=no found_ncurses=no
) )

View File

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

View File

@@ -152,7 +152,8 @@ environ_clear(struct environ *env, const char *name)
void void
environ_put(struct environ *env, const char *var, int flags) environ_put(struct environ *env, const char *var, int flags)
{ {
char *name, *value; char *name;
const char *value;
value = strchr(var, '='); value = strchr(var, '=');
if (value == NULL) if (value == NULL)

316
format.c
View File

@@ -117,6 +117,10 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2)
#define FORMAT_NOT_NOT 0x100000 #define FORMAT_NOT_NOT 0x100000
#define FORMAT_REPEAT 0x200000 #define FORMAT_REPEAT 0x200000
#define FORMAT_QUOTE_ARGUMENTS 0x400000 #define FORMAT_QUOTE_ARGUMENTS 0x400000
#define FORMAT_RELATIVE 0x800000
#define FORMAT_CLIENT_TERMCAP 0x1000000
#define FORMAT_CLIENT_TERMFEAT 0x2000000
#define FORMAT_CLIENT_ENVIRON 0x4000000
/* Limit on recursion. */ /* Limit on recursion. */
#define FORMAT_LOOP_LIMIT 100 #define FORMAT_LOOP_LIMIT 100
@@ -124,6 +128,9 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2)
/* Limit on time taken (milliseconds). */ /* Limit on time taken (milliseconds). */
#define FORMAT_TIME_LIMIT 100 #define FORMAT_TIME_LIMIT 100
/* How often to check the time in long loops. */
#define FORMAT_TIME_LOOP_CHECK 10000
/* Format expand flags. */ /* Format expand flags. */
#define FORMAT_EXPAND_TIME 0x1 #define FORMAT_EXPAND_TIME 0x1
#define FORMAT_EXPAND_NOJOBS 0x2 #define FORMAT_EXPAND_NOJOBS 0x2
@@ -569,7 +576,7 @@ format_cb_session_attached_list(struct format_tree *ft)
} }
if ((size = EVBUFFER_LENGTH(buffer)) != 0) if ((size = EVBUFFER_LENGTH(buffer)) != 0)
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); value = xmemdup(EVBUFFER_DATA(buffer), size);
evbuffer_free(buffer); evbuffer_free(buffer);
return (value); return (value);
} }
@@ -708,7 +715,7 @@ format_cb_window_linked_sessions_list(struct format_tree *ft)
} }
if ((size = EVBUFFER_LENGTH(buffer)) != 0) if ((size = EVBUFFER_LENGTH(buffer)) != 0)
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); value = xmemdup(EVBUFFER_DATA(buffer), size);
evbuffer_free(buffer); evbuffer_free(buffer);
return (value); return (value);
} }
@@ -762,7 +769,7 @@ format_cb_window_active_sessions_list(struct format_tree *ft)
} }
if ((size = EVBUFFER_LENGTH(buffer)) != 0) if ((size = EVBUFFER_LENGTH(buffer)) != 0)
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); value = xmemdup(EVBUFFER_DATA(buffer), size);
evbuffer_free(buffer); evbuffer_free(buffer);
return (value); return (value);
} }
@@ -826,7 +833,7 @@ format_cb_window_active_clients_list(struct format_tree *ft)
} }
if ((size = EVBUFFER_LENGTH(buffer)) != 0) if ((size = EVBUFFER_LENGTH(buffer)) != 0)
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); value = xmemdup(EVBUFFER_DATA(buffer), size);
evbuffer_free(buffer); evbuffer_free(buffer);
return (value); return (value);
} }
@@ -1001,7 +1008,7 @@ format_cb_pane_tabs(struct format_tree *ft)
evbuffer_add_printf(buffer, "%u", i); evbuffer_add_printf(buffer, "%u", i);
} }
if ((size = EVBUFFER_LENGTH(buffer)) != 0) if ((size = EVBUFFER_LENGTH(buffer)) != 0)
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); value = xmemdup(EVBUFFER_DATA(buffer), size);
evbuffer_free(buffer); evbuffer_free(buffer);
return (value); return (value);
} }
@@ -1016,7 +1023,7 @@ format_cb_pane_fg(struct format_tree *ft)
if (wp == NULL) if (wp == NULL)
return (NULL); return (NULL);
tty_default_colours(&gc, wp); tty_default_colours(&gc, wp, NULL);
return (xstrdup(colour_tostring(gc.fg))); return (xstrdup(colour_tostring(gc.fg)));
} }
@@ -1053,7 +1060,7 @@ format_cb_pane_bg(struct format_tree *ft)
if (wp == NULL) if (wp == NULL)
return (NULL); return (NULL);
tty_default_colours(&gc, wp); tty_default_colours(&gc, wp, NULL);
return (xstrdup(colour_tostring(gc.bg))); return (xstrdup(colour_tostring(gc.bg)));
} }
@@ -1085,7 +1092,7 @@ format_cb_session_group_list(struct format_tree *ft)
} }
if ((size = EVBUFFER_LENGTH(buffer)) != 0) if ((size = EVBUFFER_LENGTH(buffer)) != 0)
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); value = xmemdup(EVBUFFER_DATA(buffer), size);
evbuffer_free(buffer); evbuffer_free(buffer);
return (value); return (value);
} }
@@ -1125,7 +1132,7 @@ format_cb_session_group_attached_list(struct format_tree *ft)
} }
if ((size = EVBUFFER_LENGTH(buffer)) != 0) if ((size = EVBUFFER_LENGTH(buffer)) != 0)
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); value = xmemdup(EVBUFFER_DATA(buffer), size);
evbuffer_free(buffer); evbuffer_free(buffer);
return (value); return (value);
} }
@@ -1153,15 +1160,13 @@ static void *
format_cb_pane_at_top(struct format_tree *ft) format_cb_pane_at_top(struct format_tree *ft)
{ {
struct window_pane *wp = ft->wp; struct window_pane *wp = ft->wp;
struct window *w;
int status, flag; int status, flag;
char *value; char *value;
if (wp == NULL) if (wp == NULL)
return (NULL); return (NULL);
w = wp->window;
status = options_get_number(w->options, "pane-border-status"); status = window_pane_get_pane_status(wp);
if (status == PANE_STATUS_TOP) if (status == PANE_STATUS_TOP)
flag = (wp->yoff == 1); flag = (wp->yoff == 1);
else else
@@ -1183,7 +1188,7 @@ format_cb_pane_at_bottom(struct format_tree *ft)
return (NULL); return (NULL);
w = wp->window; w = wp->window;
status = options_get_number(w->options, "pane-border-status"); status = window_pane_get_pane_status(wp);
if (status == PANE_STATUS_BOTTOM) if (status == PANE_STATUS_BOTTOM)
flag = (wp->yoff + (int)wp->sy == (int)w->sy - 1); flag = (wp->yoff + (int)wp->sy == (int)w->sy - 1);
else else
@@ -1472,6 +1477,33 @@ format_cb_client_cell_width(struct format_tree *ft)
return (NULL); return (NULL);
} }
/* Callback for client_colours. */
static void *
format_cb_client_colours(struct format_tree *ft)
{
struct tty_term *term;
u_int colours;
if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED))
return (NULL);
term = ft->c->tty.term;
if (term->flags & TERM_RGBCOLOURS)
colours = 16777216;
else if (term->flags & TERM_256COLOURS)
colours = 256;
else {
colours = tty_term_number(term, TTYC_COLORS);
if (colours < 8)
colours = 2;
else if (colours < 16)
colours = 8;
else
colours = 16;
}
return (format_printf("%u", colours));
}
/* Callback for client_control_mode. */ /* Callback for client_control_mode. */
static void * static void *
format_cb_client_control_mode(struct format_tree *ft) format_cb_client_control_mode(struct format_tree *ft)
@@ -1851,15 +1883,6 @@ format_cb_keypad_flag(struct format_tree *ft)
return (NULL); return (NULL);
} }
/* Callback for loop_last_flag. */
static void *
format_cb_loop_last_flag(struct format_tree *ft)
{
if (ft->flags & FORMAT_LAST)
return (xstrdup("1"));
return (xstrdup("0"));
}
/* Callback for mouse_all_flag. */ /* Callback for mouse_all_flag. */
static void * static void *
format_cb_mouse_all_flag(struct format_tree *ft) format_cb_mouse_all_flag(struct format_tree *ft)
@@ -3218,6 +3241,9 @@ static const struct format_table_entry format_table[] = {
{ "client_cell_width", FORMAT_TABLE_STRING, { "client_cell_width", FORMAT_TABLE_STRING,
format_cb_client_cell_width format_cb_client_cell_width
}, },
{ "client_colours", FORMAT_TABLE_STRING,
format_cb_client_colours
},
{ "client_control_mode", FORMAT_TABLE_STRING, { "client_control_mode", FORMAT_TABLE_STRING,
format_cb_client_control_mode format_cb_client_control_mode
}, },
@@ -3344,9 +3370,6 @@ static const struct format_table_entry format_table[] = {
{ "last_window_index", FORMAT_TABLE_STRING, { "last_window_index", FORMAT_TABLE_STRING,
format_cb_last_window_index format_cb_last_window_index
}, },
{ "loop_last_flag", FORMAT_TABLE_STRING,
format_cb_loop_last_flag
},
{ "mouse_all_flag", FORMAT_TABLE_STRING, { "mouse_all_flag", FORMAT_TABLE_STRING,
format_cb_mouse_all_flag format_cb_mouse_all_flag
}, },
@@ -3774,7 +3797,7 @@ format_table_compare(const void *key0, const void *entry0)
} }
/* Get a format callback. */ /* Get a format callback. */
static struct format_table_entry * static const struct format_table_entry *
format_table_get(const char *key) format_table_get(const char *key)
{ {
return (bsearch(key, format_table, nitems(format_table), return (bsearch(key, format_table, nitems(format_table),
@@ -4063,12 +4086,52 @@ format_pretty_time(time_t t, int seconds)
return (xstrdup(s)); return (xstrdup(s));
} }
/* Make a relative time. */
static char *
format_relative_time(time_t t)
{
time_t now, age;
u_int d, h, m, s;
char out[32];
time(&now);
if (t > now)
return (NULL);
if (t == now)
return (xstrdup("0s"));
age = now - t;
d = age / 86400;
h = (age % 86400) / 3600;
m = (age % 3600) / 60;
s = age % 60;
if (d != 0) {
if (h != 0)
xsnprintf(out, sizeof out, "%ud%uh", d, h);
else
xsnprintf(out, sizeof out, "%ud", d);
} else if (h != 0) {
if (m != 0)
xsnprintf(out, sizeof out, "%uh%um", h, m);
else
xsnprintf(out, sizeof out, "%uh", h);
} else if (m != 0) {
if (s != 0)
xsnprintf(out, sizeof out, "%um%us", m, s);
else
xsnprintf(out, sizeof out, "%um", m);
} else
xsnprintf(out, sizeof out, "%us", s);
return (xstrdup(out));
}
/* Find a format entry. */ /* Find a format entry. */
static char * static char *
format_find(struct format_tree *ft, const char *key, int modifiers, format_find(struct format_tree *ft, const char *key, int modifiers,
const char *time_format) const char *time_format)
{ {
struct format_table_entry *fte; const struct format_table_entry *fte;
void *value; void *value;
struct format_entry *fe, fe_find; struct format_entry *fe, fe_find;
struct environ_entry *envent; struct environ_entry *envent;
@@ -4144,7 +4207,9 @@ found:
} }
if (t == 0) if (t == 0)
return (NULL); return (NULL);
if (modifiers & FORMAT_PRETTY) if (modifiers & FORMAT_RELATIVE)
found = format_relative_time(t);
else if (modifiers & FORMAT_PRETTY)
found = format_pretty_time(t, 0); found = format_pretty_time(t, 0);
else { else {
if (time_format != NULL) { if (time_format != NULL) {
@@ -4193,10 +4258,14 @@ found:
/* Check if format has not taken too long. */ /* Check if format has not taken too long. */
static int static int
format_check_time(struct format_expand_state *es) format_check_time(struct format_expand_state *es, u_int *check)
{ {
uint64_t t = get_timer(); uint64_t t;
if (check != NULL && ++*check % FORMAT_TIME_LOOP_CHECK != 0)
return (1);
t = get_timer();
if (t - es->start_time < FORMAT_TIME_LIMIT) if (t - es->start_time < FORMAT_TIME_LIMIT)
return (1); return (1);
t -= es->start_time; t -= es->start_time;
@@ -4207,21 +4276,24 @@ format_check_time(struct format_expand_state *es)
/* Unescape escaped characters. */ /* Unescape escaped characters. */
static char * static char *
format_unescape(struct format_expand_state *es, const char *s) format_unescape(struct format_expand_state *es, const char *s, size_t n)
{ {
const char *end = s + n;
char *out, *cp; char *out, *cp;
int brackets = 0; int brackets = 0;
u_int check = 0;
cp = out = xmalloc(strlen(s) + 1); cp = out = xmalloc(n + 1);
for (; *s != '\0'; s++) { for (; s != end; s++) {
if (!format_check_time(es)){ if (!format_check_time(es, &check)) {
free(out); free(out);
return (xstrdup("")); return (xstrdup(""));
} }
if (*s == '#' && s[1] == '{') if (*s == '#' && s + 1 != end && s[1] == '{')
brackets++; brackets++;
if (brackets == 0 && if (brackets == 0 &&
*s == '#' && *s == '#' &&
s + 1 != end &&
strchr(",#{}:", s[1]) != NULL) { strchr(",#{}:", s[1]) != NULL) {
*cp++ = *++s; *cp++ = *++s;
continue; continue;
@@ -4240,10 +4312,11 @@ format_strip(struct format_expand_state *es, const char *s)
{ {
char *out, *cp; char *out, *cp;
int brackets = 0; int brackets = 0;
u_int check = 0;
cp = out = xmalloc(strlen(s) + 1); cp = out = xmalloc(strlen(s) + 1);
for (; *s != '\0'; s++) { for (; *s != '\0'; s++) {
if (!format_check_time(es)){ if (!format_check_time(es, &check)) {
free(out); free(out);
return (xstrdup("")); return (xstrdup(""));
} }
@@ -4267,9 +4340,10 @@ static const char *
format_skip1(struct format_expand_state *es, const char *s, const char *end) format_skip1(struct format_expand_state *es, const char *s, const char *end)
{ {
int brackets = 0; int brackets = 0;
u_int check = 0;
for (; *s != '\0'; s++) { for (; *s != '\0'; s++) {
if (es != NULL && !format_check_time(es)) if (es != NULL && !format_check_time(es, &check))
return (NULL); return (NULL);
if (*s == '#' && s[1] == '{') if (*s == '#' && s[1] == '{')
brackets++; brackets++;
@@ -4379,7 +4453,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
/* /*
* Modifiers are a ; separated list of the forms: * Modifiers are a ; separated list of the forms:
* l,m,C,a,b,c,d,n,t,w,q,E,T,S,W,P,R,<,> * l,m,C,a,b,c,d,I,n,t,w,q,E,T,S,W,P,R,<,>
* =a * =a
* =/a * =/a
* =/a/ * =/a/
@@ -4420,7 +4494,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
} }
/* Now try single character with arguments. */ /* Now try single character with arguments. */
if (strchr("mCLNPSst=pReqW", cp[0]) == NULL) if (strchr("ImCLNPSst=pReqW", cp[0]) == NULL)
break; break;
c = cp[0]; c = cp[0];
@@ -4440,7 +4514,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
break; break;
argv = xcalloc(1, sizeof *argv); argv = xcalloc(1, sizeof *argv);
value = xstrndup(cp + 1, end - (cp + 1)); value = format_unescape(es, cp + 1, end - (cp + 1));
argv[0] = format_expand1(es, value); argv[0] = format_expand1(es, value);
free(value); free(value);
argc = 1; argc = 1;
@@ -4464,7 +4538,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
cp++; cp++;
argv = xreallocarray(argv, argc + 1, sizeof *argv); argv = xreallocarray(argv, argc + 1, sizeof *argv);
value = xstrndup(cp, end - cp); value = format_unescape(es, cp, end - cp);
argv[argc++] = format_expand1(es, value); argv[argc++] = format_expand1(es, value);
free(value); free(value);
@@ -4627,17 +4701,19 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
struct format_tree *nft; struct format_tree *nft;
struct format_expand_state next; struct format_expand_state next;
char *all, *active, *use, *expanded, *value; char *all, *active, *use, *expanded, *value;
size_t valuelen; struct evbuffer *buffer;
size_t size;
struct session *s, **l; struct session *s, **l;
int i, n, last = 0; int i, n;
if (format_choose(es, fmt, &all, &active, 0) != 0) { if (format_choose(es, fmt, &all, &active, 0) != 0) {
all = xstrdup(fmt); all = xstrdup(fmt);
active = NULL; active = NULL;
} }
value = xcalloc(1, 1); buffer = evbuffer_new();
valuelen = 1; if (buffer == NULL)
fatalx("out of memory");
l = sort_get_sessions(&n, sc); l = sort_get_sessions(&n, sc);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
@@ -4649,25 +4725,27 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
use = active; use = active;
else else
use = all; use = all;
if (i == n - 1) nft = format_create(c, item, FORMAT_NONE, ft->flags);
last = FORMAT_LAST; format_add(nft, "loop_index", "%d", i);
nft = format_create(c, item, FORMAT_NONE, ft->flags|last); format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, ft->c, s, NULL, NULL); format_defaults(nft, ft->c, s, NULL, NULL);
format_copy_state(&next, es, 0); format_copy_state(&next, es, 0);
next.ft = nft; next.ft = nft;
expanded = format_expand1(&next, use); expanded = format_expand1(&next, use);
format_free(next.ft); format_free(next.ft);
valuelen += strlen(expanded); evbuffer_add(buffer, expanded, strlen(expanded));
value = xrealloc(value, valuelen);
strlcat(value, expanded, valuelen);
free(expanded); free(expanded);
} }
free(active); free(active);
free(all); free(all);
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
value = xmemdup(EVBUFFER_DATA(buffer), size);
else
value = xstrdup("");
evbuffer_free(buffer);
return (value); return (value);
} }
@@ -4737,10 +4815,11 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
struct format_tree *nft; struct format_tree *nft;
struct format_expand_state next; struct format_expand_state next;
char *all, *active, *use, *expanded, *value; char *all, *active, *use, *expanded, *value;
size_t valuelen; struct evbuffer *buffer;
size_t size;
struct winlink *wl, **l; struct winlink *wl, **l;
struct window *w; struct window *w;
int i, n, last = 0; int i, n;
if (ft->s == NULL) { if (ft->s == NULL) {
format_log(es, "window loop but no session"); format_log(es, "window loop but no session");
@@ -4752,8 +4831,9 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
active = NULL; active = NULL;
} }
value = xcalloc(1, 1); buffer = evbuffer_new();
valuelen = 1; if (buffer == NULL)
fatalx("out of memory");
l = sort_get_winlinks_session(ft->s, &n, sc); l = sort_get_winlinks_session(ft->s, &n, sc);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
@@ -4764,10 +4844,10 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
use = active; use = active;
else else
use = all; use = all;
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, FORMAT_WINDOW|w->id, nft = format_create(c, item, FORMAT_WINDOW|w->id,
ft->flags|last); ft->flags);
format_add(nft, "loop_index", "%d", i);
format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, ft->c, ft->s, wl, NULL); format_defaults(nft, ft->c, ft->s, wl, NULL);
/* Add neighbor window data to the format tree. */ /* Add neighbor window data to the format tree. */
@@ -4785,16 +4865,18 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
expanded = format_expand1(&next, use); expanded = format_expand1(&next, use);
format_free(nft); format_free(nft);
valuelen += strlen(expanded); evbuffer_add(buffer, expanded, strlen(expanded));
value = xrealloc(value, valuelen);
strlcat(value, expanded, valuelen);
free(expanded); free(expanded);
} }
free(active); free(active);
free(all); free(all);
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
value = xmemdup(EVBUFFER_DATA(buffer), size);
else
value = xstrdup("");
evbuffer_free(buffer);
return (value); return (value);
} }
@@ -4809,9 +4891,10 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
struct format_tree *nft; struct format_tree *nft;
struct format_expand_state next; struct format_expand_state next;
char *all, *active, *use, *expanded, *value; char *all, *active, *use, *expanded, *value;
size_t valuelen; struct evbuffer *buffer;
size_t size;
struct window_pane *wp, **l; struct window_pane *wp, **l;
int i, n, last = 0; int i, n;
if (ft->w == NULL) { if (ft->w == NULL) {
format_log(es, "pane loop but no window"); format_log(es, "pane loop but no window");
@@ -4823,8 +4906,9 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
active = NULL; active = NULL;
} }
value = xcalloc(1, 1); buffer = evbuffer_new();
valuelen = 1; if (buffer == NULL)
fatalx("out of memory");
l = sort_get_panes_window(ft->w, &n, sc); l = sort_get_panes_window(ft->w, &n, sc);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
@@ -4834,26 +4918,28 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
use = active; use = active;
else else
use = all; use = all;
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, FORMAT_PANE|wp->id, nft = format_create(c, item, FORMAT_PANE|wp->id,
ft->flags|last); ft->flags);
format_add(nft, "loop_index", "%d", i);
format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, ft->c, ft->s, ft->wl, wp); format_defaults(nft, ft->c, ft->s, ft->wl, wp);
format_copy_state(&next, es, 0); format_copy_state(&next, es, 0);
next.ft = nft; next.ft = nft;
expanded = format_expand1(&next, use); expanded = format_expand1(&next, use);
format_free(nft); format_free(nft);
valuelen += strlen(expanded); evbuffer_add(buffer, expanded, strlen(expanded));
value = xrealloc(value, valuelen);
strlcat(value, expanded, valuelen);
free(expanded); free(expanded);
} }
free(active); free(active);
free(all); free(all);
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
value = xmemdup(EVBUFFER_DATA(buffer), size);
else
value = xstrdup("");
evbuffer_free(buffer);
return (value); return (value);
} }
@@ -4868,32 +4954,36 @@ format_loop_clients(struct format_expand_state *es, const char *fmt)
struct format_tree *nft; struct format_tree *nft;
struct format_expand_state next; struct format_expand_state next;
char *expanded, *value; char *expanded, *value;
size_t valuelen; struct evbuffer *buffer;
int i, n, last = 0; size_t size;
int i, n;
value = xcalloc(1, 1); buffer = evbuffer_new();
valuelen = 1; if (buffer == NULL)
fatalx("out of memory");
l = sort_get_clients(&n, sc); l = sort_get_clients(&n, sc);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
c = l[i]; c = l[i];
format_log(es, "client loop: %s", c->name); format_log(es, "client loop: %s", c->name);
if (i == n - 1) nft = format_create(c, item, 0, ft->flags);
last = FORMAT_LAST; format_add(nft, "loop_index", "%d", i);
nft = format_create(c, item, 0, ft->flags|last); format_add(nft, "loop_last_flag", "%d", i == n - 1);
format_defaults(nft, c, ft->s, ft->wl, ft->wp); format_defaults(nft, c, ft->s, ft->wl, ft->wp);
format_copy_state(&next, es, 0); format_copy_state(&next, es, 0);
next.ft = nft; next.ft = nft;
expanded = format_expand1(&next, fmt); expanded = format_expand1(&next, fmt);
format_free(nft); format_free(nft);
valuelen += strlen(expanded); evbuffer_add(buffer, expanded, strlen(expanded));
value = xrealloc(value, valuelen);
strlcat(value, expanded, valuelen);
free(expanded); free(expanded);
} }
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
value = xmemdup(EVBUFFER_DATA(buffer), size);
else
value = xstrdup("");
evbuffer_free(buffer);
return (value); return (value);
} }
@@ -5059,8 +5149,9 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
struct format_modifier *list, *cmp = NULL, *search = NULL; struct format_modifier *list, *cmp = NULL, *search = NULL;
struct format_modifier **sub = NULL, *mexp = NULL, *fm; struct format_modifier **sub = NULL, *mexp = NULL, *fm;
struct format_modifier *bool_op_n = NULL; struct format_modifier *bool_op_n = NULL;
u_int i, count, nsub = 0, nrep; u_int i, count, nsub = 0, nrep, check = 0;
struct format_expand_state next; struct format_expand_state next;
struct environ_entry *envent;
/* Set sorting defaults. */ /* Set sorting defaults. */
sc->order = SORT_ORDER; sc->order = SORT_ORDER;
@@ -5143,16 +5234,29 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
case 'n': case 'n':
modifiers |= FORMAT_LENGTH; modifiers |= FORMAT_LENGTH;
break; break;
case 'I':
if (fm->argc < 1)
break;
if (strchr(fm->argv[0], 'f') != NULL)
modifiers |= FORMAT_CLIENT_TERMFEAT;
if (strchr(fm->argv[0], 'c') != NULL)
modifiers |= FORMAT_CLIENT_TERMCAP;
if (strchr(fm->argv[0], 'e') != NULL)
modifiers |= FORMAT_CLIENT_ENVIRON;
break;
case 't': case 't':
modifiers |= FORMAT_TIMESTRING; modifiers |= FORMAT_TIMESTRING;
if (fm->argc < 1) if (fm->argc < 1)
break; break;
if (strchr(fm->argv[0], 'p') != NULL) if (strchr(fm->argv[0], 'p') != NULL)
modifiers |= FORMAT_PRETTY; modifiers |= FORMAT_PRETTY;
else if (strchr(fm->argv[0], 'r') != NULL)
modifiers |= FORMAT_RELATIVE;
else if (fm->argc >= 2 && else if (fm->argc >= 2 &&
strchr(fm->argv[0], 'f') != NULL) { strchr(fm->argv[0], 'f') != NULL) {
free(time_format); free(time_format);
time_format = format_strip(es, fm->argv[1]); time_format = format_strip(es,
fm->argv[1]);
} }
break; break;
case 'q': case 'q':
@@ -5267,10 +5371,42 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
} }
} }
/* Look up client capability, feature or environment. */
if ((modifiers & FORMAT_CLIENT_TERMCAP) ||
(modifiers & FORMAT_CLIENT_TERMFEAT) ||
(modifiers & FORMAT_CLIENT_ENVIRON)) {
if (ft->c == NULL ||
ft->c->tty.term == NULL ||
ft->c->flags & CLIENT_UNATTACHEDFLAGS) {
value = xstrdup("");
goto done;
}
if (modifiers & FORMAT_CLIENT_TERMCAP) {
if (tty_term_has_name(ft->c->tty.term, copy))
value = xstrdup("1");
else
value = xstrdup("0");
}
if (modifiers & FORMAT_CLIENT_TERMFEAT) {
if (tty_feature_present(ft->c->tty.term, copy))
value = xstrdup("1");
else
value = xstrdup("0");
}
if (modifiers & FORMAT_CLIENT_ENVIRON) {
envent = environ_find(ft->c->environ, copy);
if (envent != NULL && envent->value != NULL)
value = xstrdup(envent->value);
else
value = xstrdup("");
}
goto done;
}
/* Is this a literal string? */ /* Is this a literal string? */
if (modifiers & FORMAT_LITERAL) { if (modifiers & FORMAT_LITERAL) {
format_log(es, "literal string is '%s'", copy); format_log(es, "literal string is '%s'", copy);
value = format_unescape(es, copy); value = format_unescape(es, copy, strlen(copy));
goto done; goto done;
} }
@@ -5346,7 +5482,7 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
else { else {
value = xstrdup(""); value = xstrdup("");
for (i = 0; i < nrep; i++) { for (i = 0; i < nrep; i++) {
if (!format_check_time(es)) { if (!format_check_time(es, &check)) {
free(right); free(right);
free(left); free(left);
free(value); free(value);
@@ -5626,7 +5762,7 @@ format_expand1(struct format_expand_state *es, const char *fmt)
int ch, brackets; int ch, brackets;
char expanded[8192]; char expanded[8192];
if (fmt == NULL || *fmt == '\0' || !format_check_time(es)) if (fmt == NULL || *fmt == '\0' || !format_check_time(es, NULL))
return (xstrdup("")); return (xstrdup(""));
if (es->loop == FORMAT_LOOP_LIMIT) { if (es->loop == FORMAT_LOOP_LIMIT) {

11
grid.c
View File

@@ -292,7 +292,7 @@ grid_free_line(struct grid *gd, u_int py)
} }
/* Free several lines. */ /* Free several lines. */
static void void
grid_free_lines(struct grid *gd, u_int py, u_int ny) grid_free_lines(struct grid *gd, u_int py, u_int ny)
{ {
u_int yy; u_int yy;
@@ -320,6 +320,10 @@ grid_create(u_int sx, u_int sy, u_int hlimit)
gd->hsize = 0; gd->hsize = 0;
gd->hlimit = hlimit; gd->hlimit = hlimit;
gd->scroll_added = 0;
gd->scroll_collected = 0;
gd->scroll_generation = 0;
if (gd->sy != 0) if (gd->sy != 0)
gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata); gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata);
else else
@@ -405,6 +409,7 @@ grid_collect_history(struct grid *gd, int all)
grid_trim_history(gd, ny); grid_trim_history(gd, ny);
gd->hsize -= ny; gd->hsize -= ny;
gd->scroll_collected += ny;
if (gd->hscrolled > gd->hsize) if (gd->hscrolled > gd->hsize)
gd->hscrolled = gd->hsize; gd->hscrolled = gd->hsize;
} }
@@ -442,6 +447,7 @@ grid_scroll_history(struct grid *gd, u_int bg)
grid_compact_line(&gd->linedata[gd->hsize]); grid_compact_line(&gd->linedata[gd->hsize]);
gd->linedata[gd->hsize].time = current_time; gd->linedata[gd->hsize].time = current_time;
gd->hsize++; gd->hsize++;
gd->scroll_added++;
} }
/* Clear the history. */ /* Clear the history. */
@@ -452,6 +458,7 @@ grid_clear_history(struct grid *gd)
gd->hscrolled = 0; gd->hscrolled = 0;
gd->hsize = 0; gd->hsize = 0;
gd->scroll_generation++;
gd->linedata = xreallocarray(gd->linedata, gd->sy, gd->linedata = xreallocarray(gd->linedata, gd->sy,
sizeof *gd->linedata); sizeof *gd->linedata);
@@ -489,6 +496,7 @@ grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg)
/* Move the history offset down over the line. */ /* Move the history offset down over the line. */
gd->hscrolled++; gd->hscrolled++;
gd->hsize++; gd->hsize++;
gd->scroll_added++;
} }
/* Expand line to fit to cell. */ /* Expand line to fit to cell. */
@@ -1510,6 +1518,7 @@ grid_reflow(struct grid *gd, u_int sx)
free(gd->linedata); free(gd->linedata);
gd->linedata = target->linedata; gd->linedata = target->linedata;
free(target); free(target);
gd->scroll_generation++;
} }
/* Convert to position based on wrapped lines. */ /* Convert to position based on wrapped lines. */

View File

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

View File

@@ -483,7 +483,7 @@ input_key_vt10x(struct bufferevent *bev, key_code key)
{ {
struct utf8_data ud; struct utf8_data ud;
key_code onlykey; key_code onlykey;
char *p; const char *p;
static const char *standard_map[2] = { static const char *standard_map[2] = {
"1!9(0)=+;:'\",<.>/-8? 2", "1!9(0)=+;:'\",<.>/-8? 2",
"119900=+;;'',,..\x1f\x1f\x7f\x7f\0\0", "119900=+;;'',,..\x1f\x1f\x7f\x7f\0\0",
@@ -806,7 +806,7 @@ input_key_mouse(struct window_pane *wp, struct mouse_event *m)
return; return;
if (cmd_mouse_at(wp, m, &x, &y, 0) != 0) if (cmd_mouse_at(wp, m, &x, &y, 0) != 0)
return; return;
if (!window_pane_visible(wp)) if (!window_pane_is_visible(wp))
return; return;
if (!input_key_get_mouse(s, m, x, y, &buf, &len)) if (!input_key_get_mouse(s, m, x, y, &buf, &len))
return; return;

18
input.c
View File

@@ -1365,7 +1365,7 @@ input_esc_dispatch(struct input_ctx *ictx)
{ {
struct screen_write_ctx *sctx = &ictx->ctx; struct screen_write_ctx *sctx = &ictx->ctx;
struct screen *s = sctx->s; struct screen *s = sctx->s;
struct input_table_entry *entry; const struct input_table_entry *entry;
if (ictx->flags & INPUT_DISCARD) if (ictx->flags & INPUT_DISCARD)
return (0); return (0);
@@ -1441,7 +1441,7 @@ input_csi_dispatch(struct input_ctx *ictx)
{ {
struct screen_write_ctx *sctx = &ictx->ctx; struct screen_write_ctx *sctx = &ictx->ctx;
struct screen *s = sctx->s; struct screen *s = sctx->s;
struct input_table_entry *entry; const struct input_table_entry *entry;
struct options *oo; struct options *oo;
int i, n, m, ek, set, p; int i, n, m, ek, set, p;
u_int cx, bg = ictx->cell.cell.bg; u_int cx, bg = ictx->cell.cell.bg;
@@ -2701,7 +2701,7 @@ input_exit_osc(struct input_ctx *ictx)
case 2: case 2:
if (wp != NULL && if (wp != NULL &&
options_get_number(wp->options, "allow-set-title") && options_get_number(wp->options, "allow-set-title") &&
screen_set_title(sctx->s, p)) { screen_set_title(sctx->s, p, 1)) {
notify_pane("pane-title-changed", wp); notify_pane("pane-title-changed", wp);
server_redraw_window_borders(wp->window); server_redraw_window_borders(wp->window);
server_status_window(wp->window); server_status_window(wp->window);
@@ -2711,7 +2711,7 @@ input_exit_osc(struct input_ctx *ictx)
input_osc_4(ictx, p); input_osc_4(ictx, p);
break; break;
case 7: case 7:
if (screen_set_path(sctx->s, p) && wp != NULL) { if (wp != NULL && screen_set_path(sctx->s, p, 1)) {
server_redraw_window_borders(wp->window); server_redraw_window_borders(wp->window);
server_status_window(wp->window); server_status_window(wp->window);
} }
@@ -2779,7 +2779,7 @@ input_exit_apc(struct input_ctx *ictx)
if (wp != NULL && if (wp != NULL &&
options_get_number(wp->options, "allow-set-title") && options_get_number(wp->options, "allow-set-title") &&
screen_set_title(sctx->s, ictx->input_buf)) { screen_set_title(sctx->s, ictx->input_buf, 1)) {
notify_pane("pane-title-changed", wp); notify_pane("pane-title-changed", wp);
server_redraw_window_borders(wp->window); server_redraw_window_borders(wp->window);
server_status_window(wp->window); server_status_window(wp->window);
@@ -2822,10 +2822,10 @@ input_exit_rename(struct input_ctx *ictx)
if (o != NULL) if (o != NULL)
options_remove_or_default(o, -1, NULL); options_remove_or_default(o, -1, NULL);
if (!options_get_number(w->options, "automatic-rename")) if (!options_get_number(w->options, "automatic-rename"))
window_set_name(w, ""); window_set_name(w, "", WINDOW_NAME_FORBID_EXT);
} else { } else {
options_set_number(w->options, "automatic-rename", 0); options_set_number(w->options, "automatic-rename", 0);
window_set_name(w, ictx->input_buf); window_set_name(w, ictx->input_buf, WINDOW_NAME_FORBID_EXT);
} }
server_redraw_window_borders(w); server_redraw_window_borders(w);
server_status_window(w); server_status_window(w);
@@ -3055,7 +3055,7 @@ input_osc_10(struct input_ctx *ictx, const char *p)
return; return;
c = window_pane_get_fg_control_client(wp); c = window_pane_get_fg_control_client(wp);
if (c == -1) { if (c == -1) {
tty_default_colours(&defaults, wp); tty_default_colours(&defaults, wp, NULL);
if (COLOUR_DEFAULT(defaults.fg)) if (COLOUR_DEFAULT(defaults.fg))
c = window_pane_get_fg(wp); c = window_pane_get_fg(wp);
else else
@@ -3225,7 +3225,7 @@ static int
input_osc_52_parse(struct input_ctx *ictx, const char *p, u_char **out, input_osc_52_parse(struct input_ctx *ictx, const char *p, u_char **out,
int *outlen, char *clip) int *outlen, char *clip)
{ {
char *end; const char *end;
size_t len; size_t len;
const char *allow = "cpqs01234567"; const char *allow = "cpqs01234567";
u_int i, j = 0; u_int i, j = 0;

View File

@@ -25,11 +25,10 @@
#include "tmux.h" #include "tmux.h"
#define DEFAULT_SESSION_MENU \ #define DEFAULT_SESSION_MENU \
" 'Next' 'n' {switch-client -n}" \ " #{S/t:#{?#{&&:#{<:#{loop_index},6},#{!:#{session_active}}},'Switch To #[underscore]#{session_name}' '' {switch-client -t=#{session_id}#} ,}}" \
" 'Previous' 'p' {switch-client -p}" \
" ''" \ " ''" \
" 'Renumber' 'N' {move-window -r}" \ " 'Renumber' 'N' {move-window -r}" \
" 'Rename' 'r' {command-prompt -I \"#S\" {rename-session -- '%%'}}" \ " 'Rename' 'r' {command-prompt -I '#S' {rename-session -- '%%'}}" \
" 'Detach' 'd' {detach-client}" \ " 'Detach' 'd' {detach-client}" \
" ''" \ " ''" \
" 'New Session' 's' {new-session}" \ " 'New Session' 's' {new-session}" \
@@ -65,7 +64,7 @@
" ''" \ " ''" \
" '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Up,}' 'u' {swap-pane -U}" \ " '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Up,}' 'u' {swap-pane -U}" \
" '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Down,}' 'd' {swap-pane -D}" \ " '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Down,}' 'd' {swap-pane -D}" \
" '#{?pane_marked_set,,-}Swap Marked' 's' {swap-pane}" \ " '#{?#{!:#{pane_floating_flag}},#{?pane_marked_set,,-}Swap Marked,}' 's' {swap-pane}" \
" ''" \ " ''" \
" 'Kill' 'X' {kill-pane}" \ " 'Kill' 'X' {kill-pane}" \
" 'Respawn' 'R' {respawn-pane -k}" \ " 'Respawn' 'R' {respawn-pane -k}" \
@@ -406,8 +405,10 @@ key_bindings_init(void)
"bind -N 'Choose a window from a list' w { choose-tree -Zw }", "bind -N 'Choose a window from a list' w { choose-tree -Zw }",
"bind -N 'Kill the active pane' x { confirm-before -p\"kill-pane #P? (y/n)\" kill-pane }", "bind -N 'Kill the active pane' x { confirm-before -p\"kill-pane #P? (y/n)\" kill-pane }",
"bind -N 'Zoom the active pane' z { resize-pane -Z }", "bind -N 'Zoom the active pane' z { resize-pane -Z }",
"bind -N 'Swap the active pane with the pane above' '{' { swap-pane -U }", "bind -N 'Move pane to top-left corner' '{' { resize-pane -x50% -y50%; move-pane -P top-left }",
"bind -N 'Swap the active pane with the pane below' '}' { swap-pane -D }", "bind -N 'Move pane to top-right corner' '}' { resize-pane -x50% -y50%; move-pane -P top-right }",
"bind -N 'Move pane to bottom-left corner' 'M-{' { resize-pane -x50% -y50%; move-pane -P bottom-left }",
"bind -N 'Move pane to bottom-right corner' 'M-}' { resize-pane -x50% -y50%; move-pane -P bottom-right }",
"bind -N 'Show messages' '~' { show-messages }", "bind -N 'Show messages' '~' { show-messages }",
"bind -N 'Enter copy mode and scroll up' PPage { copy-mode -u }", "bind -N 'Enter copy mode and scroll up' PPage { copy-mode -u }",
"bind -N 'Select the pane above the active pane' -r Up { select-pane -U }", "bind -N 'Select the pane above the active pane' -r Up { select-pane -U }",
@@ -429,13 +430,13 @@ key_bindings_init(void)
"bind -N 'Move the visible part of the window left' -r S-Left { refresh-client -L 10 }", "bind -N 'Move the visible part of the window left' -r S-Left { refresh-client -L 10 }",
"bind -N 'Move the visible part of the window right' -r S-Right { refresh-client -R 10 }", "bind -N 'Move the visible part of the window right' -r S-Right { refresh-client -R 10 }",
"bind -N 'Reset so the visible part of the window follows the cursor' -r DC { refresh-client -c }", "bind -N 'Reset so the visible part of the window follows the cursor' -r DC { refresh-client -c }",
"bind -N 'Resize the pane up by 5' -r M-Up { resize-pane -U 5 }", "bind -N 'Resize the pane up by 5' -r M-Up if -F '#{?floating_pane_flag}' { resizep -D-5 } { resize-pane -U 5 }",
"bind -N 'Resize the pane down by 5' -r M-Down { resize-pane -D 5 }", "bind -N 'Resize the pane down by 5' -r M-Down { resize-pane -D 5 }",
"bind -N 'Resize the pane left by 5' -r M-Left { resize-pane -L 5 }", "bind -N 'Resize the pane left by 5' -r M-Left if -F '#{?floating_pane_flag}' { resizep -R-5 } { resize-pane -L 5 }",
"bind -N 'Resize the pane right by 5' -r M-Right { resize-pane -R 5 }", "bind -N 'Resize the pane right by 5' -r M-Right resize-pane -R 5",
"bind -N 'Resize the pane up' -r C-Up { resize-pane -U }", "bind -N 'Resize the pane up' -r C-Up if -F '#{?floating_pane_flag}' { resizep -D-1 } { resize-pane -U }",
"bind -N 'Resize the pane down' -r C-Down { resize-pane -D }", "bind -N 'Resize the pane down' -r C-Down { resize-pane -D }",
"bind -N 'Resize the pane left' -r C-Left { resize-pane -L }", "bind -N 'Resize the pane left' -r C-Left if -F '#{?floating_pane_flag}' { resizep -R-1 } { resize-pane -L }",
"bind -N 'Resize the pane right' -r C-Right { resize-pane -R }", "bind -N 'Resize the pane right' -r C-Right { resize-pane -R }",
/* Menu keys */ /* Menu keys */
@@ -448,6 +449,7 @@ key_bindings_init(void)
/* Mouse button 1 drag on pane. */ /* Mouse button 1 drag on pane. */
"bind -n MouseDrag1Pane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }", "bind -n MouseDrag1Pane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }",
"bind -n M-MouseDrag1Pane { move-pane -M }",
/* Mouse wheel up on pane. */ /* Mouse wheel up on pane. */
"bind -n WheelUpPane { if -F '#{||:#{alternate_on},#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -e } }", "bind -n WheelUpPane { if -F '#{||:#{alternate_on},#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -e } }",
@@ -466,6 +468,7 @@ key_bindings_init(void)
/* Mouse button 1 drag on border. */ /* Mouse button 1 drag on border. */
"bind -n MouseDrag1Border { resize-pane -M }", "bind -n MouseDrag1Border { resize-pane -M }",
"bind -n M-MouseDrag1Border { move-pane -M }",
/* Mouse button 1 down on status line. */ /* Mouse button 1 down on status line. */
"bind -n MouseDown1Status { switch-client -t= }", "bind -n MouseDown1Status { switch-client -t= }",
@@ -482,8 +485,8 @@ key_bindings_init(void)
"bind -n WheelUpStatus { previous-window }", "bind -n WheelUpStatus { previous-window }",
/* Mouse button 3 down on status left. */ /* Mouse button 3 down on status left. */
"bind -n MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }", "bind -n MouseDown3StatusLeft { run -C \"display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU "\" }",
"bind -n M-MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }", "bind -n M-MouseDown3StatusLeft { run -C \"display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU "\" }",
/* Mouse button 3 down on status line. */ /* Mouse button 3 down on status line. */
"bind -n MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}", "bind -n MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}",
@@ -511,8 +514,8 @@ key_bindings_init(void)
"bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }", "bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }",
"bind -Tcopy-mode C-n { send -X cursor-down }", "bind -Tcopy-mode C-n { send -X cursor-down }",
"bind -Tcopy-mode C-p { send -X cursor-up }", "bind -Tcopy-mode C-p { send -X cursor-up }",
"bind -Tcopy-mode C-r { command-prompt -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }", "bind -Tcopy-mode C-r { command-prompt -P -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }",
"bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }", "bind -Tcopy-mode C-s { command-prompt -P -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }",
"bind -Tcopy-mode C-v { send -X page-down }", "bind -Tcopy-mode C-v { send -X page-down }",
"bind -Tcopy-mode C-w { send -X copy-pipe-and-cancel }", "bind -Tcopy-mode C-w { send -X copy-pipe-and-cancel }",
"bind -Tcopy-mode Escape { send -X cancel }", "bind -Tcopy-mode Escape { send -X cancel }",
@@ -520,18 +523,18 @@ key_bindings_init(void)
"bind -Tcopy-mode Space { send -X page-down }", "bind -Tcopy-mode Space { send -X page-down }",
"bind -Tcopy-mode , { send -X jump-reverse }", "bind -Tcopy-mode , { send -X jump-reverse }",
"bind -Tcopy-mode \\; { send -X jump-again }", "bind -Tcopy-mode \\; { send -X jump-again }",
"bind -Tcopy-mode F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }", "bind -Tcopy-mode F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
"bind -Tcopy-mode N { send -X search-reverse }", "bind -Tcopy-mode N { send -X search-reverse }",
"bind -Tcopy-mode P { send -X toggle-position }", "bind -Tcopy-mode P { send -X toggle-position }",
"bind -Tcopy-mode R { send -X rectangle-toggle }", "bind -Tcopy-mode R { send -X rectangle-toggle }",
"bind -Tcopy-mode T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", "bind -Tcopy-mode T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
"bind -Tcopy-mode X { send -X set-mark }", "bind -Tcopy-mode X { send -X set-mark }",
"bind -Tcopy-mode f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }", "bind -Tcopy-mode f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
"bind -Tcopy-mode g { command-prompt -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-from-pane }", "bind -Tcopy-mode r { send -X refresh-toggle }",
"bind -Tcopy-mode t { command-prompt -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 }",
"bind -Tcopy-mode MouseDown1Pane select-pane", "bind -Tcopy-mode MouseDown1Pane select-pane",
@@ -547,15 +550,15 @@ key_bindings_init(void)
"bind -Tcopy-mode Down { send -X cursor-down }", "bind -Tcopy-mode Down { send -X cursor-down }",
"bind -Tcopy-mode Left { send -X cursor-left }", "bind -Tcopy-mode Left { send -X cursor-left }",
"bind -Tcopy-mode Right { send -X cursor-right }", "bind -Tcopy-mode Right { send -X cursor-right }",
"bind -Tcopy-mode M-1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }", "bind -Tcopy-mode M-1 { command-prompt -P -Np'(repeat)' -I1 { send -N '%%' } }",
"bind -Tcopy-mode M-2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }", "bind -Tcopy-mode M-2 { command-prompt -P -Np'(repeat)' -I2 { send -N '%%' } }",
"bind -Tcopy-mode M-3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }", "bind -Tcopy-mode M-3 { command-prompt -P -Np'(repeat)' -I3 { send -N '%%' } }",
"bind -Tcopy-mode M-4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }", "bind -Tcopy-mode M-4 { command-prompt -P -Np'(repeat)' -I4 { send -N '%%' } }",
"bind -Tcopy-mode M-5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }", "bind -Tcopy-mode M-5 { command-prompt -P -Np'(repeat)' -I5 { send -N '%%' } }",
"bind -Tcopy-mode M-6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }", "bind -Tcopy-mode M-6 { command-prompt -P -Np'(repeat)' -I6 { send -N '%%' } }",
"bind -Tcopy-mode M-7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }", "bind -Tcopy-mode M-7 { command-prompt -P -Np'(repeat)' -I7 { send -N '%%' } }",
"bind -Tcopy-mode M-8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }", "bind -Tcopy-mode M-8 { command-prompt -P -Np'(repeat)' -I8 { send -N '%%' } }",
"bind -Tcopy-mode M-9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }", "bind -Tcopy-mode M-9 { command-prompt -P -Np'(repeat)' -I9 { send -N '%%' } }",
"bind -Tcopy-mode M-< { send -X history-top }", "bind -Tcopy-mode M-< { send -X history-top }",
"bind -Tcopy-mode M-> { send -X history-bottom }", "bind -Tcopy-mode M-> { send -X history-bottom }",
"bind -Tcopy-mode M-R { send -X top-line }", "bind -Tcopy-mode M-R { send -X top-line }",
@@ -574,6 +577,8 @@ key_bindings_init(void)
"bind -Tcopy-mode M-Down { send -X halfpage-down }", "bind -Tcopy-mode M-Down { send -X halfpage-down }",
"bind -Tcopy-mode C-Up { send -X scroll-up }", "bind -Tcopy-mode C-Up { send -X scroll-up }",
"bind -Tcopy-mode C-Down { send -X scroll-down }", "bind -Tcopy-mode C-Down { send -X scroll-down }",
"bind -Tcopy-mode M-C-Up { send -X previous-prompt }",
"bind -Tcopy-mode M-C-Down { send -X next-prompt }",
/* Copy mode (vi) keys. */ /* Copy mode (vi) keys. */
"bind -Tcopy-mode-vi '#' { send -FX search-backward -- '#{copy_cursor_word}' }", "bind -Tcopy-mode-vi '#' { send -FX search-backward -- '#{copy_cursor_word}' }",
@@ -594,25 +599,25 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi Space { send -X begin-selection }", "bind -Tcopy-mode-vi Space { send -X begin-selection }",
"bind -Tcopy-mode-vi '$' { send -X end-of-line }", "bind -Tcopy-mode-vi '$' { send -X end-of-line }",
"bind -Tcopy-mode-vi , { send -X jump-reverse }", "bind -Tcopy-mode-vi , { send -X jump-reverse }",
"bind -Tcopy-mode-vi / { command-prompt -T search -p'(search down)' { send -X search-forward -- '%%' } }", "bind -Tcopy-mode-vi / { command-prompt -P -T search -p'(search down)' { send -X search-forward -- '%%' } }",
"bind -Tcopy-mode-vi 0 { send -X start-of-line }", "bind -Tcopy-mode-vi 0 { send -X start-of-line }",
"bind -Tcopy-mode-vi 1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }", "bind -Tcopy-mode-vi 1 { command-prompt -P -Np'(repeat)' -I1 { send -N '%%' } }",
"bind -Tcopy-mode-vi 2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }", "bind -Tcopy-mode-vi 2 { command-prompt -P -Np'(repeat)' -I2 { send -N '%%' } }",
"bind -Tcopy-mode-vi 3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }", "bind -Tcopy-mode-vi 3 { command-prompt -P -Np'(repeat)' -I3 { send -N '%%' } }",
"bind -Tcopy-mode-vi 4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }", "bind -Tcopy-mode-vi 4 { command-prompt -P -Np'(repeat)' -I4 { send -N '%%' } }",
"bind -Tcopy-mode-vi 5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }", "bind -Tcopy-mode-vi 5 { command-prompt -P -Np'(repeat)' -I5 { send -N '%%' } }",
"bind -Tcopy-mode-vi 6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }", "bind -Tcopy-mode-vi 6 { command-prompt -P -Np'(repeat)' -I6 { send -N '%%' } }",
"bind -Tcopy-mode-vi 7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }", "bind -Tcopy-mode-vi 7 { command-prompt -P -Np'(repeat)' -I7 { send -N '%%' } }",
"bind -Tcopy-mode-vi 8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }", "bind -Tcopy-mode-vi 8 { command-prompt -P -Np'(repeat)' -I8 { send -N '%%' } }",
"bind -Tcopy-mode-vi 9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }", "bind -Tcopy-mode-vi 9 { command-prompt -P -Np'(repeat)' -I9 { send -N '%%' } }",
"bind -Tcopy-mode-vi : { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }", "bind -Tcopy-mode-vi : { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }",
"bind -Tcopy-mode-vi \\; { send -X jump-again }", "bind -Tcopy-mode-vi \\; { send -X jump-again }",
"bind -Tcopy-mode-vi ? { command-prompt -T search -p'(search up)' { send -X search-backward -- '%%' } }", "bind -Tcopy-mode-vi ? { command-prompt -P -T search -p'(search up)' { send -X search-backward -- '%%' } }",
"bind -Tcopy-mode-vi A { send -X append-selection-and-cancel }", "bind -Tcopy-mode-vi A { send -X append-selection-and-cancel }",
"bind -Tcopy-mode-vi B { send -X previous-space }", "bind -Tcopy-mode-vi B { send -X previous-space }",
"bind -Tcopy-mode-vi D { send -X copy-pipe-end-of-line-and-cancel }", "bind -Tcopy-mode-vi D { send -X copy-pipe-end-of-line-and-cancel }",
"bind -Tcopy-mode-vi E { send -X next-space-end }", "bind -Tcopy-mode-vi E { send -X next-space-end }",
"bind -Tcopy-mode-vi F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }", "bind -Tcopy-mode-vi F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
"bind -Tcopy-mode-vi G { send -X history-bottom }", "bind -Tcopy-mode-vi G { send -X history-bottom }",
"bind -Tcopy-mode-vi H { send -X top-line }", "bind -Tcopy-mode-vi H { send -X top-line }",
"bind -Tcopy-mode-vi J { send -X scroll-down }", "bind -Tcopy-mode-vi J { send -X scroll-down }",
@@ -621,14 +626,14 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi M { send -X middle-line }", "bind -Tcopy-mode-vi M { send -X middle-line }",
"bind -Tcopy-mode-vi N { send -X search-reverse }", "bind -Tcopy-mode-vi N { send -X search-reverse }",
"bind -Tcopy-mode-vi P { send -X toggle-position }", "bind -Tcopy-mode-vi P { send -X toggle-position }",
"bind -Tcopy-mode-vi T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", "bind -Tcopy-mode-vi T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
"bind -Tcopy-mode-vi V { send -X select-line }", "bind -Tcopy-mode-vi V { send -X select-line }",
"bind -Tcopy-mode-vi W { send -X next-space }", "bind -Tcopy-mode-vi W { send -X next-space }",
"bind -Tcopy-mode-vi X { send -X set-mark }", "bind -Tcopy-mode-vi X { send -X set-mark }",
"bind -Tcopy-mode-vi ^ { send -X back-to-indentation }", "bind -Tcopy-mode-vi ^ { send -X back-to-indentation }",
"bind -Tcopy-mode-vi b { send -X previous-word }", "bind -Tcopy-mode-vi b { send -X previous-word }",
"bind -Tcopy-mode-vi e { send -X next-word-end }", "bind -Tcopy-mode-vi e { send -X next-word-end }",
"bind -Tcopy-mode-vi f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }", "bind -Tcopy-mode-vi f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
"bind -Tcopy-mode-vi g { send -X history-top }", "bind -Tcopy-mode-vi g { send -X history-top }",
"bind -Tcopy-mode-vi h { send -X cursor-left }", "bind -Tcopy-mode-vi h { send -X cursor-left }",
"bind -Tcopy-mode-vi j { send -X cursor-down }", "bind -Tcopy-mode-vi j { send -X cursor-down }",
@@ -638,8 +643,8 @@ 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-from-pane }", "bind -Tcopy-mode-vi r { send -X refresh-toggle }",
"bind -Tcopy-mode-vi t { command-prompt -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 }",
"bind -Tcopy-mode-vi '{' { send -X previous-paragraph }", "bind -Tcopy-mode-vi '{' { send -X previous-paragraph }",

View File

@@ -379,6 +379,8 @@ layout_construct(struct layout_cell *lcparent, const char **layout,
struct layout_cell *lcchild; struct layout_cell *lcchild;
*lc = layout_construct_cell(lcparent, layout); *lc = layout_construct_cell(lcparent, layout);
if (*lc == NULL)
return (-1);
switch (**layout) { switch (**layout) {
case ',': case ',':

403
layout.c
View File

@@ -20,17 +20,25 @@
#include <sys/types.h> #include <sys/types.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include "tmux.h" #include "tmux.h"
/* /*
* The window layout is a tree of cells each of which can be one of: a * The window layout is a tree of cells each of which can be one of: a
* left-right container for a list of cells, a top-bottom container for a list * left-right container for a list of cells, a top-bottom container for a list
* of cells, or a container for a window pane. * of cells, or a container for a window pane. 'Node' will be used to refer to
* a cell which contains a list of cells, and 'leaf' to refer to a cell that
* contains a window pane. A leaf is considered to be 'tiled' if it is to be
* drawn as a part of the tiled layout. A 'neighbour' is a sibling that is also
* tiled. A cell's 'split' size refers to the side that is shortened when
* splitting it, determined by the parent's type.
* *
* Each window has a pointer to the root of its layout tree (containing its * Each window has a pointer to the root of its layout tree (containing its
* panes), every pane has a pointer back to the cell containing it, and each * panes), every pane has a pointer back to the cell containing it, and each
* cell a pointer to its parent cell. * cell a pointer to its parent cell. Every cell has a position in the root
* layout tree. This position is retained through cell state changes such as
* floating or hiding.
*/ */
static u_int layout_resize_check(struct window *, struct layout_cell *, static u_int layout_resize_check(struct window *, struct layout_cell *,
@@ -240,6 +248,49 @@ layout_fix_zindexes(struct window *w, struct layout_cell *lc)
} }
} }
static int
layout_cell_is_tiled(struct layout_cell *lc)
{
int is_leaf = lc->type == LAYOUT_WINDOWPANE;
int is_floating = lc->flags & LAYOUT_CELL_FLOATING;
return is_leaf && !is_floating;
}
static int
layout_cell_has_tiled_child(struct layout_cell *lc)
{
struct layout_cell *lcchild;
if (lc->type == LAYOUT_WINDOWPANE)
return (0);
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (layout_cell_is_tiled(lcchild) ||
layout_cell_has_tiled_child(lcchild))
return (1);
}
return (0);
}
static int
layout_cell_is_first_tiled(struct layout_cell *lc)
{
struct layout_cell *lcchild, *lcparent = lc->parent;
if (lcparent == NULL)
return (layout_cell_is_tiled(lc));
TAILQ_FOREACH(lcchild, &lcparent->cells, entry) {
if (layout_cell_is_tiled(lcchild) ||
layout_cell_has_tiled_child(lcchild))
break;
}
return (lcchild == lc);
}
/* Fix cell offsets for a child cell. */ /* Fix cell offsets for a child cell. */
static void static void
layout_fix_offsets1(struct layout_cell *lc) layout_fix_offsets1(struct layout_cell *lc)
@@ -250,7 +301,8 @@ layout_fix_offsets1(struct layout_cell *lc)
if (lc->type == LAYOUT_LEFTRIGHT) { if (lc->type == LAYOUT_LEFTRIGHT) {
xoff = lc->xoff; xoff = lc->xoff;
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue; continue;
lcchild->xoff = xoff; lcchild->xoff = xoff;
lcchild->yoff = lc->yoff; lcchild->yoff = lc->yoff;
@@ -261,7 +313,8 @@ layout_fix_offsets1(struct layout_cell *lc)
} else { } else {
yoff = lc->yoff; yoff = lc->yoff;
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue; continue;
lcchild->xoff = lc->xoff; lcchild->xoff = lc->xoff;
lcchild->yoff = yoff; lcchild->yoff = yoff;
@@ -299,7 +352,7 @@ layout_cell_is_top(struct window *w, struct layout_cell *lc)
if (next == NULL) if (next == NULL)
return (0); return (0);
if (next->type == LAYOUT_TOPBOTTOM && if (next->type == LAYOUT_TOPBOTTOM &&
lc != TAILQ_FIRST(&next->cells)) !layout_cell_is_first_tiled(lc))
return (0); return (0);
lc = next; lc = next;
} }
@@ -310,15 +363,22 @@ layout_cell_is_top(struct window *w, struct layout_cell *lc)
static int static int
layout_cell_is_bottom(struct window *w, struct layout_cell *lc) layout_cell_is_bottom(struct window *w, struct layout_cell *lc)
{ {
struct layout_cell *next; struct layout_cell *next, *edge;
while (lc != w->layout_root) { while (lc != w->layout_root) {
next = lc->parent; next = lc->parent;
if (next == NULL) if (next == NULL)
return (0); return (0);
if (next->type == LAYOUT_TOPBOTTOM && if (next->type == LAYOUT_TOPBOTTOM) {
lc != TAILQ_LAST(&next->cells, layout_cells)) edge = TAILQ_LAST(&next->cells, layout_cells);
while (edge != NULL) {
if (~edge->flags & LAYOUT_CELL_FLOATING)
break;
edge = TAILQ_PREV(edge, layout_cells, entry);
}
if (lc != edge)
return (0); return (0);
}
lc = next; lc = next;
} }
return (1); return (1);
@@ -346,9 +406,10 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
struct window_pane *wp; struct window_pane *wp;
struct layout_cell *lc; struct layout_cell *lc;
int status, scrollbars, sb_pos, sb_w, sb_pad; int status, scrollbars, sb_pos, sb_w, sb_pad;
u_int sx, sy; int old_xoff, old_yoff, changed = 0;
u_int sx, sy, old_sx, old_sy;
status = options_get_number(w->options, "pane-border-status"); status = window_get_pane_status(w);
scrollbars = options_get_number(w->options, "pane-scrollbars"); scrollbars = options_get_number(w->options, "pane-scrollbars");
sb_pos = options_get_number(w->options, "pane-scrollbars-position"); sb_pos = options_get_number(w->options, "pane-scrollbars-position");
@@ -356,6 +417,11 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
if ((lc = wp->layout_cell) == NULL || wp == skip) if ((lc = wp->layout_cell) == NULL || wp == skip)
continue; continue;
old_xoff = wp->xoff;
old_yoff = wp->yoff;
old_sx = wp->sx;
old_sy = wp->sy;
wp->xoff = lc->xoff; wp->xoff = lc->xoff;
wp->yoff = lc->yoff; wp->yoff = lc->yoff;
sx = lc->sx; sx = lc->sx;
@@ -376,7 +442,7 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
if (sb_pad < 0) if (sb_pad < 0)
sb_pad = 0; sb_pad = 0;
if (sb_pos == PANE_SCROLLBARS_LEFT) { if (sb_pos == PANE_SCROLLBARS_LEFT) {
if ((int)sx - sb_w < PANE_MINIMUM) { if ((int)sx - sb_w - sb_pad < PANE_MINIMUM) {
wp->xoff = wp->xoff + wp->xoff = wp->xoff +
(int)sx - PANE_MINIMUM; (int)sx - PANE_MINIMUM;
sx = PANE_MINIMUM; sx = PANE_MINIMUM;
@@ -393,7 +459,15 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
} }
window_pane_resize(wp, sx, sy); window_pane_resize(wp, sx, sy);
if (wp->xoff != old_xoff ||
wp->yoff != old_yoff ||
wp->sx != old_sx ||
wp->sy != old_sy)
changed = 1;
} }
if (changed)
redraw_invalidate_scene(w);
} }
/* Count the number of available cells in a layout. */ /* Count the number of available cells in a layout. */
@@ -426,7 +500,7 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
u_int available, minimum; u_int available, minimum;
int status, scrollbars; int status, scrollbars;
status = options_get_number(w->options, "pane-border-status"); status = window_get_pane_status(w);
scrollbars = options_get_number(w->options, "pane-scrollbars"); scrollbars = options_get_number(w->options, "pane-scrollbars");
if (lc->type == LAYOUT_WINDOWPANE) { if (lc->type == LAYOUT_WINDOWPANE) {
@@ -491,13 +565,20 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc,
/* Child cell runs in a different direction. */ /* Child cell runs in a different direction. */
if (lc->type != type) { if (lc->type != type) {
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue; continue;
layout_resize_adjust(w, lcchild, type, change); layout_resize_adjust(w, lcchild, type, change);
} }
return; return;
} }
/*
* If a node doesn't contain any tiled cells, there is nothing to do.
*/
if (!layout_cell_has_tiled_child(lc))
return;
/* /*
* Child cell runs in the same direction. Adjust each child equally * Child cell runs in the same direction. Adjust each child equally
* until no further change is possible. * until no further change is possible.
@@ -506,7 +587,8 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc,
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (change == 0) if (change == 0)
break; break;
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue; continue;
if (change > 0) { if (change > 0) {
layout_resize_adjust(w, lcchild, type, 1); layout_resize_adjust(w, lcchild, type, 1);
@@ -521,12 +603,57 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc,
} }
} }
/* Find and return the nearest neighbour to a cell in a specific direction. */
static struct layout_cell *
layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction)
{
struct layout_cell *lcn = lc;
while (1) {
if (direction)
lcn = TAILQ_NEXT(lcn, entry);
else
lcn = TAILQ_PREV(lcn, layout_cells, entry);
if (lcn == NULL ||
layout_cell_is_tiled(lcn) ||
layout_cell_has_tiled_child(lcn))
return (lcn);
}
}
/*
* Find and return the nearest neighbour. Prefers cells "after" the specified
* cell. This behavior defines how cell dimensions are redistributed when a cell
* is hidden/shown and floated/tiled.
*/
struct layout_cell *
layout_cell_get_neighbour(struct layout_cell *lc)
{
struct layout_cell *lcother, *lcparent = lc->parent;
int direction = 1;
if (lcparent == NULL)
return (NULL);
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
direction = !direction;
lcother = layout_cell_get_neighbour_direction(lc, direction);
if (lcother == NULL)
lcother = layout_cell_get_neighbour_direction(lc, !direction);
return (lcother);
}
/* Destroy a cell and redistribute the space. */ /* Destroy a cell and redistribute the space. */
void void
layout_destroy_cell(struct window *w, struct layout_cell *lc, layout_destroy_cell(struct window *w, struct layout_cell *lc,
struct layout_cell **lcroot) struct layout_cell **lcroot)
{ {
struct layout_cell *lcother = NULL, *lcparent; struct layout_cell *lcother = NULL, *lcparent;
int change;
/* 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;
@@ -537,27 +664,27 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
return; return;
} }
if (~lc->flags & LAYOUT_CELL_FLOATING) { if (!layout_cell_is_tiled(lc)) {
/* Merge the space into the previous or next cell. */ TAILQ_REMOVE(&lcparent->cells, lc, entry);
if (lc == TAILQ_FIRST(&lcparent->cells)) layout_free_cell(lc);
lcother = TAILQ_NEXT(lc, entry); goto out;
}
lcother = layout_cell_get_neighbour(lc);
if (lcother != NULL) {
if (lcparent->type == LAYOUT_LEFTRIGHT)
change = lc->sx + 1;
else else
lcother = TAILQ_PREV(lc, layout_cells, entry); change = lc->sy + 1;
} layout_resize_adjust(w, lcother, lcparent->type, change);
if (lcother != NULL && (~lcother->flags & LAYOUT_CELL_FLOATING)) { } else
if (lcparent->type == LAYOUT_LEFTRIGHT) { layout_remove_tile(w, lcparent);
layout_resize_adjust(w, lcother, lcparent->type,
lc->sx + 1);
} else {
layout_resize_adjust(w, lcother, lcparent->type,
lc->sy + 1);
}
}
/* Remove this from the parent's list. */ /* Remove this from the parent's list. */
TAILQ_REMOVE(&lcparent->cells, lc, entry); TAILQ_REMOVE(&lcparent->cells, lc, entry);
layout_free_cell(lc); layout_free_cell(lc);
out:
/* /*
* If the parent now has one cell, remove the parent from the tree and * If the parent now has one cell, remove the parent from the tree and
* replace it by that cell. * replace it by that cell.
@@ -568,7 +695,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
lc->parent = lcparent->parent; lc->parent = lcparent->parent;
if (lc->parent == NULL) { if (lc->parent == NULL) {
if (~lc->flags & LAYOUT_CELL_FLOATING) { if (layout_cell_is_tiled(lc)) {
lc->xoff = 0; lc->xoff = 0;
lc->yoff = 0; lc->yoff = 0;
} }
@@ -686,6 +813,76 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
layout_resize_pane(wp, type, change, 1); layout_resize_pane(wp, type, change, 1);
} }
/* Resize a floating pane to an absolute size. */
void
layout_resize_floating_pane_to(struct window_pane *wp, enum layout_type type,
u_int size, char **cause)
{
struct layout_cell *lc = wp->layout_cell;
if (~lc->flags & LAYOUT_CELL_FLOATING) {
*cause = xstrdup("pane is not floating");
return;
}
if (window_pane_get_pane_lines(wp) != PANE_LINES_NONE &&
size >= PANE_MINIMUM + 2)
size -= 2;
if (size < PANE_MINIMUM || size > PANE_MAXIMUM) {
*cause = xstrdup("size is too big or too small");
return;
}
if (type == LAYOUT_TOPBOTTOM) {
if (lc->sy == size)
return;
lc->sy = size;
} else {
if (lc->sx == size)
return;
lc->sx = size;
}
redraw_invalidate_scene(wp->window);
}
/* Resize a floating pane relative to its current size. */
void
layout_resize_floating_pane(struct window_pane *wp, enum layout_type type,
int change, int opposite, char **cause)
{
struct layout_cell *lc = wp->layout_cell;
u_int size;
if (~lc->flags & LAYOUT_CELL_FLOATING) {
*cause = xstrdup("pane is not floating");
return;
}
if (change == 0)
return;
if (type == LAYOUT_TOPBOTTOM) {
size = lc->sy + change;
if (size < PANE_MINIMUM || size > PANE_MAXIMUM) {
*cause = xstrdup("change is too big or too small");
return;
}
lc->sy = size;
if (opposite)
lc->yoff -= change;
} else {
size = lc->sx + change;
if (size < PANE_MINIMUM || size > PANE_MAXIMUM) {
*cause = xstrdup("change is too big or too small");
return;
}
lc->sx = size;
if (opposite)
lc->xoff -= change;
}
redraw_invalidate_scene(wp->window);
}
/* Resize a layout cell. */
void void
layout_resize_layout(struct window *w, struct layout_cell *lc, layout_resize_layout(struct window *w, struct layout_cell *lc,
enum layout_type type, int change, int opposite) enum layout_type type, int change, int opposite)
@@ -719,9 +916,7 @@ void
layout_resize_pane(struct window_pane *wp, enum layout_type type, int change, layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
int opposite) int opposite)
{ {
struct layout_cell *lc, *lcparent; struct layout_cell *lc = wp->layout_cell, *lcparent;
lc = wp->layout_cell;
/* Find next parent of the same type. */ /* Find next parent of the same type. */
lcparent = lc->parent; lcparent = lc->parent;
@@ -733,8 +928,11 @@ layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
return; return;
/* If this is the last cell, move back one. */ /* If this is the last cell, move back one. */
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) {
do
lc = TAILQ_PREV(lc, layout_cells, entry); lc = TAILQ_PREV(lc, layout_cells, entry);
while (lc->flags & LAYOUT_CELL_FLOATING);
}
layout_resize_layout(wp->window, lc, type, change, opposite); layout_resize_layout(wp->window, lc, type, change, opposite);
} }
@@ -933,7 +1131,8 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc)
count = 0; count = 0;
previous = 0; previous = 0;
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue; continue;
count++; count++;
if (lc->type == LAYOUT_LEFTRIGHT) if (lc->type == LAYOUT_LEFTRIGHT)
@@ -953,7 +1152,8 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc)
/* Resize children into the new size. */ /* Resize children into the new size. */
idx = 0; idx = 0;
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue; continue;
if (lc->type == LAYOUT_TOPBOTTOM) { if (lc->type == LAYOUT_TOPBOTTOM) {
lcchild->sx = lc->sx; lcchild->sx = lc->sx;
@@ -999,7 +1199,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size,
lc = wp->window->layout_root; lc = wp->window->layout_root;
else else
lc = wp->layout_cell; lc = wp->layout_cell;
status = options_get_number(wp->window->options, "pane-border-status"); status = window_get_pane_status(wp->window);
scrollbars = options_get_number(wp->window->options, "pane-scrollbars"); scrollbars = options_get_number(wp->window->options, "pane-scrollbars");
/* Copy the old cell size. */ /* Copy the old cell size. */
@@ -1160,14 +1360,21 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size,
* layout_assign_pane before much else happens! * layout_assign_pane before much else happens!
*/ */
struct layout_cell * struct layout_cell *
layout_floating_pane(struct window *w, u_int sx, u_int sy, int ox, int oy) layout_floating_pane(struct window *w, struct window_pane *wp, u_int sx,
u_int sy, int ox, int oy)
{ {
struct layout_cell *lc = w->layout_root, *lcnew, *lcparent; struct layout_cell *lc, *lcnew, *lcparent;
if (lc->type == LAYOUT_WINDOWPANE) { if (wp == NULL)
lc = w->layout_root;
else
lc = wp->layout_cell;
lcparent = lc->parent;
if (lcparent == NULL) {
/* /*
* Adding a pane to a root that doesn't have a container. Must * Adding a pane to a root that isn't node. Must create and
* create and insert a new root. * insert a new root.
*/ */
lcparent = layout_create_cell(NULL); lcparent = layout_create_cell(NULL);
layout_make_node(lcparent, LAYOUT_TOPBOTTOM); layout_make_node(lcparent, LAYOUT_TOPBOTTOM);
@@ -1177,11 +1384,10 @@ layout_floating_pane(struct window *w, u_int sx, u_int sy, int ox, int oy)
/* Insert the old cell. */ /* Insert the old cell. */
lc->parent = lcparent; lc->parent = lcparent;
TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry); TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry);
} else }
lcparent = w->layout_root;
lcnew = layout_create_cell(lcparent); lcnew = layout_create_cell(lcparent);
TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry); TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry);
lcnew->flags |= LAYOUT_CELL_FLOATING; lcnew->flags |= LAYOUT_CELL_FLOATING;
layout_set_size(lcnew, sx, sy, ox, oy); layout_set_size(lcnew, sx, sy, ox, oy);
@@ -1222,7 +1428,7 @@ layout_spread_cell(struct window *w, struct layout_cell *parent)
number++; number++;
if (number <= 1) if (number <= 1)
return (0); return (0);
status = options_get_number(w->options, "pane-border-status"); status = window_get_pane_status(w);
if (parent->type == LAYOUT_LEFTRIGHT) if (parent->type == LAYOUT_LEFTRIGHT)
size = parent->sx; size = parent->sx;
@@ -1302,6 +1508,7 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args,
enum layout_type type; enum layout_type type;
u_int curval; u_int curval;
int size = -1; int size = -1;
char *error = NULL;
if (window_pane_is_floating(wp)) { if (window_pane_is_floating(wp)) {
*cause = xstrdup("can't split a floating pane"); *cause = xstrdup("can't split a floating pane");
@@ -1328,15 +1535,16 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args,
if (args_has(args, 'l')) { if (args_has(args, 'l')) {
size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval,
item, cause); item, &error);
} else if (args_has(args, 'p')) { } else if (args_has(args, 'p')) {
size = args_strtonum_and_expand(args, 'p', 0, 100, item, size = args_strtonum_and_expand(args, 'p', 0, 100, item,
cause); &error);
if (*cause == NULL) if (error == NULL)
size = curval * size / 100; size = curval * size / 100;
} }
if (*cause != NULL) { if (error != NULL) {
*cause = xstrdup("invalid tiled geometry"); xasprintf(cause, "invalid tiled geometry %s", error);
free(error);
return (NULL); return (NULL);
} }
@@ -1356,36 +1564,54 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args,
/* Get a new floating cell. */ /* Get a new floating cell. */
struct layout_cell * struct layout_cell *
layout_get_floating_cell(struct cmdq_item *item, struct args *args, layout_get_floating_cell(struct cmdq_item *item, struct args *args,
struct window *w, __unused struct window_pane *wp, char **cause) enum pane_lines lines, struct window *w, struct window_pane *wp,
char **cause)
{ {
struct layout_cell *lcnew; struct layout_cell *lcnew;
int sx = w->sx / 2, sy = w->sy / 4; int sx = w->sx / 2, sy = w->sy / 4;
int ox = INT_MAX, oy = INT_MAX; int ox = INT_MAX, oy = INT_MAX;
char *error;
if (args_has(args, 'x')) { if (args_has(args, 'x')) {
sx = args_percentage_and_expand(args, 'x', 0, w->sx - 1, w->sx, sx = args_percentage_and_expand(args, 'x', 0, PANE_MAXIMUM,
item, cause); w->sx, item, &error);
if (*cause != NULL) if (error != NULL) {
xasprintf(cause, "position %s", error);
free(error);
return (NULL); return (NULL);
} }
if (lines != PANE_LINES_NONE)
sx -= 2;
}
if (args_has(args, 'y')) { if (args_has(args, 'y')) {
sy = args_percentage_and_expand(args, 'y', 0, w->sy - 1, w->sy, sy = args_percentage_and_expand(args, 'y', 0, PANE_MAXIMUM,
item, cause); w->sy, item, &error);
if (*cause != NULL) if (error != NULL) {
xasprintf(cause, "position %s", error);
free(error);
return (NULL); return (NULL);
} }
if (lines != PANE_LINES_NONE)
sy -= 2;
}
if (args_has(args, 'X')) { if (args_has(args, 'X')) {
ox = args_percentage_and_expand(args, 'X', -sx, w->sx, ox = args_percentage_and_expand(args, 'X', -sx, w->sx,
w->sx, item, cause); w->sx, item, &error);
if (*cause != NULL) if (error != NULL) {
xasprintf(cause, "size %s", error);
free(error);
return (NULL); return (NULL);
} }
}
if (args_has(args, 'Y')) { if (args_has(args, 'Y')) {
oy = args_percentage_and_expand(args, 'Y', -sy, w->sy, oy = args_percentage_and_expand(args, 'Y', -sy, w->sy,
w->sy, item, cause); w->sy, item, &error);
if (*cause != NULL) if (error != NULL) {
xasprintf(cause, "size %s", error);
free(error);
return (NULL); return (NULL);
} }
}
if (ox == INT_MAX) { if (ox == INT_MAX) {
if (w->last_new_pane_x == 0) if (w->last_new_pane_x == 0)
@@ -1396,7 +1622,9 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args,
ox = 4; ox = 4;
} }
w->last_new_pane_x = ox; w->last_new_pane_x = ox;
} } else
if (lines != PANE_LINES_NONE)
ox += 1;
if (oy == INT_MAX) { if (oy == INT_MAX) {
if (w->last_new_pane_y == 0) if (w->last_new_pane_y == 0)
oy = 2; oy = 2;
@@ -1406,8 +1634,55 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args,
oy = 2; oy = 2;
} }
w->last_new_pane_y = oy; w->last_new_pane_y = oy;
} else
if (lines != PANE_LINES_NONE)
oy += 1;
if (sx < PANE_MINIMUM || sx > PANE_MAXIMUM) {
*cause = xstrdup("invalid width");
return (NULL);
}
if (sy < PANE_MINIMUM || sy > PANE_MAXIMUM) {
*cause = xstrdup("invalid height");
return (NULL);
} }
lcnew = layout_floating_pane(w, sx, sy, ox, oy); lcnew = layout_floating_pane(w, wp, sx, sy, ox, oy);
return (lcnew); return (lcnew);
} }
/*
* Removes a cell from the tiled layout by giving the cell's space to the
* nearest neighbour.
*/
int
layout_remove_tile(struct window *w, struct layout_cell *lc)
{
struct layout_cell *lcneighbour, *lcparent;
enum layout_type type;
int change;
if (lc->flags & LAYOUT_CELL_FLOATING)
return (0);
lcneighbour = layout_cell_get_neighbour(lc);
if (lcneighbour == NULL) {
if (lc->parent != NULL)
layout_remove_tile(w, lc->parent);
} else if ((lcparent = lcneighbour->parent) != NULL) {
type = lcparent->type;
/*
* Adding the size of the layout cell plus its border to the
* neighbour.
*/
if (type == LAYOUT_TOPBOTTOM)
change = lc->sy + 1;
else
change = lc->sx + 1;
layout_resize_adjust(w, lcneighbour, type, change);
}
/* Zeroing out the cell geometry until the cell is retiled. */
layout_set_size(lc, 0, 0, 0, 0);
return (1);
}

19
menu.c
View File

@@ -257,8 +257,7 @@ menu_reapply_styles(struct menu_data *md, struct client *c)
} }
void void
menu_draw_cb(struct client *c, void *data, menu_draw_cb(struct client *c, void *data)
__unused struct screen_redraw_ctx *rctx)
{ {
struct menu_data *md = data; struct menu_data *md = data;
struct tty *tty = &c->tty; struct tty *tty = &c->tty;
@@ -281,10 +280,8 @@ menu_draw_cb(struct client *c, void *data,
&md->style_gc, &md->border_style_gc, &md->selected_style_gc); &md->style_gc, &md->border_style_gc, &md->selected_style_gc);
screen_write_stop(&ctx); screen_write_stop(&ctx);
for (i = 0; i < screen_size_y(&md->s); i++) { for (i = 0; i < screen_size_y(&md->s); i++)
tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, NULL);
&grid_default_cell, NULL);
}
} }
void void
@@ -440,7 +437,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event)
break; break;
} }
} }
while (name == NULL || *name == '-') { while ((name == NULL || *name == '-') && md->choice != 0) {
md->choice--; md->choice--;
name = menu->items[md->choice].name; name = menu->items[md->choice].name;
} }
@@ -450,7 +447,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event)
case KEYC_HOME: case KEYC_HOME:
md->choice = 0; md->choice = 0;
name = menu->items[md->choice].name; name = menu->items[md->choice].name;
while (name == NULL || *name == '-') { while ((name == NULL || *name == '-') && md->choice != count - 1) {
md->choice++; md->choice++;
name = menu->items[md->choice].name; name = menu->items[md->choice].name;
} }
@@ -460,7 +457,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event)
case KEYC_END: case KEYC_END:
md->choice = count - 1; md->choice = count - 1;
name = menu->items[md->choice].name; name = menu->items[md->choice].name;
while (name == NULL || *name == '-') { while ((name == NULL || *name == '-') && md->choice != 0) {
md->choice--; md->choice--;
name = menu->items[md->choice].name; name = menu->items[md->choice].name;
} }
@@ -638,7 +635,7 @@ menu_display(struct menu *menu, int flags, int starting_choice,
style, selected_style, border_style, fs, cb, data); style, selected_style, border_style, fs, cb, data);
if (md == NULL) if (md == NULL)
return (-1); return (-1);
server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb, server_client_set_overlay(c, 0, menu_check_cb, menu_mode_cb,
menu_key_cb, menu_free_cb, menu_resize_cb, md); menu_draw_cb, menu_key_cb, menu_free_cb, menu_resize_cb, md);
return (0); return (0);
} }

View File

@@ -37,6 +37,7 @@ enum mode_tree_preview {
}; };
struct mode_tree_item; struct mode_tree_item;
struct mode_tree_prompt;
TAILQ_HEAD(mode_tree_list, mode_tree_item); TAILQ_HEAD(mode_tree_list, mode_tree_item);
struct mode_tree_data { struct mode_tree_data {
@@ -49,6 +50,7 @@ struct mode_tree_data {
const struct menu_item *menu; const struct menu_item *menu;
struct sort_criteria sort_crit; struct sort_criteria sort_crit;
const char *view_name;
mode_tree_build_cb buildcb; mode_tree_build_cb buildcb;
mode_tree_draw_cb drawcb; mode_tree_draw_cb drawcb;
@@ -76,6 +78,10 @@ struct mode_tree_data {
u_int current; u_int current;
struct screen screen; struct screen screen;
struct prompt *prompt;
struct mode_tree_prompt *prompt_data;
u_int prompt_cx;
int prompt_top;
int preview; int preview;
char *search; char *search;
@@ -83,6 +89,7 @@ struct mode_tree_data {
int no_matches; int no_matches;
enum mode_tree_search_dir search_dir; enum mode_tree_search_dir search_dir;
int search_icase; int search_icase;
int help;
}; };
struct mode_tree_item { struct mode_tree_item {
@@ -122,7 +129,25 @@ struct mode_tree_menu {
u_int line; u_int line;
}; };
/*
* Wrapper around a prompt owned by a mode tree. The mode tree holds a reference
* while the prompt is alive; the wrapper callbacks forward to the caller's
* callbacks and drop that reference when the prompt is freed.
*/
struct mode_tree_prompt {
struct mode_tree_data *mtd;
struct client *c;
mode_tree_prompt_input_cb inputcb;
prompt_free_cb freecb;
void *data;
};
static void mode_tree_free_items(struct mode_tree_list *); static void mode_tree_free_items(struct mode_tree_list *);
static void mode_tree_draw_help(struct mode_tree_data *,
struct screen_write_ctx *);
static void mode_tree_draw_prompt(struct mode_tree_data *,
struct screen_write_ctx *);
static enum cmd_retval mode_tree_prompt_accept(struct cmdq_item *, void *);
static const struct menu_item mode_tree_menu_items[] = { static const struct menu_item mode_tree_menu_items[] = {
{ "Scroll Left", '<', NULL }, { "Scroll Left", '<', NULL },
@@ -134,31 +159,31 @@ static const struct menu_item mode_tree_menu_items[] = {
}; };
static const char* mode_tree_help_start[] = { static const char* mode_tree_help_start[] = {
"\r\033[1m Up, k \033[0m\016x\017 \033[0mMove cursor up\n", "#[bold] Up, k #[default]#[acs]x#[default] Move cursor up",
"\r\033[1m Down, j \033[0m\016x\017 \033[0mMove cursor down\n", "#[bold] Down, j #[default]#[acs]x#[default] Move cursor down",
"\r\033[1m g \033[0m\016x\017 \033[0mGo to top\n", "#[bold] g #[default]#[acs]x#[default] Go to top",
"\r\033[1m G \033[0m\016x\017 \033[0mGo to bottom\n", "#[bold] G #[default]#[acs]x#[default] Go to bottom",
"\r\033[1m PPage, C-b \033[0m\016x\017 \033[0mPage up\n", "#[bold] PPage, C-b #[default]#[acs]x#[default] Page up",
"\r\033[1m NPage, C-f \033[0m\016x\017 \033[0mPage down\n", "#[bold] NPage, C-f #[default]#[acs]x#[default] Page down",
"\r\033[1m Left, h \033[0m\016x\017 \033[0mCollapse %1\n", "#[bold] Left, h #[default]#[acs]x#[default] Collapse %1",
"\r\033[1m Right, l \033[0m\016x\017 \033[0mExpand %1\n", "#[bold] Right, l #[default]#[acs]x#[default] Expand %1",
"\r\033[1m M-- \033[0m\016x\017 \033[0mCollapse all %1s\n", "#[bold] M-- #[default]#[acs]x#[default] Collapse all %1s",
"\r\033[1m M-+ \033[0m\016x\017 \033[0mExpand all %1s\n", "#[bold] M-+ #[default]#[acs]x#[default] Expand all %1s",
"\r\033[1m t \033[0m\016x\017 \033[0mToggle %1 tag\n", "#[bold] t #[default]#[acs]x#[default] Toggle %1 tag",
"\r\033[1m T \033[0m\016x\017 \033[0mUntag all %1s\n", "#[bold] T #[default]#[acs]x#[default] Untag all %1s",
"\r\033[1m C-t \033[0m\016x\017 \033[0mTag all %1s\n", "#[bold] C-t #[default]#[acs]x#[default] Tag all %1s",
"\r\033[1m C-s \033[0m\016x\017 \033[0mSearch forward\n", "#[bold] C-s #[default]#[acs]x#[default] Search forward",
"\r\033[1m C-r \033[0m\016x\017 \033[0mSearch backward\n", "#[bold] C-r #[default]#[acs]x#[default] Search backward",
"\r\033[1m n \033[0m\016x\017 \033[0mRepeat search forward\n", "#[bold] n #[default]#[acs]x#[default] Repeat search forward",
"\r\033[1m N \033[0m\016x\017 \033[0mRepeat search backward\n", "#[bold] N #[default]#[acs]x#[default] Repeat search backward",
"\r\033[1m f \033[0m\016x\017 \033[0mFilter %1s\n", "#[bold] f #[default]#[acs]x#[default] Filter %1s",
"\r\033[1m O \033[0m\016x\017 \033[0mChange sort order\n", "#[bold] O #[default]#[acs]x#[default] Change sort order",
"\r\033[1m r \033[0m\016x\017 \033[0mReverse sort order\n", "#[bold] r #[default]#[acs]x#[default] Reverse sort order",
"\r\033[1m v \033[0m\016x\017 \033[0mToggle preview\n", "#[bold] v #[default]#[acs]x#[default] Toggle preview",
NULL NULL
}; };
static const char* mode_tree_help_end[] = { static const char* mode_tree_help_end[] = {
"\r\033[1m q, Escape \033[0m\016x\017 \033[0mExit mode\033[H", "#[bold] q, Escape #[default]#[acs]x#[default] Exit mode",
NULL NULL
}; };
#define MODE_TREE_HELP_DEFAULT_WIDTH 39 #define MODE_TREE_HELP_DEFAULT_WIDTH 39
@@ -378,6 +403,13 @@ mode_tree_get_current_name(struct mode_tree_data *mtd)
return (mtd->line_list[mtd->current].item->name); return (mtd->line_list[mtd->current].item->name);
} }
void
mode_tree_select_top(struct mode_tree_data *mtd)
{
mtd->current = 0;
mtd->offset = 0;
}
void void
mode_tree_expand_current(struct mode_tree_data *mtd) mode_tree_expand_current(struct mode_tree_data *mtd)
{ {
@@ -505,7 +537,9 @@ mode_tree_start(struct window_pane *wp, struct args *args,
mtd->modedata = modedata; mtd->modedata = modedata;
mtd->menu = menu; mtd->menu = menu;
if (args_has(args, 'N') > 1) if (drawcb == NULL)
mtd->preview = MODE_TREE_PREVIEW_OFF;
else if (args_has(args, 'N') > 1)
mtd->preview = MODE_TREE_PREVIEW_BIG; mtd->preview = MODE_TREE_PREVIEW_BIG;
else if (args_has(args, 'N')) else if (args_has(args, 'N'))
mtd->preview = MODE_TREE_PREVIEW_OFF; mtd->preview = MODE_TREE_PREVIEW_OFF;
@@ -637,6 +671,7 @@ mode_tree_free(struct mode_tree_data *mtd)
if (mtd->zoomed == 0) if (mtd->zoomed == 0)
server_unzoom_window(wp->window); server_unzoom_window(wp->window);
mode_tree_clear_prompt(mtd);
mode_tree_free_items(&mtd->children); mode_tree_free_items(&mtd->children);
mode_tree_clear_lines(mtd); mode_tree_clear_lines(mtd);
screen_free(&mtd->screen); screen_free(&mtd->screen);
@@ -700,6 +735,12 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
return (mti); return (mti);
} }
void
mode_tree_view_name(struct mode_tree_data *mtd, const char *name)
{
mtd->view_name = name;
}
void void
mode_tree_draw_as_parent(struct mode_tree_item *mti) mode_tree_draw_as_parent(struct mode_tree_item *mti)
{ {
@@ -884,9 +925,12 @@ mode_tree_draw(struct mode_tree_data *mtd)
screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL); screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL);
if (mtd->sort_crit.order_seq != NULL) { if (mtd->sort_crit.order_seq != NULL) {
xasprintf(&text, " %s (sort: %s%s)", mti->name, xasprintf(&text, " %s (sort: %s%s)%s%s%s", mti->name,
sort_order_to_string(mtd->sort_crit.order), sort_order_to_string(mtd->sort_crit.order),
mtd->sort_crit.reversed ? ", reversed" : ""); mtd->sort_crit.reversed ? ", reversed" : "",
mtd->view_name == NULL ? "" : " (view: ",
mtd->view_name == NULL ? "" : mtd->view_name,
mtd->view_name == NULL ? "" : ")");
} else } else
xasprintf(&text, " %s", mti->name); xasprintf(&text, " %s", mti->name);
if (w - 2 >= strlen(text)) { if (w - 2 >= strlen(text)) {
@@ -918,10 +962,150 @@ mode_tree_draw(struct mode_tree_data *mtd)
} }
done: done:
if (mtd->help)
mode_tree_draw_help(mtd, &ctx);
if (mtd->prompt != NULL)
mode_tree_draw_prompt(mtd, &ctx);
else {
s->mode &= ~MODE_CURSOR;
screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0); screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
}
screen_write_stop(&ctx); screen_write_stop(&ctx);
} }
static void
mode_tree_draw_prompt(struct mode_tree_data *mtd, struct screen_write_ctx *ctx)
{
struct screen *s = &mtd->screen;
struct prompt_draw_data pdd;
u_int sx = screen_size_x(s), sy = screen_size_y(s);
u_int py;
if (sx == 0 || sy == 0)
return;
if (mtd->prompt_top)
py = 0;
else
py = sy - 1;
pdd.ctx = ctx;
pdd.cursor_x = &mtd->prompt_cx;
pdd.area_x = 0;
pdd.area_width = sx;
pdd.prompt_line = py;
s->mode |= MODE_CURSOR;
prompt_draw(mtd->prompt, &pdd);
screen_write_cursormove(ctx, mtd->prompt_cx, py, 0);
}
void
mode_tree_clear_prompt(struct mode_tree_data *mtd)
{
struct prompt *prompt = mtd->prompt;
if (mtd->prompt != NULL) {
mtd->prompt = NULL;
prompt_free(prompt);
mtd->screen.mode &= ~MODE_CURSOR;
}
}
int
mode_tree_has_prompt(struct mode_tree_data *mtd)
{
return (mtd->prompt != NULL);
}
static enum cmd_retval
mode_tree_prompt_accept(struct cmdq_item *item, void *data)
{
struct mode_tree_data *mtd = data;
struct client *c = cmdq_get_client(item);
key_code key = 'y';
if (mtd->prompt != NULL && c != NULL)
mode_tree_key(mtd, c, &key, NULL, NULL, NULL);
mode_tree_remove_ref(mtd);
return (CMD_RETURN_NORMAL);
}
static enum prompt_result
mode_tree_prompt_input_callback(void *data, const char *s,
enum prompt_key_result key)
{
struct mode_tree_prompt *mtp = data;
if (mtp->inputcb != NULL)
return (mtp->inputcb(mtp->c, mtp->data, s, key));
return (PROMPT_CLOSE);
}
static void
mode_tree_prompt_free_callback(void *data)
{
struct mode_tree_prompt *mtp = data;
if (mtp->mtd->prompt_data == mtp)
mtp->mtd->prompt_data = NULL;
if (mtp->freecb != NULL)
mtp->freecb(mtp->data);
mode_tree_remove_ref(mtp->mtd);
free(mtp);
}
void
mode_tree_set_prompt(struct mode_tree_data *mtd, struct client *c,
const char *prompt, const char *input, enum prompt_type type, int flags,
mode_tree_prompt_input_cb inputcb, prompt_free_cb freecb, void *data)
{
struct session *s;
struct options *oo;
struct prompt_create_data pd;
struct mode_tree_prompt *mtp;
if (c != NULL && c->session != NULL) {
s = c->session;
oo = s->options;
} else {
s = NULL;
oo = global_s_options;
}
mode_tree_clear_prompt(mtd);
mtp = xcalloc(1, sizeof *mtp);
mtp->mtd = mtd;
mtp->inputcb = inputcb;
mtp->freecb = freecb;
mtp->data = data;
mtd->references++;
mtd->prompt_top = options_get_number(oo, "status-position") == 0;
memset(&pd, 0, sizeof pd);
prompt_set_options(&pd, s);
pd.prompt = prompt;
pd.input = input;
pd.type = type;
pd.flags = flags|PROMPT_ISMODE;
pd.inputcb = mode_tree_prompt_input_callback;
pd.freecb = mode_tree_prompt_free_callback;
pd.data = mtp;
mtd->prompt = prompt_create(&pd);
mtd->prompt_data = mtp;
mode_tree_draw(mtd);
mtd->wp->flags |= PANE_REDRAW;
if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT) && c != NULL) {
mtd->references++;
cmdq_append(c, cmdq_get_callback(mode_tree_prompt_accept, mtd));
}
}
static struct mode_tree_item * static struct mode_tree_item *
mode_tree_search_backward(struct mode_tree_data *mtd) mode_tree_search_backward(struct mode_tree_data *mtd)
{ {
@@ -1042,41 +1226,37 @@ mode_tree_search_set(struct mode_tree_data *mtd)
mtd->wp->flags |= PANE_REDRAW; mtd->wp->flags |= PANE_REDRAW;
} }
static int static enum prompt_result
mode_tree_search_callback(__unused struct client *c, void *data, const char *s, mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
__unused int done) enum prompt_key_result key)
{ {
struct mode_tree_data *mtd = data; struct mode_tree_data *mtd = data;
if (mtd->dead) if (mtd->dead)
return (0); return (PROMPT_CLOSE);
free(mtd->search); free(mtd->search);
if (s == NULL || *s == '\0') { if (s == NULL || *s == '\0')
mtd->search = NULL; mtd->search = NULL;
return (0); else {
}
mtd->search = xstrdup(s); mtd->search = xstrdup(s);
mtd->search_icase = mode_tree_is_lowercase(s); mtd->search_icase = mode_tree_is_lowercase(s);
mode_tree_search_set(mtd); mode_tree_search_set(mtd);
return (0);
} }
static void if (key == PROMPT_KEY_HANDLED)
mode_tree_search_free(void *data) return (PROMPT_CONTINUE);
{ return (PROMPT_CLOSE);
mode_tree_remove_ref(data);
} }
static int static enum prompt_result
mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
__unused int done) enum prompt_key_result key)
{ {
struct mode_tree_data *mtd = data; struct mode_tree_data *mtd = data;
if (mtd->dead) if (mtd->dead)
return (0); return (PROMPT_CLOSE);
if (mtd->filter != NULL) if (mtd->filter != NULL)
free(mtd->filter); free(mtd->filter);
@@ -1089,13 +1269,20 @@ mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
mode_tree_draw(mtd); mode_tree_draw(mtd);
mtd->wp->flags |= PANE_REDRAW; mtd->wp->flags |= PANE_REDRAW;
return (0); if (key == PROMPT_KEY_HANDLED)
return (PROMPT_CONTINUE);
return (PROMPT_CLOSE);
} }
static void static void
mode_tree_filter_free(void *data) mode_tree_clear_filter(struct mode_tree_data *mtd)
{ {
mode_tree_remove_ref(data); free(mtd->filter);
mtd->filter = NULL;
mode_tree_build(mtd);
mode_tree_draw(mtd);
mtd->wp->flags |= PANE_REDRAW;
} }
static void static void
@@ -1165,12 +1352,28 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
} }
static void static void
mode_tree_display_help(__unused struct mode_tree_data *mtd, struct client *c) mode_tree_draw_help_line(struct screen_write_ctx *ctx,
const struct grid_cell *gc, const char *line, const char *item, u_int x,
u_int y, u_int w)
{ {
struct session *s = c->session; char *expanded;
u_int px, py, w, h = 0;
expanded = cmd_template_replace(line, item, 1);
screen_write_cursormove(ctx, x, y, 0);
screen_write_clearcharacter(ctx, w, gc->bg);
screen_write_cursormove(ctx, x, y, 0);
format_draw(ctx, gc, w, expanded, NULL, 0);
free(expanded);
}
static void
mode_tree_draw_help(struct mode_tree_data *mtd, struct screen_write_ctx *ctx)
{
struct screen *s = &mtd->screen;
struct grid_cell gc;
const char **line, **lines = NULL, *item = "item"; const char **line, **lines = NULL, *item = "item";
char *new_line; u_int sx = screen_size_x(s), sy = screen_size_y(s);
u_int x, y, w, h = 0, box_w, box_h;
if (mtd->helpcb == NULL) if (mtd->helpcb == NULL)
w = MODE_TREE_HELP_DEFAULT_WIDTH; w = MODE_TREE_HELP_DEFAULT_WIDTH;
@@ -1186,33 +1389,32 @@ mode_tree_display_help(__unused struct mode_tree_data *mtd, struct client *c)
for (line = mode_tree_help_end; *line != NULL; line++) for (line = mode_tree_help_end; *line != NULL; line++)
h++; h++;
if (c->tty.sx < w || c->tty.sy < h) box_w = w + 2;
box_h = h + 2;
if (sx < box_w || sy < box_h)
return; return;
px = (c->tty.sx - w) / 2; x = (sx - box_w) / 2;
py = (c->tty.sy - h) / 2; y = (sy - box_h) / 2;
if (popup_display(POPUP_CLOSEANYKEY|POPUP_NOJOB, BOX_LINES_DEFAULT, memcpy(&gc, &grid_default_cell, sizeof gc);
NULL, px, py, w, h, NULL, NULL, 0, NULL, NULL, NULL, c, s, NULL, screen_write_cursormove(ctx, x, y, 0);
NULL, NULL, NULL) != 0) screen_write_box(ctx, box_w, box_h, BOX_LINES_DEFAULT, &gc, NULL);
return;
popup_write(c, "\033[H\033[?25l\033[?7l\033)0", 17); y++;
for (line = mode_tree_help_start; *line != NULL; line++) { x++;
new_line = cmd_template_replace(*line, item, 1); for (line = mode_tree_help_start; *line != NULL; line++, y++)
popup_write(c, new_line, strlen(new_line)); mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w);
free(new_line); for (line = lines; line != NULL && *line != NULL; line++, y++)
mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w);
for (line = mode_tree_help_end; *line != NULL; line++, y++)
mode_tree_draw_help_line(ctx, &gc, *line, item, x, y, w);
} }
for (line = lines; line != NULL && *line != NULL; line++) {
new_line = cmd_template_replace(*line, item, 1); static void
popup_write(c, new_line, strlen(new_line)); mode_tree_display_help(struct mode_tree_data *mtd)
free(new_line); {
} mtd->help = 1;
for (line = mode_tree_help_end; *line != NULL; line++) { mode_tree_draw(mtd);
new_line = cmd_template_replace(*line, item, 1);
popup_write(c, new_line, strlen(new_line));
free(new_line);
}
popup_write(c, "\033[H", 3);
} }
int int
@@ -1221,14 +1423,77 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
{ {
struct mode_tree_line *line; struct mode_tree_line *line;
struct mode_tree_item *current, *parent, *mti; struct mode_tree_item *current, *parent, *mti;
u_int i, x, y; u_int i, x, y, py, sx;
int choice, preview; int choice, preview;
enum prompt_key_result result;
int redraw;
struct prompt *prompt;
struct mode_tree_prompt *mtp;
if (mtd->line_size == 0) { if (mtd->line_size == 0) {
*key = KEYC_NONE; *key = KEYC_NONE;
return (1); return (1);
} }
if (mtd->prompt != NULL) {
redraw = 0;
prompt = mtd->prompt;
mtp = mtd->prompt_data;
if (mtp != NULL)
mtp->c = c;
if (KEYC_IS_MOUSE(*key)) {
if (m == NULL ||
MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1 ||
MOUSE_DRAG(m->b) || MOUSE_RELEASE(m->b) ||
cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0)
result = PROMPT_KEY_NOT_HANDLED;
else {
sx = screen_size_x(&mtd->screen);
if (mtd->prompt_top)
py = 0;
else
py = screen_size_y(&mtd->screen) - 1;
if (y == py) {
result = prompt_mouse(prompt, x, 0, sx,
&redraw);
} else
result = PROMPT_KEY_NOT_HANDLED;
}
} else
result = prompt_key(prompt, *key, &redraw);
if (mtd->prompt_data == mtp && mtp != NULL)
mtp->c = NULL;
/*
* Only an explicit close or the prompt marking itself closed
* ends it; cursor movement and editing keep it open.
*/
if (mtd->prompt == prompt &&
(result == PROMPT_KEY_CLOSE || prompt_closed(prompt)))
mode_tree_clear_prompt(mtd);
if (redraw || mtd->prompt != prompt) {
mode_tree_draw(mtd);
mtd->wp->flags |= PANE_REDRAW;
}
if (result != PROMPT_KEY_NOT_HANDLED) {
*key = KEYC_NONE;
return (0);
}
}
if (mtd->help) {
if (KEYC_IS_MOUSE(*key)) {
*key = KEYC_NONE;
return (0);
}
mtd->help = 0;
mode_tree_draw(mtd);
*key = KEYC_NONE;
return (0);
}
if (KEYC_IS_MOUSE(*key) && m != NULL) { if (KEYC_IS_MOUSE(*key) && m != NULL) {
if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) { if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
*key = KEYC_NONE; *key = KEYC_NONE;
@@ -1294,7 +1559,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
return (1); return (1);
case KEYC_F1: case KEYC_F1:
case 'h'|KEYC_CTRL: case 'h'|KEYC_CTRL:
mode_tree_display_help(mtd, c); mode_tree_display_help(mtd);
break; break;
case KEYC_UP: case KEYC_UP:
case 'k': case 'k':
@@ -1424,11 +1689,10 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
case '?': case '?':
case '/': case '/':
case 's'|KEYC_CTRL: case 's'|KEYC_CTRL:
mtd->references++;
mtd->search_dir = MODE_TREE_SEARCH_FORWARD; mtd->search_dir = MODE_TREE_SEARCH_FORWARD;
status_prompt_set(c, NULL, "(search) ", "", mode_tree_set_prompt(mtd, c, "(search) ", "",
mode_tree_search_callback, mode_tree_search_free, mtd, PROMPT_TYPE_SEARCH, PROMPT_NOFORMAT,
PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); mode_tree_search_callback, NULL, mtd);
break; break;
case 'n': case 'n':
mtd->search_dir = MODE_TREE_SEARCH_FORWARD; mtd->search_dir = MODE_TREE_SEARCH_FORWARD;
@@ -1439,10 +1703,13 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
mode_tree_search_set(mtd); mode_tree_search_set(mtd);
break; break;
case 'f': case 'f':
mtd->references++; mode_tree_set_prompt(mtd, c, "(filter) ", mtd->filter,
status_prompt_set(c, NULL, "(filter) ", mtd->filter, PROMPT_TYPE_SEARCH, PROMPT_NOFORMAT,
mode_tree_filter_callback, mode_tree_filter_free, mtd, mode_tree_filter_callback, NULL, mtd);
PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); break;
case 'c':
mode_tree_clear_prompt(mtd);
mode_tree_clear_filter(mtd);
break; break;
case 'v': case 'v':
switch (mtd->preview) { switch (mtd->preview) {

View File

@@ -95,7 +95,7 @@ check_window_name(struct window *w)
name = format_window_name(w); name = format_window_name(w);
if (strcmp(name, w->name) != 0) { if (strcmp(name, w->name) != 0) {
log_debug("@%u new name %s (was %s)", w->id, name, w->name); log_debug("@%u new name %s (was %s)", w->id, name, w->name);
window_set_name(w, name); window_set_name(w, name, WINDOW_NAME_FORBID_EXT);
server_redraw_window_borders(w); server_redraw_window_borders(w);
server_status_window(w); server_status_window(w);
} else } else
@@ -166,7 +166,7 @@ parse_window_name(const char *in)
if (*name == '/') if (*name == '/')
name = basename(name); name = basename(name);
name = clean_name(name, "#"); name = clean_name(name, WINDOW_NAME_FORBID);
free(copy); free(copy);
if (name == NULL) if (name == NULL)
return (xstrdup("")); return (xstrdup(""));

View File

@@ -70,13 +70,13 @@ static const char *options_table_pane_scrollbars_position_list[] = {
"right", "left", NULL "right", "left", NULL
}; };
static const char *options_table_pane_status_list[] = { static const char *options_table_pane_status_list[] = {
"off", "top", "bottom", NULL "off", "top", "bottom", "top-floating", "bottom-floating", NULL
}; };
static const char *options_table_pane_border_indicators_list[] = { static const char *options_table_pane_border_indicators_list[] = {
"off", "colour", "arrows", "both", NULL "off", "colour", "arrows", "both", NULL
}; };
static const char *options_table_pane_border_lines_list[] = { static const char *options_table_pane_border_lines_list[] = {
"single", "double", "heavy", "simple", "number", "spaces", NULL "single", "double", "heavy", "simple", "number", "spaces", "none", NULL
}; };
static const char *options_table_popup_border_lines_list[] = { static const char *options_table_popup_border_lines_list[] = {
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL "single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
@@ -327,9 +327,10 @@ const struct options_table_entry options_table[] = {
}, },
{ .name = "cursor-colour", { .name = "cursor-colour",
.type = OPTIONS_TABLE_COLOUR, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_num = -1, .flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "",
.text = "Colour of the cursor." .text = "Colour of the cursor."
}, },
@@ -634,19 +635,29 @@ const struct options_table_entry options_table[] = {
}, },
{ .name = "display-panes-active-colour", { .name = "display-panes-active-colour",
.type = OPTIONS_TABLE_COLOUR, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
.default_num = 1, .flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "red",
.text = "Colour of the active pane for 'display-panes'." .text = "Colour of the active pane for 'display-panes'."
}, },
{ .name = "display-panes-colour", { .name = "display-panes-colour",
.type = OPTIONS_TABLE_COLOUR, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
.default_num = 4, .flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "blue",
.text = "Colour of not active panes for 'display-panes'." .text = "Colour of not active panes for 'display-panes'."
}, },
{ .name = "display-panes-format",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION,
.default_str = "#[align=right]#{pane_width}x#{pane_height}",
.text = "Format of text shown by 'display-panes', expanded for each "
"pane."
},
{ .name = "display-panes-time", { .name = "display-panes-time",
.type = OPTIONS_TABLE_NUMBER, .type = OPTIONS_TABLE_NUMBER,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
@@ -730,7 +741,7 @@ const struct options_table_entry options_table[] = {
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the command prompt when in command mode, if " .text = "Style of the command prompt when in command mode, if "
"'mode-keys' is set to 'vi'." "'status-keys' is set to 'vi'."
}, },
{ .name = "message-format", { .name = "message-format",
@@ -755,7 +766,9 @@ const struct options_table_entry options_table[] = {
{ .name = "message-style", { .name = "message-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
.default_str = "bg=yellow,fg=black,fill=yellow", .default_str = "bg=yellow,fg=black,"
"#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},,"
"fill=yellow}",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of messages and the command prompt. " .text = "Style of messages and the command prompt. "
@@ -980,9 +993,10 @@ const struct options_table_entry options_table[] = {
}, },
{ .name = "prompt-cursor-colour", { .name = "prompt-cursor-colour",
.type = OPTIONS_TABLE_COLOUR, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
.default_num = -1, .flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "",
.text = "Colour of the cursor when in the command prompt." .text = "Colour of the cursor when in the command prompt."
}, },
@@ -1134,9 +1148,10 @@ const struct options_table_entry options_table[] = {
}, },
{ .name = "clock-mode-colour", { .name = "clock-mode-colour",
.type = OPTIONS_TABLE_COLOUR, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_num = 4, .flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "blue",
.text = "Colour of the clock in clock mode." .text = "Colour of the clock in clock mode."
}, },
@@ -1356,7 +1371,7 @@ const struct options_table_entry options_table[] = {
{ .name = "pane-border-lines", { .name = "pane-border-lines",
.type = OPTIONS_TABLE_CHOICE, .type = OPTIONS_TABLE_CHOICE,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.choices = options_table_pane_border_lines_list, .choices = options_table_pane_border_lines_list,
.default_num = PANE_LINES_SINGLE, .default_num = PANE_LINES_SINGLE,
.text = "Type of characters used to draw pane border lines. Some of " .text = "Type of characters used to draw pane border lines. Some of "
@@ -1365,7 +1380,7 @@ const struct options_table_entry options_table[] = {
{ .name = "pane-border-status", { .name = "pane-border-status",
.type = OPTIONS_TABLE_CHOICE, .type = OPTIONS_TABLE_CHOICE,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.choices = options_table_pane_status_list, .choices = options_table_pane_status_list,
.default_num = PANE_STATUS_OFF, .default_num = PANE_STATUS_OFF,
.text = "Position of the pane status lines." .text = "Position of the pane status lines."

View File

@@ -662,6 +662,18 @@ options_parse_get(struct options *oo, const char *s, int *idx, int only)
return (o); return (o);
} }
const struct options_table_entry *
options_search(const char *name)
{
const struct options_table_entry *oe;
for (oe = options_table; oe->name != NULL; oe++) {
if (strcmp(oe->name, name) == 0)
return (oe);
}
return (NULL);
}
char * char *
options_match(const char *s, int *idx, int *ambiguous) options_match(const char *s, int *idx, int *ambiguous)
{ {
@@ -978,8 +990,11 @@ options_string_to_style(struct options *oo, const char *name,
struct format_tree *ft) struct format_tree *ft)
{ {
struct options_entry *o; struct options_entry *o;
const struct options_table_entry *oe;
const struct grid_cell *dgc = &grid_default_cell;
const char *s; const char *s;
char *expanded; char *expanded;
int failed;
o = options_get(oo, name); o = options_get(oo, name);
if (o == NULL || !OPTIONS_IS_STRING(o)) if (o == NULL || !OPTIONS_IS_STRING(o))
@@ -988,20 +1003,27 @@ options_string_to_style(struct options *oo, const char *name,
if (o->cached) if (o->cached)
return (&o->style); return (&o->style);
s = o->value.string; s = o->value.string;
oe = o->tableentry;
log_debug("%s: %s is '%s'", __func__, name, s); log_debug("%s: %s is '%s'", __func__, name, s);
style_set(&o->style, &grid_default_cell); style_set(&o->style, dgc);
o->cached = (strstr(s, "#{") == NULL); o->cached = (strstr(s, "#{") == NULL);
if (ft != NULL && !o->cached) { if (ft != NULL && !o->cached) {
expanded = format_expand(ft, s); expanded = format_expand(ft, s);
if (style_parse(&o->style, &grid_default_cell, expanded) != 0) { if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_COLOUR))
failed = style_parse_colour(&o->style, dgc, expanded);
else
failed = style_parse(&o->style, dgc, expanded);
free(expanded); free(expanded);
if (failed != 0)
return (NULL); return (NULL);
}
free(expanded);
} else { } else {
if (style_parse(&o->style, &grid_default_cell, s) != 0) if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_COLOUR))
failed = style_parse_colour(&o->style, dgc, s);
else
failed = style_parse(&o->style, dgc, s);
if (failed != 0)
return (NULL); return (NULL);
} }
return (&o->style); return (&o->style);
@@ -1029,6 +1051,12 @@ options_from_string_check(const struct options_table_entry *oe,
xasprintf(cause, "invalid style: %s", value); xasprintf(cause, "invalid style: %s", value);
return (-1); return (-1);
} }
if ((oe->flags & OPTIONS_TABLE_IS_COLOUR) &&
strstr(value, "#{") == NULL &&
style_parse_colour(&sy, &grid_default_cell, value) != 0) {
xasprintf(cause, "invalid colour: %s", value);
return (-1);
}
return (0); return (0);
} }
@@ -1219,6 +1247,15 @@ options_push_changes(const char *name)
if (strcmp(name, "status") == 0 || if (strcmp(name, "status") == 0 ||
strcmp(name, "status-interval") == 0) strcmp(name, "status-interval") == 0)
status_timer_start_all(); status_timer_start_all();
if (strcmp(name, "status") == 0 ||
strcmp(name, "status-position") == 0 ||
strcmp(name, "pane-border-indicators") == 0 ||
strcmp(name, "pane-border-lines") == 0 ||
strcmp(name, "pane-border-status") == 0 ||
strcmp(name, "pane-scrollbars") == 0 ||
strcmp(name, "pane-scrollbars-position") == 0 ||
strcmp(name, "pane-scrollbars-style") == 0)
redraw_invalidate_all_scenes();
if (strcmp(name, "monitor-silence") == 0) if (strcmp(name, "monitor-silence") == 0)
alerts_reset_all(); alerts_reset_all();
if (strcmp(name, "window-style") == 0 || if (strcmp(name, "window-style") == 0 ||

View File

@@ -176,7 +176,6 @@ paste_add(const char *prefix, char *data, size_t size)
} }
pb = xmalloc(sizeof *pb); pb = xmalloc(sizeof *pb);
pb->name = NULL; pb->name = NULL;
do { do {
free(pb->name); free(pb->name);
@@ -296,7 +295,6 @@ paste_set(char *data, size_t size, const char *name, char **cause)
} }
pb = xmalloc(sizeof *pb); pb = xmalloc(sizeof *pb);
pb->name = newname; pb->name = newname;
pb->data = data; pb->data = data;

157
popup.c
View File

@@ -75,12 +75,6 @@ struct popup_data {
u_int lb; u_int lb;
}; };
struct popup_editor {
char *path;
popup_finish_edit_cb cb;
void *arg;
};
static const struct menu_item popup_menu_items[] = { static const struct menu_item popup_menu_items[] = {
{ "Close", 'q', NULL }, { "Close", 'q', NULL },
{ "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL }, { "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL },
@@ -94,15 +88,6 @@ static const struct menu_item popup_menu_items[] = {
{ NULL, KEYC_NONE, NULL } { NULL, KEYC_NONE, NULL }
}; };
static const struct menu_item popup_internal_menu_items[] = {
{ "Close", 'q', NULL },
{ "", KEYC_NONE, NULL },
{ "Fill Space", 'F', NULL },
{ "Centre", 'C', NULL },
{ NULL, KEYC_NONE, NULL }
};
static void static void
popup_free(struct popup_data *pd) popup_free(struct popup_data *pd)
{ {
@@ -208,7 +193,8 @@ popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults); memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults);
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER; ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
ttyctx->palette = &pd->palette; ttyctx->style_ctx.defaults = &ttyctx->defaults;
ttyctx->style_ctx.palette = &pd->palette;
ttyctx->redraw_cb = popup_redraw_cb; ttyctx->redraw_cb = popup_redraw_cb;
ttyctx->set_client_cb = popup_set_client_cb; ttyctx->set_client_cb = popup_set_client_cb;
ttyctx->arg = pd; ttyctx->arg = pd;
@@ -287,15 +273,15 @@ popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx)
} }
static void static void
popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) popup_draw_cb(struct client *c, void *data)
{ {
struct popup_data *pd = data; struct popup_data *pd = data;
struct tty *tty = &c->tty; struct tty *tty = &c->tty;
struct screen s; struct screen s;
struct screen_write_ctx ctx; struct screen_write_ctx ctx;
u_int i, px = pd->px, py = pd->py; u_int i, px = pd->px, py = pd->py;
struct colour_palette *palette = &pd->palette;
struct grid_cell defaults; struct grid_cell defaults;
struct tty_style_ctx style_ctx;
popup_reapply_styles(pd); popup_reapply_styles(pd);
@@ -321,9 +307,13 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx)
memcpy(&defaults, &pd->defaults, sizeof defaults); memcpy(&defaults, &pd->defaults, sizeof defaults);
if (defaults.fg == 8) if (defaults.fg == 8)
defaults.fg = palette->fg; defaults.fg = pd->palette.fg;
if (defaults.bg == 8) if (defaults.bg == 8)
defaults.bg = palette->bg; defaults.bg = pd->palette.bg;
style_ctx.defaults = &defaults;
style_ctx.palette = &pd->palette;
style_ctx.dim = 0;
style_ctx.hyperlinks = s.hyperlinks;
if (pd->md != NULL) { if (pd->md != NULL) {
c->overlay_check = menu_check_cb; c->overlay_check = menu_check_cb;
@@ -332,15 +322,13 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx)
c->overlay_check = NULL; c->overlay_check = NULL;
c->overlay_data = NULL; c->overlay_data = NULL;
} }
for (i = 0; i < pd->sy; i++) { for (i = 0; i < pd->sy; i++)
tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults, tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &style_ctx);
palette);
}
screen_free(&s); screen_free(&s);
if (pd->md != NULL) { if (pd->md != NULL) {
c->overlay_check = NULL; c->overlay_check = NULL;
c->overlay_data = NULL; c->overlay_data = NULL;
menu_draw_cb(c, pd->md, rctx); menu_draw_cb(c, pd->md);
} }
c->overlay_check = popup_check_cb; c->overlay_check = popup_check_cb;
c->overlay_data = pd; c->overlay_data = pd;
@@ -435,7 +423,7 @@ popup_make_pane(struct popup_data *pd, enum layout_type type)
pd->job = NULL; pd->job = NULL;
} }
screen_set_title(&pd->s, new_wp->base.title); screen_set_title(&pd->s, new_wp->base.title, 0);
screen_free(&new_wp->base); screen_free(&new_wp->base);
memcpy(&new_wp->base, &pd->s, sizeof wp->base); memcpy(&new_wp->base, &pd->s, sizeof wp->base);
screen_resize(&new_wp->base, new_wp->sx, new_wp->sy, 1); screen_resize(&new_wp->base, new_wp->sx, new_wp->sy, 1);
@@ -647,10 +635,6 @@ popup_key_cb(struct client *c, void *data, struct key_event *event)
menu: menu:
pd->menu = menu_create(""); pd->menu = menu_create("");
if (pd->flags & POPUP_INTERNAL) {
menu_add_items(pd->menu, popup_internal_menu_items, NULL, c,
NULL);
} else
menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL); menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL);
if (m->x >= (pd->menu->width + 4) / 2) if (m->x >= (pd->menu->width + 4) / 2)
x = m->x - (pd->menu->width + 4) / 2; x = m->x - (pd->menu->width + 4) / 2;
@@ -858,9 +842,6 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
pd->psx = sx; pd->psx = sx;
pd->psy = sy; pd->psy = sy;
if (flags & POPUP_NOJOB)
pd->ictx = input_init(NULL, NULL, &pd->palette, NULL);
else {
pd->job = job_run(shellcmd, argc, argv, env, s, cwd, pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
popup_job_update_cb, popup_job_complete_cb, NULL, pd, popup_job_update_cb, popup_job_complete_cb, NULL, pd,
JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy); JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
@@ -868,117 +849,9 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
popup_free(pd); popup_free(pd);
return (-1); return (-1);
} }
pd->ictx = input_init(NULL, job_get_event(pd->job), pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette, c);
&pd->palette, c);
}
server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb, server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd); popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
return (0); return (0);
} }
void
popup_write(struct client *c, const char *data, size_t size)
{
struct popup_data *pd = c->overlay_data;
if (!popup_present(c))
return;
c->overlay_check = NULL;
c->overlay_data = NULL;
input_parse_screen(pd->ictx, &pd->s, popup_init_ctx_cb, pd, data, size);
c->overlay_check = popup_check_cb;
c->overlay_data = pd;
}
static void
popup_editor_free(struct popup_editor *pe)
{
unlink(pe->path);
free(pe->path);
free(pe);
}
static void
popup_editor_close_cb(int status, void *arg)
{
struct popup_editor *pe = arg;
FILE *f;
char *buf = NULL;
off_t len = 0;
if (status != 0) {
pe->cb(NULL, 0, pe->arg);
popup_editor_free(pe);
return;
}
f = fopen(pe->path, "r");
if (f != NULL) {
fseeko(f, 0, SEEK_END);
len = ftello(f);
fseeko(f, 0, SEEK_SET);
if (len == 0 ||
(uintmax_t)len > (uintmax_t)SIZE_MAX ||
(buf = malloc(len)) == NULL ||
fread(buf, len, 1, f) != 1) {
free(buf);
buf = NULL;
len = 0;
}
fclose(f);
}
pe->cb(buf, len, pe->arg); /* callback now owns buffer */
popup_editor_free(pe);
}
int
popup_editor(struct client *c, const char *buf, size_t len,
popup_finish_edit_cb cb, void *arg)
{
struct popup_editor *pe;
int fd;
FILE *f;
char *cmd;
char path[] = _PATH_TMP "tmux.XXXXXXXX";
const char *editor;
u_int px, py, sx, sy;
editor = options_get_string(global_options, "editor");
if (*editor == '\0')
return (-1);
fd = mkstemp(path);
if (fd == -1)
return (-1);
f = fdopen(fd, "w");
if (f == NULL)
return (-1);
if (fwrite(buf, len, 1, f) != 1) {
fclose(f);
return (-1);
}
fclose(f);
pe = xcalloc(1, sizeof *pe);
pe->path = xstrdup(path);
pe->cb = cb;
pe->arg = arg;
sx = c->tty.sx * 9 / 10;
sy = c->tty.sy * 9 / 10;
px = (c->tty.sx / 2) - (sx / 2);
py = (c->tty.sy / 2) - (sy / 2);
xasprintf(&cmd, "%s %s", editor, path);
if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT,
NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL,
NULL, NULL, popup_editor_close_cb, pe) != 0) {
popup_editor_free(pe);
free(cmd);
return (-1);
}
free(cmd);
return (0);
}

12
proc.c
View File

@@ -57,6 +57,7 @@ struct tmuxpeer {
struct imsgbuf ibuf; struct imsgbuf ibuf;
struct event event; struct event event;
uid_t uid; uid_t uid;
gid_t gid;
int flags; int flags;
#define PEER_BAD 0x1 #define PEER_BAD 0x1
@@ -301,7 +302,6 @@ proc_add_peer(struct tmuxproc *tp, int fd,
void (*dispatchcb)(struct imsg *, void *), void *arg) void (*dispatchcb)(struct imsg *, void *), void *arg)
{ {
struct tmuxpeer *peer; struct tmuxpeer *peer;
gid_t gid;
peer = xcalloc(1, sizeof *peer); peer = xcalloc(1, sizeof *peer);
peer->parent = tp; peer->parent = tp;
@@ -314,8 +314,10 @@ proc_add_peer(struct tmuxproc *tp, int fd,
imsgbuf_allow_fdpass(&peer->ibuf); imsgbuf_allow_fdpass(&peer->ibuf);
event_set(&peer->event, fd, EV_READ, proc_event_cb, peer); event_set(&peer->event, fd, EV_READ, proc_event_cb, peer);
if (getpeereid(fd, &peer->uid, &gid) != 0) if (getpeereid(fd, &peer->uid, &peer->gid) != 0) {
peer->uid = (uid_t)-1; peer->uid = (uid_t)-1;
peer->gid = (gid_t)-1;
}
log_debug("add peer %p: %d (%p)", peer, fd, arg); log_debug("add peer %p: %d (%p)", peer, fd, arg);
TAILQ_INSERT_TAIL(&tp->peers, peer, entry); TAILQ_INSERT_TAIL(&tp->peers, peer, entry);
@@ -384,3 +386,9 @@ proc_get_peer_uid(struct tmuxpeer *peer)
{ {
return (peer->uid); return (peer->uid);
} }
gid_t
proc_get_peer_gid(struct tmuxpeer *peer)
{
return (peer->gid);
}

264
prompt-history.c Normal file
View File

@@ -0,0 +1,264 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
static char *prompt_find_history_file(void);
static void prompt_add_typed_history(char *);
/* Prompt history. */
static char **prompt_hlist[PROMPT_NTYPES];
static u_int prompt_hsize[PROMPT_NTYPES];
/* Find the history file to load/save from/to. */
static char *
prompt_find_history_file(void)
{
const char *home, *history_file;
char *path;
history_file = options_get_string(global_options, "history-file");
if (*history_file == '\0')
return (NULL);
if (*history_file == '/')
return (xstrdup(history_file));
if (history_file[0] != '~' || history_file[1] != '/')
return (NULL);
if ((home = find_home()) == NULL)
return (NULL);
xasprintf(&path, "%s%s", home, history_file + 1);
return (path);
}
/* Add loaded history item to the appropriate list. */
static void
prompt_add_typed_history(char *line)
{
char *typestr;
enum prompt_type type = PROMPT_TYPE_INVALID;
typestr = strsep(&line, ":");
if (line != NULL)
type = prompt_type(typestr);
if (type == PROMPT_TYPE_INVALID) {
/*
* Invalid types are not expected, but this provides backward
* compatibility with old history files.
*/
if (line != NULL)
*(--line) = ':';
prompt_add_history(typestr, PROMPT_TYPE_COMMAND);
} else
prompt_add_history(line, type);
}
/* Load prompt history from file. */
void
prompt_load_history(void)
{
FILE *f;
char *history_file, *line, *tmp;
size_t length;
if ((history_file = prompt_find_history_file()) == NULL)
return;
log_debug("loading history from %s", history_file);
f = fopen(history_file, "r");
if (f == NULL) {
log_debug("%s: %s", history_file, strerror(errno));
free(history_file);
return;
}
free(history_file);
for (;;) {
if ((line = fgetln(f, &length)) == NULL)
break;
if (length > 0) {
if (line[length - 1] == '\n') {
line[length - 1] = '\0';
prompt_add_typed_history(line);
} else {
tmp = xmalloc(length + 1);
memcpy(tmp, line, length);
tmp[length] = '\0';
prompt_add_typed_history(tmp);
free(tmp);
}
}
}
fclose(f);
}
/* Save prompt history to file. */
void
prompt_save_history(void)
{
FILE *f;
u_int i, type;
char *history_file;
if ((history_file = prompt_find_history_file()) == NULL)
return;
log_debug("saving history to %s", history_file);
f = fopen(history_file, "w");
if (f == NULL) {
log_debug("%s: %s", history_file, strerror(errno));
free(history_file);
return;
}
free(history_file);
for (type = 0; type < PROMPT_NTYPES; type++) {
for (i = 0; i < prompt_hsize[type]; i++) {
fputs(prompt_type_string(type), f);
fputc(':', f);
fputs(prompt_hlist[type][i], f);
fputc('\n', f);
}
}
fclose(f);
}
/* Get previous line from the history. */
const char *
prompt_up_history(u_int *idx, u_int type)
{
/*
* History runs from 0 to size - 1. Index is from 0 to size. Zero is
* empty.
*/
if (type >= PROMPT_NTYPES)
return (NULL);
if (prompt_hsize[type] == 0 || idx[type] == prompt_hsize[type])
return (NULL);
idx[type]++;
return (prompt_hlist[type][prompt_hsize[type] - idx[type]]);
}
/* Get next line from the history. */
const char *
prompt_down_history(u_int *idx, u_int type)
{
if (type >= PROMPT_NTYPES)
return ("");
if (prompt_hsize[type] == 0 || idx[type] == 0)
return ("");
idx[type]--;
if (idx[type] == 0)
return ("");
return (prompt_hlist[type][prompt_hsize[type] - idx[type]]);
}
/* Add line to the history. */
void
prompt_add_history(const char *line, u_int type)
{
u_int i, oldsize, newsize, freecount, hlimit, new = 1;
size_t movesize;
if (type >= PROMPT_NTYPES)
return;
oldsize = prompt_hsize[type];
if (oldsize > 0 &&
strcmp(prompt_hlist[type][oldsize - 1], line) == 0)
new = 0;
hlimit = options_get_number(global_options, "prompt-history-limit");
if (hlimit > oldsize) {
if (new == 0)
return;
newsize = oldsize + new;
} else {
newsize = hlimit;
freecount = oldsize + new - newsize;
if (freecount > oldsize)
freecount = oldsize;
if (freecount == 0)
return;
for (i = 0; i < freecount; i++)
free(prompt_hlist[type][i]);
movesize = (oldsize - freecount) *
sizeof *prompt_hlist[type];
if (movesize > 0) {
memmove(&prompt_hlist[type][0],
&prompt_hlist[type][freecount], movesize);
}
}
if (newsize == 0) {
free(prompt_hlist[type]);
prompt_hlist[type] = NULL;
} else if (newsize != oldsize) {
prompt_hlist[type] =
xreallocarray(prompt_hlist[type], newsize,
sizeof *prompt_hlist[type]);
}
if (new == 1 && newsize > 0)
prompt_hlist[type][newsize - 1] = xstrdup(line);
prompt_hsize[type] = newsize;
}
/* Get history size. */
u_int
prompt_history_size(enum prompt_type type)
{
if (type >= PROMPT_NTYPES)
return (0);
return (prompt_hsize[type]);
}
/* Get history entry. */
const char *
prompt_history_get(enum prompt_type type, u_int idx)
{
if (type >= PROMPT_NTYPES)
return (NULL);
if (idx >= prompt_hsize[type])
return (NULL);
return (prompt_hlist[type][idx]);
}
/* Clear prompt history. */
void
prompt_history_clear(enum prompt_type type)
{
u_int idx;
if (type >= PROMPT_NTYPES)
return;
for (idx = 0; idx < prompt_hsize[type]; idx++)
free(prompt_hlist[type][idx]);
free(prompt_hlist[type]);
prompt_hlist[type] = NULL;
prompt_hsize[type] = 0;
}

1597
prompt.c Normal file

File diff suppressed because it is too large Load Diff

140
regress/check-names.sh Normal file
View File

@@ -0,0 +1,140 @@
#!/bin/sh
# Name validation policy.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
fail()
{
echo "$*"
$TMUX kill-server 2>/dev/null
exit 1
}
must_fail()
{
"$@" >/dev/null 2>&1 && fail "unexpected success: $*"
return 0
}
must_equal()
{
got=$1
want=$2
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
}
$TMUX new-session -d -x 80 -y 24 || exit 1
$TMUX set-option -qg allow-set-title on || exit 1
$TMUX set-option -qg allow-rename on || exit 1
$TMUX set-option -qg automatic-rename off || exit 1
# Commands reject ':' and '.' for sessions and windows, but allow '#'.
$TMUX rename-session 'session#ok' || fail "session name with # rejected"
must_equal "$($TMUX display-message -p '#{session_name}')" 'session#ok'
must_fail $TMUX rename-session 'session:bad'
must_fail $TMUX rename-session 'session.bad'
$TMUX rename-window 'window#ok' || fail "window name with # rejected"
must_equal "$($TMUX display-message -p '#{window_name}')" 'window#ok'
must_fail $TMUX rename-window 'window:bad'
must_fail $TMUX rename-window 'window.bad'
$TMUX set-option -q @name 'format#ok' || exit 1
$TMUX rename-session '#{@name}' || fail "format in session name not expanded"
must_equal "$($TMUX display-message -p '#{session_name}')" 'format#ok'
$TMUX rename-window '#{@name}' || fail "format in window name not expanded"
must_equal "$($TMUX display-message -p '#{window_name}')" 'format#ok'
must_fail $TMUX rename-session '#{session_name}:bad'
must_fail $TMUX rename-window '#{window_name}.bad'
pid=$($TMUX display-message -p '#{pid}')
created=$($TMUX new-session -dP -F '#{session_id}:#{window_id}' \
-s 'new-session#ok' -n 'new-window#ok') || \
fail "new-session name with # rejected"
created_session=${created%:*}
created_window=${created#*:}
must_equal "$($TMUX display-message -pt "$created_session" '#{session_name}')" \
'new-session#ok'
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
'new-window#ok'
$TMUX kill-session -t "$created_session"
must_fail $TMUX new-session -d -s 'new-session:bad'
must_fail $TMUX new-session -d -s 'new-session.bad'
must_fail $TMUX new-session -d -n 'new-window:bad'
must_fail $TMUX new-session -d -n 'new-window.bad'
created_window=$($TMUX new-window -dP -F '#{window_id}' \
-n 'created-window#ok') || \
fail "new-window name with # rejected"
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
'created-window#ok'
must_fail $TMUX new-window -d -n 'created-window:bad'
must_fail $TMUX new-window -d -n 'created-window.bad'
created=$($TMUX new-session -dP -F '#{session_id}:#{window_id}' \
-s 'new-session-#{pid}' -n 'new-window-#{pid}') || \
fail "format in new-session name not expanded"
created_session=${created%:*}
created_window=${created#*:}
must_equal "$($TMUX display-message -pt "$created_session" '#{session_name}')" \
"new-session-$pid"
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
"new-window-$pid"
$TMUX kill-session -t "$created_session"
created_window=$($TMUX new-window -dP -F '#{window_id}' -n '#{@name}') || \
fail "format in new-window name not expanded"
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
'format#ok'
must_fail $TMUX new-session -d -s 'new-session-#{pid}:bad'
must_fail $TMUX new-session -d -n 'new-window-#{pid}.bad'
must_fail $TMUX new-window -d -n '#{window_name}:bad'
# Invalid UTF-8 is never allowed for command names.
invalid=$(printf '\302')
must_fail $TMUX rename-session "bad${invalid}name"
must_fail $TMUX rename-window "bad${invalid}name"
must_fail $TMUX new-session -d -s "bad${invalid}name"
must_fail $TMUX new-session -d -n "bad${invalid}name"
must_fail $TMUX new-window -d -n "bad${invalid}name"
must_fail $TMUX set-buffer -b "bad${invalid}name" data
# Titles set by commands allow '#', ':' and '.'.
$TMUX select-pane -T 'title#:.ok' || fail "command title rejected"
must_equal "$($TMUX display-message -p '#{pane_title}')" 'title#:.ok'
# Buffer names allow '#', ':' and '.'.
$TMUX set-buffer -b 'buffer#:.ok' data || fail "buffer name rejected"
must_equal "$($TMUX list-buffers -F '#{buffer_name}')" 'buffer#:.ok'
# Window names from escape sequences reject '#', ':' and '.' by cleaning them.
$TMUX send-keys "printf '\\033kescape#:.ok\\033\\\\'" Enter || exit 1
sleep 1
must_equal "$($TMUX display-message -p '#{window_name}')" 'escape___ok'
# Titles from escape sequences reject only '#'.
$TMUX send-keys "printf '\\033]2;escape#:.ok\\007'" Enter || exit 1
sleep 1
must_equal "$($TMUX display-message -p '#{pane_title}')" 'escape_:.ok'
# Invalid UTF-8 from escape sequences is ignored.
$TMUX rename-window 'before-invalid' || exit 1
$TMUX send-keys "printf '\\033kbad\\302name\\033\\\\'" Enter || exit 1
sleep 1
must_equal "$($TMUX display-message -p '#{window_name}')" 'before-invalid'
$TMUX select-pane -T 'before-invalid-title' || exit 1
$TMUX send-keys "printf '\\033]2;bad\\302title\\007'" Enter || exit 1
sleep 1
must_equal "$($TMUX display-message -p '#{pane_title}')" 'before-invalid-title'
$TMUX kill-server 2>/dev/null
exit 0

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

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

View File

@@ -0,0 +1,203 @@
#!/bin/sh
# Test new-pane, resize-pane, and move-pane -x/-y/-X/-Y with floating panes.
# Verifies that size and position are correctly applied with the default border,
# with -B none, and with the pane-border-lines window option set to none. Also
# verifies that zero sizes are rejected.
#
# With a border, -x/-y/-X/-Y specify the onscreen footprint including the
# border: new-pane subtracts 2 from width/height and adds 1 to x/y-position
# so that the border lands on the specified screen coordinates. resize-pane
# -x/-y similarly subtracts 2 from the requested size. move-pane -X/-Y adds 1
# to the position. Without a border all values are used directly.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
fail()
{
echo "$*" >&2
$TMUX kill-server 2>/dev/null
exit 1
}
must_fail()
{
"$@" >/dev/null 2>&1 && fail "unexpected success: $*"
return 0
}
must_equal()
{
got=$1
want=$2
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
}
$TMUX new-session -d -x 80 -y 24 || exit 1
# --- Default border (single-line) ---
# Explicit values: border subtracts 2 from size and adds 1 to position.
# -x 20 -> pane_width 18; -y 6 -> pane_height 4;
# -X 8 -> pane_left 9; -Y 3 -> pane_top 4.
id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|| fail "new-pane with explicit geometry failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 9
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 4
# resize-pane with border: requested size decremented by 2.
$TMUX resize-pane -t "$id" -x 30 || fail "resize-pane -x 30 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 28
$TMUX resize-pane -t "$id" -y 10 || fail "resize-pane -y 10 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 8
# resize-pane with percentage and border.
# 75% of 80 = 60, 60 - 2 = 58; 75% of 24 = 18, 18 - 2 = 16.
$TMUX resize-pane -t "$id" -x 75% || fail "resize-pane -x 75% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 58
$TMUX resize-pane -t "$id" -y 75% || fail "resize-pane -y 75% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 16
$TMUX kill-pane -t "$id" || exit 1
# Percentage width, height and position with border.
# 50% of 80 = 40, 40-2 = 38; 50% of 24 = 12, 12-2 = 10.
# 10% of 80 = 8, 8+1 = 9; 10% of 24 = 2, 2+1 = 3.
id=$($TMUX new-pane -dPF '#{pane_id}' -x 50% -y 50% -X 10% -Y 10% 'sleep 100') \
|| fail "new-pane with percentage geometry failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 38
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 10
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 9
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 3
# move-pane -X/-Y with border: border lands at the given column/row, content
# is one cell inside. -X 5 -> pane_left 6; -Y 1 -> pane_top 2.
$TMUX move-pane -t "$id" -X 5 || fail "move-pane -X 5 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 6
$TMUX move-pane -t "$id" -Y 1 || fail "move-pane -Y 1 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 2
# 25% of 80 = 20, 20+1 = 21; 25% of 24 = 6, 6+1 = 7.
$TMUX move-pane -t "$id" -X 25% || fail "move-pane -X 25% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 21
$TMUX move-pane -t "$id" -Y 25% || fail "move-pane -Y 25% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 7
$TMUX kill-pane -t "$id" || exit 1
# --- -B none ---
# Without a border all values are used directly.
id=$($TMUX new-pane -dPF '#{pane_id}' -B none -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|| fail "new-pane -B none with explicit geometry failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 20
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 6
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 3
# resize-pane without border: size used directly.
$TMUX resize-pane -t "$id" -x 30 || fail "resize-pane -x 30 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 30
$TMUX resize-pane -t "$id" -y 10 || fail "resize-pane -y 10 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 10
# resize-pane with percentage, no border: 75% of 80 = 60; 75% of 24 = 18.
$TMUX resize-pane -t "$id" -x 75% || fail "resize-pane -x 75% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 60
$TMUX resize-pane -t "$id" -y 75% || fail "resize-pane -y 75% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 18
$TMUX kill-pane -t "$id" || exit 1
# Percentage geometry without border: 50% of 80 = 40; 10% of 80 = 8 etc.
id=$($TMUX new-pane -dPF '#{pane_id}' -B none -x 50% -y 50% -X 10% -Y 10% 'sleep 100') \
|| fail "new-pane -B none with percentage geometry failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 40
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 12
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 2
# move-pane without border: -X/-Y set the content position directly.
# -X 5 -> pane_left 5; -Y 1 -> pane_top 1.
$TMUX move-pane -t "$id" -X 5 || fail "move-pane -X 5 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 5
$TMUX move-pane -t "$id" -Y 1 || fail "move-pane -Y 1 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 1
# 25% of 80 = 20; 25% of 24 = 6.
$TMUX move-pane -t "$id" -X 25% || fail "move-pane -X 25% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 20
$TMUX move-pane -t "$id" -Y 25% || fail "move-pane -Y 25% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 6
$TMUX kill-pane -t "$id" || exit 1
# --- Window option pane-border-lines none ---
$TMUX set-option -w pane-border-lines none || exit 1
# Inherits none from window option: same behaviour as -B none.
id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|| fail "new-pane with window pane-border-lines none failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 20
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 6
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 3
$TMUX resize-pane -t "$id" -x 30 || fail "resize-pane -x 30 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 30
$TMUX resize-pane -t "$id" -y 10 || fail "resize-pane -y 10 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 10
$TMUX resize-pane -t "$id" -x 75% || fail "resize-pane -x 75% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 60
$TMUX resize-pane -t "$id" -y 75% || fail "resize-pane -y 75% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 18
$TMUX kill-pane -t "$id" || exit 1
id=$($TMUX new-pane -dPF '#{pane_id}' -x 50% -y 50% -X 10% -Y 10% 'sleep 100') \
|| fail "new-pane with window pane-border-lines none and percentages failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 40
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 12
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 2
# move-pane with window pane-border-lines none: same behaviour as -B none.
$TMUX move-pane -t "$id" -X 5 || fail "move-pane -X 5 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 5
$TMUX move-pane -t "$id" -Y 1 || fail "move-pane -Y 1 failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 1
$TMUX move-pane -t "$id" -X 25% || fail "move-pane -X 25% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 20
$TMUX move-pane -t "$id" -Y 25% || fail "move-pane -Y 25% failed"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 6
$TMUX kill-pane -t "$id" || exit 1
# --- Invalid sizes ---
# Zero width and height must be rejected by new-pane with the default border.
$TMUX set-option -w -u pane-border-lines || exit 1
must_fail $TMUX new-pane -d -x 0 -y 6 -X 8 -Y 3 'sleep 100'
must_fail $TMUX new-pane -d -x 20 -y 0 -X 8 -Y 3 'sleep 100'
# Zero width and height must be rejected by resize-pane.
id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|| fail "new-pane for resize-pane invalid size tests failed"
must_fail $TMUX resize-pane -t "$id" -x 0
must_fail $TMUX resize-pane -t "$id" -y 0
$TMUX kill-pane -t "$id" || exit 1
# Same rejections apply with no border.
$TMUX set-option -w pane-border-lines none || exit 1
must_fail $TMUX new-pane -d -x 0 -y 6 -X 8 -Y 3 'sleep 100'
must_fail $TMUX new-pane -d -x 20 -y 0 -X 8 -Y 3 'sleep 100'
id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|| fail "new-pane for resize-pane invalid size tests (no border) failed"
must_fail $TMUX resize-pane -t "$id" -x 0
must_fail $TMUX resize-pane -t "$id" -y 0
$TMUX kill-pane -t "$id" || exit 1
$TMUX kill-server 2>/dev/null
exit 0

View File

@@ -67,6 +67,7 @@ $TMUX set @true 1 || exit 1
$TMUX set @false 0 || exit 1 $TMUX set @false 0 || exit 1
$TMUX set @warm Summer || exit 1 $TMUX set @warm Summer || exit 1
$TMUX set @cold Winter || exit 1 $TMUX set @cold Winter || exit 1
$TMUX set @v 'foo:bar' || exit 1
# Plain string without substitutions et al # Plain string without substitutions et al
test_format "abc xyz" "abc xyz" test_format "abc xyz" "abc xyz"
@@ -74,6 +75,8 @@ test_format "abc xyz" "abc xyz"
# Test basic escapes for "#", "{", "#{" "}", "#}", "," # Test basic escapes for "#", "{", "#{" "}", "#}", ","
test_format "##" "#" test_format "##" "#"
test_format "#," "," test_format "#," ","
test_format "#{s/#:/_/:@v}" "foo_bar"
test_format "#{s/o/#:/:@v}" "f:::bar"
test_format "{" "{" test_format "{" "{"
test_format "##{" "#{" test_format "##{" "#{"
test_format "#}" "}" test_format "#}" "}"

300
regress/prompt-keys.sh Normal file
View File

@@ -0,0 +1,300 @@
#!/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)
OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client)
IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test)
$OUT kill-server 2>/dev/null
$IN kill-server 2>/dev/null
trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" EXIT
fail() {
echo "[FAIL] $1"
exit 1
}
# Capture the outer pane: what the inner client rendered. No -e, so we match
# plain visible text, not styles or escape sequences.
capture() {
$OUT capture-pane -p
}
# The "(search) ..." prompt row of a mode prompt.
search_row() {
capture | grep '(search)' | head -1
}
# The inner status line is the last row of the outer capture (status is at the
# bottom). Used to assert a prompt is in the pane and not on the status line.
status_line() {
capture | tail -1
}
# Inner mode must still be a tree mode (the prompt did not close or crash it).
in_tree_mode() {
[ "$($IN display-message -p '#{pane_mode}')" = "tree-mode" ]
}
# Small settle for the key -> inner server -> inner client -> outer pane round
# trip to redraw before we capture. Matches the short waits other regress tests
# use; we do not depend on exact timing beyond the redraw completing.
settle() {
sleep 0.5
}
# Assert the mode search prompt currently shows exactly "(search) <want>".
search_is() {
want=$1; msg=$2
search_row | grep -qF "(search) $want" || \
fail "$msg (wanted '(search) $want', got '$(search_row)')"
}
# --- Inner session under test. ---------------------------------------------
#
# Two windows so the tree has content; status on so we can distinguish the pane
# from the status line; fixed size and manual sizing so the layout is stable.
# A root-table key opens a status-line command prompt whose accept action writes
# the final buffer into @r, so we can recover it exactly.
$IN new -d -x80 -y24 "sh -c 'exec sleep 1000'" || exit 1
$IN set -g status on || exit 1
$IN set -g status-position bottom || exit 1
$IN set -g status-keys emacs || exit 1
$IN set -g window-size manual || exit 1
$IN new-window -d "sh -c 'exec sleep 1000'" || exit 1
$IN bind -n M-r command-prompt -p ">" "set -g @r '%%'" || exit 1
# --- Outer session: attach the inner one inside its pane. -------------------
$OUT new -d -x80 -y24 || exit 1
$OUT set -g status off || exit 1
$OUT set -g window-size manual || exit 1
$OUT send-keys -l "$IN attach" || exit 1
$OUT send-keys Enter || exit 1
sleep 1
# ===========================================================================
# Mode prompt (choose-tree search): the thorough engine vehicle.
# ===========================================================================
$IN choose-tree || exit 1
settle
in_tree_mode || fail "choose-tree did not enter tree-mode"
# --- 1. Search prompt is drawn in the pane, not on the status line. ---
$IN send-keys C-s || exit 1
settle
search_row | grep -q '(search)' || fail "search prompt not drawn in the pane"
status_line | grep -q '(search)' && \
fail "search prompt drawn on the status line, not in the pane"
# --- 2. emacs editing: insert and delete at middle, start and end. ---
# Cursor position is checked behaviourally: move, insert a marker, read the row.
# This needs no cursor coordinates and fails if a movement/edit key is wrong.
# Middle insert: "abcd", Left Left (cursor between b and c), insert X -> abXcd.
$IN send-keys -l "abcd" || exit 1
settle
search_is "abcd" "literal input not shown in search prompt"
$IN send-keys Left Left || exit 1
$IN send-keys -l "X" || exit 1
settle
search_is "abXcd" "middle insert wrong (Left/insert)"
# Middle delete: BSpace removes X (before cursor), DC removes c (at cursor).
$IN send-keys BSpace || exit 1
$IN send-keys DC || exit 1
settle
search_is "abd" "middle delete wrong (BSpace/Delete)"
# Clear, then start/end insert with C-a and C-e.
$IN send-keys C-u || exit 1
$IN send-keys -l "mno" || exit 1
$IN send-keys C-a || exit 1
$IN send-keys -l "S" || exit 1
$IN send-keys C-e || exit 1
$IN send-keys -l "E" || exit 1
settle
search_is "SmnoE" "C-a/C-e start/end insert wrong"
# Word kill: "hello world", C-w removes the last word leaving "hello " (with the
# separating space). capture-pane trims trailing spaces, so make the space
# visible by inserting a marker after it: the buffer becomes "hello Z".
$IN send-keys C-u || exit 1
$IN send-keys -l "hello world" || exit 1
$IN send-keys C-w || exit 1
$IN send-keys -l "Z" || exit 1
settle
search_is "hello Z" "C-w did not kill a word"
# C-a then C-k kills the whole line.
$IN send-keys C-a || exit 1
$IN send-keys C-k || exit 1
settle
search_row | grep -q '(search) [^ ]' && fail "C-a C-k did not clear the line"
# --- 3. Editing kept the prompt open the whole time. ---
in_tree_mode || fail "editing keys closed the mode"
search_row | grep -q '(search)' || fail "editing keys closed the prompt"
# --- 4. Unicode wide character: insert, render, delete as one unit. ---
$IN send-keys C-u || exit 1
$IN send-keys -l "a中b" || exit 1
settle
search_is "a中b" "wide character not shown"
# Left moves over "b" (one column); BSpace deletes the wide "中" as a single
# width-2 unit, leaving "ab".
$IN send-keys Left || exit 1
$IN send-keys BSpace || exit 1
settle
search_is "ab" "wide character not deleted as one unit"
# --- 5. Control character: quote-next inserts it literally, shown as ^G. ---
$IN send-keys C-u || exit 1
$IN send-keys -l "a" || exit 1
$IN send-keys C-v || exit 1 # quote next key
$IN send-keys C-g || exit 1 # literal BEL -> displayed as ^G
$IN send-keys -l "b" || exit 1
settle
search_is "a^Gb" "control character not shown as ^G"
# Deleted as a single unit too.
$IN send-keys Left || exit 1
$IN send-keys BSpace || exit 1
settle
search_is "ab" "control character not deleted as one unit"
# --- 6. Kill and yank: C-w fills the yank buffer, C-y pastes it at the cursor. ---
# (prompt_key only fills the yank buffer from C-w; C-y then yanks that text, or
# the top paste buffer if nothing has been killed. Establish our own kill here
# so the result is deterministic.)
$IN send-keys C-u || exit 1
$IN send-keys -l "one two" || exit 1
$IN send-keys C-w || exit 1 # kill "two", buffer "one "
$IN send-keys C-y || exit 1 # yank it back -> "one two"
settle
search_is "one two" "C-y did not yank the killed text"
$IN send-keys C-y || exit 1 # yank again at cursor -> "one twotwo"
settle
search_is "one twotwo" "second C-y did not yank again"
# --- 7. History: accept a string, reopen, Up recalls it. ---
$IN send-keys C-u || exit 1
$IN send-keys -l "alpha" || exit 1
$IN send-keys Enter || exit 1
settle
$IN send-keys C-s || exit 1
settle
$IN send-keys Up || exit 1
settle
search_is "alpha" "history (Up) did not recall the previous entry"
# --- 8. Escape closes the prompt but leaves the mode open. ---
$IN send-keys Escape || exit 1
settle
search_row | grep -q '(search)' && fail "Escape left a dangling search prompt"
in_tree_mode || fail "Escape closed the mode as well as the prompt"
# Leave the mode.
$IN send-keys q || exit 1
settle
# ===========================================================================
# Status-line prompt (command-prompt): the same engine on the status line.
# Keys go through the inner client's terminal (outer send-keys); the accepted
# buffer is recovered exactly via %% -> @r.
# ===========================================================================
# --- 10. Prompt is drawn on the status line (the last row). ---
$IN set -g @r "" || exit 1
$OUT send-keys M-r || exit 1
settle
status_line | grep -q '>' || fail "status-line prompt not drawn on the status line"
# --- 11. emacs cursor-marker edit, accept recovers the exact buffer. ---
$OUT send-keys -l "abc" || exit 1
$OUT send-keys Home || exit 1
$OUT send-keys -l "X" || exit 1
settle
status_line | grep -qF "> Xabc" || \
fail "status-line edit wrong (got '$(status_line)')"
$OUT send-keys Enter || exit 1
settle
[ "$($IN show -gv @r)" = "Xabc" ] || \
fail "status-line accept recovered '$($IN show -gv @r)', wanted 'Xabc'"
# --- 12. Unicode on the status line: insert, move, delete wide char. ---
$IN set -g @r "" || exit 1
$OUT send-keys M-r || exit 1
settle
$OUT send-keys -l "a㋡b" || exit 1
settle
status_line | grep -qF "a㋡b" || \
fail "status-line wide character not shown (got '$(status_line)')"
# Home, insert Z (start); End, BSpace (delete b); BSpace (delete wide char).
$OUT send-keys Home || exit 1
$OUT send-keys -l "Z" || exit 1
$OUT send-keys End || exit 1
$OUT send-keys BSpace || exit 1
$OUT send-keys BSpace || exit 1
settle
$OUT send-keys Enter || exit 1
settle
[ "$($IN show -gv @r)" = "Za" ] || \
fail "status-line wide edit recovered '$($IN show -gv @r)', wanted 'Za'"
# --- 13. Overflow: more text than fits stays within the line and is kept. ---
big="0123456789012345678901234567890123456789012345678901234567890123456789ABCDEFGHIJ"
$IN set -g @r "" || exit 1
$OUT send-keys M-r || exit 1
settle
$OUT send-keys -l "$big" || exit 1
settle
# The drawn status line must not exceed the client width (80): no wrap, no crash.
width=$(status_line | awk '{print length($0)}')
[ "$width" -le 80 ] || fail "overflowing prompt drew $width columns, wider than 80"
$OUT send-keys Enter || exit 1
settle
# The whole buffer was kept despite only part being visible.
[ "$($IN show -gv @r)" = "$big" ] || fail "overflowing prompt lost buffer content"
# --- 14. Escape closes the status-line prompt cleanly. ---
$IN set -g @r "SENTINEL" || exit 1
$OUT send-keys M-r || exit 1
settle
$OUT send-keys -l "discard" || exit 1
$OUT send-keys Escape || exit 1
settle
status_line | grep -q '> discard' && fail "Escape left a dangling status-line prompt"
[ "$($IN show -gv @r)" = "SENTINEL" ] || fail "Escape ran the prompt's accept action"
# ===========================================================================
# Two clients attached to the same window: a mode prompt must render on both.
# ===========================================================================
$OUT new-window || exit 1
$OUT set -g status off || exit 1
$OUT send-keys -l "$IN attach" || exit 1
$OUT send-keys Enter || exit 1
sleep 1
$IN choose-tree || exit 1
settle
$IN send-keys C-s || exit 1
settle
$IN send-keys -l "dual" || exit 1
settle
for w in $($OUT list-windows -F '#{window_index}'); do
$OUT capture-pane -t ":$w" -p | grep -qF "(search) dual" || \
fail "mode prompt not shown on client in outer window $w"
done
$IN send-keys Escape || exit 1
settle
# --- Inner tmux is still alive and responsive. ---
$IN display-message -p '#{version}' >/dev/null 2>&1 || fail "inner tmux died"
exit 0

336
regress/prompt-mechanics.sh Normal file
View File

@@ -0,0 +1,336 @@
#!/bin/sh
# Exercise the prompt mechanics shared by all three host paths of the prompt
# engine in prompt.c:
#
# status.c status_prompt_set - the status-line command prompt.
# window.c window_pane_set_prompt - a prompt drawn over a pane (-P).
# mode-tree.c mode_tree_set_prompt - search/filter prompts in tree modes.
#
# prompt-keys.sh covers the editing keys; this test covers that each path OPENS
# and DRAWS in the right place and that the prompt flags select the right engine
# behaviour: -1 (single), -N (numeric), -i (incremental), -k (key), -e
# (backspace exit), -I (prefill), multi prompts, type-scoped history and command
# completion.
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)
OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client)
IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test)
$OUT kill-server 2>/dev/null
$IN kill-server 2>/dev/null
trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" EXIT
fail() {
echo "[FAIL] $1"
exit 1
}
# Capture the outer pane: what the inner client rendered. Pane-area prompts
# (over a pane or in a tree mode) and the status-line prompt all end up here.
capture() {
$OUT capture-pane -p
}
# The inner status line is the last row of the outer capture.
status_line() {
capture | tail -1
}
# The recovered buffer. Every prompt below accepts into the @r option so the
# exact final string can be checked; reset it to a sentinel first so we can tell
# "accept ran" from "prompt cancelled".
got() {
$IN show -gv @r
}
reset() {
$IN set -g @r "SENTINEL" || exit 1
}
# Settle for the key -> inner server -> inner client -> outer pane redraw round
# trip, as in prompt-keys.sh.
settle() {
sleep 0.5
}
# --- Inner session under test. ---------------------------------------------
#
# The window is created at -y23 so that, with a one-row bottom status, the pane
# area (23) exactly fills the 24-row client: a pane prompt drawn on the pane's
# bottom row lands on visible row 23, just above the status line on row 24. A
# second, distinctively named window gives the tree something to filter.
$IN new -d -x80 -y23 -n YAK "sh -c 'exec sleep 1000'" || exit 1
$IN set -g status on || exit 1
$IN set -g status-position bottom || exit 1
$IN set -g status-keys emacs || exit 1
$IN set -g window-size manual || exit 1
$IN new-window -d -n ZEBRA "sh -c 'exec sleep 1000'" || exit 1
# One root-table key per path/flag. The accept template records the final
# buffer in @r (or both buffers, for the multi-prompt case).
$IN bind -n M-s command-prompt -p '(stat)' "set -g @r '%%'" || exit 1
$IN bind -n M-p command-prompt -P -p '(pane)' "set -g @r '%%'" || exit 1
$IN bind -n M-o command-prompt -P -1 -p '(one)' "set -g @r '%%'" || exit 1
$IN bind -n M-n command-prompt -P -N -I 5 -p '(num)' "set -g @r '%%'" || exit 1
$IN bind -n M-i command-prompt -P -i -p '(inc)' "set -g @r '%%'" || exit 1
$IN bind -n M-k command-prompt -P -k -p '(key)' "set -g @r '%%'" || exit 1
$IN bind -n M-e command-prompt -P -e -p '(bs)' "set -g @r '%%'" || exit 1
$IN bind -n M-j command-prompt -P -I hello -p '(pre)' "set -g @r '%%'" || exit 1
$IN bind -n M-m command-prompt -p 'first,second' "set -g @r '%1/%2'" || exit 1
$IN bind -n M-c command-prompt -p '(cmd)' "set -g @r '%%'" || exit 1
$IN bind -n M-h command-prompt -T search -p '(srch)' "set -g @r '%%'" || exit 1
# --- Outer session: attach the inner one inside its pane. -------------------
$OUT new -d -x80 -y24 || exit 1
$OUT set -g status off || exit 1
$OUT set -g window-size manual || exit 1
$OUT send-keys -l "$IN attach" || exit 1
$OUT send-keys Enter || exit 1
sleep 1
# ===========================================================================
# 1. Each path opens and draws in the right place.
# ===========================================================================
# --- 1a. status.c: drawn on the status line (the last row). ---
reset
$OUT send-keys M-s || exit 1
settle
status_line | grep -qF '(stat)' || \
fail "status-line prompt not on the status line (got '$(status_line)')"
$OUT send-keys -l "go" || exit 1
$OUT send-keys Enter || exit 1
settle
[ "$(got)" = "go" ] || fail "status-line accept recovered '$(got)', wanted 'go'"
# --- 1b. window.c: drawn over the pane, not on the status line. ---
reset
$OUT send-keys M-p || exit 1
settle
capture | grep -qF '(pane)' || fail "pane prompt not drawn in the pane"
status_line | grep -qF '(pane)' && \
fail "pane prompt drawn on the status line, not over the pane"
$OUT send-keys -l "deep" || exit 1
$OUT send-keys Enter || exit 1
settle
[ "$(got)" = "deep" ] || fail "pane prompt accept recovered '$(got)', wanted 'deep'"
# --- 1c. mode-tree.c: search prompt drawn in the pane. ---
$IN choose-tree || exit 1
settle
[ "$($IN display-message -p '#{pane_mode}')" = "tree-mode" ] || \
fail "choose-tree did not enter tree-mode"
$IN send-keys C-s || exit 1
settle
capture | grep -qF '(search)' || fail "mode-tree search prompt not drawn in the pane"
status_line | grep -qF '(search)' && \
fail "mode-tree search prompt drawn on the status line"
$IN send-keys Escape || exit 1
settle
# --- 1d. mode-tree.c: filter prompt opens, applies, prefills, and clears. ---
$IN send-keys f || exit 1
settle
capture | grep -qF '(filter)' || fail "mode-tree filter prompt not drawn"
$IN send-keys -l "ZEBRA" || exit 1
$IN send-keys Enter || exit 1
settle
capture | grep -q 'filter: active' || fail "accepting the filter did not apply it"
# Reopening the filter prompt prefills it with the current filter.
$IN send-keys f || exit 1
settle
capture | grep -qF '(filter) ZEBRA' || \
fail "filter prompt not prefilled with the current filter"
$IN send-keys Escape || exit 1
settle
# 'c' clears the filter.
$IN send-keys c || exit 1
settle
capture | grep -q 'filter: active' && fail "'c' did not clear the filter"
$IN send-keys q || exit 1
settle
# ===========================================================================
# 2. Flags select the right engine behaviour.
# ===========================================================================
# --- 2a. -1 (PROMPT_SINGLE): one keystroke closes and accepts that char. ---
reset
$OUT send-keys M-o || exit 1
settle
capture | grep -qF '(one)' || fail "single prompt did not open"
$OUT send-keys -l "q" || exit 1
settle
[ "$(got)" = "q" ] || fail "single prompt recovered '$(got)', wanted 'q'"
capture | grep -qF '(one)' && fail "single prompt stayed open after one key"
# --- 2b. -N (PROMPT_NUMERIC): prefilled, digits append, non-digit closes. ---
reset
$OUT send-keys M-n || exit 1
settle
capture | grep -qF '(num) 5' || fail "numeric prompt not prefilled with 5"
$OUT send-keys -l "7" || exit 1 # 57
$OUT send-keys Enter || exit 1 # Enter is a non-digit: close
settle
[ "$(got)" = "57" ] || fail "numeric accept recovered '$(got)', wanted '57'"
# A non-digit key closes the prompt with the existing buffer, dropping the key.
reset
$OUT send-keys M-n || exit 1
settle
$OUT send-keys -l "x" || exit 1
settle
[ "$(got)" = "5" ] || fail "numeric non-digit close recovered '$(got)', wanted '5'"
capture | grep -qF '(num)' && fail "numeric prompt stayed open after a non-digit"
# --- 2c. -i (PROMPT_INCREMENTAL): callback fires on every edit, stays open. ---
# The incremental code path prefixes the buffer with '=' (or +/-), so the '='
# proves the value came through the incremental callback, not a plain accept.
reset
$OUT send-keys M-i || exit 1
settle
[ "$(got)" = "=" ] || fail "incremental prompt did not fire on open (got '$(got)')"
$OUT send-keys -l "a" || exit 1
settle
[ "$(got)" = "=a" ] || fail "incremental did not fire after 'a' (got '$(got)')"
$OUT send-keys -l "b" || exit 1
settle
[ "$(got)" = "=ab" ] || fail "incremental did not fire after 'b' (got '$(got)')"
capture | grep -qF '(inc)' || fail "incremental prompt closed during editing"
$OUT send-keys Escape || exit 1
settle
capture | grep -qF '(inc)' && fail "Escape did not close the incremental prompt"
# --- 2d. -k (PROMPT_KEY): the next key closes and delivers its name. ---
reset
$OUT send-keys M-k || exit 1
settle
capture | grep -qF '(key)' || fail "key prompt did not open"
$OUT send-keys -l "z" || exit 1
settle
[ "$(got)" = "z" ] || fail "key prompt recovered '$(got)', wanted 'z'"
capture | grep -qF '(key)' && fail "key prompt stayed open after a key"
# --- 2e. -e (PROMPT_BSPACE_EXIT): backspace on empty cancels (no accept). ---
reset
$OUT send-keys M-e || exit 1
settle
$OUT send-keys BSpace || exit 1
settle
[ "$(got)" = "SENTINEL" ] || fail "backspace-exit ran the accept action (got '$(got)')"
capture | grep -qF '(bs)' && fail "backspace on empty did not close the prompt"
# --- 2f. -I (prefill): prompt opens with the given buffer. ---
reset
$OUT send-keys M-j || exit 1
settle
capture | grep -qF '(pre) hello' || fail "prefill not shown (got '$(capture | grep -F '(pre)')')"
$OUT send-keys Enter || exit 1
settle
[ "$(got)" = "hello" ] || fail "prefill accept recovered '$(got)', wanted 'hello'"
# --- 2g. Multi prompt: accept advances to the next, both are delivered. ---
reset
$OUT send-keys M-m || exit 1
settle
capture | grep -qF 'first' || fail "first of multi prompt not shown"
$OUT send-keys -l "X" || exit 1
$OUT send-keys Enter || exit 1
settle
capture | grep -qF 'second' || fail "multi prompt did not advance to the second"
$OUT send-keys -l "Y" || exit 1
$OUT send-keys Enter || exit 1
settle
[ "$(got)" = "X/Y" ] || fail "multi prompt recovered '$(got)', wanted 'X/Y'"
# ===========================================================================
# 3. Type-scoped history and command completion.
# ===========================================================================
# --- 3a. Command history is recalled with Up; search history is separate. ---
reset
$OUT send-keys M-c || exit 1
settle
$OUT send-keys -l "alpha" || exit 1
$OUT send-keys Enter || exit 1
settle
[ "$(got)" = "alpha" ] || fail "command prompt accept recovered '$(got)'"
# Reopen the command prompt: Up recalls the command-type entry.
$OUT send-keys M-c || exit 1
settle
$OUT send-keys Up || exit 1
settle
capture | grep -qF '(cmd) alpha' || fail "Up did not recall command history"
$OUT send-keys Escape || exit 1
settle
# A search-type prompt must NOT recall the command-type entry (separate rings).
$OUT send-keys M-h || exit 1
settle
$OUT send-keys Up || exit 1
settle
capture | grep -qF 'alpha' && fail "search prompt recalled command-type history"
$OUT send-keys Escape || exit 1
settle
# --- 3b. Tab completion works in a command prompt (command type only). ---
$OUT send-keys M-c || exit 1
settle
$OUT send-keys -l "new-w" || exit 1
$OUT send-keys Tab || exit 1
settle
status_line | grep -qF 'new-window' || \
fail "Tab did not complete new-w to new-window (got '$(status_line)')"
$OUT send-keys Escape || exit 1
settle
# A search-type prompt does not complete commands: Tab is literal / inert.
$OUT send-keys M-h || exit 1
settle
$OUT send-keys -l "new-w" || exit 1
$OUT send-keys Tab || exit 1
settle
status_line | grep -qF 'new-window' && fail "search prompt completed a command"
$OUT send-keys Escape || exit 1
settle
# ===========================================================================
# 4. Robustness: re-entrancy guard and Escape, then liveness.
# ===========================================================================
# --- 4a. A second prompt while one is open is refused (status path). ---
client=$($IN list-clients -F '#{client_name}' | head -1)
reset
$OUT send-keys M-s || exit 1
settle
$OUT send-keys -l "AAA" || exit 1
settle
$IN command-prompt -t"$client" -p '(re)' "set -g @r 'REENTERED'" 2>/dev/null
settle
capture | grep -qF '(re)' && fail "a second status prompt opened over the first"
status_line | grep -qF '(stat) AAA' || fail "first status prompt was disturbed"
$OUT send-keys Escape || exit 1
settle
[ "$(got)" = "SENTINEL" ] || fail "Escape ran the status prompt accept action"
# --- 4b. A second pane prompt while one is open is refused (pane path). ---
reset
$OUT send-keys M-p || exit 1
settle
$OUT send-keys -l "BBB" || exit 1
settle
$IN command-prompt -P -t"$client" -p '(re)' "set -g @r 'REENTERED'" 2>/dev/null
settle
capture | grep -qF '(re)' && fail "a second pane prompt opened over the first"
capture | grep -qF '(pane) BBB' || fail "first pane prompt was disturbed"
$OUT send-keys Escape || exit 1
settle
[ "$(got)" = "SENTINEL" ] || fail "Escape ran the pane prompt accept action"
# --- 4c. Inner tmux survived every path and flag. ---
$IN display-message -p '#{version}' >/dev/null 2>&1 || fail "inner tmux died"
exit 0

View File

@@ -0,0 +1,79 @@
#!/bin/sh
# Exercise screen-redraw.c bidirectional-isolate handling. When the client is
# UTF-8 and its terminal has the Bidi capability, each drawn span is wrapped in
# directional isolate characters (U+2066 .. U+2069) so a bidi terminal does not
# reorder pane contents across borders (the REDRAW_ISOLATES path).
#
# The Bidi capability is added with terminal-overrides before the client
# attaches, so the attached client picks it up. The isolate characters appear in
# the captured output around each span.
#
# Run with GENERATE=1 to (re)create the golden files.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
RESULTS=screen-redraw-results
TMP=$(mktemp)
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
0 1 15
fail() {
echo "$*" >&2
exit 1
}
compare() {
sleep 1
$TMUX capturep -p >$TMP || exit 1
if [ -n "$GENERATE" ]; then
cp $TMP "$RESULTS/$1.result" || exit 1
echo "generated $1"
else
cmp -s $TMP "$RESULTS/$1.result" || \
fail "scene $1 differs from $RESULTS/$1.result"
fi
}
new_scene() {
$TMUX2 neww -d "sh -c 'printf \"BIDI-LEFT 12345\nBIDI-RIGHT 67890\"; exec sleep 100'" || exit 1
$TMUX2 selectw -t:\$ || exit 1
$TMUX2 resizew -x40 -y12 || exit 1
}
C="sh -c 'printf \"BIDI-PANE 12345\nBIDI-PANE 67890\"; exec sleep 100'"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new -d -x40 -y12 "sh -c 'printf \"BIDI-BASE 12345\nBIDI-BASE 67890\"; exec sleep 100'" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
# Add the Bidi capability before the client attaches.
$TMUX2 set -ag terminal-overrides ",*:Bidi=\\E[8h" || exit 1
$TMUX new -d -x40 -y12 || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g window-size manual || exit 1
$TMUX set -g default-terminal "tmux-256color" || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
# Single pane: content is wrapped in isolates.
new_scene
compare bidi-single
# Two panes: borders and both panes are isolated.
new_scene
$TMUX2 splitw -h "$C" || exit 1
compare bidi-split
exit 0

View File

@@ -0,0 +1,162 @@
#!/bin/sh
# Exercise the scene caching in screen-redraw.c. A scene is built once and reused
# until it is invalidated; redraw_get_scene rebuilds it when the window changes,
# the generation number is bumped (panes moved/resized/swapped), or the offset
# changes. These tests make such a change in place and capture afterwards: if the
# matching invalidation did not fire, the stale cached scene would be drawn and
# the capture would not match the golden.
#
# Each scene is rendered in an inner tmux attached inside an outer tmux pane. The
# outer pane is captured and compared with a golden in screen-redraw-results/.
#
# Run with GENERATE=1 to (re)create the golden files.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
RESULTS=screen-redraw-results
TMP=$(mktemp)
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
0 1 15
fail() {
echo "$*" >&2
exit 1
}
compare() {
sleep 1
$TMUX capturep -p >$TMP || exit 1
if [ -n "$GENERATE" ]; then
cp $TMP "$RESULTS/$1.result" || exit 1
echo "generated $1"
else
cmp -s $TMP "$RESULTS/$1.result" || \
fail "scene $1 differs from $RESULTS/$1.result"
fi
}
new_scene() {
$TMUX2 neww -d "sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"BASE%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
$TMUX2 selectw -t:\$ || exit 1
$TMUX2 resizew -x40 -y14 || exit 1
}
C="sh -c 'i=0; while [ \$i -lt 5 ]; do printf \"PANE%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new -d -x40 -y14 "sh -c 'exec sleep 100'" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
$TMUX new -d -x40 -y14 || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g window-size manual || exit 1
$TMUX set -g default-terminal "tmux-256color" || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
# --- Generation change: a pane is resized in place. ---
# Two columns; first capture pins the initial divider position. The scene is
# built here and cached.
new_scene
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
compare cache-resize-before
# Move the divider left. resize-pane bumps the generation, so the cached scene
# must be rebuilt and the divider must appear in its new position.
$TMUX2 resize-pane -t0 -L 6 || exit 1
compare cache-resize-after
# --- Generation change: panes are swapped/rotated in place. ---
# Three columns, each pane titled so the order is visible. The pane status line
# makes a swap show up in the captured scene.
new_scene
$TMUX2 setw pane-border-format " #{pane_title} " || exit 1
$TMUX2 setw pane-border-status top || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t:.0 -T AAA || exit 1
$TMUX2 selectp -t:.1 -T BBB || exit 1
$TMUX2 selectp -t:.2 -T CCC || exit 1
$TMUX2 selectp -t:.0 || exit 1
compare cache-rotate-before
# Rotate the panes; the titles must move with them in the rebuilt scene.
# rotate-window keeps the cell geometry, so only its scene invalidation makes
# this differ from cache-rotate-before.
$TMUX2 rotate-window || exit 1
$TMUX2 selectp -t:.0 || exit 1
compare cache-rotate-after
# --- Generation change: two panes swapped in place. ---
# swap-pane likewise moves panes between cells without changing geometry; the
# swapped titles must appear in the rebuilt scene.
new_scene
$TMUX2 setw pane-border-format " #{pane_title} " || exit 1
$TMUX2 setw pane-border-status top || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t:.0 -T LEFT || exit 1
$TMUX2 selectp -t:.1 -T RIGHT || exit 1
$TMUX2 selectp -t:.0 || exit 1
compare cache-swap-before
$TMUX2 swap-pane -d -s:.0 -t:.1 || exit 1
$TMUX2 selectp -t:.0 || exit 1
compare cache-swap-after
# --- Window change: the client switches between two differently laid out
# windows and back. The scene is keyed on the window, so each must show its own
# layout and switching back must not show the other window's cached scene. ---
# Window A: a single pane (no internal border).
$TMUX2 neww -d "sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"WINA%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
$TMUX2 selectw -t:\$ || exit 1
$TMUX2 resizew -x40 -y14 || exit 1
A=$($TMUX2 display -p '#{window_id}') || exit 1
# Window B: a top/bottom split (a horizontal border).
$TMUX2 neww -d "sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"WINB%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
$TMUX2 selectw -t:\$ || exit 1
$TMUX2 resizew -x40 -y14 || exit 1
$TMUX2 splitw -v "$C" || exit 1
B=$($TMUX2 display -p '#{window_id}') || exit 1
$TMUX2 selectw -t$A || exit 1
compare cache-window-a
$TMUX2 selectw -t$B || exit 1
compare cache-window-b
$TMUX2 selectw -t$A || exit 1
compare cache-window-a-again
# --- Size change: the client (and so the scene) is resized. ---
# With window-size latest the inner window tracks the client size, so resizing
# the outer window resizes the inner client and the scene. The cached scene must
# be rebuilt at the new size (the scene->sx/sy mismatch path in
# redraw_get_scene), not drawn at the old size.
$TMUX2 set -g window-size latest || exit 1
$TMUX2 neww -d "sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"SIZE%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
$TMUX2 selectw -t:\$ || exit 1
$TMUX2 splitw -v "$C" || exit 1
compare cache-resizeclient-before
$TMUX resizew -x30 -y10 || exit 1
compare cache-resizeclient-after
exit 0

View File

@@ -0,0 +1,221 @@
#!/bin/sh
# Exercise screen-redraw.c drawing of floating panes: their borders and titles,
# clipping at the window edge, and the interaction with the area outside the
# window (when the window is smaller than the attached client).
#
# Each scene is rendered in an inner tmux attached inside an outer tmux pane.
# The outer pane is captured (the full client scene drawn by screen-redraw.c)
# and compared with a golden file in screen_redraw_results/.
#
# Run with GENERATE=1 to (re)create the golden files.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
RESULTS=screen-redraw-results
TMP=$(mktemp)
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
0 1 15
fail() {
echo "$*" >&2
exit 1
}
compare() {
sleep 1
$TMUX capturep -p $2 >$TMP || exit 1
if [ -n "$GENERATE" ]; then
cp $TMP "$RESULTS/$1.result" || exit 1
echo "generated $1"
else
cmp -s $TMP "$RESULTS/$1.result" || \
fail "scene $1 differs from $RESULTS/$1.result"
fi
}
# new_scene <width> <height>: fresh inner window of the given window size.
new_scene() {
$TMUX2 neww -d "sh -c 'printf base; exec sleep 100'" || exit 1
$TMUX2 selectw -t:\$ || exit 1
$TMUX2 resizew -x$1 -y$2 || exit 1
}
# tile_2x2: split the current window into a 2x2 grid of tiled panes.
tile_2x2() {
C="sh -c 'exec sleep 100'"
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 select-layout tiled || exit 1
}
# fill_base_pattern: make clipped floating panes visibly cover every column.
fill_base_pattern() {
$TMUX2 respawnp -k "sh -c 'i=0; while [ \$i -lt 12 ]; do printf \"%02d:abcdefghijklmnopqrstuvwxyz0123456789\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
}
# fill_all_panes: make each pane visibly nonblank.
fill_all_panes() {
for pane in $($TMUX2 list-panes -F '#{pane_id}'); do
$TMUX2 respawnp -k -t "$pane" "sh -c 'i=0; while [ \$i -lt 5 ]; do printf \"TILE%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
done
}
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new -d -x40 -y12 "sh -c 'printf base; exec sleep 100'" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
$TMUX2 set -g pane-border-format " #{pane_title} " || exit 1
$TMUX new -d -x40 -y12 || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g window-size manual || exit 1
$TMUX set -g default-terminal "tmux-256color" || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
# Basic floating pane, well inside the window.
new_scene 40 12
$TMUX2 new-pane -x20 -y6 -X8 -Y3 "sh -c 'printf FLOAT; exec sleep 100'" || exit 1
compare floating-basic
# Floating pane with a title on its border. pane-border-status also draws the
# base pane's title, so set it explicitly (the default is the hostname, which is
# not stable).
new_scene 40 12
$TMUX2 setw pane-border-status top || exit 1
$TMUX2 selectp -t:.0 -T base || exit 1
$TMUX2 new-pane -x20 -y6 -X8 -Y2 -T title \
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
compare floating-title
# Larger floating pane with double border lines.
new_scene 40 12
$TMUX2 new-pane -x28 -y8 -X4 -Y1 -B double \
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
compare floating-border-double
# Floating pane with no border lines: redraw_mark_pane_borders returns early so
# the float has no border at all, only its (clipped) content over the base pane.
new_scene 40 12
$TMUX2 new-pane -x20 -y6 -X10 -Y3 -B none \
"sh -c 'printf NOBORDER; exec sleep 100'" || exit 1
compare floating-noborder
# Floating pane positioned past the right and bottom edges: must clip.
new_scene 40 12
$TMUX2 new-pane -x20 -y6 -X30 -Y8 "sh -c 'printf CLIP; exec sleep 100'" || exit 1
compare floating-clip-edge
# Window smaller than the client (outside area filled), with a floating pane
# that overlaps the boundary into the outside region.
new_scene 28 8
$TMUX2 new-pane -x16 -y5 -X18 -Y4 "sh -c 'printf OUT; exec sleep 100'" || exit 1
compare floating-outside
# Floating pane clipped at the top-left corner (negative offsets).
new_scene 40 12
$TMUX2 new-pane -x18 -y6 -X-4 -Y-2 "sh -c 'printf TL; exec sleep 100'" || exit 1
compare floating-clip-topleft
# Floating panes clipped at each edge. The patterned base pane makes sure the
# clipped edge still obscures both content and borders underneath.
new_scene 40 12
fill_base_pattern
$TMUX2 new-pane -x20 -y6 -X-10 -Y3 "sh -c 'printf LEFT; exec sleep 100'" || exit 1
compare floating-clip-left
new_scene 40 12
fill_base_pattern
$TMUX2 new-pane -x20 -y6 -X30 -Y3 "sh -c 'printf RIGHT; exec sleep 100'" || exit 1
compare floating-clip-right
new_scene 40 12
fill_base_pattern
$TMUX2 new-pane -x20 -y6 -X10 -Y-3 "sh -c 'printf TOP; exec sleep 100'" || exit 1
compare floating-clip-top
new_scene 40 12
fill_base_pattern
$TMUX2 new-pane -x20 -y6 -X10 -Y9 "sh -c 'printf BOTTOM; exec sleep 100'" || exit 1
compare floating-clip-bottom
# Floating pane over a tiled 2x2 grid: the float draws a complete box and must
# NOT merge its borders with the tiled pane borders underneath.
new_scene 40 12
tile_2x2
fill_all_panes
$TMUX2 new-pane -x16 -y6 -X11 -Y3 "sh -c 'printf FLT; exec sleep 100'" || exit 1
compare floating-over-tiled
# Same, but the float uses double border lines while the tiled panes use single:
# the two border styles must coexist without merging.
new_scene 40 12
tile_2x2
fill_all_panes
$TMUX2 new-pane -x16 -y6 -X11 -Y3 -B double \
"sh -c 'printf FLT; exec sleep 100'" || exit 1
compare floating-over-tiled-double
# Two overlapping floating panes: the later (top) float draws over the earlier.
new_scene 40 12
$TMUX2 new-pane -x16 -y6 -X4 -Y2 "sh -c 'printf AAA; exec sleep 100'" || exit 1
$TMUX2 new-pane -x16 -y6 -X14 -Y6 "sh -c 'printf BBB; exec sleep 100'" || exit 1
compare floating-overlap
# Two floating panes with different per-pane configuration: one has its status on
# top with single borders, the other has its status on the bottom with heavy
# borders (pane-border-status and pane-border-lines are per-pane options).
new_scene 40 12
$TMUX2 setw pane-border-format " #{pane_title} " || exit 1
$TMUX2 new-pane -x16 -y4 -X3 -Y1 -T one \
"sh -c 'printf ONE; exec sleep 100'" || exit 1
$TMUX2 set -p pane-border-status top || exit 1
$TMUX2 set -p pane-border-lines single || exit 1
$TMUX2 new-pane -x16 -y4 -X18 -Y6 -T two \
"sh -c 'printf TWO; exec sleep 100'" || exit 1
$TMUX2 set -p pane-border-status bottom || exit 1
$TMUX2 set -p pane-border-lines heavy || exit 1
compare floating-mixed-config
# Floating pane over a pane that has a scrollbar: the float must draw over the
# scrollbar. Captured with -e since the scrollbar is drawn with styles.
new_scene 40 12
$TMUX2 setw pane-scrollbars on || exit 1
$TMUX2 setw pane-scrollbars-style "bg=black,fg=white,width=1,pad=0" || exit 1
$TMUX2 new-pane -x16 -y6 -X22 -Y3 "sh -c 'printf OVERSB; exec sleep 100'" || exit 1
compare floating-over-scrollbar -e
# Floating pane overlapping the client status line: the status line is not part
# of the window scene, so the float is clipped at the window's bottom edge.
new_scene 40 11
$TMUX2 set status on || exit 1
$TMUX2 set status-position bottom || exit 1
$TMUX2 set status-format[0] "" || exit 1
$TMUX2 new-pane -x20 -y6 -X8 -Y7 "sh -c 'printf OVERST; exec sleep 100'" || exit 1
compare floating-over-status
# A window left with only a floating pane: killing the single tiled pane removes
# it from the layout, so the area it occupied is no longer owned by any pane and
# is drawn as EMPTY cells (middle dots) around the float. This is the only way to
# produce a REDRAW_SPAN_EMPTY span.
new_scene 40 12
$TMUX2 new-pane -x20 -y6 -X8 -Y3 "sh -c 'printf FLOAT; exec sleep 100'" || exit 1
tiled=$($TMUX2 list-panes -F '#{pane_floating_flag} #{pane_id}' | \
awk '$1==0{print $2; exit}') || exit 1
$TMUX2 kill-pane -t "$tiled" || exit 1
compare floating-empty
exit 0

View File

@@ -0,0 +1,212 @@
#!/bin/sh
# Exercise screen-redraw.c pane-border-indicators: the arrow indicators that
# point at the active pane, and the two-pane border colour split.
#
# Arrows are drawn as glyphs (captured plain). The two-pane colour split is
# drawn with styles, so that scene is captured with escapes (-e) and uses
# distinct active/inactive border styles so the split is visible.
#
# Each scene is rendered in an inner tmux attached inside an outer tmux pane.
# The outer pane is captured and compared with a golden in screen-redraw-results/.
#
# Run with GENERATE=1 to (re)create the golden files.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
RESULTS=screen-redraw-results
TMP=$(mktemp)
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
0 1 15
fail() {
echo "$*" >&2
exit 1
}
# compare <name> [-e]: capture the outer pane and compare (or generate).
compare() {
sleep 1
$TMUX capturep -p $2 >$TMP || exit 1
if [ -n "$GENERATE" ]; then
cp $TMP "$RESULTS/$1.result" || exit 1
echo "generated $1"
else
cmp -s $TMP "$RESULTS/$1.result" || \
fail "scene $1 differs from $RESULTS/$1.result"
fi
}
new_scene() {
$TMUX2 neww -d "sh -c 'exec sleep 100'" || exit 1
$TMUX2 selectw -t:\$ || exit 1
$TMUX2 resizew -x40 -y12 || exit 1
}
C="sh -c 'exec sleep 100'"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new -d -x40 -y12 "sh -c 'exec sleep 100'" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
$TMUX new -d -x40 -y12 || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g window-size manual || exit 1
$TMUX set -g default-terminal "tmux-256color" || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
# --- Arrows: must appear for whichever pane is active (GitHub #4780). ---
$TMUX2 set -g pane-border-indicators arrows || exit 1
# Two panes, left active: arrow points left.
new_scene
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
compare arrows-2pane-left
# Two panes, right active: arrow points right (the case that regressed).
new_scene
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t1 || exit 1
compare arrows-2pane-right
# Three columns, middle active.
new_scene
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t1 || exit 1
compare arrows-3pane
# Four panes (2x2), one active.
new_scene
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 select-layout tiled || exit 1
$TMUX2 selectp -t0 || exit 1
compare arrows-4pane
# --- Two-pane border colour split. ---
# Distinct active/inactive styles so the coloured halves are visible; captured
# with -e to record the SGR.
$TMUX2 set -g pane-border-indicators colour || exit 1
# Left/right split: the border is vertical and is split into a top half (active
# pane colour) and a bottom half (the LAYOUT_LEFTRIGHT case).
new_scene
$TMUX2 setw pane-active-border-style fg=red || exit 1
$TMUX2 setw pane-border-style fg=green || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
compare two-pane-colour-vertical -e
# Top/bottom split: the border is horizontal and is split into a left half and a
# right half instead (the LAYOUT_TOPBOTTOM case).
new_scene
$TMUX2 setw pane-active-border-style fg=red || exit 1
$TMUX2 setw pane-border-style fg=green || exit 1
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
compare two-pane-colour-horizontal -e
# The colour split only applies with exactly two tiled panes. With three panes
# the split is suppressed: the whole border uses the active pane's colour and
# there is no coloured half (redraw_check_two_pane_colours returns 0).
new_scene
$TMUX2 setw pane-active-border-style fg=red || exit 1
$TMUX2 setw pane-border-style fg=green || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
compare colour-three-suppressed -e
# A floating pane is ignored by the two-pane count, so two tiled panes plus a
# float still split. The float itself is never coloured by the indicator.
new_scene
$TMUX2 setw pane-active-border-style fg=red || exit 1
$TMUX2 setw pane-border-style fg=green || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 new-pane -x16 -y6 -X10 -Y2 "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
compare colour-two-plus-float -e
# --- Both indicators together. ---
# pane-border-indicators both enables the arrow and the colour split at once
# (it satisfies both the arrow and the colour branches). Two panes so the colour
# split also applies.
$TMUX2 set -g pane-border-indicators both || exit 1
new_scene
$TMUX2 setw pane-active-border-style fg=red || exit 1
$TMUX2 setw pane-border-style fg=green || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
compare indicators-both -e
# --- Marked pane. ---
# A marked pane (select-pane -m) has its border drawn reversed. Captured with -e
# to record the reverse attribute. The marked pane is made non-active so its
# reversed border is distinct from the active pane.
$TMUX2 set -g pane-border-indicators off || exit 1
# Left/right split, right pane marked.
new_scene
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 selectp -t1 -m || exit 1
compare marked-pane-lr -e
# Top/bottom split, bottom pane marked.
new_scene
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 selectp -t1 -m || exit 1
compare marked-pane-tb -e
# Three columns, middle pane marked (reversed border on both sides).
new_scene
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 selectp -t1 -m || exit 1
compare marked-pane-three -e
# Floating pane marked: the whole floating box border is reversed.
new_scene
$TMUX2 new-pane -x20 -y6 -X8 -Y3 "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 selectp -t1 -m || exit 1
compare marked-pane-float -e
# Marked pane together with a pane status line: the title border is reversed too.
new_scene
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 setw pane-border-format " #{pane_index}:#{pane_title} " || exit 1
$TMUX2 setw pane-border-status top || exit 1
$TMUX2 selectp -t:.0 -T left || exit 1
$TMUX2 selectp -t:.1 -T right || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 selectp -t1 -m || exit 1
compare marked-pane-status -e
exit 0

View File

@@ -0,0 +1,116 @@
#!/bin/sh
# Exercise screen-redraw.c when the window is smaller than the attached client,
# so part of the client is outside the window. screen-redraw.c fills the outside
# area and draws a real border along the window's right and/or bottom edge (not
# just where panes meet). This checks redraw_get_window_offset and the OUTSIDE
# span handling, including how the window-edge border joins the pane borders.
#
# Each scene is rendered in an inner tmux attached inside an outer tmux pane.
# The outer client is 40x14; the inner window is made smaller with resizew. The
# outer pane is captured and compared with a golden in screen-redraw-results/.
#
# Run with GENERATE=1 to (re)create the golden files.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
RESULTS=screen-redraw-results
TMP=$(mktemp)
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
0 1 15
fail() {
echo "$*" >&2
exit 1
}
compare() {
sleep 1
$TMUX capturep -p >$TMP || exit 1
if [ -n "$GENERATE" ]; then
cp $TMP "$RESULTS/$1.result" || exit 1
echo "generated $1"
else
cmp -s $TMP "$RESULTS/$1.result" || \
fail "scene $1 differs from $RESULTS/$1.result"
fi
}
# new_scene <width> <height>: fresh inner window smaller than the 40x14 client.
new_scene() {
$TMUX2 neww -d "sh -c 'i=0; while [ \$i -lt $(($2 - 1)) ]; do printf \"OUT%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
$TMUX2 selectw -t:\$ || exit 1
$TMUX2 resizew -x$1 -y$2 || exit 1
}
C="sh -c 'i=0; while [ \$i -lt 8 ]; do printf \"PAN%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new -d -x40 -y14 "sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"OUT%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
$TMUX new -d -x40 -y14 || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g window-size manual || exit 1
$TMUX set -g default-terminal "tmux-256color" || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
# Single pane, narrower than the client: a border along the right edge.
new_scene 28 14
compare outside-right-single
# Narrower than the client with a left/right split: the inter-pane border plus a
# real border on the window's right edge, then the outside area.
new_scene 28 14
$TMUX2 splitw -h "$C" || exit 1
compare outside-right-split
# Shorter than the client with a top/bottom split: a real border on the window's
# bottom edge.
new_scene 40 9
$TMUX2 splitw -v "$C" || exit 1
compare outside-bottom-split
# Smaller in both dimensions with a 2x2 grid: borders on the right and bottom
# edges meeting the internal pane borders at the corner.
new_scene 28 9
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 select-layout tiled || exit 1
compare outside-both-2x2
# Window BIGGER than the client: only part of the window is viewed and the view
# can be panned (refresh-client). This exercises a non-zero scene offset.
# A 2x2 grid in a 60x20 window viewed through the 40x14 client.
new_scene 60 20
$TMUX2 splitw -h "$C" || exit 1
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 selectp -t0 || exit 1
$TMUX2 splitw -v "$C" || exit 1
$TMUX2 select-layout tiled || exit 1
# Default view: the top-left of the window.
$TMUX2 refresh-client -U 100 || exit 1
$TMUX2 refresh-client -L 100 || exit 1
compare bigger-topleft
# Panned to the bottom-right of the window (down and right to the limit).
$TMUX2 refresh-client -D 100 || exit 1
$TMUX2 refresh-client -R 100 || exit 1
compare bigger-bottomright
exit 0

View File

@@ -0,0 +1,92 @@
#!/bin/sh
# Exercise drawing of popups (display-popup) over the window scene. A popup is an
# overlay drawn on top of the redraw scene (the overlay_draw path in
# screen-redraw.c), so this guards against regressions in how popups appear.
#
# A popup is modal and stays open until its command exits, so each scene fully
# re-creates the servers and re-attaches; the popup is opened in the background
# (display-popup blocks the client that runs it) and the outer pane is captured
# while it is open.
#
# Run with GENERATE=1 to (re)create the golden files.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
RESULTS=screen-redraw-results
TMP=$(mktemp)
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
0 1 15
fail() {
echo "$*" >&2
exit 1
}
compare() {
sleep 1
$TMUX capturep -p >$TMP || exit 1
if [ -n "$GENERATE" ]; then
cp $TMP "$RESULTS/$1.result" || exit 1
echo "generated $1"
else
cmp -s $TMP "$RESULTS/$1.result" || \
fail "scene $1 differs from $RESULTS/$1.result"
fi
}
C="sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"POP%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'"
# setup: fresh inner window attached inside a fresh outer pane, 40x14.
setup() {
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new -d -x40 -y14 "$C" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
$TMUX2 resizew -x40 -y14 || exit 1
$TMUX new -d -x40 -y14 || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g window-size manual || exit 1
$TMUX set -g default-terminal "tmux-256color" || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
}
# popup <args>: open a popup running a fixed command, in the background (it stays
# open because the command sleeps; the servers are killed at the next setup).
popup() {
$TMUX2 display-popup "$@" -E "sh -c 'printf POPUP; exec sleep 100'" &
sleep 1
}
# Basic popup over a single pane.
setup
popup -w20 -h6 -x6 -y3
compare popup-basic
# Popup over a split: drawn on top of the pane border.
setup
$TMUX2 splitw -h "$C" || exit 1
popup -w24 -h8 -x8 -y3
compare popup-over-split
# Popup with no border lines (-B).
setup
popup -B -w20 -h6 -x6 -y3
compare popup-noborder
# Popup with double border lines.
setup
popup -b double -w20 -h6 -x6 -y3
compare popup-double
exit 0

View File

@@ -0,0 +1,12 @@

View File

@@ -0,0 +1,12 @@

View File

@@ -0,0 +1,12 @@
│ │
→ ←
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │

View File

@@ -0,0 +1,12 @@
─↑─────────────────┼────────────────────

View File

@@ -0,0 +1,12 @@
BIDI-LEFT 12345
BIDI-RIGHT 67890

View File

@@ -0,0 +1,12 @@
BIDI-LEFT 12345 BIDI-PANE 12345
BIDI-RIGHT 67890 BIDI-PANE 67890
⁩│⁦
⁩│⁦
⁩│⁦
⁩│⁦
⁩│⁦
⁩│⁦
⁩│⁦
⁩│⁦
⁩│⁦
⁩│⁦

View File

@@ -0,0 +1,14 @@
│PAN06 abcdefghij
│PAN07 abcdefghij
─────────┼──────────────────────────────
│PAN00 abcdefghij
│PAN01 abcdefghij
│PAN02 abcdefghij
│PAN03 abcdefghij
│PAN04 abcdefghij
│PAN05 abcdefghij
│PAN06 abcdefghij
│PAN07 abcdefghij

View File

@@ -0,0 +1,14 @@
OUT11 abcdefghij │PAN00 abcd
OUT12 abcdefghij │PAN01 abcd
OUT13 abcdefghij │PAN02 abcd
OUT14 abcdefghij │PAN03 abcd
OUT15 abcdefghij │PAN04 abcd
OUT16 abcdefghij │PAN05 abcd
OUT17 abcdefghij │PAN06 abcd
OUT18 abcdefghij │PAN07 abcd
─────────────────────────────┼──────────
PAN00 abcdefghij │PAN00 abcd
PAN01 abcdefghij │PAN01 abcd
PAN02 abcdefghij │PAN02 abcd
PAN03 abcdefghij │PAN03 abcd

View File

@@ -0,0 +1,14 @@
hij │PANE00 abcdefghij
BASE07 abcdefg│PANE01 abcdefghij
hij │PANE02 abcdefghij
BASE08 abcdefg│PANE03 abcdefghij
hij │PANE04 abcdefghij
BASE09 abcdefg│
hij │
BASE10 abcdefg│
hij │
BASE11 abcdefg│
hij │
BASE12 abcdefg│
hij │

View File

@@ -0,0 +1,14 @@
BASE00 abcdefghij │PANE00 abcdefghij
BASE01 abcdefghij │PANE01 abcdefghij
BASE02 abcdefghij │PANE02 abcdefghij
BASE03 abcdefghij │PANE03 abcdefghij
BASE04 abcdefghij │PANE04 abcdefghij
BASE05 abcdefghij │
BASE06 abcdefghij │
BASE07 abcdefghij │
BASE08 abcdefghij │
BASE09 abcdefghij │
BASE10 abcdefghij │
BASE11 abcdefghij │
BASE12 abcdefghij │

View File

@@ -0,0 +1,10 @@
SIZE09 abcdefghij
SIZE10 abcdefghij
SIZE11 abcdefghij
SIZE12 abcdefghij
──────────────────────────────
PANE02 abcdefghij
PANE03 abcdefghij
PANE04 abcdefghij

View File

@@ -0,0 +1,14 @@
SIZE07 abcdefghij
SIZE08 abcdefghij
SIZE09 abcdefghij
SIZE10 abcdefghij
SIZE11 abcdefghij
SIZE12 abcdefghij
────────────────────────────────────────
PANE00 abcdefghij
PANE01 abcdefghij
PANE02 abcdefghij
PANE03 abcdefghij
PANE04 abcdefghij

View File

@@ -0,0 +1,14 @@
── BBB ───┬── CCC ──┬── AAA ────────────
PANE00 abc│cdefghij │BASE01 abcdefghij
defghij │PANE03 ab│BASE02 abcdefghij
PANE01 abc│cdefghij │BASE03 abcdefghij
defghij │PANE04 ab│BASE04 abcdefghij
PANE02 abc│cdefghij │BASE05 abcdefghij
defghij │ │BASE06 abcdefghij
PANE03 abc│ │BASE07 abcdefghij
defghij │ │BASE08 abcdefghij
PANE04 abc│ │BASE09 abcdefghij
defghij │ │BASE10 abcdefghij
│ │BASE11 abcdefghij
│ │BASE12 abcdefghij
│ │

View File

@@ -0,0 +1,14 @@
── AAA ───┬── BBB ──┬── CCC ────────────
BASE07 abc│PANE00 ab│PANE00 abcdefghij
defghij │cdefghij │PANE01 abcdefghij
BASE08 abc│PANE01 ab│PANE02 abcdefghij
defghij │cdefghij │PANE03 abcdefghij
BASE09 abc│PANE02 ab│PANE04 abcdefghij
defghij │cdefghij │
BASE10 abc│PANE03 ab│
defghij │cdefghij │
BASE11 abc│PANE04 ab│
defghij │cdefghij │
BASE12 abc│ │
defghij │ │
│ │

View File

@@ -0,0 +1,14 @@
── RIGHT ───────────┬── LEFT ───────────
PANE00 abcdefghij │BASE01 abcdefghij
PANE01 abcdefghij │BASE02 abcdefghij
PANE02 abcdefghij │BASE03 abcdefghij
PANE03 abcdefghij │BASE04 abcdefghij
PANE04 abcdefghij │BASE05 abcdefghij
│BASE06 abcdefghij
│BASE07 abcdefghij
│BASE08 abcdefghij
│BASE09 abcdefghij
│BASE10 abcdefghij
│BASE11 abcdefghij
│BASE12 abcdefghij

View File

@@ -0,0 +1,14 @@
── LEFT ────────────┬── RIGHT ──────────
BASE01 abcdefghij │PANE00 abcdefghij
BASE02 abcdefghij │PANE01 abcdefghij
BASE03 abcdefghij │PANE02 abcdefghij
BASE04 abcdefghij │PANE03 abcdefghij
BASE05 abcdefghij │PANE04 abcdefghij
BASE06 abcdefghij │
BASE07 abcdefghij │
BASE08 abcdefghij │
BASE09 abcdefghij │
BASE10 abcdefghij │
BASE11 abcdefghij │
BASE12 abcdefghij │

View File

@@ -0,0 +1,14 @@
WINA00 abcdefghij
WINA01 abcdefghij
WINA02 abcdefghij
WINA03 abcdefghij
WINA04 abcdefghij
WINA05 abcdefghij
WINA06 abcdefghij
WINA07 abcdefghij
WINA08 abcdefghij
WINA09 abcdefghij
WINA10 abcdefghij
WINA11 abcdefghij
WINA12 abcdefghij

View File

@@ -0,0 +1,14 @@
WINA00 abcdefghij
WINA01 abcdefghij
WINA02 abcdefghij
WINA03 abcdefghij
WINA04 abcdefghij
WINA05 abcdefghij
WINA06 abcdefghij
WINA07 abcdefghij
WINA08 abcdefghij
WINA09 abcdefghij
WINA10 abcdefghij
WINA11 abcdefghij
WINA12 abcdefghij

View File

@@ -0,0 +1,14 @@
WINB07 abcdefghij
WINB08 abcdefghij
WINB09 abcdefghij
WINB10 abcdefghij
WINB11 abcdefghij
WINB12 abcdefghij
────────────────────────────────────────
PANE00 abcdefghij
PANE01 abcdefghij
PANE02 abcdefghij
PANE03 abcdefghij
PANE04 abcdefghij

View File

@@ -0,0 +1,12 @@
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │

View File

@@ -0,0 +1,12 @@
│
┌────────────────┐
│ │
│ │
│ │
│ │
│ │
│ │
└────────────────┘
│
│
│

View File

@@ -0,0 +1,14 @@
═══════════════════╬════════════════════

View File

@@ -0,0 +1,14 @@
━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━

View File

@@ -0,0 +1,14 @@

View File

@@ -0,0 +1,14 @@
1
1
1
1
1
1
0000000000000000000011111111111111111111
2
2
2
2
2
2
2

View File

@@ -0,0 +1,14 @@
|
|
|
|
|
|
-------------------+--------------------
|
|
|
|
|
|
|

View File

@@ -0,0 +1,14 @@
───────────────────┼────────────────────

View File

@@ -0,0 +1,14 @@

View File

@@ -0,0 +1,12 @@
base
┌────────────────────┐
│FLOAT │
│ │
│ │
│ │
│ │
│ │
└────────────────────┘

View File

@@ -0,0 +1,12 @@
bas╔════════════════════════════╗
║FLOAT ║
║ ║
║ ║
║ ║
║ ║
║ ║
║ ║
║ ║
╚════════════════════════════╝

View File

@@ -0,0 +1,12 @@
01:abcdefghijklmnopqrstuvwxyz0123456789
02:abcdefghijklmnopqrstuvwxyz0123456789
03:abcdefghijklmnopqrstuvwxyz0123456789
04:abcdefghijklmnopqrstuvwxyz0123456789
05:abcdefghijklmnopqrstuvwxyz0123456789
06:abcdefghijklmnopqrstuvwxyz0123456789
07:abcdefghijklmnopqrstuvwxyz0123456789
08:abcdefghijklmnopqrstuvwxyz0123456789
09:abcdef┌────────────────────┐23456789
10:abcdef│BOTTOM │23456789
11:abcdef│ │23456789
│ │

View File

@@ -0,0 +1,12 @@
base
┌──────────
│CLIP

View File

@@ -0,0 +1,12 @@
01:abcdefghijklmnopqrstuvwxyz0123456789
02:abcdefghijklmnopqrstuvwxyz0123456789
──────────┐ijklmnopqrstuvwxyz0123456789
│ijklmnopqrstuvwxyz0123456789
│ijklmnopqrstuvwxyz0123456789
│ijklmnopqrstuvwxyz0123456789
│ijklmnopqrstuvwxyz0123456789
│ijklmnopqrstuvwxyz0123456789
│ijklmnopqrstuvwxyz0123456789
──────────┘ijklmnopqrstuvwxyz0123456789
11:abcdefghijklmnopqrstuvwxyz0123456789

View File

@@ -0,0 +1,12 @@
01:abcdefghijklmnopqrstuvwxyz0123456789
02:abcdefghijklmnopqrstuvwxyz0123456789
03:abcdefghijklmnopqrstuvwxyz┌──────────
04:abcdefghijklmnopqrstuvwxyz│RIGHT
05:abcdefghijklmnopqrstuvwxyz│
06:abcdefghijklmnopqrstuvwxyz│
07:abcdefghijklmnopqrstuvwxyz│
08:abcdefghijklmnopqrstuvwxyz│
09:abcdefghijklmnopqrstuvwxyz│
10:abcdefghijklmnopqrstuvwxyz└──────────
11:abcdefghijklmnopqrstuvwxyz0123456789

View File

@@ -0,0 +1,12 @@
01:abcdef│ │23456789
02:abcdef│ │23456789
03:abcdef│ │23456789
04:abcdef└────────────────────┘23456789
05:abcdefghijklmnopqrstuvwxyz0123456789
06:abcdefghijklmnopqrstuvwxyz0123456789
07:abcdefghijklmnopqrstuvwxyz0123456789
08:abcdefghijklmnopqrstuvwxyz0123456789
09:abcdefghijklmnopqrstuvwxyz0123456789
10:abcdefghijklmnopqrstuvwxyz0123456789
11:abcdefghijklmnopqrstuvwxyz0123456789

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