Compare commits

...

652 Commits

Author SHA1 Message Date
Dane Jensen
4a91d5601e Merge branch 'master' into floating_panes 2026-07-02 12:10:44 -07:00
Thomas Adam
63a8af0e15 Merge branch 'obsd-master' 2026-07-02 12:00:06 +01:00
nicm
6b9b6c0b0b Copy UTF-8 data correctly. 2026-07-02 10:34:14 +00:00
Nicholas Marriott
390805d8b9 Make output nicer for tests. 2026-07-02 10:46:32 +01:00
Nicholas Marriott
70f37ef815 No vis.h in portable. 2026-07-02 10:28:48 +01:00
Nicholas Marriott
b6fca55447 Add tests for input path. 2026-07-02 10:26:25 +01:00
Thomas Adam
e22c56f02f Merge branch 'obsd-master' 2026-07-02 10:15:06 +01:00
Nicholas Marriott
01c86e64b7 No x flag. 2026-07-02 10:17:04 +01:00
Thomas Adam
ca81c36383 Merge branch 'obsd-master' 2026-07-02 10:00:06 +01:00
Nicholas Marriott
a9a04c7bc0 Targets tests. 2026-07-02 10:03:00 +01:00
nicm
0a81f4d742 Add a raw grid output to capture-pane for debugging and regression
tests.
2026-07-02 08:51:05 +00:00
nicm
0ad5035457 Do not crash if no client. 2026-07-02 08:47:25 +00:00
Nicholas Marriott
d626cef6de Environment tests. 2026-07-02 09:01:49 +01:00
Nicholas Marriott
7713cbebd2 Add options tests. 2026-07-02 08:15:22 +01:00
Nicholas Marriott
baf16f0419 Format tests. 2026-07-02 07:36:26 +01:00
Nicholas Marriott
9dcc2da348 Add format modifiers test. 2026-07-02 07:36:26 +01:00
Dane Jensen
7de5eb1254 Merge branch 'master' into floating_panes 2026-07-01 14:37:05 -07:00
Thomas Adam
f50a38de23 Merge branch 'obsd-master' 2026-07-01 22:15:06 +01:00
nicm
3ef51a469c move-pane needs -D. 2026-07-01 20:53:38 +00:00
Thomas Adam
d5688bf165 Merge branch 'obsd-master' 2026-07-01 21:30:06 +01:00
nicm
8dc9e24c74 Revert previous, it is not the right change. 2026-07-01 20:26:39 +00:00
nicm
735bbf9da7 Disable time expansion if expanding an inner format or more %s are
needed.
2026-07-01 20:18:05 +00:00
Nicholas Marriott
6ad86ebdf1 Merge branch 'release_3.7b' 2026-07-01 20:06:23 +01:00
Nicholas Marriott
3d58e04c93 no point in having stuff i cannot test 2026-07-01 19:05:24 +01:00
Nicholas Marriott
9fb67a2cdd Missing \. 2026-07-01 19:00:37 +01:00
Nicholas Marriott
d9d12c5c43 More tweaks. 2026-07-01 18:59:34 +01:00
Nicholas Marriott
728ad24d4a utf8proc. 2026-07-01 18:55:28 +01:00
Nicholas Marriott
5b37471db7 Spaces. 2026-07-01 18:53:37 +01:00
Nicholas Marriott
c41e98be3c yaml is poo. 2026-07-01 18:53:16 +01:00
Nicholas Marriott
35f429cb6d macOS too. 2026-07-01 18:52:47 +01:00
Nicholas Marriott
85fc0ea57c Add a test for regress. 2026-07-01 18:43:59 +01:00
Nicholas Marriott
6f588402c3 Remove a char which differs between platforms. 2026-07-01 18:43:01 +01:00
Nicholas Marriott
fada35478b Colours changed in tests. 2026-07-01 18:30:55 +01:00
Nicholas Marriott
bd5d7a8a7a Update prompt test. 2026-07-01 18:15:40 +01:00
Nicholas Marriott
8108356978 Update names test. 2026-07-01 18:02:45 +01:00
Nicholas Marriott
5e32eddb8c Fix up merge. 2026-07-01 18:00:15 +01:00
Thomas Adam
1818b6ac16 Merge branch 'obsd-master' 2026-07-01 17:52:07 +01:00
Nicholas Marriott
e802909de0 Fix so that the end of a synchronized update again triggers a redraw. 2026-07-01 17:49:40 +01:00
nicm
824ef37f5f Change layout sets to build the layout using the new mechanics, from
Dane Jensen.
2026-07-01 16:43:14 +00:00
nicm
565db46a19 Instead of fully redrawing the pane when sync ends, only redraw lines
that have been changed.
2026-07-01 15:44:13 +00:00
Thomas Adam
87aa44f4bc Merge branch 'obsd-master' 2026-07-01 14:30:07 +01:00
nicm
e9396e59d9 Add a couple of format c/f and c/b to emit colours. 2026-07-01 13:12:17 +00:00
Thomas Adam
002ac977f8 Merge branch 'obsd-master' 2026-07-01 09:15:07 +01:00
nicm
724f85d275 Do not draw to tty if PANE_REDRAW has been set since it is just going to
be replaced.
2026-07-01 07:48:48 +00:00
Thomas Adam
b3fa1c0b9c Merge branch 'obsd-master' 2026-07-01 07:30:06 +01:00
nicm
e69950a9ca Add overline for ghostty also, GitHub issue 5309. 2026-07-01 06:17:58 +00:00
Thomas Adam
41b4743eba Merge branch 'obsd-master' 2026-07-01 00:00:06 +01:00
nicm
1e9a16a748 And some other indentation. 2026-06-30 22:48:12 +00:00
nicm
74a5069c0a Fix some indentation. 2026-06-30 22:44:21 +00:00
Thomas Adam
8d470017bc Merge branch 'obsd-master' 2026-06-30 22:00:06 +01:00
Nicholas Marriott
928c7a9771 More checks for macOS. 2026-06-30 21:28:28 +01:00
nicm
eda7e563d0 Change a malloc to calloc. 2026-06-30 20:27:36 +00:00
Thomas Adam
90fc51542f Merge branch 'obsd-master' 2026-06-30 11:30:06 +01:00
nicm
f8674cc993 Add default features for Ghostty, GitHub issue 5305 from Glib Shpychka. 2026-06-30 10:07:05 +00:00
Thomas Adam
843adbf454 Merge branch 'obsd-master' 2026-06-30 10:30:06 +01:00
nicm
abefc3f705 Redraw when sync stops again (accidentally turned off), from Japin Li in
GitHub issue 5304.
2026-06-30 09:20:43 +00:00
Thomas Adam
dddd7b44c8 Merge branch 'obsd-master' 2026-06-30 08:15:06 +01:00
Nicholas Marriott
25c874c489 Merge branch 'release_3.7a' 2026-06-30 07:46:06 +01:00
Nicholas Marriott
0e418b62d2 Remove C-r from help. 2026-06-30 07:44:51 +01:00
nicm
a97f643bd3 Remove C-r from help since it does not in fact exist. 2026-06-30 06:44:46 +00:00
Nicholas Marriott
78a2145a47 Update CHANGES. 2026-06-30 07:42:25 +01:00
Nicholas Marriott
dbe50934b1 Add caching of scrollbar options to 3.7a branch. 2026-06-30 07:41:02 +01:00
Dane Jensen
9e0ca948bf Fixed merge errors. 2026-06-29 15:34:45 -07:00
Dane Jensen
8e070f1a34 Merge branch 'master' into floating_panes 2026-06-29 15:25:54 -07:00
Thomas Adam
543d104f85 Merge branch 'obsd-master' 2026-06-29 20:15:07 +01:00
nicm
e06207c914 Cache scrollbar options in window to avoid a slow lookup when working
out visible ranges.
2026-06-29 19:03:34 +00:00
nicm
166267c87a Allow :. in names again, forbidding them is overly pernickety. Only
forbid invalid UTF-8 and #(.
2026-06-29 19:53:42 +01:00
Thomas Adam
91e0737b8a Merge branch 'obsd-master' 2026-06-29 19:30:07 +01:00
nicm
65a032b205 Allow :. in names again, forbidding them is overly pernickety. Only
forbid invalid UTF-8 and #(.
2026-06-29 18:17:28 +00:00
Thomas Adam
80c7dcdbd7 Merge branch 'obsd-master' 2026-06-29 18:30:06 +01:00
nicm
bcd54d12d8 Add link=/nolink to styles to specific a hyperlink. GitHub issue 4280
from Moritz Angermann.
2026-06-29 17:08:52 +00:00
Thomas Adam
726e19a886 Merge branch 'obsd-master' 2026-06-29 18:00:06 +01:00
nicm
95719e342d Limit hyperlink URIs to 1024 bytes which seems enough and allows us not
to have to worry about gigantic URIs in styles, part of a change from
Moritz Angermann.
2026-06-29 16:44:06 +00:00
nicm
132a63d1da Allow empty window and session names. 2026-06-29 17:36:40 +01:00
Thomas Adam
822f700b15 Merge branch 'obsd-master' 2026-06-29 17:30:06 +01:00
nicm
5df51a06f5 Allow empty window and session names. 2026-06-29 16:20:45 +00:00
Nicholas Marriott
ad74c5ed7f Add additional grid consistency checks for macOS. 2026-06-29 10:20:38 +01:00
Thomas Adam
d5284be91d Merge branch 'obsd-master' 2026-06-29 09:00:09 +01:00
nicm
c29b41e527 Make pane scrollbars able to auto-hide after a short timeout. This
replaces the previous "modal" behaviour where the pane would be resized,
which tended to make a mess. Instead, the scrollbar appears when
scrolling or when hovered over with the mouse and disappears a
(configurable) short period later. From Michael Grant.
2026-06-29 07:45:09 +00:00
nicm
4e612612dc Only forbid #( in names and titles (styles are #[ and are useful). 2026-06-29 08:11:08 +01:00
Thomas Adam
705fbf92ee Merge branch 'obsd-master' 2026-06-28 21:49:05 +01:00
Nicholas Marriott
2dc704abb6 Always set SIXEL aspect ratio to 1:1, GitHub issue 5291 from James Holderness. 2026-06-28 17:01:52 +01:00
Nicholas Marriott
6c2ef75681 Bump version. 2026-06-28 16:54:51 +01:00
nicm
96899dc527 Only forbid #( in names and titles (styles are #[ and are useful). 2026-06-28 15:53:18 +00:00
Your Name
b0db912bde Update check-names test. 2026-06-28 16:52:28 +01:00
nicm
7cd33ce0dd Set view name immediately when entering mode. 2026-06-27 12:37:36 +00:00
nicm
e0dd40f812 Clear lines before writing in copy mode to avoid leaving stray text when
new line is shorter than old.
2026-06-27 12:27:43 +00:00
nicm
79f0f97495 Make split-window create a new floating pane if used in a floating pane,
rather than just complaining. Also improve documentation. From Dane
Jensen.
2026-06-27 10:23:14 +00:00
nicm
5e0b0a2aa3 Pulled code out of layout_split_pane for reuse and added
layout_insert_tile. From Dane Jensen.
2026-06-27 10:21:26 +00:00
nicm
e24969b422 Add a helper to swap a node into the place of a leaf, from Dane Jensen. 2026-06-27 10:18:32 +00:00
nicm
dd02989e88 Invalidate scene when changing window Z index, from Dane Jensen. 2026-06-27 10:16:15 +00:00
nicm
7383c04205 Preseve prompt flags across line clear, from Aung Myo Kyaw. 2026-06-27 10:05:38 +00:00
Nicholas Marriott
84291b021f Fix check so as to not use NULL name. 2026-06-27 10:58:39 +01:00
Thomas Adam
91e30f4f22 Merge branch 'obsd-master' 2026-06-26 21:15:06 +01:00
nicm
5be44a6c03 Use linked sessions not active. 2026-06-26 19:53:47 +00:00
Thomas Adam
8cc20a5618 Merge branch 'obsd-master' 2026-06-26 19:00:06 +01:00
nicm
c0f213b056 Add info modes to tree mode like for clients. 2026-06-26 17:43:15 +00:00
Nicholas Marriott
a2915cfdef Fuzzy test. 2026-06-26 16:09:55 +01:00
Thomas Adam
4b921f3821 Merge branch 'obsd-master' 2026-06-26 15:56:03 +01:00
nicm
575f84ce0f Add switch-mode a fast switcher with fuzzy searching, bound to Tab (for
windows) or BTab (S-Tab, for sessions) by default.
2026-06-26 14:40:30 +00:00
Thomas Adam
1048796003 Merge branch 'obsd-master' 2026-06-26 15:15:06 +01:00
nicm
973c4ab569 Drop mouse movement events rather than redrawing unnecessarily. 2026-06-26 13:58:00 +00:00
Thomas Adam
445677dc00 Merge branch 'obsd-master' 2026-06-26 14:15:06 +01:00
Thomas Adam
da27529d91 Merge branch 'obsd-master' 2026-06-26 14:00:06 +01:00
nicm
f59921ce18 Use underscore for current in status line. 2026-06-26 12:52:36 +00:00
nicm
ec3c65122f message-command-style should be inverse of message-style. 2026-06-26 12:41:08 +00:00
Thomas Adam
9fa5e81836 Merge branch 'obsd-master' 2026-06-26 13:15:06 +01:00
nicm
4869a5165a Add tree-mode-selection-style to continue the process of moving away
from mode-style.
2026-06-26 12:06:17 +00:00
Thomas Adam
30e84ff687 Merge branch 'obsd-master' 2026-06-26 13:00:09 +01:00
nicm
73fd01c986 Some colour fixes and tweaks (marked pane, cursor colours now work). 2026-06-26 11:36:22 +00:00
Thomas Adam
3729af7438 Merge branch 'obsd-master' 2026-06-26 12:00:06 +01:00
Nicholas Marriott
1e5aef279a Update tests for colour/position changes. 2026-06-26 11:37:36 +01:00
nicm
19db019e0c Do not force theme colours to default when capturing. 2026-06-26 10:29:38 +00:00
Nicholas Marriott
666464d7a9 Add X11 colours script. 2026-06-26 11:28:15 +01:00
Thomas Adam
391f880d07 Merge branch 'obsd-master' 2026-06-26 11:15:06 +01:00
nicm
d0bf3bf06d Add ability to float a tiled pane to break-pane, from Dane Jensen. 2026-06-26 10:02:01 +00:00
Thomas Adam
edb027e910 Merge branch 'obsd-master' 2026-06-26 11:00:07 +01:00
nicm
dd2fef6673 Do not count arguments starting - as optional arguments, makes old
resize-pane syntax work. GitHub issue 5275, based on a changed from Dane
Jensen.
2026-06-26 09:54:56 +00:00
nicm
8e79c4a03d Bump style buffer to 1024, GitHub issue 5279 from Moritz Angermann. 2026-06-26 09:38:44 +00:00
Thomas Adam
19d70a85fc Merge branch 'obsd-master' 2026-06-26 09:30:07 +01:00
nicm
f5e9bcc1fc Another couple of bits look nicer in grey. 2026-06-26 08:19:44 +00:00
nicm
3631f81236 Apply better colours to various builtin bits in modes. 2026-06-26 08:10:48 +00:00
Thomas Adam
1e6f64d5c9 Merge branch 'obsd-master' 2026-06-26 01:00:06 +01:00
nicm
569e9d932f Make the default colours more conservative, its really the greys that
matter.
2026-06-25 23:48:20 +00:00
Thomas Adam
6a7f4a1894 Merge branch 'obsd-master' 2026-06-26 00:30:07 +01:00
nicm
8c55a388cd Add nicer dark and light colour sets (themes) used on terminals with 256
or more colours. Currently based these on emacs but they could change.
Terminals with fewer colours use the ANSI colours. A new "theme" option
overrides the detected theme (set to "terminal" to go back to ANSI
colours).
2026-06-25 23:17:25 +00:00
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
Dane Jensen
6ec0afe5f7 Merge branch 'master' into floating_panes 2026-06-22 11:32:30 -07: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
Dane Jensen
41e731fc51 Fixed keybind for toggling between floating and tiled. 2026-06-21 13:16:34 -07:00
Dane Jensen
19c8da04d0 Merge branch 'master' into floating_panes 2026-06-21 13:12:34 -07: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
Dane Jensen
8925000c42 Removed cmd-tile-float-pane.c after moving functionality into cmd-break-pane.c and cmd-join-pane.c 2026-06-20 12:35:36 -07:00
Dane Jensen
568c99be4e Removed cmd-tile-float-pane.c. 2026-06-20 12:32:35 -07:00
Dane Jensen
69fc42afc9 Floating and tiling functionality moved into break-pane and join-pane 2026-06-20 12:28:04 -07:00
Dane Jensen
19843cd826 bugfix
There is a signature and functionality mismatch with the recent merge in
`layout_floating_pane` that has been fixed. It now more gracefully
handles inputs.
2026-06-20 12:22:08 -07:00
Dane Jensen
4eb96fc144 Merge branch 'master' into floating_panes 2026-06-20 12:12:51 -07: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
Dane Jensen
3d396f13ed A few fixes. 2026-06-19 11:33:24 -07:00
Dane Jensen
7d8401d707 formatting fix. 2026-06-19 11:33:24 -07:00
Dane Jensen
c3aac57dda Updated documentation. 2026-06-19 11:33:24 -07:00
Dane Jensen
5602571c01 Moved functionality to float and tile panes into break-pane and
`join-pane`.
2026-06-19 11:33:24 -07:00
Dane Jensen
c04236052c Merge branch 'master' into floating_panes 2026-06-19 11:31:46 -07: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
Dane Jensen
584cafa284 fix(floating_panes): Fixed tiled cell detection and removed dead guard.
When checking for space to split a layout cell, the window pane associated with that cell is needed to check for options. This fixes an incomplete scan for that window pane while updating the documentation to be more clear. There was also a redundant guard that just added noise that is now removed.
2026-06-18 15:05:38 -07:00
Dane Jensen
8a4a150a1a Fixed tiled cell detection and removed dead guard. 2026-06-18 15:01:21 -07:00
Dane Jensen
de41b93b73 Merge branch 'master' into floating_panes 2026-06-18 12:54:11 -07: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
Dane Jensen
4aa1dd3ae8 Merge pull request #5239 from daneofmanythings/fp--missing-flag-move-pane
fix(floating_panes): Added missing `-D` flag from `move-pane` .args field.
2026-06-17 15:01:03 -07:00
Dane Jensen
be33d01663 Added missing -D flag from move-pane .args field. 2026-06-17 14:59:34 -07:00
Dane Jensen
fdcb93a467 Merge branch 'master' into floating_panes 2026-06-17 14:32:01 -07: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
Dane Jensen
c54f7ec95e Remove extra code. 2026-06-16 13:43:13 -07:00
Dane Jensen
f8448764e2 Merge branch 'master' into floating_panes 2026-06-16 13:38:33 -07: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
Dane Jensen
f71e81c483 Merge pull request #5230 from daneofmanythings/fp--is_visible
fix(floating_panes): changed `window_pane_visible` to `window_pane_is_visible`
2026-06-15 21:20:03 -07:00
Dane Jensen
9bd53b8565 changed window_pane_visible to window_pane_is_visible 2026-06-15 21:18:47 -07:00
Dane Jensen
dde9dda1a4 Merge branch 'master' into floating_panes 2026-06-15 16:52:21 -07: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
Dane Jensen
55c8b0c2a7 Merge branch 'master' into floating_panes 2026-06-15 13:00:51 -07: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
Nicholas Marriott
fd1fca51fa Remove old code. 2026-06-15 08:59:43 +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
Dane Jensen
00620b57eb Bug fix for parsing floating args. 2026-06-14 22:14:08 -07:00
Dane Jensen
bba9f38636 Bug fix for parsing floating args. 2026-06-14 22:11:57 -07:00
Dane Jensen
9c37acd002 hide-pane and show-pane now use the new layout mechanics. 2026-06-14 21:57:11 -07:00
Dane Jensen
195a9b357a hide-pane and show-pane now use the new layout mechanics. 2026-06-14 21:48:03 -07:00
Dane Jensen
cd4c4bf723 tile-pane and float-pane now use the new layout mechanics
The layout position of every cell is kept throughout the state changing actions, and can therefore be restored.
2026-06-14 20:07:42 -07:00
Dane Jensen
e244be2540 Bug fixes and adjustments from feedback. 2026-06-14 20:02:24 -07:00
Dane Jensen
5dad31b06e slight renaming. 2026-06-14 19:59:41 -07:00
Dane Jensen
64ded18960 Updating tile-pane and float-pane to use new layout mechanics. 2026-06-14 19:59:37 -07:00
Dane Jensen
4dd496790a Merge branch 'master' into floating_panes 2026-06-14 19:54:43 -07: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
Dane Jensen
db9e782d50 Merge branch 'master' into floating_panes 2026-06-12 12:07:41 -07: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
Dane Jensen
d4869ebdc5 Enabled swapping with floating panes from the pane menu. 2026-06-11 14:57:37 -07:00
Dane Jensen
d1fe0d449b Enabled swapping marked panes from the pane mouse menu. 2026-06-11 14:55:43 -07:00
Dane Jensen
e2b52abd47 Merge pull request #5197 from daneofmanythings/fp--hidden-pane-flag-location
fix(floating_panes): Move the hidden panes format logic to their alphabetical positions.
2026-06-11 14:52:31 -07:00
Dane Jensen
f58eb4dc0e put the hidden panes format specifiers in the correct positions. 2026-06-11 14:48:42 -07: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
Dane Jensen
8497b588a2 Merge master into floating_panes 2026-06-11 12:05:11 -07: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
Dane Jensen
57641f67dd Merge pull request #5193 from daneofmanythings/fp--swap-floating-panes
fix(floating_panes): `swap-pane` can now affect floating panes.
2026-06-10 23:35:16 -07:00
Dane Jensen
b675c2149c This guard doesn't need to be here. Why restrict this? 2026-06-10 15:10:36 -07:00
Dane Jensen
4fbc30267c remove old logic. 2026-06-10 15:09:10 -07:00
Dane Jensen
2a830aa567 Merge branch 'master' into floating_panes 2026-06-10 12:47:50 -07: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
Dane Jensen
84229d4735 Floating layout cells can now be placed anywhere in the layout tree.
Allowing layout cells to exist anywhere in the layout tree will enable floating and tiling commands to restore position in the tree with little effort. This enables that by slightly altering the floating pane creation logic, and adding some utility functions. These functions will be soon be used elsewhere as well.
2026-06-09 22:59:13 +00:00
Dane Jensen
e370ce5a00 Added function comment. 2026-06-09 15:44:24 -07:00
Dane Jensen
2a1ad05671 There was an bug with the earlier commit, reproduction: "splitw; newp
-t0; killp -t0". Added logic to handle previously unforseen states, like
a floating cell before a tiled cell at the top of the screen.
2026-06-09 15:37:30 -07: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
Dane Jensen
7568bff8e6 Layout cells for floating panes are now inserted after the cell of a
provided window pane.
2026-06-09 13:33:45 -07:00
Dane Jensen
6766c8ec1d Initial commit. 2026-06-09 13:33:45 -07:00
Dane Jensen
2955c1fdc7 Merge master into floating_panes. 2026-06-09 20:33:15 +00:00
Dane Jensen
45e9e541a4 Merge branch 'master' into fp-merge 2026-06-09 13:29:32 -07: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
Dane Jensen
b2ac4475cc Merge pull request #5182 from daneofmanythings/fp-merge
Merging master into floating panes.
2026-06-08 23:01:12 +00:00
Dane Jensen
a7952492c1 Merged master into floating_panes 2026-06-08 15:58:38 -07: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
Nicholas Marriott
0b1089e105 Merge branch 'master' into floating_panes 2026-06-04 14:10:25 +01:00
Dane Jensen
6b3bdaff97 The backporting of work on staging left a few scattered compile errors.
fix(floating_panes): Leftover compile errors
2026-06-04 00:51:01 +00:00
Dane Jensen
7c908bca7a Fixed vestigal compile errors 2026-06-03 17:46:29 -07:00
Nicholas Marriott
90ede52728 Merge branch 'master' into floating_panes 2026-06-02 20:36:08 +01:00
Nicholas Marriott
17974ea7a0 Merge branch 'master' into floating_panes 2026-06-01 21:36:29 +01:00
Nicholas Marriott
25852623f0 Merge branch 'master' into floating_panes 2026-06-01 21:30:20 +01:00
Michael Grant
446aa08286 Bug fix vertical position of scrollbar had wrong vertical offset when status-position top. 2026-06-01 20:52:51 +01:00
Michael Grant
12b3cc3750 Merge branch 'floating_panes' of github.com:tmux/tmux into floating_panes 2026-06-01 20:20:58 +01:00
Michael Grant
6dd288d459 Fix bug: pane-border-status top, can't resize floating panes by dragging the border. 2026-06-01 20:20:42 +01:00
Nicholas Marriott
d8547fc721 Merge branch 'master' into floating_panes 2026-06-01 19:35:29 +01:00
Nicholas Marriott
68fdc4fcbe Merge branch 'master' into floating_panes 2026-06-01 15:28:25 +01:00
Nicholas Marriott
398cb6fb4a Merge branch 'master' into floating_panes 2026-06-01 12:20:03 +01:00
Nicholas Marriott
7302549903 Merge branch 'master' into floating_panes 2026-06-01 08:44:44 +01:00
Nicholas Marriott
e971bc79d6 Merge branch 'master' into floating_panes 2026-05-30 12:48:11 +01:00
Nicholas Marriott
650ad28f12 Merge branch 'master' into floating_panes 2026-05-30 11:59:37 +01:00
Nicholas Marriott
2637477eea Merge branch 'master' into floating_panes 2026-05-30 09:28:19 +01:00
Nicholas Marriott
e5c69afa98 Merge branch 'master' into floating_panes 2026-05-29 09:40:53 +01:00
Nicholas Marriott
ec901e9e31 Merge branch 'master' into floating_panes 2026-05-28 12:54:25 +01:00
Nicholas Marriott
d814529285 Merge branch 'master' into floating_panes 2026-05-28 12:07:54 +01:00
Nicholas Marriott
82dc3d57f2 Merge branch 'master' into floating_panes 2026-05-28 10:15:31 +01:00
Nicholas Marriott
3f77cb95c5 Change tty_draw_pane to draw in the right place I think. 2026-05-28 09:39:44 +01:00
Nicholas Marriott
68e9ff797c Merge branch 'master' into floating_panes 2026-05-28 09:39:25 +01:00
Nicholas Marriott
97ee6e2587 Break screen_write_collect_flush into some helper functions. 2026-05-28 08:24:54 +01:00
Nicholas Marriott
e0215480cd Merge branch 'master' into floating_panes 2026-05-27 22:22:38 +01:00
Nicholas Marriott
bd33d9c47d Merge branch 'master' into floating_panes 2026-05-27 22:21:39 +01:00
Nicholas Marriott
3a025d39db Merge branch 'master' into floating_panes 2026-05-27 21:43:14 +01:00
Nicholas Marriott
1ed454d102 Do not reduce the end position when removing padding. 2026-05-27 21:41:51 +01:00
Nicholas Marriott
52250d518d Redraw line directly for wide cells in screen_write_cell, fix up selection and
generally tidy.
2026-05-27 21:41:48 +01:00
Nicholas Marriott
bfa2ff2bdf Remove unused variable. 2026-05-27 20:33:41 +01:00
Nicholas Marriott
c62b27f014 Add a helper to replace a loop. 2026-05-27 20:33:03 +01:00
Nicholas Marriott
20cf0d3ea9 Merge branch 'master' into floating_panes 2026-05-27 20:27:48 +01:00
Nicholas Marriott
02caf7ce4a Remove a difference from master. 2026-05-27 19:36:20 +01:00
Nicholas Marriott
e1c7367a2e Merge branch 'master' into floating_panes 2026-05-27 19:31:56 +01:00
Nicholas Marriott
fc4641c5a5 Merge branch 'master' into floating_panes 2026-05-27 16:54:02 +01:00
Nicholas Marriott
8f9e2c7db2 Trivial tidying up of FP clears. 2026-05-27 14:30:57 +01:00
Nicholas Marriott
46be03d88e Clamp range with to size of window (visible ranges cannot be outside window). 2026-05-26 13:02:07 +01:00
Nicholas Marriott
f9bd5eb79e If a pane is outside the window, treat as obscured. 2026-05-26 12:48:16 +01:00
Nicholas Marriott
d89a4ab3df Redraw obscured panes instead of scrolling also. 2026-05-26 11:41:06 +01:00
Nicholas Marriott
57b18d8a18 Move redraw of overlapped panes into common functions. 2026-05-26 11:20:28 +01:00
Nicholas Marriott
95465ca03a Merge branch 'master' into floating_panes 2026-05-25 16:19:05 +01:00
Nicholas Marriott
6513808720 And a bad merge. 2026-05-25 15:50:25 +01:00
Nicholas Marriott
6c1773542d Some trivial bits from master. 2026-05-25 15:49:22 +01:00
Nicholas Marriott
c60422241b Merge branch 'master' into floating_panes 2026-05-25 09:21:36 +01:00
Michael Grant
92dcb22e92 Clean up in screen_redraw_draw_pane and fix an offset bug. 2026-05-25 08:17:57 +01:00
Nicholas Marriott
f75b1e5f07 Do not check overlays in tty_draw_line_clear, the caller should be doing it. 2026-05-24 12:25:51 +01:00
Nicholas Marriott
fb9e47d8c9 Some chanegs I made in the wrong branch. 2026-05-24 12:10:46 +01:00
Nicholas Marriott
43ae6e0a6b tty_clear_area should not work out popup ranges because tty_clear_line is going
to do it anyway.
2026-05-24 12:07:42 +01:00
Nicholas Marriott
724d6d7c95 Change to fall into redraw for alignment test. 2026-05-24 11:43:35 +01:00
Michael Grant
95c20aef20 Fix the alignmenttest. 2026-05-24 11:22:44 +01:00
Nicholas Marriott
791f77b879 Some missing calls to tty_check_overlay_range. 2026-05-24 10:18:28 +01:00
Nicholas Marriott
a6986864db Merge branch 'master' into floating_panes 2026-05-24 10:05:56 +01:00
Michael Grant
30866d06d5 Add visible range checks to screen_write.c functions and remove checks from tty.c. 2026-05-24 09:58:30 +01:00
Michael Grant
8b474429b7 Merge branch 'floating_panes' of github.com:tmux/tmux into floating_panes 2026-05-23 06:16:15 +01:00
Michael Grant
91b4e02805 Fix merge messup where i is no longer x, use px and ri->px instead here. 2026-05-23 06:15:12 +01:00
Nicholas Marriott
e65e653ea5 Merge branch 'master' into floating_panes 2026-05-22 21:31:28 +01:00
Nicholas Marriott
29a1a1f8b0 Check overlay/BCE first to avoid walking panes unless needed. 2026-05-22 18:03:01 +01:00
Nicholas Marriott
71de737ee8 Merge branch 'master' into floating_panes 2026-05-22 17:10:08 +01:00
Nicholas Marriott
55fbacb469 Fix merge error. 2026-05-22 13:07:54 +01:00
Nicholas Marriott
2a6aac4c14 Merge branch 'master' into floating_panes 2026-05-22 12:44:49 +01:00
Nicholas Marriott
4fd8431ad0 Merge branch 'master' into floating_panes 2026-05-22 12:39:09 +01:00
Nicholas Marriott
d3e8dd3623 Use a flags bit for obscured rather than an int. 2026-05-22 10:58:37 +01:00
Nicholas Marriott
1fa6ebacf4 Merge branch 'master' into floating_panes 2026-05-22 10:40:29 +01:00
Nicholas Marriott
8fa822207d Cache obscure flag in screen_write_ctx. 2026-05-22 09:07:38 +01:00
Nicholas Marriott
52ad3c8425 Change pane_is_obscured to take the context since it always uses its pane. 2026-05-22 09:03:51 +01:00
Nicholas Marriott
e5abcd217c Start at the current pane in the zindex list for working out if a pane is
obscured.
2026-05-22 08:58:48 +01:00
Nicholas Marriott
4a0118aea1 Merge branch 'master' into floating_panes 2026-05-21 12:07:17 +01:00
Dane Jensen
90a748f7fb Added logic to prevent hidden panes from being selected as the window's active pane.
Additional logic added to find any valid pane if the best fit checks fail.
2026-05-20 19:33:30 +00:00
Dane Jensen
2434653867 fix: added conditionals and more finding logic to avoid displaying hidden panes. 2026-05-20 12:19:56 -07:00
Nicholas Marriott
1def587884 Remove code that was already applied. 2026-05-20 13:47:11 +01:00
Nicholas Marriott
c56bc9ed05 Store pane ID for mouse last pane instead of a pointer to the pane. 2026-05-20 13:41:13 +01:00
Nicholas Marriott
2a9ba51907 Merge branch 'master' into floating_panes 2026-05-20 12:06:49 +01:00
Nicholas Marriott
406ae3d8a6 Remove some trivial differences. 2026-05-20 09:46:38 +01:00
Nicholas Marriott
8a674265f3 Merge branch 'master' into floating_panes 2026-05-20 09:35:03 +01:00
Dane Jensen
ed43033c39 Changed the semantics from un/minimise-pane to hide/show-pane
Several functions and flags were semantically changed. No logic was altered.
2026-05-20 04:14:59 +00:00
Dane Jensen
0a7b008b21 Changed minimised semantics to hide semantics 2026-05-19 21:09:53 -07:00
Nicholas Marriott
f8e908b89c More trivial merge errors. 2026-05-19 13:26:27 +01:00
Nicholas Marriott
2695c5305e More merge fixes. 2026-05-19 13:23:21 +01:00
Nicholas Marriott
55fdfef9d1 Merge fixes. 2026-05-19 13:19:21 +01:00
Nicholas Marriott
2f6da393e2 Merge branch 'master' into floating_panes 2026-05-19 13:05:14 +01:00
Nicholas Marriott
57b17bbb70 window_pane_move is not used. 2026-05-19 10:54:35 +01:00
Dane Jensen
7c23da598d Fixed null dereference in window.c:window_pane_float_geometry when there is no target wp.
Conditions for the bug: start tmux, run `minimise-pane; new-pane`. Now fixed.
2026-05-19 00:21:48 +00:00
Dane Jensen
b7cc218a03 Fixed null dereference. 2026-05-18 17:14:19 -07:00
Dane Jensen
0ae8019e7f Better api design and abstraction for new-pane and split-window.
While working on #5043, feedback was given by @mgrant0 regarding api design that also required addressing in `new-pane` and `split-window`. This MR addresses that feedback (no subflags, flag restriction for semantic reasons, unified 'float' and 'tile' flags across commands).
2026-05-18 22:46:53 +00:00
Dane Jensen
c3fc0229e5 style fixes 2026-05-18 15:41:28 -07:00
Dane Jensen
76196b8ffa Moved new-pane into split-window. 2026-05-18 13:48:50 -07:00
Nicholas Marriott
fd4e683151 Merge branch 'master' into floating_panes 2026-05-18 11:10:09 +01:00
Nicholas Marriott
2b05d8531d Merge branch 'master' into floating_panes 2026-05-17 15:55:09 +01:00
Nicholas Marriott
8e966688a7 If outside floating pane, do not carry on to check as if tiled pane. 2026-05-17 15:49:21 +01:00
Michael Grant
c4ec9234e9 Fix to ignore minimised panes when redoing the layout. 2026-05-15 18:23:39 +01:00
Michael Grant
2e0eabbf0a Fix screen_redraw_get_visible_ranges to ignore minimised panes. 2026-05-15 18:20:29 +01:00
Michael Grant
1b2435fab6 Fix bug where tty output of command outputing was getting written to the window aafter it was minimised. 2026-05-13 22:46:32 +01:00
Michael Grant
1ff3dafe4e Fix crash when minimising pane. 2026-05-13 22:28:14 +01:00
Nicholas Marriott
6b16e93f1b Merge branch 'master' into floating_panes 2026-05-12 12:38:31 +01:00
Nicholas Marriott
b7467a3110 Merge pull request #5039 from daneofmanythings/layout-null-dereference
fix: fixed site of null dereference in layout.c
2026-05-12 10:27:54 +01:00
Nicholas Marriott
4224ecd2ab Merge pull request #5042 from daneofmanythings/floating_panes--two-panes-crash
fix(floating_panes): null dereference in `screen_redraw_two_panes` from `screen_redraw_border_arrows`
2026-05-01 12:54:38 +01:00
Dane Jensen
e18c10d340 Passing in '&type' to avoid null dereference 2026-04-27 21:51:02 -07:00
Dane Jensen
0719dcf2e7 fixed site of null dereference in layout.c 2026-04-25 15:05:58 -07:00
Nicholas Marriott
3400a312dd Merge pull request #5026 from daneofmanythings/floating_panes--new-pane-doc-fixup
docs(floating_panes): A couple documentation fixes from #4979
2026-04-24 16:06:12 +03:00
Michael Grant
b2f9144e10 Merge pull request #5029 from daneofmanythings/floating_panes--fix-join-pane
fix(floating_panes): Z-index related crashing in `join-pane`
2026-04-24 13:56:37 +01:00
Dane Jensen
a74f173902 more. 2026-04-23 21:53:06 -07:00
Dane Jensen
a1cd68e3f8 Fixed z-index related crash in join-pane 2026-04-23 20:23:50 -07:00
Dane Jensen
eec2c19ad1 oversights. 2026-04-23 17:07:53 -07:00
Michael Grant
05fdd04262 Fix the z-index of the pane. (nic's fix.) 2026-04-24 00:49:22 +01:00
Nicholas Marriott
3e36ddbceb Merge pull request #4979 from daneofmanythings/floating_panes--new-pane
refactor: Combined the code for `split-window` and `new-pane`
2026-04-22 10:29:07 +03:00
Dane Jensen
0a55e5ca54 Fixed logical error in handing default cascading. 2026-04-17 12:44:50 -07:00
Dane Jensen
a7ad0c4434 Option flag adjustment for eventual consistency. 2026-04-16 11:38:33 -07:00
Michael Grant
271bac5347 Merge branch 'master' into floating_panes 2026-04-15 23:26:02 -04:00
Dane Jensen
914ffc8887 touchup. 2026-04-15 12:33:08 -07:00
Dane Jensen
7a4e35e317 doc fix. 2026-04-14 13:08:19 -07:00
Dane Jensen
1bc85cb59e Adjustment from feedback and slight touchups. 2026-04-14 12:40:59 -07:00
Dane Jensen
f992c68fd8 slight touchup 2026-04-13 22:04:08 -07:00
Dane Jensen
4bb7a86753 Tested the new commands and updated documentation. 2026-04-13 21:39:30 -07:00
Dane Jensen
950194fb63 Migrated cmd-split-window.c into cmd-new-pane.c for code reuse. 2026-04-06 20:10:15 -07:00
Michael Grant
b528fc438c Merge. 2026-04-05 19:40:56 -04:00
Michael Grant
811604a663 Fix bugs with tiling floating panes. 2026-04-05 19:04:10 -04:00
Michael Grant
4232bf9e2f Add new tile-pane and float-pane commands. 2026-03-30 18:01:55 -04:00
Michael Grant
8a3620e438 Initial commit. 2026-03-30 17:47:28 -04:00
Michael Grant
1408861511 Fix minimise window gesture to be like MacOS and Windows where if you click an active window in the status line it minimises it and if its already minimised it unminimises it. The ux feel now is natural. 2026-03-28 09:25:19 +00:00
Michael Grant
44229e8af6 Fix a crash when clicking a floating pane in the status line which was minimised after it was made active by window-tree. Correct the way window-tree displays floating panes so they can be easily unminimmised. 2026-03-28 08:35:35 +00:00
Michael Grant
d002323de2 Merge branch 'master' into floating_panes 2026-03-26 20:51:42 +00:00
Michael Grant
999c7246c3 Formatting. 2026-03-26 08:46:28 +00:00
Michael Grant
c10a597bf0 add -R to be able to set floating pane inactive border style. 2026-03-24 18:15:24 +00:00
Michael Grant
75ed7b27c6 Add -S and -s options to style floating panes. 2026-03-24 17:49:55 +00:00
Michael Grant
ef01e9daf8 Reviewed and removed the xxx fix-me comments. 2026-03-24 14:35:59 +00:00
Michael Grant
866cb2d16f Update tmux.1 man page to add new-pane floating pane section. 2026-03-24 14:29:26 +00:00
Michael Grant
6a62d49720 Add options to new-pane: -k -m message to wait for key before closing a floating pane. 2026-03-24 14:23:58 +00:00
Michael Grant
f201d246fd Fix next-layout to ignore floating panes. 2026-03-24 12:41:04 +00:00
Michael Grant
0328fe44e4 Fix sixel image writing so that it is aware of floating panes. 2026-03-22 09:28:15 +00:00
Michael Grant
bb85983a29 Merge pull request #4929 from daneofmanythings/floating_panes-pane_status_format
feat: Panes' status format now displays title/flag information
2026-03-20 09:46:44 +00:00
Michael Grant
4341d6a095 Merge branch 'master' into floating_panes 2026-03-20 08:05:13 +00:00
Michael Grant
10a9ce1ed2 Prohibit swapping 2 floating panes, that doesn't make sense. 2026-03-20 07:59:25 +00:00
Michael Grant
943490cfa1 Fix issue when a wp disappears in the middle of a drag event. 2026-03-20 07:49:49 +00:00
Michael Grant
7e6bbc63ab Fix a slew of possible int vs u_int bugs which would likely have caused an overflow crash. 2026-03-18 13:04:21 +00:00
Michael Grant
0d195698f8 Fix dispay bugs where the cursor was on a line that had 2 visible ranges because it was partially obscured by a floating pane. 2026-03-18 09:38:17 +00:00
Dane Jensen
08fd890a89 Added status format functionality to panes along with flags 2026-03-17 22:50:04 -07:00
Michael Grant
ab0081294c Fix scrolling issue when a floating pane overlaps a tiled pane. 2026-03-17 17:08:02 +00:00
Michael Grant
c42a939e98 Fix some int/u_int bugs and added some logging. 2026-03-17 17:05:57 +00:00
Michael Grant
6c9e2f7e6c Fix no cursor in popup. 2026-03-16 12:07:41 +00:00
Michael Grant
992f04f328 Merge branch 'master' into floating_panes 2026-03-16 09:12:08 +00:00
Michael Grant
8a90fce476 Fix for window size taller than tty. 2026-03-10 13:21:01 +00:00
Michael Grant
e928e80a42 Fix scrollbar issue not respecting oy when window size taller than tty. 2026-03-09 16:28:06 +00:00
Michael Grant
28c04b21f6 Minor fixup. 2026-03-09 08:35:24 +00:00
Michael Grant
46427f1dd7 Merge branch 'master' into floating_panes 2026-03-09 08:31:58 +00:00
Michael Grant
528e856566 Merge pull request #4893 from daneofmanythings/floating_panes__float_format
feat(format variable): added format variable for floating panes.
2026-03-08 17:14:16 +00:00
Michael Grant
14a934d412 Fix screen clearing issue to surgically clear around floating panes. 2026-03-08 17:11:55 +00:00
Dane Jensen
0faed7aeb5 added format flag for floating panes. 2026-03-04 12:08:43 -08:00
Michael Grant
8e6450c8fc Merge branch 'master' into floating_panes 2026-03-04 13:16:52 +00:00
Michael Grant
6898e26a7a Fix bug with utf-8 extended characters not respecting visible range. 2026-02-24 01:19:24 +00:00
Michael Grant
e2cf40ef99 Fix crash cause by not having a wp pointer in tty.c. 2026-02-23 08:08:31 +00:00
Michael Grant
96515c9f47 Merge branch 'master' into floating_panes 2026-02-18 09:28:38 +00:00
Michael Grant
96e18faa93 Merge branch 'master' into floating_panes 2026-02-11 09:40:21 +00:00
Michael Grant
476c6e89ad Check the overlay visible ranges when we have a tty and check the panes visible ranges when we have a wp. Overlays checked at tty level (for most part) and pane overlays checked at screen level where possible to keep separation of abstraction relatively clean. 2026-02-11 09:30:48 +00:00
Michael Grant
b46a96d454 Second try with static visual_ranges using calloc. 2026-01-29 14:34:18 +00:00
Michael Grant
f67cf7d053 Return static range as last resort. 2026-01-29 11:46:21 +00:00
Nicholas Marriott
2ad0f4a9e7 Merge branch 'master' into floating_panes 2026-01-29 08:43:41 +00:00
Michael Grant
1ec9ca497b Merge with visible_ranges banch. Convert visible_ranges to new style. 2026-01-28 15:26:00 +00:00
Michael Grant
dd5d0f4f0c Merge master. 2026-01-23 09:29:39 +00:00
Nicholas Marriott
d1a6ce8e7f Associate each visible_ranges with some other object (tty, popup_data, etc) so
it is easier to keep track of its lifecycle, but still avoid allocating for
each use.
2026-01-21 21:29:56 +00:00
Michael Grant
b22537e8a4 Conflict resolve. 2026-01-21 19:36:05 +00:00
Michael Grant
a25c14d472 Change overlay_ranges into visible_ranges. 2026-01-21 19:34:19 +00:00
Nicholas Marriott
7730d38339 Skip correct width when moving to next position. 2026-01-21 15:58:11 +00:00
Nicholas Marriott
58e498c9d3 Use right cell for empty check. 2026-01-21 15:35:33 +00:00
Nicholas Marriott
35485f2b5e Fix selection with tty_draw_line. 2026-01-21 12:23:46 +00:00
Nicholas Marriott
b108653f02 Change overlay_ranges to visible_ranges. 2026-01-20 21:18:33 +00:00
Nicholas Marriott
25f72cf240 Rewrite tty_draw_line to be simpler and not to check overlay ranges. 2026-01-20 21:18:20 +00:00
Thomas Adam
d1df8dcccc focus-follows-mouse: honour floating panes
Make sure the z-ordering of floating panes is honoured when
focus-follows-mouse is in use.
2026-01-09 19:00:17 +00:00
Michael Grant
a50d00aa47 Merge pull request #4799 from tmux/master
Sync from master
2026-01-09 11:30:16 +01:00
Michael Grant
34add944f2 Merge branch 'master' into feature-floating-window-panes 2026-01-07 23:41:29 +01:00
Michael Grant
95f85efc49 Fix some of the offset issues when windows size is greater than tty size. 2026-01-07 22:28:23 +00:00
Michael Grant
f2f6a05e2c Add cmd-minimise-pane.c 2026-01-06 16:44:38 +00:00
Michael Grant
bd442a27ee Fix some redraw issues. Fix scrollbar left/right display issues. 2025-12-24 10:45:17 +00:00
Michael Grant
73c0eb96c3 Merge branch 'master' into feature-floating-window-panes 2025-12-18 23:14:59 +01:00
Michael Grant
6a4a4a432b Add support to minimise panes, both tiled and floating. New PREFIX _ key binding to minimise a pane. New functions minimise-pane and unminimise-pane. Add double-click on pane in status to minimise pane. Single click on pane in status unminimises pane. 2025-12-18 18:19:17 +00:00
Michael Grant
263529e886 Cleanup from previous commit. 2025-12-09 20:11:44 +00:00
Michael Grant
818797ff08 Fix scrolling issue that collided with PR #4738 Fix y offset of mouse if status at top. 2025-12-09 17:47:36 +00:00
Michael Grant
d263dbe9dd Merge branch 'tmux:master' into feature-floating-window-panes 2025-12-09 13:35:31 +00:00
Michael Grant
67319ad9d9 Fix tty-clear-area and make aware of floating panes. 2025-12-09 13:34:26 +00:00
Michael Grant
2591df66cc Bugfix status line at top and floating panes. 2025-12-09 00:14:06 +00:00
Michael Grant
2ac78bccb5 Bugfix status line at top. 2025-12-08 15:01:54 +00:00
Michael Grant
47de44001e Merge changes from master. 2025-12-08 14:30:57 +00:00
Michael Grant
eaa467618b 1. Rework floating panes to have a stub layout_cell, 2. Add new <..> format to list-windows & select-layout for floating anes, 3. Fix zooming to work with floating panes, 4. Fix several display issues. 2025-12-08 14:28:17 +00:00
Michael Grant
ac01f15bfe Merge branch 'master' into feature-floating-window-panes 2025-12-06 21:14:38 +00:00
Michael Grant
5df5217e2e Merge branch 'tmux:master' into feature-floating-window-panes 2025-11-28 10:34:17 +00:00
Michael Grant
a6ec2b76c2 Merge branch 'tmux:master' into feature-floating-window-panes 2025-11-19 14:23:05 +00:00
Michael Grant
bc01b300c4 Merge branch 'tmux:master' into feature-floating-window-panes 2025-11-14 21:10:00 +00:00
Michael Grant
c8d4d8cb66 Bugfix fix some uninitialised variable warnings. 2025-11-14 11:02:12 +01:00
Michael Grant
315202b007 Merge branch 'tmux:master' into feature-floating-window-panes 2025-11-14 09:29:06 +00:00
Michael Grant
7194fdca38 Fix many display problems with floating windows up against the window edge and borders of other windows and many off-by-one errors. 2025-11-10 23:07:06 +01:00
Michael Grant
31d0945059 Fix dragging a window to y==0 when pane border status enabled. window_get_active_at() needs to return the pane at the top of the window when called with y==0, otherwise it returns null as if there is no pane at the top line. 2025-11-10 23:04:03 +01:00
Michael Grant
466e79d572 Bugfix for size of pane_border status not properly clipping. 2025-11-07 22:53:42 +01:00
Michael Grant
3bb4f72a4b Add new key binding ctrl-b * to create new floating pane. New panes created at increasing offsets. 2025-11-07 12:25:59 +01:00
Michael Grant
329e9d54ab Add support for clicking on a floating pane border to make it active. Including bugfix to click bottom border of floating panes. 2025-11-07 11:43:41 +01:00
Michael Grant
9c45dd693b Comment cleanup. 2025-11-07 01:24:39 +01:00
Michael Grant
cc671e4495 Fix mouse drag in copy-mode to properly select around floating panes. 2025-11-07 01:24:19 +01:00
Michael Grant
61ca158de1 Fix pane borders, including fixing 2 side-by-side or top-bottom panes with split coulering. 2025-11-06 12:16:45 +01:00
Michael Grant
7e0038c691 Bugfix split pane border colours. 2025-11-04 15:48:23 +01:00
Michael Grant
04792d0656 Bugfix, max margin should be right side of window. 2025-11-03 22:38:35 +01:00
Michael Grant
6c5cc40039 Bugfix, more changes to allow xoff and yoff to be int. 2025-11-03 21:56:15 +01:00
Michael Grant
6b462474f0 Bugfix so floating wp can be moved to top of window. 2025-11-02 11:52:21 +01:00
Michael Grant
9a061a2fee Bugfix floating panes display when off window edge. 2025-11-02 09:56:57 +01:00
Michael Grant
6dd552d689 Fix floating pane redraw bugs. Allow floating panes to be partly out of the window. This required changing xoff and yoff from u_int to int and it required a fair bit of casting for example when xoff is added to sx or comparing px to xoff. It makes sense for px and sx to be u_int since they refers to things which should never be negative. 2025-11-01 21:47:54 +01:00
Michael Grant
34e858ea05 Add support to drag and resize floating window panes. 2025-10-30 21:42:32 +01:00
Michael Grant
39d2839e37 Bugfix visible_ranges calculation, no longer need to inject vr into tty_draw_line. 2025-10-28 12:50:13 +00:00
Michael Grant
7980d00e8c Attempt to take care of case of partially obscured wide characters by floating panes. (Not yet tested!) 2025-10-28 11:01:27 +00:00
Michael Grant
25ce5b4281 Add separate z-index list. Each window has its own z-order list of panes now. 2025-10-28 08:07:32 +00:00
Michael Grant
0cd3ab6299 Fix active border colour if only floating panes in the window. 2025-10-27 23:35:53 +00:00
Michael Grant
28d038fb16 Bugfix calculation error on bottom border when pane border status on and scrollbar enabled. 2025-10-27 22:18:07 +00:00
Michael Grant
10dc308e6b Add checking the redraw of floating panes and the pane border status. 2025-10-27 21:28:31 +00:00
Michael Grant
6344bab6cc Fix typo with visible_ranges struct name. Add support for checking visual ranges to tty_draw_pane. 2025-10-27 19:54:21 +00:00
Michael Grant
984fbacccf Change the visible_ranges array to work more like the overlay_ranges array, except be able to grow. 2025-10-26 14:18:27 +00:00
Michael Grant
cc83ca6628 Add new newp command. 2025-10-26 14:02:57 +00:00
Michael Grant
04fc6ea11b Renamed floating window panes simple new-pane. 2025-10-24 14:24:26 +01:00
Michael Grant
8a9e2fccbd Move floating window stuff to new file: cmd-new-pane.c. 2025-10-24 10:25:18 +01:00
Michael Grant
cc1324e2d9 Cleanup and simplification. Array of visual ranges now just simple array. 2025-10-24 09:53:12 +01:00
Michael Grant
b2226d1608 Add scrollbar width to right border calculation. 2025-10-24 00:06:18 +01:00
Michael Grant
8ce0af3fc9 Fix a fencepost error (not sure why wp->sy+1 isn't the border). 2025-10-23 23:59:49 +01:00
Michael Grant
b315a6c3d1 Add mouse detection on top & left borders of floating panes. 2025-10-23 23:25:48 +01:00
Michael Grant
baf642b7d2 window_redraw_active_switch now brings floating pane to front. Fix compile warning in window_add_pane. 2025-10-23 20:30:32 +01:00
Michael Grant
8db76e9057 Bugfix fix redraw of overlapping floating panes. 2025-10-23 18:05:04 +01:00
Michael Grant
e3b7bf9b31 Add -x, -y, -w, -h args to set the xoff, yoff, sx, and sy of the pane. 2025-10-23 00:12:54 +01:00
Michael Grant
38724f2e86 Return error if you try to split a floating pane. 2025-10-22 23:37:58 +01:00
Michael Grant
65fc09fac9 Hide cursor behind floating panes. 2025-10-22 23:29:21 +01:00
Michael Grant
3197b715b3 Bugfix display of above-below windows with a floating pane. 2025-10-22 22:50:10 +01:00
Michael Grant
26362dfc72 Bugfix display of 2 side-by-side panes with overlapping floating pane. 2025-10-22 22:07:42 +01:00
Michael Grant
275781ce45 Merge branch 'feature-floating-window-panes' of github.com:mgrant0/tmux into feature-floating-window-panes 2025-10-21 09:17:13 +01:00
Michael Grant
72dbbfedce Manage visible ranges when drawing scrollbars. 2025-10-21 09:13:33 +01:00
Michael Grant
a6c6eabb5a Merge branch 'tmux:master' into feature-floating-window-panes 2025-10-21 08:14:38 +01:00
Michael Grant
379e4d976c Fix scrolling, redraw, and borders of floating panes. 2025-10-19 14:11:08 +01:00
Michael Grant
a6514e2656 Merge branch 'feature-floating-window-panes' of github.com:mgrant0/tmux into feature-floating-window-panes 2025-10-16 10:24:40 +01:00
Michael Grant
032af23e18 Merge branch 'tmux:master' into feature-floating-window-panes 2025-10-16 09:35:25 +01:00
Michael Grant
b5f9293014 Possible fixes leak 2025-10-13 08:45:32 +01:00
Michael Grant
a37db55689 Bugfix. 2025-10-12 10:38:47 +01:00
Michael Grant
1b959d0eff Cleanup before reworking collect_scroll. 2025-10-12 05:48:30 +01:00
Michael Grant
ce03f1abea Cleanup - screen_redraw_get_visible_ranges returns a value rather than pass addr of arg. Bugfix to redraw code. 2025-10-11 18:07:22 +01:00
Michael Grant
5faf41b695 Add support in screen-write.c to properly display cmd output when there are floating panes on the screen. 2025-10-10 23:59:47 +01:00
Michael Grant
7634daa834 Add function screen_redraw_get_visual_ranges to figure out what parts of floating panes obscure a target pane being redrawn. 2025-10-06 22:19:25 +01:00
Michael Grant
968f439672 Initial commit. Add new new-floating-window command to create panes without a layout_cell indicating they are floating panes. 2025-10-04 09:15:51 +01:00
251 changed files with 21668 additions and 5427 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
~~~ ~~~

75
.github/workflows/regress.yml vendored Normal file
View File

@@ -0,0 +1,75 @@
name: 'Run Tests'
on:
workflow_dispatch:
schedule:
- cron: '33 3 * * *'
permissions:
contents: read
concurrency:
group: tmux-tests
cancel-in-progress: true
jobs:
regress:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- name: ubuntu-24.04-x64
runner: ubuntu-24.04
make: make
# - name: ubuntu-24.04-arm64
# runner: ubuntu-24.04-arm
# make: make
# - name: macos-26-arm64
# runner: macos-26
# make: gmake
steps:
- name: checkout
uses: actions/checkout@v4
- name: dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
autoconf \
automake \
bison \
build-essential \
libevent-dev \
libncurses-dev \
libutf8proc-dev \
pkg-config
- name: dependencies
if: runner.os == 'macOS'
run: |
brew install \
autoconf \
automake \
bison \
libevent \
make \
ncurses \
utf8proc \
pkg-config
- name: build
run: |
sh autogen.sh
./configure --enable-utf8proc
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
- name: test
run: |
cd regress
${{ matrix.make }}

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

13
CHANGES
View File

@@ -1,3 +1,16 @@
CHANGES FROM 3.7a TO 3.7b
* Fix so that the end of a synchronized update again triggers a redraw.
CHANGES FROM 3.7 TO 3.7a
* Fix crash in break-pane when no name is provided.
* Scrollbar options are now cached rather than being looked up for every redraw
(issue 5298).
* Only forbid #( in names, allow #[, empty names, : and .
CHANGES FROM 3.6b TO 3.7 CHANGES FROM 3.6b TO 3.7
* Add floating panes. These are panes which sit above the layout ("tiled * Add floating panes. These are panes which sit above the layout ("tiled

View File

@@ -102,6 +102,7 @@ dist_tmux_SOURCES = \
cmd-display-panes.c \ cmd-display-panes.c \
cmd-find-window.c \ cmd-find-window.c \
cmd-find.c \ cmd-find.c \
cmd-hide-pane.c \
cmd-if-shell.c \ cmd-if-shell.c \
cmd-join-pane.c \ cmd-join-pane.c \
cmd-kill-pane.c \ cmd-kill-pane.c \
@@ -162,6 +163,7 @@ dist_tmux_SOURCES = \
file.c \ file.c \
format.c \ format.c \
format-draw.c \ format-draw.c \
fuzzy.c \
grid-reader.c \ grid-reader.c \
grid-view.c \ grid-view.c \
grid.c \ grid.c \
@@ -184,6 +186,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 +213,15 @@ 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-switch.c \
window-tree.c \ window-tree.c \
window-visible.c \
window.c \ window.c \
xmalloc.c \ xmalloc.c \
xmalloc.h xmalloc.h

View File

@@ -149,7 +149,7 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause,
int optional_argument) int optional_argument)
{ {
struct args_value *argument, *new; struct args_value *argument, *new;
const char *s; const char *s, *as;
new = xcalloc(1, sizeof *new); new = xcalloc(1, sizeof *new);
if (*string != '\0') { if (*string != '\0') {
@@ -180,12 +180,24 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause,
xasprintf(cause, "-%c expects an argument", flag); xasprintf(cause, "-%c expects an argument", flag);
return (-1); return (-1);
} }
if (optional_argument && argument->type == ARGS_STRING) {
as = argument->string;
if (as[0] == '-' && (as[1] == '-' || isalpha((u_char)as[1]))) {
args_free_value(new);
free(new);
log_debug("%s: -%c (optional)", __func__, flag);
args_set(args, flag, NULL, ARGS_ENTRY_OPTIONAL_VALUE);
return (0);
}
}
args_copy_value(new, argument); args_copy_value(new, argument);
(*i)++; (*i)++;
out: out:
s = args_value_as_string(new); s = args_value_as_string(new);
log_debug("%s: -%c = %s", __func__, flag, s); log_debug("%s: -%c = %s", __func__, flag, s);
args_set(args, flag, new, 0); args_set(args, flag, new, 0);
return (0); return (0);
} }
@@ -998,7 +1010,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 +1078,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) {

View File

@@ -45,7 +45,7 @@ attributes_tostring(int attr)
(attr & GRID_ATTR_UNDERSCORE_3) ? "curly-underscore," : "", (attr & GRID_ATTR_UNDERSCORE_3) ? "curly-underscore," : "",
(attr & GRID_ATTR_UNDERSCORE_4) ? "dotted-underscore," : "", (attr & GRID_ATTR_UNDERSCORE_4) ? "dotted-underscore," : "",
(attr & GRID_ATTR_UNDERSCORE_5) ? "dashed-underscore," : "", (attr & GRID_ATTR_UNDERSCORE_5) ? "dashed-underscore," : "",
(attr & GRID_ATTR_OVERLINE) ? "overline," : "", (attr & GRID_ATTR_OVERLINE) ? "overline," : "",
(attr & GRID_ATTR_NOATTR) ? "noattr," : ""); (attr & GRID_ATTR_NOATTR) ? "noattr," : "");
if (len > 0) if (len > 0)
buf[len - 1] = '\0'; buf[len - 1] = '\0';

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

@@ -34,9 +34,10 @@ const struct cmd_entry cmd_break_pane_entry = {
.name = "break-pane", .name = "break-pane",
.alias = "breakp", .alias = "breakp",
.args = { "abdPF:n:s:t:", 0, 0, NULL }, .args = { "abdPF:n:s:t:Wx:X:y:Y:", 0, 0, NULL },
.usage = "[-abdP] [-F format] [-n window-name] [-s src-pane] " .usage = "[-abdPW] [-F format] [-n window-name] [-s src-pane] "
"[-t dst-window]", "[-t dst-window] [-x width] [-y height] [-X x-position] "
"[-Y y-position]",
.source = { 's', CMD_FIND_PANE, 0 }, .source = { 's', CMD_FIND_PANE, 0 },
.target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX }, .target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX },
@@ -45,6 +46,52 @@ const struct cmd_entry cmd_break_pane_entry = {
.exec = cmd_break_pane_exec .exec = cmd_break_pane_exec
}; };
static enum cmd_retval
cmd_break_pane_float(struct cmdq_item *item, struct args *args,
struct window *w, struct window_pane *wp)
{
struct layout_cell *lc = wp->layout_cell;
u_int sx = lc->saved_sx, sy = lc->saved_sy;
int ox = lc->saved_xoff, oy = lc->saved_yoff;
char *cause = NULL;
enum pane_lines lines = window_get_pane_lines(w);
if (window_pane_is_floating(wp)) {
cmdq_error(item, "pane is already floating");
return (CMD_RETURN_ERROR);
}
if (window_pane_is_hidden(wp)) {
cmdq_error(item, "can't float a hidden pane");
return (CMD_RETURN_ERROR);
}
if (w->flags & WINDOW_ZOOMED) {
cmdq_error(item, "can't float a pane while window is zoomed");
return (CMD_RETURN_ERROR);
}
if (layout_floating_args_parse(item, args, lines, w, &sx, &sy, &ox, &oy,
&cause) != 0) {
cmdq_error(item, "failed to float pane: %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
layout_remove_tile(w, lc);
layout_set_size(lc, sx, sy, ox, oy);
lc->flags |= LAYOUT_CELL_FLOATING;
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
if (!args_has(args, 'd'))
window_set_active_pane(w, wp, 1);
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
notify_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval static enum cmd_retval
cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
{ {
@@ -62,7 +109,10 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
int idx = target->idx, before; int idx = target->idx, before;
const char *template, *name = args_get(args, 'n'); const char *template, *name = args_get(args, 'n');
if (name != NULL && !check_name(name, WINDOW_NAME_FORBID)) { if (args_has(args, 'W'))
return (cmd_break_pane_float(item, args, w, wp));
if (name != NULL && !check_name(name)) {
cmdq_error(item, "invalid window name: %s", name); cmdq_error(item, "invalid window name: %s", name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -86,7 +136,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (name != NULL) { if (name != NULL) {
window_set_name(w, name, WINDOW_NAME_FORBID); window_set_name(w, name, 0);
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);
@@ -114,9 +164,9 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
w->active = wp; w->active = wp;
w->latest = tc; w->latest = tc;
if (name != NULL) { if (name == NULL) {
newname = default_window_name(w); newname = default_window_name(w);
window_set_name(w, newname, WINDOW_NAME_FORBID); window_set_name(w, newname, 0);
free(newname); free(newname);
} else { } else {
window_set_name(w, name, 0); window_set_name(w, name, 0);

View File

@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
.name = "capture-pane", .name = "capture-pane",
.alias = "capturep", .alias = "capturep",
.args = { "ab:CeE:FHJLMNpPqS:Tt:", 0, 0, NULL }, .args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL },
.usage = "[-aCeFHJLMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " .usage = "[-aCeFHJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
"[-S start-line] " CMD_TARGET_PANE_USAGE, "[-S start-line] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -75,6 +75,96 @@ cmd_capture_pane_append(char *buf, size_t *len, const char *line,
return (buf); return (buf);
} }
static char *
cmd_capture_pane_cell(struct screen *s, u_int xx, u_int yy)
{
struct grid *gd = s->grid;
struct hyperlinks *hl = s->hyperlinks;
struct grid_cell gc;
char *line, *data, *link, *linkid, *f, *b, *u;
char c[UTF8_SIZE + 1];
const char *uri, *iid;
u_int flags;
grid_get_cell(gd, xx, yy, &gc);
memcpy(c, gc.data.data, gc.data.size);
c[gc.data.size] = '\0';
utf8_stravis(&data, c, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
if (gc.link != 0 && hyperlinks_get(hl, gc.link, &uri, &iid, NULL)) {
xasprintf(&link, "%s", uri);
if (iid != NULL && *iid != '\0')
xasprintf(&linkid, "%s", iid);
else
xasprintf(&linkid, "NONE");
} else {
xasprintf(&link, "NONE");
xasprintf(&linkid, "NONE");
}
flags = gc.flags;
if (gc.fg & COLOUR_FLAG_256)
flags |= GRID_FLAG_FG256;
if (gc.bg & COLOUR_FLAG_256)
flags |= GRID_FLAG_BG256;
xasprintf(&f, "%s[%x]", colour_tostring(gc.fg), gc.fg);
xasprintf(&b, "%s[%x]", colour_tostring(gc.bg), gc.bg);
xasprintf(&u, "%s[%x]", colour_tostring(gc.us), gc.us);
xasprintf(&line, "\t\tC %u,%u data=(%u,%u,%s) flags=%s[%x] "
"attr=%s[%x] fg=%s bg=%s us=%s link=%s linkid=%s\n",
yy, xx, gc.data.width, gc.data.size, data,
grid_cell_flags_string(flags), flags,
grid_cell_attr_string(gc.attr), gc.attr, f, b, u, link, linkid);
free(f);
free(b);
free(u);
free(link);
free(linkid);
free(data);
return (line);
}
static char *
cmd_capture_pane_grid(struct window_pane *wp, size_t *len)
{
struct screen *s = &wp->base;
struct grid *gd = s->grid;
struct grid_line *gl;
char *buf = xstrdup(""), *line;
char p[11];
u_int yy, xx, total = gd->hsize + gd->sy;
xasprintf(&line, "G %ux%u (%u/%u)\n", gd->sx, gd->sy, gd->hsize,
gd->hlimit);
buf = cmd_capture_pane_append(buf, len, line, strlen(line));
free(line);
for (yy = 0; yy < total; yy++) {
gl = grid_get_line(gd, yy);
if (yy < gd->hsize)
snprintf(p, sizeof p, "-");
else
snprintf(p, sizeof p, "%u", yy - gd->hsize);
xasprintf(&line, "\tL %u (%s) flags=%s[%x] %u/%u\n", yy,
p, grid_line_flags_string(gl->flags), gl->flags,
gl->cellused, gl->cellsize);
buf = cmd_capture_pane_append(buf, len, line, strlen(line));
free(line);
for (xx = 0; xx < gd->sx; xx++) {
line = cmd_capture_pane_cell(s, xx, yy);
buf = cmd_capture_pane_append(buf, len, line,
strlen(line));
free(line);
}
}
return (buf);
}
static char * static char *
cmd_capture_pane_pending(struct args *args, struct window_pane *wp, cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
size_t *len) size_t *len)
@@ -318,11 +408,14 @@ 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);
} }
len = 0; len = 0;
if (args_has(args, 'P') && !args_has(args, 'H')) if (args_has(args, 'R'))
buf = cmd_capture_pane_grid(wp, &len);
else if (args_has(args, 'P') && !args_has(args, 'H'))
buf = cmd_capture_pane_pending(args, wp, &len); buf = cmd_capture_pane_pending(args, wp, &len);
else else
buf = cmd_capture_pane_history(args, item, wp, &len); buf = cmd_capture_pane_history(args, item, wp, &len);

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,21 @@ 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 },
.flags = 0,
.exec = cmd_choose_tree_exec
};
const struct cmd_entry cmd_switch_mode_entry = {
.name = "switch-mode",
.alias = NULL,
.args = { "F:kst:wZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-kswZ] [-F format] " CMD_TARGET_PANE_USAGE " [command]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -116,6 +129,8 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item)
mode = &window_client_mode; mode = &window_client_mode;
} else if (cmd_get_entry(self) == &cmd_customize_mode_entry) } else if (cmd_get_entry(self) == &cmd_customize_mode_entry)
mode = &window_customize_mode; mode = &window_customize_mode;
else if (cmd_get_entry(self) == &cmd_switch_mode_entry)
mode = &window_switch_mode;
else else
mode = &window_tree_mode; mode = &window_tree_mode;

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;
status_prompt_set(tc, target, cdata->prompts[0].prompt, if (pane) {
cdata->prompts[0].input, cmd_command_prompt_callback, cdata->flags |= PROMPT_ISPANE;
cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type); 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,
cdata->prompts[0].input, cmd_command_prompt_callback,
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_pane *wp; struct window *w = s->curw->window;
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;
} }
@@ -978,15 +994,20 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
} else if (cmd_find_from_client(&current, cmdq_get_client(item), } else if (cmd_find_from_client(&current, cmdq_get_client(item),
flags) == 0) { flags) == 0) {
fs->current = &current; fs->current = &current;
/* No active pane, window empty, return the window instead. */
if (current.wp == NULL) {
type = CMD_FIND_WINDOW;
}
log_debug("%s: current is from client", __func__); log_debug("%s: current is from client", __func__);
} else { } else {
if (~flags & CMD_FIND_QUIET) if (~flags & CMD_FIND_QUIET)
cmdq_error(item, "no current target"); cmdq_error(item, "no current target");
goto error; goto error;
} }
/*
if (!cmd_find_valid_state(fs->current)) if (!cmd_find_valid_state(fs->current))
fatalx("invalid current find state"); fatalx("invalid current find state");
*/
/* An empty or NULL target is the current. */ /* An empty or NULL target is the current. */
if (target == NULL || *target == '\0') if (target == NULL || *target == '\0')
goto current; goto current;
@@ -995,7 +1016,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
strcmp(target, "{active}") == 0 || strcmp(target, "{active}") == 0 ||
strcmp(target, "{current}") == 0) { strcmp(target, "{current}") == 0) {
c = cmdq_get_client(item); c = cmdq_get_client(item);
if (c == NULL) { if (c == NULL || c->session == NULL) {
cmdq_error(item, "no current client"); cmdq_error(item, "no current client");
goto error; goto error;
} }
@@ -1025,7 +1046,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
fs->w = fs->wl->window; fs->w = fs->wl->window;
fs->wp = fs->w->active; fs->wp = fs->w->active;
} }
break; goto found;
} }
if (fs->wp == NULL) { if (fs->wp == NULL) {
if (~flags & CMD_FIND_QUIET) if (~flags & CMD_FIND_QUIET)

213
cmd-hide-pane.c Normal file
View File

@@ -0,0 +1,213 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Michael Grant <mgrant@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 <stdlib.h>
#include <string.h>
#include "tmux.h"
/*
* Hide or show panes.
*/
static enum cmd_retval cmd_hide_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_show_pane_exec(struct cmd *, struct cmdq_item *);
const struct cmd_entry cmd_hide_pane_entry = {
.name = "hide-pane",
.alias = "hidep",
.args = { "at:", 0, 1, NULL },
.usage = "[-a] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
.flags = CMD_AFTERHOOK,
.exec = cmd_hide_pane_exec
};
const struct cmd_entry cmd_show_pane_entry = {
.name = "show-pane",
.alias = "showp",
.args = { "at:", 0, 1, NULL },
.usage = "[-a] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
.flags = CMD_AFTERHOOK,
.exec = cmd_show_pane_exec
};
static enum cmd_retval
cmd_hide_pane_hide(struct window *w, struct window_pane *wp)
{
struct window_pane *wpp = NULL;
struct layout_cell *lc = wp->layout_cell;
if (window_pane_is_hidden(wp))
return (CMD_RETURN_NORMAL);
if (wp == w->active) {
/* Find previous active pane. */
TAILQ_FOREACH(wpp, &w->last_panes, sentry) {
if (wpp != wp && window_pane_is_visible(wpp))
break;
}
if (wpp == NULL) {
TAILQ_FOREACH(wpp, &w->z_index, zentry) {
if (wpp != wp &&
window_pane_is_visible(wpp))
break;
}
}
}
lc->flags |= LAYOUT_CELL_HIDDEN;
layout_remove_tile(w, lc);
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
window_pane_stack_remove(&w->last_panes, wp);
if (wpp != NULL) {
window_set_active_pane(w, wpp, 1);
} else if (wp == w->active) {
/* No visible previous active pane; null active pane
* to show dots background. */
w->active = NULL;
if (options_get_number(global_options, "focus-events"))
window_pane_update_focus(wp);
notify_window("window-pane-changed", w);
notify_window("window-layout-changed", w);
server_redraw_window(w);
} else {
notify_window("window-layout-changed", w);
server_redraw_window(w);
}
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_hide_pane_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct window_pane *wp, *active_pane = w->active;
u_int id;
char *cause = NULL;
enum cmd_retval rv;
window_unzoom(w, 1);
if (args_has(args, 'a')) {
TAILQ_FOREACH(wp, &w->z_index, zentry) {
if (!window_pane_is_visible(wp) || wp == active_pane)
continue;
rv = cmd_hide_pane_hide(w, wp);
if (rv != CMD_RETURN_NORMAL)
return (rv);
}
return (CMD_RETURN_NORMAL);
} else {
wp = target->wp;
if (wp == NULL) {
id = args_strtonum_and_expand(args, 't', 0, INT_MAX,
item, &cause);
if (cause != NULL) {
cmdq_error(item, "%s target pane", cause);
return (CMD_RETURN_ERROR);
}
wp = window_pane_find_by_id(id);
}
if (wp == NULL) {
cmdq_error(item, "No target pane to hide.");
return (CMD_RETURN_ERROR);
}
return (cmd_hide_pane_hide(w, wp));
}
}
static enum cmd_retval
cmd_hide_pane_show(struct window *w, struct window_pane *wp)
{
struct layout_cell *lc = wp->layout_cell;
if (!window_pane_is_hidden(wp))
return (CMD_RETURN_NORMAL);
lc->flags &= ~LAYOUT_CELL_HIDDEN;
layout_insert_tile(w, lc);
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
window_set_active_pane(w, wp, 1);
notify_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_show_pane_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct window_pane *wp;
u_int id;
char *cause = NULL;
enum cmd_retval rv;
window_unzoom(w, 1);
if (args_has(args, 'a')) {
TAILQ_FOREACH(wp, &w->z_index, zentry) {
if (!window_pane_is_visible(wp))
continue;
rv = cmd_hide_pane_show(w, wp);
if (rv != CMD_RETURN_NORMAL)
return (rv);
}
return (CMD_RETURN_NORMAL);
} else {
wp = target->wp;
if (wp == NULL) {
id = args_strtonum_and_expand(args, 't', 0, INT_MAX,
item, &cause);
if (cause != NULL) {
cmdq_error(item, "%s target pane", cause);
return (CMD_RETURN_ERROR);
}
wp = window_pane_find_by_id(id);
}
if (wp == NULL) {
cmdq_error(item, "No target pane to show.");
return (CMD_RETURN_ERROR);
}
return (cmd_hide_pane_show(w, wp));
}
}

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 = { "bdD::fhMvl: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,344 @@ 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
cmd_join_pane_tile(struct cmdq_item *item, struct args *args, struct window *w,
struct window_pane *wp)
{
struct layout_cell *lc = wp->layout_cell;
if (!window_pane_is_floating(wp)) {
cmdq_error(item, "pane is not floating");
return (CMD_RETURN_ERROR);
}
if (window_pane_is_hidden(wp)) {
cmdq_error(item, "can't tile a hidden pane");
return (CMD_RETURN_ERROR);
}
if (w->flags & WINDOW_ZOOMED) {
cmdq_error(item, "can't tile a pane while window is zoomed");
return (CMD_RETURN_ERROR);
}
layout_save_size(lc);
lc->flags &= ~LAYOUT_CELL_FLOATING;
if (layout_insert_tile(w, lc) == 0) {
cmdq_error(item, "no space for a new pane");
return (CMD_RETURN_ERROR);
}
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
if (!args_has(args, 'd'))
window_set_active_pane(w, wp, 1);
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
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,9 +413,10 @@ 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, *src_lc;
dst_s = target->s; dst_s = target->s;
dst_wl = target->wl; dst_wl = target->wl;
@@ -81,11 +425,35 @@ 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_lc = src_wp->layout_cell;
src_w = src_wl->window; src_w = src_wl->window;
server_unzoom_window(src_w); server_unzoom_window(src_w);
if (!args_has(args, 't') && window_pane_is_floating(src_wp))
return (cmd_join_pane_tile(item, args, src_w, src_wp));
if (src_wp == dst_wp) { if (src_wp == dst_wp) {
cmdq_error(item, "source and target panes must be different"); cmdq_error(item, "source and target panes must be different");
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
@@ -97,6 +465,12 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
free(cause); free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (window_pane_is_floating(src_wp)) {
lc->saved_sx = src_lc->sx;
lc->saved_sy = src_lc->sy;
lc->saved_xoff = src_lc->xoff;
lc->saved_yoff = src_lc->yoff;
}
layout_close_pane(src_wp); layout_close_pane(src_wp);

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,22 +50,17 @@ 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 (args_has(args, 'a')) { if (filter != NULL && !args_has(args, 'a')) {
server_unzoom_window(wl->window); cmdq_error(item, "-f only valid with -a");
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) { return (CMD_RETURN_ERROR);
if (loopwp == wp)
continue;
server_client_remove_pane(loopwp);
layout_close_pane(loopwp);
window_remove_pane(wl->window, loopwp);
}
server_redraw_window(wl->window);
return (CMD_RETURN_NORMAL);
} }
if (args_has(args, 'a'))
return (cmd_kill_pane_all(item, filter));
if (wp == NULL) { if (wp == NULL) {
cmdq_error(item, "no active pane to kill"); cmdq_error(item, "no active pane to kill");
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
@@ -69,3 +68,48 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item)
server_kill_pane(wp); server_kill_pane(wp);
return (CMD_RETURN_NORMAL); 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;
server_unzoom_window(wl->window);
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
if (loopwp == wp)
continue;
if (!cmd_kill_pane_filter(item, s, wl, loopwp, filter))
continue;
server_client_remove_pane(loopwp);
layout_close_pane(loopwp);
window_remove_pane(wl->window, loopwp);
}
server_redraw_window(wl->window);
return (CMD_RETURN_NORMAL);
}
static int
cmd_kill_pane_filter(struct cmdq_item *item, struct session *s,
struct winlink *wl, struct window_pane *wp, 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, 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,39 +82,76 @@ 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'))
if (RB_PREV(winlinks, &s->windows, wl) == NULL && return (cmd_kill_window_all(item, filter));
RB_NEXT(winlinks, &s->windows, wl) == NULL)
return (CMD_RETURN_NORMAL);
/* Kill all windows except the current one. */
do {
found = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window != wl->window) {
server_kill_window(loop->window, 0);
found++;
break;
}
}
} while (found != 0);
/*
* If the current window appears in the session more than once,
* kill it as well.
*/
found = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window == wl->window)
found++;
}
if (found > 1)
server_kill_window(wl->window, 0);
server_renumber_all();
return (CMD_RETURN_NORMAL);
}
server_kill_window(wl->window, 1); server_kill_window(wl->window, 1);
return (CMD_RETURN_NORMAL); 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 &&
RB_NEXT(winlinks, &s->windows, wl) == NULL)
return (CMD_RETURN_NORMAL);
/* Kill all windows except the current one. */
do {
found = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window != wl->window &&
cmd_kill_window_filter(item, s, loop, filter)) {
server_kill_window(loop->window, 0);
found++;
break;
}
}
} while (found != 0);
/*
* If the current window appears in the session more than once, kill it
* as well if it matches the filter.
*/
found = kill_current = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window == wl->window) {
found++;
if (cmd_kill_window_filter(item, s, loop, filter))
kill_current = 1;
}
}
if (kill_current && found > 1)
server_kill_window(wl->window, 0);
server_renumber_all();
return (CMD_RETURN_NORMAL);
}
static int
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,11 +223,16 @@ 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);
format_defaults(ft, NULL, NULL, NULL, NULL); format_defaults(ft, tc, NULL, NULL, NULL);
format_add(ft, "notes_only", "%d", notes_only); format_add(ft, "notes_only", "%d", notes_only);
format_add(ft, "key_has_repeat", "%d", key_bindings_has_repeat(l, n)); format_add(ft, "key_has_repeat", "%d", key_bindings_has_repeat(l, n));
format_add(ft, "key_string_width", "%u", cmd_list_keys_get_width(l, n)); format_add(ft, "key_string_width", "%u", cmd_list_keys_get_width(l, n));
@@ -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

@@ -101,22 +101,22 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
if ((tmp = args_get(args, 'n')) != NULL) { if ((tmp = args_get(args, 'n')) != NULL) {
ename = format_single(item, tmp, c, NULL, NULL, NULL); ename = format_single(item, tmp, c, NULL, NULL, NULL);
if (!check_name(ename, WINDOW_NAME_FORBID)) { if (!check_name(ename)) {
cmdq_error(item, "invalid window name: %s", ename); cmdq_error(item, "invalid window name: %s", ename);
free(ename); free(ename);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
wname = clean_name(ename, WINDOW_NAME_FORBID); wname = clean_name(ename, 0);
free(ename); free(ename);
} }
if ((tmp = args_get(args, 's')) != NULL) { if ((tmp = args_get(args, 's')) != NULL) {
ename = format_single(item, tmp, c, NULL, NULL, NULL); ename = format_single(item, tmp, c, NULL, NULL, NULL);
if (!check_name(ename, SESSION_NAME_FORBID)) { if (!check_name(ename)) {
cmdq_error(item, "invalid session name: %s", ename); cmdq_error(item, "invalid session name: %s", ename);
free(ename); free(ename);
goto fail; goto fail;
} }
sname = clean_name(ename, SESSION_NAME_FORBID); sname = clean_name(ename, 0);
free(ename); free(ename);
} }
if (args_has(args, 'A')) { if (args_has(args, 'A')) {
@@ -152,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 {
if (!check_name(group, SESSION_NAME_FORBID)) { if (!check_name(group)) {
cmdq_error(item, cmdq_error(item,
"invalid session group name: %s", group); "invalid session group name: %s", group);
goto fail; goto fail;
} }
prefix = clean_name(group, SESSION_NAME_FORBID); prefix = clean_name(group, 0);
} }
} }

View File

@@ -73,12 +73,12 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
name = args_get(args, 'n'); name = args_get(args, 'n');
if (name != NULL) { 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)) { if (!check_name(expanded)) {
cmdq_error(item, "invalid window name: %s", expanded); cmdq_error(item, "invalid window name: %s", expanded);
free(expanded); free(expanded);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
wname = clean_name(expanded, WINDOW_NAME_FORBID); wname = clean_name(expanded, 0);
free(expanded); free(expanded);
} }
if (args_has(args, 'S') && wname != NULL && target->idx == -1) { if (args_has(args, 'S') && wname != NULL && target->idx == -1) {

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));
if (!check_name(tmp, SESSION_NAME_FORBID)) { if (!check_name(tmp)) {
cmdq_error(item, "invalid session name: %s", tmp); cmdq_error(item, "invalid session name: %s", tmp);
free(tmp); free(tmp);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
newname = clean_name(tmp, SESSION_NAME_FORBID); newname = clean_name(tmp, 0);
free(tmp); free(tmp);
if (strcmp(newname, s->name) == 0) { if (strcmp(newname, s->name) == 0) {
free(newname); free(newname);

View File

@@ -51,13 +51,13 @@ cmd_rename_window_exec(struct cmd *self, struct cmdq_item *item)
char *name; char *name;
name = format_single_from_target(item, args_string(args, 0)); name = format_single_from_target(item, args_string(args, 0));
if (!check_name(name, WINDOW_NAME_FORBID)) { if (!check_name(name)) {
cmdq_error(item, "invalid window name: %s", name); cmdq_error(item, "invalid window name: %s", name);
free(name); free(name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
window_set_name(wl->window, name, WINDOW_NAME_FORBID); window_set_name(wl->window, name, 0);
options_set_number(wl->window->options, "automatic-rename", 0); options_set_number(wl->window->options, "automatic-rename", 0);
free(name); free(name);

View File

@@ -18,6 +18,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <ctype.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -31,18 +32,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 +59,20 @@ 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;
int adjust, x, y, status, opposite = 0;
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,31 @@ 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);
} }
layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x); if (window_pane_is_floating(wp)) {
if (layout_resize_floating_pane_to(wp, LAYOUT_LEFTRIGHT,
x, &cause) != 0) {
cmdq_error(item, "size %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
} else
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 +128,62 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
y++; y++;
break; break;
} }
layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y); if (window_pane_is_floating(wp)) {
if (layout_resize_floating_pane_to(wp, LAYOUT_TOPBOTTOM,
y, &cause) != 0) {
cmdq_error(item, "size %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
} else
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); if (args_count(args) == 0)
server_redraw_window(wl->window); argval = "1";
else
argval = args_string(args, 0);
}
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;
if (layout_resize_floating_pane(wp, type, adjust,
opposite, &cause) != 0) {
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,28 +206,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 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);
@@ -193,15 +247,13 @@ 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; left = wp->xoff - 1;
right = wp->xoff + sx; right = wp->xoff + sx;
if (window_pane_show_scrollbar(wp, scrollbars) && if (window_pane_scrollbar_reserve(wp) &&
sb_pos == PANE_SCROLLBARS_LEFT) { w->sb_pos == PANE_SCROLLBARS_LEFT) {
left -= wp->scrollbar_style.width + wp->scrollbar_style.pad; left -= wp->scrollbar_style.width + wp->scrollbar_style.pad;
} else if (window_pane_show_scrollbar(wp, scrollbars) && } else if (window_pane_scrollbar_reserve(wp) &&
sb_pos == PANE_SCROLLBARS_RIGHT) { w->sb_pos == PANE_SCROLLBARS_RIGHT) {
right += wp->scrollbar_style.width + wp->scrollbar_style.pad; right += wp->scrollbar_style.width + wp->scrollbar_style.pad;
} }
@@ -300,7 +352,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;

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) {
free(event->buf); if (server_client_handle_key(tc, event) != 0)
free(event); return (item);
} else {
if (server_client_handle_key_after(tc, event, after,
&new_after) != 0)
return (new_after);
} }
free(event->buf);
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,15 +85,17 @@ 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)
is_floating = !args_has(args, 'L'); is_floating = !args_has(args, 'L');
else else
is_floating = 0; is_floating = window_pane_is_floating(wp);
flags = is_floating ? SPAWN_FLOATING : 0; flags = is_floating ? SPAWN_FLOATING : 0;
if (args_has(args, 'b')) if (args_has(args, 'b'))
@@ -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,9 +196,11 @@ 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')) {
@@ -195,6 +209,12 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
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) {
switch (window_pane_start_input(new_wp, item, &cause)) { switch (window_pane_start_input(new_wp, item, &cause)) {
@@ -205,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;
@@ -239,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);
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); 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

@@ -79,12 +79,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
if (src_wp == dst_wp) if (src_wp == dst_wp)
goto out; goto out;
if (window_pane_is_floating(src_wp) ||
window_pane_is_floating(dst_wp)) {
cmdq_error(item, "cannot swap floating panes");
return (CMD_RETURN_ERROR);
}
server_client_remove_pane(src_wp); server_client_remove_pane(src_wp);
server_client_remove_pane(dst_wp); server_client_remove_pane(dst_wp);
@@ -114,10 +108,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 +143,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);

6
cmd.c
View File

@@ -69,6 +69,7 @@ extern const struct cmd_entry cmd_load_buffer_entry;
extern const struct cmd_entry cmd_lock_client_entry; extern const struct cmd_entry cmd_lock_client_entry;
extern const struct cmd_entry cmd_lock_server_entry; extern const struct cmd_entry cmd_lock_server_entry;
extern const struct cmd_entry cmd_lock_session_entry; extern const struct cmd_entry cmd_lock_session_entry;
extern const struct cmd_entry cmd_hide_pane_entry;
extern const struct cmd_entry cmd_move_pane_entry; extern const struct cmd_entry cmd_move_pane_entry;
extern const struct cmd_entry cmd_move_window_entry; extern const struct cmd_entry cmd_move_window_entry;
extern const struct cmd_entry cmd_new_pane_entry; extern const struct cmd_entry cmd_new_pane_entry;
@@ -106,6 +107,7 @@ extern const struct cmd_entry cmd_show_environment_entry;
extern const struct cmd_entry cmd_show_hooks_entry; extern const struct cmd_entry cmd_show_hooks_entry;
extern const struct cmd_entry cmd_show_messages_entry; extern const struct cmd_entry cmd_show_messages_entry;
extern const struct cmd_entry cmd_show_options_entry; extern const struct cmd_entry cmd_show_options_entry;
extern const struct cmd_entry cmd_show_pane_entry;
extern const struct cmd_entry cmd_show_prompt_history_entry; extern const struct cmd_entry cmd_show_prompt_history_entry;
extern const struct cmd_entry cmd_show_window_options_entry; extern const struct cmd_entry cmd_show_window_options_entry;
extern const struct cmd_entry cmd_source_file_entry; extern const struct cmd_entry cmd_source_file_entry;
@@ -115,6 +117,7 @@ extern const struct cmd_entry cmd_suspend_client_entry;
extern const struct cmd_entry cmd_swap_pane_entry; extern const struct cmd_entry cmd_swap_pane_entry;
extern const struct cmd_entry cmd_swap_window_entry; extern const struct cmd_entry cmd_swap_window_entry;
extern const struct cmd_entry cmd_switch_client_entry; extern const struct cmd_entry cmd_switch_client_entry;
extern const struct cmd_entry cmd_switch_mode_entry;
extern const struct cmd_entry cmd_unbind_key_entry; extern const struct cmd_entry cmd_unbind_key_entry;
extern const struct cmd_entry cmd_unlink_window_entry; extern const struct cmd_entry cmd_unlink_window_entry;
extern const struct cmd_entry cmd_wait_for_entry; extern const struct cmd_entry cmd_wait_for_entry;
@@ -162,6 +165,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_lock_client_entry, &cmd_lock_client_entry,
&cmd_lock_server_entry, &cmd_lock_server_entry,
&cmd_lock_session_entry, &cmd_lock_session_entry,
&cmd_hide_pane_entry,
&cmd_move_pane_entry, &cmd_move_pane_entry,
&cmd_move_window_entry, &cmd_move_window_entry,
&cmd_new_pane_entry, &cmd_new_pane_entry,
@@ -199,6 +203,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_show_hooks_entry, &cmd_show_hooks_entry,
&cmd_show_messages_entry, &cmd_show_messages_entry,
&cmd_show_options_entry, &cmd_show_options_entry,
&cmd_show_pane_entry,
&cmd_show_prompt_history_entry, &cmd_show_prompt_history_entry,
&cmd_show_window_options_entry, &cmd_show_window_options_entry,
&cmd_source_file_entry, &cmd_source_file_entry,
@@ -208,6 +213,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_swap_pane_entry, &cmd_swap_pane_entry,
&cmd_swap_window_entry, &cmd_swap_window_entry,
&cmd_switch_client_entry, &cmd_switch_client_entry,
&cmd_switch_mode_entry,
&cmd_unbind_key_entry, &cmd_unbind_key_entry,
&cmd_unlink_window_entry, &cmd_unlink_window_entry,
&cmd_wait_for_entry, &cmd_wait_for_entry,

166
colour.c
View File

@@ -26,6 +26,85 @@
#include "tmux.h" #include "tmux.h"
/* Theme colour slots and their server options. */
static const struct {
const char *name;
const char *dark_option;
const char *light_option;
int terminal_colour;
} colour_theme_table[] = {
{ "themeblack",
"dark-theme-black",
"light-theme-black",
0
},
{ "themewhite",
"dark-theme-white",
"light-theme-white",
7
},
{ "themelightgrey",
"dark-theme-light-grey",
"light-theme-light-grey",
7
},
{ "themedarkgrey",
"dark-theme-dark-grey",
"light-theme-dark-grey",
0
},
{ "themegreen",
"dark-theme-green",
"light-theme-green",
2
},
{ "themeyellow",
"dark-theme-yellow",
"light-theme-yellow",
3
},
{ "themered",
"dark-theme-red",
"light-theme-red",
1
},
{ "themeblue",
"dark-theme-blue",
"light-theme-blue",
4
},
{ "themecyan",
"dark-theme-cyan",
"light-theme-cyan",
6
},
{ "thememagenta",
"dark-theme-magenta",
"light-theme-magenta",
5
}
};
/* Get theme colour option. */
const char *
colour_theme_option(u_int n, enum client_theme theme)
{
if (n >= nitems(colour_theme_table))
return (NULL);
if (theme == THEME_LIGHT)
return (colour_theme_table[n].light_option);
return (colour_theme_table[n].dark_option);
}
/* Get theme terminal colour. */
int
colour_theme_terminal_colour(u_int n)
{
if (n >= nitems(colour_theme_table))
return (8);
return (colour_theme_table[n].terminal_colour);
}
static int static int
colour_dist_sq(int R, int G, int B, int r, int g, int b) colour_dist_sq(int R, int G, int B, int r, int g, int b)
{ {
@@ -120,6 +199,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) || (c & COLOUR_FLAG_THEME))
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)
@@ -130,6 +231,13 @@ colour_tostring(int c)
if (c == -1) if (c == -1)
return ("none"); return ("none");
if (c & COLOUR_FLAG_THEME) {
c &= 0xff;
if (c >= 0 && (u_int)c < nitems(colour_theme_table))
return (colour_theme_table[c].name);
return ("invalid");
}
if (c & COLOUR_FLAG_RGB) { if (c & COLOUR_FLAG_RGB) {
colour_split_rgb(c, &r, &g, &b); colour_split_rgb(c, &r, &g, &b);
xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b); xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b);
@@ -182,6 +290,58 @@ colour_tostring(int c)
return ("invalid"); return ("invalid");
} }
/* Convert colour to an SGR escape sequence. */
const char *
colour_toescape(struct client *c, int colour, int bg)
{
static char s[32];
u_char r, g, b;
int n, flags = (TERM_256COLOURS|TERM_RGBCOLOURS);
u_int o = (bg ? 40 : 30);
if (c != NULL && (c->tty.flags & TTY_OPENED) && c->tty.term != NULL)
flags = c->tty.term->flags;
if (colour & COLOUR_FLAG_THEME) {
n = colour & 0xff;
if (c != NULL && (u_int)n < COLOUR_THEME_COUNT)
colour = c->theme_colours[n];
else
colour = colour_theme_terminal_colour(n);
}
if (colour == 8 || colour == 9) {
xsnprintf(s, sizeof s, "\033[%dm", o + 9);
return (s);
}
if ((~flags & TERM_RGBCOLOURS) & (colour & COLOUR_FLAG_RGB)) {
colour_split_rgb(colour, &r, &g, &b);
colour = colour_find_rgb(r, g, b);
}
if ((~flags & TERM_256COLOURS) & (colour & COLOUR_FLAG_256))
colour = colour_256to16(colour);
if (colour & COLOUR_FLAG_RGB) {
colour_split_rgb(colour, &r, &g, &b);
xsnprintf(s, sizeof s, "\033[%d;2;%u;%u;%um", o + 8, r, g, b);
return (s);
}
if (colour & COLOUR_FLAG_256) {
xsnprintf(s, sizeof s, "\033[%d;5;%um", o + 8, colour & 0xff);
return (s);
}
if (colour >= 0 && colour <= 7) {
xsnprintf(s, sizeof s, "\033[%dm", colour + o);
return (s);
}
if (colour >= 90 && colour <= 97) {
xsnprintf(s, sizeof s, "\033[%dm", colour + o - 30);
return (s);
}
return (NULL);
}
/* Convert background colour to theme. */ /* Convert background colour to theme. */
enum client_theme enum client_theme
colour_totheme(int c) colour_totheme(int c)
@@ -230,6 +390,7 @@ colour_fromstring(const char *s)
const char *cp; const char *cp;
int n; int n;
u_char r, g, b; u_char r, g, b;
u_int i;
if (*s == '#' && strlen(s) == 7) { if (*s == '#' && strlen(s) == 7) {
for (cp = s + 1; isxdigit((u_char) *cp); cp++) for (cp = s + 1; isxdigit((u_char) *cp); cp++)
@@ -260,6 +421,11 @@ colour_fromstring(const char *s)
if (strcasecmp(s, "terminal") == 0) if (strcasecmp(s, "terminal") == 0)
return (9); return (9);
for (i = 0; i < nitems(colour_theme_table); i++) {
if (strcasecmp(s, colour_theme_table[i].name) == 0)
return (i|COLOUR_FLAG_THEME);
}
if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0) if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0)
return (0); return (0);
if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0) if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0)

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

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

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)

View File

@@ -710,6 +710,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
"AFTER" }; "AFTER" };
size_t size = strlen(expanded); size_t size = strlen(expanded);
struct screen *os = octx->s, s[TOTAL]; struct screen *os = octx->s, s[TOTAL];
struct hyperlinks *hl = os->hyperlinks;
struct screen_write_ctx ctx[TOTAL]; struct screen_write_ctx ctx[TOTAL];
u_int ocx = os->cx, ocy = os->cy, n, i, width[TOTAL]; u_int ocx = os->cx, ocy = os->cy, n, i, width[TOTAL];
u_int map[] = { LEFT, u_int map[] = { LEFT,
@@ -723,7 +724,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
struct grid_cell gc, current_default, base_default; struct grid_cell gc, current_default, base_default;
struct style sy, saved_sy; struct style sy, saved_sy;
struct utf8_data *ud = &sy.gc.data; struct utf8_data *ud = &sy.gc.data;
const char *cp, *end; const char *cp, *end, *link_uri;
enum utf8_state more; enum utf8_state more;
char *tmp; char *tmp;
struct format_range *fr = NULL, *fr1; struct format_range *fr = NULL, *fr1;
@@ -838,6 +839,17 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
sy.gc.fg = base->fg; sy.gc.fg = base->fg;
} }
/*
* Resolve any hyperlink and store it in the cell. The URI
* doubles as the internal ID so repeated links share one entry
* and the ID stays stable across redraws.
*/
link_uri = style_link(&sy);
if (link_uri != NULL && hl != NULL)
sy.gc.link = hyperlinks_put(hl, link_uri, link_uri);
else
sy.gc.link = 0;
/* If this style has a fill colour, store it for later. */ /* If this style has a fill colour, store it for later. */
if (sy.fill != 8) if (sy.fill != 8)
fill = sy.fill; fill = sy.fill;

419
format.c
View File

@@ -117,6 +117,12 @@ 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
#define FORMAT_COLOUR_ESC_FG 0x8000000
#define FORMAT_COLOUR_ESC_BG 0x10000000
/* Limit on recursion. */ /* Limit on recursion. */
#define FORMAT_LOOP_LIMIT 100 #define FORMAT_LOOP_LIMIT 100
@@ -124,6 +130,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 +578,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 +717,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 +771,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 +835,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 +1010,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 +1025,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 +1062,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 +1094,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 +1134,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 +1162,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 +1190,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 +1479,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 +1885,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)
@@ -2144,6 +2169,20 @@ format_cb_pane_height(struct format_tree *ft)
return (NULL); return (NULL);
} }
/* Callback for pane_hidden_flag. */
static void *
format_cb_pane_hidden_flag(struct format_tree *ft)
{
struct window_pane *wp = ft->wp;
if (wp != NULL) {
if (window_pane_is_hidden(wp))
return (xstrdup("1"));
return (xstrdup("0"));
}
return (NULL);
}
/* Callback for pane_id. */ /* Callback for pane_id. */
static void * static void *
format_cb_pane_id(struct format_tree *ft) format_cb_pane_id(struct format_tree *ft)
@@ -3218,6 +3257,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 +3386,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
}, },
@@ -3449,6 +3488,9 @@ static const struct format_table_entry format_table[] = {
{ "pane_height", FORMAT_TABLE_STRING, { "pane_height", FORMAT_TABLE_STRING,
format_cb_pane_height format_cb_pane_height
}, },
{ "pane_hidden_flag", FORMAT_TABLE_STRING,
format_cb_pane_hidden_flag
},
{ "pane_id", FORMAT_TABLE_STRING, { "pane_id", FORMAT_TABLE_STRING,
format_cb_pane_id format_cb_pane_id
}, },
@@ -3774,7 +3816,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 +4105,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 +4226,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 +4277,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 +4295,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)
{ {
char *out, *cp; const char *end = s + n;
int brackets = 0; char *out, *cp;
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 +4331,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 +4359,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 +4472,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/
@@ -4398,7 +4491,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
break; break;
/* Check single character modifiers with no arguments. */ /* Check single character modifiers with no arguments. */
if (strchr("labcdnwETSWPL!<>", cp[0]) != NULL && if (strchr("labdnwETSWPL!<>", cp[0]) != NULL &&
format_is_end(cp[1])) { format_is_end(cp[1])) {
format_add_modifier(&list, count, cp, 1, NULL, 0); format_add_modifier(&list, count, cp, 1, NULL, 0);
cp++; cp++;
@@ -4420,7 +4513,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=pReqWc", cp[0]) == NULL)
break; break;
c = cp[0]; c = cp[0];
@@ -4440,7 +4533,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 +4557,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);
@@ -4481,6 +4574,47 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
return (list); return (list);
} }
/* Match using the fuzzy matcher. */
static char *
format_match_fuzzy(const char *pattern, const char *text, int positions)
{
struct evbuffer *buffer;
bitstr_t *bs;
char *value;
size_t size;
u_int i, width;
width = format_width(text);
if (width == 0)
width = 1;
bs = fuzzy_match(pattern, text, width, NULL);
if (bs == NULL)
return (xstrdup(positions ? "" : "0"));
if (!positions) {
free(bs);
return (xstrdup("1"));
}
buffer = evbuffer_new();
if (buffer == NULL)
fatalx("out of memory");
for (i = 0; i < width; i++) {
if (!bit_test(bs, i))
continue;
if (EVBUFFER_LENGTH(buffer) != 0)
evbuffer_add(buffer, ",", 1);
evbuffer_add_printf(buffer, "%u", i);
}
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
value = xmemdup(EVBUFFER_DATA(buffer), size);
else
value = xstrdup("");
evbuffer_free(buffer);
free(bs);
return (value);
}
/* Match against an fnmatch(3) pattern or regular expression. */ /* Match against an fnmatch(3) pattern or regular expression. */
static char * static char *
format_match(struct format_modifier *fm, const char *pattern, const char *text) format_match(struct format_modifier *fm, const char *pattern, const char *text)
@@ -4491,6 +4625,10 @@ format_match(struct format_modifier *fm, const char *pattern, const char *text)
if (fm->argc >= 1) if (fm->argc >= 1)
s = fm->argv[0]; s = fm->argv[0];
if (strchr(s, 'p') != NULL)
return (format_match_fuzzy(pattern, text, 1));
if (strchr(s, 'z') != NULL)
return (format_match_fuzzy(pattern, text, 0));
if (strchr(s, 'r') == NULL) { if (strchr(s, 'r') == NULL) {
if (strchr(s, 'i') != NULL) if (strchr(s, 'i') != NULL)
flags |= FNM_CASEFOLD; flags |= FNM_CASEFOLD;
@@ -4627,17 +4765,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 +4789,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 +4879,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 +4895,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 +4908,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 +4929,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 +4955,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 +4970,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 +4982,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 +5018,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 +5213,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;
@@ -5136,6 +5291,12 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
break; break;
case 'c': case 'c':
modifiers |= FORMAT_COLOUR; modifiers |= FORMAT_COLOUR;
if (fm->argc < 1)
break;
if (strchr(fm->argv[0], 'f') != NULL)
modifiers |= FORMAT_COLOUR_ESC_FG;
if (strchr(fm->argv[0], 'b') != NULL)
modifiers |= FORMAT_COLOUR_ESC_BG;
break; break;
case 'd': case 'd':
modifiers |= FORMAT_DIRNAME; modifiers |= FORMAT_DIRNAME;
@@ -5143,16 +5304,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 +5441,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;
} }
@@ -5289,11 +5495,28 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
/* Is this a colour? */ /* Is this a colour? */
if (modifiers & FORMAT_COLOUR) { if (modifiers & FORMAT_COLOUR) {
new = format_expand1(es, copy); new = format_expand1(es, copy);
c = colour_fromstring(new); if (modifiers & (FORMAT_COLOUR_ESC_FG|FORMAT_COLOUR_ESC_BG)) {
if (c == -1 || (c = colour_force_rgb(c)) == -1) if (strcasecmp(new, "none") == 0)
value = xstrdup(""); value = xstrdup("\033[0m");
else else if ((c = colour_fromstring(new)) == -1)
xasprintf(&value, "%06x", c & 0xffffff); value = xstrdup("");
else {
if (modifiers & FORMAT_COLOUR_ESC_BG)
cp = colour_toescape(ft->c, c, 1);
else
cp = colour_toescape(ft->c, c, 0);
if (cp == NULL)
value = xstrdup("");
else
value = xstrdup(cp);
}
} else {
c = colour_fromstring(new);
if (c == -1 || (c = colour_force_rgb(c)) == -1)
value = xstrdup("");
else
xasprintf(&value, "%06x", c & 0xffffff);
}
free(new); free(new);
goto done; goto done;
} }
@@ -5346,7 +5569,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 +5849,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) {

655
fuzzy.c Normal file
View File

@@ -0,0 +1,655 @@
/* $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 <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
/*
* Fuzzy matching in the style of fzf. The pattern is split into groups by |
* and each group is split on spaces into terms. A row matches if any group
* matches; within a group all positive terms must match and all inverse terms
* must not match.
*
* Plain positive terms are fuzzy subsequences. A leading ' makes a term an
* exact substring match, ^ anchors a term at the start and $ anchors it at
* the end. A leading ! inverts the term. Plain inverse terms are exact
* substring matches rather than inverse fuzzy matches, like fzf.
*
* Both the pattern and the text are UTF-8. The text may contain tmux style
* directives (#[...]); these and their contents are invisible to matching and
* occupy no columns, but align= styles do move the surrounding text and are
* accounted for exactly as format_draw lays it out (the no-list layout, see
* format_draw_none). Matching is smart-case: case is ignored unless the pattern
* contains an uppercase character (ASCII case folding only; other characters
* are compared exactly by their UTF-8 data).
*
* On a match a bitstr_t of the requested display width is returned with a bit
* set for every column occupied by a matched character, so the caller can
* highlight them; NULL is returned if there is no match. A cheap fzf-style
* score (matches at the start, after word boundaries and in contiguous runs
* score higher) is also produced so callers can rank best-match-first.
*/
#define FUZZY_BONUS_EXACT 1000
#define FUZZY_BONUS_PREFIX 200
#define FUZZY_BONUS_SUFFIX 100
#define FUZZY_BONUS_START 12
#define FUZZY_BONUS_BOUNDARY 8
#define FUZZY_BONUS_CONSECUTIVE 6
#define FUZZY_PENALTY_LEADING 1
#define FUZZY_PENALTY_LEADING_MAX 10
#define FUZZY_PENALTY_GAP 1
/* A single visible character of the text. */
struct fuzzy_char {
enum style_align align;
struct utf8_data ud; /* original UTF-8 data */
u_int width; /* display width */
u_int offset; /* within its alignment */
};
/* One parsed query term. */
struct fuzzy_term {
int inverse;
int exact;
int prefix;
int suffix;
const char *text;
size_t len;
};
/* Is this character a word boundary, so a match after it scores higher? */
static int
fuzzy_is_boundary(const struct utf8_data *ud)
{
static const char *boundary = " -_/.:";
if (ud->size != 1)
return (0);
return (strchr(boundary, ud->data[0]) != NULL);
}
/*
* Compare two characters, folding ASCII case if wanted. UTF-8 is compared
* directly without case folding.
*/
static int
fuzzy_char_equal(const struct utf8_data *a, const struct utf8_data *b, int fold)
{
if (fold &&
a->size == 1 &&
b->size == 1 &&
a->data[0] < 0x80 &&
b->data[0] < 0x80)
return (tolower(a->data[0]) == tolower(b->data[0]));
return (a->size == b->size && memcmp(a->data, b->data, a->size) == 0);
}
/* Map a style alignment onto one of the four layout columns. */
static enum style_align
fuzzy_align(enum style_align align)
{
if (align == STYLE_ALIGN_DEFAULT)
return (STYLE_ALIGN_LEFT);
return (align);
}
/* Add a visible character to the array, updating the alignment width. */
static void
fuzzy_add(struct fuzzy_char **cs, u_int *ncs, u_int *alloc, enum style_align a,
const struct utf8_data *ud, u_int *widths)
{
struct fuzzy_char *fc;
if (*ncs == *alloc) {
*alloc = (*alloc == 0) ? 64 : *alloc * 2;
*cs = xreallocarray(*cs, *alloc, sizeof **cs);
}
fc = &(*cs)[(*ncs)++];
fc->align = a;
memcpy(&fc->ud, ud, sizeof fc->ud);
fc->width = ud->width;
fc->offset = widths[a];
widths[a] += ud->width;
}
/* Decode a character as UTF-8. */
static const char *
fuzzy_decode_one(const char *cp, const char *end, struct utf8_data *ud)
{
enum utf8_state more;
const char *start = cp;
if ((more = utf8_open(ud, (u_char)*cp)) == UTF8_MORE) {
while (++cp != end && more == UTF8_MORE)
more = utf8_append(ud, (u_char)*cp);
if (more == UTF8_DONE)
return (cp);
cp = start;
}
utf8_set(ud, (u_char)*cp);
return (cp + 1);
}
/*
* Scan the text into an array of visible characters, skipping styles and
* recording the alignment and intra-alignment offset of each. Returns the
* array and its length and fills in the per-alignment widths.
*/
static struct fuzzy_char *
fuzzy_scan(const char *text, u_int *ncs, u_int *widths)
{
struct fuzzy_char *cs = NULL;
u_int alloc = 0, n, leading, i;
enum style_align current = STYLE_ALIGN_LEFT;
struct style sy;
const char *cp = text, *textend = text + strlen(text);
const char *end;
struct utf8_data ud, hash, bracket;
char *tmp;
*ncs = 0;
memset(widths, 0, sizeof *widths * (STYLE_ALIGN_ABSOLUTE_CENTRE + 1));
style_set(&sy, &grid_default_cell);
utf8_set(&hash, '#');
utf8_set(&bracket, '[');
while (*cp != '\0') {
/* Handle a run of #s, which may introduce a style. */
if (*cp == '#') {
for (n = 0; cp[n] == '#'; n++)
/* nothing */;
if (cp[n] != '[') {
/* Escaped #s: ##->#, so half (rounded up). */
leading = (n % 2 == 0) ? n / 2 : n / 2 + 1;
for (i = 0; i < leading; i++) {
fuzzy_add(&cs, ncs, &alloc, current,
&hash, widths);
}
cp += n;
continue;
}
/* Even count: all #s escaped, the [ is literal. */
for (i = 0; i < n / 2; i++)
fuzzy_add(&cs, ncs, &alloc, current, &hash,
widths);
if (n % 2 == 0) {
fuzzy_add(&cs, ncs, &alloc, current, &bracket,
widths);
cp += n + 1;
continue;
}
/* Odd count: this is a style, find and parse it. */
end = format_skip(cp + n + 1, "]");
if (end == NULL)
break;
tmp = xstrndup(cp + n + 1, end - (cp + n + 1));
if (style_parse(&sy, &grid_default_cell, tmp) == 0)
current = fuzzy_align(sy.align);
free(tmp);
cp = end + 1;
continue;
}
/* Decode one character, multibyte or single byte. */
cp = fuzzy_decode_one(cp, textend, &ud);
/*
* Skip non-printable single bytes (control characters and raw
* bytes left over from a failed decode); keep printable ASCII
* and any decoded UTF-8.
*/
if (ud.size == 1 && (ud.data[0] <= 0x1f || ud.data[0] >= 0x7f))
continue;
fuzzy_add(&cs, ncs, &alloc, current, &ud, widths);
}
return (cs);
}
/*
* Work out the display column of a visible character given the trimmed widths
* and start columns of each alignment. Returns 0 and sets the column if the
* character is visible, otherwise returns -1.
*/
static int
fuzzy_column(const struct fuzzy_char *fc, const u_int *start, const u_int *src,
const u_int *vis, u_int *column)
{
enum style_align a = fc->align;
if (fc->offset < src[a] || fc->offset >= src[a] + vis[a])
return (-1);
*column = start[a] + (fc->offset - src[a]);
return (0);
}
/* Decode a UTF-8 term into an array of characters. */
static u_int
fuzzy_decode(const char *tok, size_t len, struct utf8_data *out)
{
const char *cp = tok, *end = tok + len;
u_int n = 0;
while (cp != end)
cp = fuzzy_decode_one(cp, end, &out[n++]);
return (n);
}
/* Add the score for a fuzzy token matched at the given positions. */
static int
fuzzy_score_positions(const u_int *pos, u_int npos, const struct fuzzy_char *cs)
{
u_int i, gap, span;
int score = 0;
if (npos == 0)
return (0);
if (pos[0] == 0)
score += FUZZY_BONUS_START;
else {
if (fuzzy_is_boundary(&cs[pos[0] - 1].ud))
score += FUZZY_BONUS_BOUNDARY;
if (pos[0] < FUZZY_PENALTY_LEADING_MAX)
score -= pos[0] * FUZZY_PENALTY_LEADING;
else {
score -= FUZZY_PENALTY_LEADING_MAX *
FUZZY_PENALTY_LEADING;
}
}
for (i = 1; i < npos; i++) {
if (pos[i] == pos[i - 1] + 1)
score += FUZZY_BONUS_CONSECUTIVE;
else if (fuzzy_is_boundary(&cs[pos[i] - 1].ud))
score += FUZZY_BONUS_BOUNDARY;
}
span = pos[npos - 1] - pos[0] + 1;
gap = span - npos;
score -= gap * FUZZY_PENALTY_GAP;
return (score);
}
/*
* Match a token as a subsequence of the visible characters. Returns if the
* token matches.
*/
static int
fuzzy_match_fuzzy(const struct utf8_data *tok, u_int toklen,
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
{
u_int pi, ci, *pos;
int found, value;
if (toklen == 0 || ncs == 0)
return (0);
pos = xcalloc(toklen, sizeof *pos);
/* First find a subsequence from the start. */
ci = 0;
for (pi = 0; pi < toklen; pi++) {
while (ci != ncs &&
!fuzzy_char_equal(&tok[pi], &cs[ci].ud, fold))
ci++;
if (ci == ncs) {
free(pos);
return (0);
}
pos[pi] = ci++;
}
/* Then compact it backwards to prefer a shorter span. */
ci = pos[toklen - 1];
for (pi = toklen; pi > 0; pi--) {
found = 0;
for (;;) {
if (fuzzy_char_equal(&tok[pi - 1], &cs[ci].ud, fold)) {
pos[pi - 1] = ci;
found = 1;
break;
}
if (ci == 0)
break;
ci--;
}
if (!found) {
free(pos);
return (0);
}
if (pi != 1)
ci--;
}
value = fuzzy_score_positions(pos, toklen, cs);
*score += value;
for (pi = 0; pi < toklen; pi++)
matched[pos[pi]] = 1;
free(pos);
return (1);
}
/* Score an exact, prefix or suffix match. */
static int
fuzzy_score_exact(u_int start, u_int toklen, u_int ncs,
const struct fuzzy_char *cs, int prefix, int suffix)
{
int score;
score = FUZZY_BONUS_EXACT + toklen * FUZZY_BONUS_CONSECUTIVE;
if (prefix)
score += FUZZY_BONUS_PREFIX;
if (suffix)
score += FUZZY_BONUS_SUFFIX;
if (start == 0)
score += FUZZY_BONUS_START;
else if (fuzzy_is_boundary(&cs[start - 1].ud))
score += FUZZY_BONUS_BOUNDARY;
if (start < FUZZY_PENALTY_LEADING_MAX)
score -= start * FUZZY_PENALTY_LEADING;
else
score -= FUZZY_PENALTY_LEADING_MAX * FUZZY_PENALTY_LEADING;
if (!prefix && !suffix)
score -= ncs - (start + toklen);
return (score);
}
/* Match an exact, prefix or suffix term against the visible characters. */
static int
fuzzy_match_exact(const struct utf8_data *tok, u_int toklen,
struct fuzzy_char *cs, u_int ncs, int fold, int prefix, int suffix,
int *score, char *matched)
{
u_int start, end, i, j, best = 0;
int ok, found = 0, value, bestscore = 0;
if (toklen == 0 || toklen > ncs)
return (0);
if (prefix && suffix) {
if (toklen != ncs)
return (0);
start = 0;
end = 1;
} else if (prefix) {
start = 0;
end = 1;
} else if (suffix) {
start = ncs - toklen;
end = start + 1;
} else {
start = 0;
end = ncs - toklen + 1;
}
for (i = start; i < end; i++) {
ok = 1;
for (j = 0; j < toklen; j++) {
if (!fuzzy_char_equal(&tok[j], &cs[i + j].ud, fold)) {
ok = 0;
break;
}
}
if (!ok)
continue;
value = fuzzy_score_exact(i, toklen, ncs, cs, prefix, suffix);
if (!found || value > bestscore) {
found = 1;
best = i;
bestscore = value;
}
}
if (!found)
return (0);
*score += bestscore;
if (matched != NULL) {
for (i = 0; i < toklen; i++)
matched[best + i] = 1;
}
return (1);
}
/* Parse one term. */
static int
fuzzy_parse_term(const char *start, const char *end, struct fuzzy_term *term)
{
memset(term, 0, sizeof *term);
if (start == end)
return (0);
if (*start == '!') {
term->inverse = 1;
start++;
}
if (start == end)
return (0);
if (*start == '\'') {
term->exact = 1;
start++;
} else if (*start == '^') {
term->exact = 1;
term->prefix = 1;
start++;
}
if (start == end)
return (0);
if (end[-1] == '$') {
term->exact = 1;
term->suffix = 1;
end--;
}
if (start == end)
return (0);
if (term->inverse)
term->exact = 1;
term->text = start;
term->len = end - start;
return (1);
}
/* Match one parsed term. */
static int
fuzzy_match_term(const struct fuzzy_term *term, struct utf8_data *tok,
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
{
u_int toklen;
int value = 0, matched_term;
toklen = fuzzy_decode(term->text, term->len, tok);
if (term->exact) {
matched_term = fuzzy_match_exact(tok, toklen, cs, ncs, fold,
term->prefix, term->suffix, &value,
term->inverse ? NULL : matched);
} else {
matched_term = fuzzy_match_fuzzy(tok, toklen, cs, ncs, fold,
&value, term->inverse ? NULL : matched);
}
if (term->inverse)
return (!matched_term);
if (!matched_term)
return (0);
*score += value;
return (1);
}
/* Match one AND group of terms. */
static int
fuzzy_match_group(const char *start, const char *end, struct utf8_data *tok,
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
{
const char *cp = start, *sp;
struct fuzzy_term term;
int any = 0;
*score = 0;
while (cp != end) {
while (cp != end && *cp == ' ')
cp++;
if (cp == end)
break;
sp = cp;
while (cp != end && *cp != ' ')
cp++;
if (!fuzzy_parse_term(sp, cp, &term))
return (0);
any = 1;
if (!fuzzy_match_term(&term, tok, cs, ncs, fold, score,
matched))
return (0);
}
return (any);
}
/*
* Fuzzy match pattern against text, which is drawn into a region of the given
* display width. Returns a bitstr_t of width bits with a bit set for each
* column occupied by a matched character, or NULL if there is no match. A
* higher returned score is better.
*/
bitstr_t *
fuzzy_match(const char *pattern, const char *text, u_int width, u_int *score)
{
struct fuzzy_char *cs;
char *matched = NULL, *best = NULL, *groupmatched;
struct utf8_data *tok;
bitstr_t *mask;
u_int ncs, i, j, column;
u_int widths[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
u_int start[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
u_int src[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
u_int vis[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
u_int wl, wc, wr, wa;
const char *cp, *sp;
int bestscore = 0, groupscore, found = 0, fold;
if (width == 0)
return (NULL);
/* An empty query matches everything, with nothing highlighted. */
for (cp = pattern; *cp == ' ' || *cp == '|'; cp++)
/* nothing */;
if (*cp == '\0') {
if (score != NULL)
*score = 0;
return (bit_alloc(width));
}
/* Smart-case: fold unless the pattern has an uppercase character. */
fold = 1;
for (cp = pattern; *cp != '\0'; cp++) {
if (*cp >= 'A' && *cp <= 'Z') {
fold = 0;
break;
}
}
/* Scan the text into visible characters. */
cs = fuzzy_scan(text, &ncs, widths);
matched = xcalloc(ncs == 0 ? 1 : ncs, sizeof *matched);
best = xcalloc(ncs == 0 ? 1 : ncs, sizeof *best);
tok = xreallocarray(NULL, strlen(pattern) + 1, sizeof *tok);
/* Match each |-separated group and keep the best-scoring one. */
cp = pattern;
while (*cp != '\0') {
while (*cp == ' ' || *cp == '|')
cp++;
if (*cp == '\0')
break;
sp = cp;
while (*cp != '\0' && *cp != '|')
cp++;
memset(matched, 0, ncs == 0 ? 1 : ncs);
groupmatched = matched;
if (fuzzy_match_group(sp, cp, tok, cs, ncs, fold,
&groupscore, groupmatched)) {
if (!found || groupscore > bestscore) {
found = 1;
bestscore = groupscore;
memcpy(best, matched, ncs == 0 ? 1 : ncs);
}
}
}
free(tok);
if (!found) {
free(best);
free(matched);
free(cs);
return (NULL);
}
/*
* Work out the trimmed widths and start columns of each alignment,
* mirroring format_draw_none.
*/
wl = widths[STYLE_ALIGN_LEFT];
wc = widths[STYLE_ALIGN_CENTRE];
wr = widths[STYLE_ALIGN_RIGHT];
wa = widths[STYLE_ALIGN_ABSOLUTE_CENTRE];
while (wl + wc + wr > width) {
if (wc > 0)
wc--;
else if (wr > 0)
wr--;
else
wl--;
}
if (wa > width)
wa = width;
start[STYLE_ALIGN_LEFT] = 0;
src[STYLE_ALIGN_LEFT] = 0;
vis[STYLE_ALIGN_LEFT] = wl;
start[STYLE_ALIGN_RIGHT] = width - wr;
src[STYLE_ALIGN_RIGHT] = widths[STYLE_ALIGN_RIGHT] - wr;
vis[STYLE_ALIGN_RIGHT] = wr;
start[STYLE_ALIGN_CENTRE] =
wl + ((width - wr) - wl) / 2 - wc / 2;
src[STYLE_ALIGN_CENTRE] = widths[STYLE_ALIGN_CENTRE] / 2 - wc / 2;
vis[STYLE_ALIGN_CENTRE] = wc;
start[STYLE_ALIGN_ABSOLUTE_CENTRE] = (width - wa) / 2;
src[STYLE_ALIGN_ABSOLUTE_CENTRE] = 0;
vis[STYLE_ALIGN_ABSOLUTE_CENTRE] = wa;
/* Set a bit for each column of each matched character. */
mask = bit_alloc(width);
for (i = 0; i < ncs; i++) {
if (!best[i])
continue;
if (fuzzy_column(&cs[i], start, src, vis, &column) != 0)
continue;
for (j = 0; j < cs[i].width && column + j < width; j++)
bit_set(mask, column + j);
}
free(best);
free(matched);
free(cs);
if (score != NULL)
*score = (bestscore < 0) ? 0 : (u_int)bestscore;
return (mask);
}

230
grid.c
View File

@@ -18,6 +18,9 @@
#include <sys/types.h> #include <sys/types.h>
#ifdef __APPLE__
#include <assert.h>
#endif
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -56,6 +59,62 @@ static const struct grid_cell_entry grid_cleared_entry = {
{ .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED { .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED
}; };
#ifdef __APPLE__
static void
grid_check_lines(struct grid *gd)
{
u_int i, j;
for (i = 0; i < gd->hsize + gd->sy; i++) {
for (j = i + 1; j < gd->hsize + gd->sy; j++) {
if (gd->linedata[i].celldata != NULL)
assert(gd->linedata[i].celldata != gd->linedata[j].celldata);
if (gd->linedata[i].extddata != NULL)
assert(gd->linedata[i].extddata != gd->linedata[j].extddata);
}
}
}
void
grid_check_is_clear(struct grid *gd)
{
struct grid_line *gl;
u_int yy, ny;
assert(gd != NULL);
if (gd->sy == 0) {
assert(gd->linedata == NULL);
return;
}
assert(gd->linedata != NULL);
ny = gd->hsize + gd->sy;
for (yy = 0; yy < ny; yy++) {
gl = &gd->linedata[yy];
assert(gl->celldata == NULL);
assert(gl->cellused == 0);
assert(gl->cellsize == 0);
assert(gl->extddata == NULL);
assert(gl->extdsize == 0);
assert(gl->flags == 0);
assert(gl->time == 0);
}
}
#else
static void
grid_check_lines(__unused struct grid *gd)
{
}
void
grid_check_is_clear(__unused struct grid *gd)
{
}
#endif
/* Store cell in entry. */ /* Store cell in entry. */
static void static void
grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc, grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc,
@@ -86,7 +145,8 @@ grid_need_extended_cell(const struct grid_cell_entry *gce,
return (1); return (1);
if (gc->data.size > 1 || gc->data.width > 1) if (gc->data.size > 1 || gc->data.width > 1)
return (1); return (1);
if ((gc->fg & COLOUR_FLAG_RGB) || (gc->bg & COLOUR_FLAG_RGB)) if ((gc->fg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) ||
(gc->bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)))
return (1); return (1);
if (gc->us != 8) /* only supports 256 or RGB */ if (gc->us != 8) /* only supports 256 or RGB */
return (1); return (1);
@@ -218,7 +278,7 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved)
if (bg != 8) if (bg != 8)
gee->bg = bg; gee->bg = bg;
} else if (bg != 8) { } else if (bg != 8) {
if (bg & COLOUR_FLAG_RGB) { if (bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) {
grid_get_extended_cell(gl, gce, gce->flags); grid_get_extended_cell(gl, gce, gce->flags);
gee = grid_extended_cell(gl, gce, &grid_cleared_cell); gee = grid_extended_cell(gl, gce, &grid_cleared_cell);
gee->bg = bg; gee->bg = bg;
@@ -286,13 +346,21 @@ grid_set_tab(struct grid_cell *gc, u_int width)
static void static void
grid_free_line(struct grid *gd, u_int py) grid_free_line(struct grid *gd, u_int py)
{ {
free(gd->linedata[py].celldata); struct grid_line *gl = &gd->linedata[py];
free(gd->linedata[py].extddata);
memset(&gd->linedata[py], 0, sizeof gd->linedata[py]); #ifdef __APPLE__
assert(gl->cellused <= gl->cellsize);
assert(gl->extdsize == 0 || gl->extddata != NULL);
assert(gl->cellsize == 0 || gl->celldata != NULL);
#endif
free(gl->celldata);
free(gl->extddata);
memset(gl, 0, sizeof *gl);
} }
/* 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;
@@ -307,24 +375,18 @@ grid_create(u_int sx, u_int sy, u_int hlimit)
{ {
struct grid *gd; struct grid *gd;
gd = xmalloc(sizeof *gd); gd = xcalloc(1, sizeof *gd);
gd->sx = sx; gd->sx = sx;
gd->sy = sy; gd->sy = sy;
if (hlimit != 0) if (hlimit != 0)
gd->flags = GRID_HISTORY; gd->flags = GRID_HISTORY;
else
gd->flags = 0;
gd->hscrolled = 0;
gd->hsize = 0;
gd->hlimit = hlimit; gd->hlimit = hlimit;
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
gd->linedata = NULL;
grid_check_is_clear(gd);
return (gd); return (gd);
} }
@@ -405,6 +467,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 +505,9 @@ 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++;
grid_check_lines(gd);
} }
/* Clear the history. */ /* Clear the history. */
@@ -452,6 +518,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 +556,9 @@ 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++;
grid_check_lines(gd);
} }
/* Expand line to fit to cell. */ /* Expand line to fit to cell. */
@@ -750,6 +820,8 @@ grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg)
} }
if (py != 0 && (py < dy || py >= dy + ny)) if (py != 0 && (py < dy || py >= dy + ny))
gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED; gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
grid_check_lines(gd);
} }
/* Move a group of cells. */ /* Move a group of cells. */
@@ -788,9 +860,16 @@ grid_string_cells_fg(const struct grid_cell *gc, int *values)
{ {
size_t n; size_t n;
u_char r, g, b; u_char r, g, b;
int c;
n = 0; n = 0;
if (gc->fg & COLOUR_FLAG_256) { if (gc->fg & COLOUR_FLAG_THEME) {
c = colour_theme_terminal_colour(gc->fg & 0xff);
if (c == 8)
values[n++] = 39;
else
values[n++] = c + 30;
} else if (gc->fg & COLOUR_FLAG_256) {
values[n++] = 38; values[n++] = 38;
values[n++] = 5; values[n++] = 5;
values[n++] = gc->fg & 0xff; values[n++] = gc->fg & 0xff;
@@ -837,9 +916,16 @@ grid_string_cells_bg(const struct grid_cell *gc, int *values)
{ {
size_t n; size_t n;
u_char r, g, b; u_char r, g, b;
int c;
n = 0; n = 0;
if (gc->bg & COLOUR_FLAG_256) { if (gc->bg & COLOUR_FLAG_THEME) {
c = colour_theme_terminal_colour(gc->bg & 0xff);
if (c == 8)
values[n++] = 49;
else
values[n++] = c + 40;
} else if (gc->bg & COLOUR_FLAG_256) {
values[n++] = 48; values[n++] = 48;
values[n++] = 5; values[n++] = 5;
values[n++] = gc->bg & 0xff; values[n++] = gc->bg & 0xff;
@@ -886,9 +972,19 @@ grid_string_cells_us(const struct grid_cell *gc, int *values)
{ {
size_t n; size_t n;
u_char r, g, b; u_char r, g, b;
int c;
n = 0; n = 0;
if (gc->us & COLOUR_FLAG_256) { if (gc->us & COLOUR_FLAG_THEME) {
c = colour_theme_terminal_colour(gc->us & 0xff);
if (c == 8)
values[n++] = 59;
else {
values[n++] = 58;
values[n++] = 5;
values[n++] = c;
}
} else if (gc->us & COLOUR_FLAG_256) {
values[n++] = 58; values[n++] = 58;
values[n++] = 5; values[n++] = 5;
values[n++] = gc->us & 0xff; values[n++] = gc->us & 0xff;
@@ -1215,6 +1311,8 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
sy++; sy++;
dy++; dy++;
} }
grid_check_lines(dst);
} }
/* Mark line as dead. */ /* Mark line as dead. */
@@ -1510,6 +1608,9 @@ 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++;
grid_check_lines(gd);
} }
/* Convert to position based on wrapped lines. */ /* Convert to position based on wrapped lines. */
@@ -1611,3 +1712,98 @@ grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
return (0); return (0);
return (utf8_cstrhas(set, &gc.data)); return (utf8_cstrhas(set, &gc.data));
} }
/* Line flags to string. */
const char *
grid_line_flags_string(int flags)
{
static char s[128];
*s = '\0';
if (flags & GRID_LINE_WRAPPED)
strlcat(s, "WRAPPED,", sizeof s);
if (flags & GRID_LINE_EXTENDED)
strlcat(s, "EXTENDED,", sizeof s);
if (flags & GRID_LINE_DEAD)
strlcat(s, "DEAD,", sizeof s);
if (flags & GRID_LINE_START_PROMPT)
strlcat(s, "START_PROMPT,", sizeof s);
if (flags & GRID_LINE_START_OUTPUT)
strlcat(s, "START_OUTPUT,", sizeof s);
if (flags & GRID_LINE_HYPERLINK)
strlcat(s, "HYPERLINK,", sizeof s);
if (*s == '\0')
return ("NONE");
s[strlen(s) - 1] = '\0';
return (s);
}
/* Cell flags to string. */
const char *
grid_cell_flags_string(int flags)
{
static char s[128];
*s = '\0';
if (flags & GRID_FLAG_FG256)
strlcat(s, "FG256,", sizeof s);
if (flags & GRID_FLAG_BG256)
strlcat(s, "BG256,", sizeof s);
if (flags & GRID_FLAG_PADDING)
strlcat(s, "PADDING,", sizeof s);
if (flags & GRID_FLAG_EXTENDED)
strlcat(s, "EXTENDED,", sizeof s);
if (flags & GRID_FLAG_SELECTED)
strlcat(s, "SELECTED,", sizeof s);
if (flags & GRID_FLAG_CLEARED)
strlcat(s, "CLEARED,", sizeof s);
if (flags & GRID_FLAG_TAB)
strlcat(s, "TAB,", sizeof s);
if (flags & GRID_FLAG_NOPALETTE)
strlcat(s, "NOPALETTE,", sizeof s);
if (*s == '\0')
return ("NONE");
s[strlen(s) - 1] = '\0';
return (s);
}
/* Cell attributes to string. */
const char *
grid_cell_attr_string(int attr)
{
static char s[256];
*s = '\0';
if (attr & GRID_ATTR_CHARSET)
strlcat(s, "CHARSET,", sizeof s);
if (attr & GRID_ATTR_BRIGHT)
strlcat(s, "BRIGHT,", sizeof s);
if (attr & GRID_ATTR_DIM)
strlcat(s, "DIM,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE)
strlcat(s, "UNDERSCORE,", sizeof s);
if (attr & GRID_ATTR_BLINK)
strlcat(s, "BLINK,", sizeof s);
if (attr & GRID_ATTR_REVERSE)
strlcat(s, "REVERSE,", sizeof s);
if (attr & GRID_ATTR_HIDDEN)
strlcat(s, "HIDDEN,", sizeof s);
if (attr & GRID_ATTR_ITALICS)
strlcat(s, "ITALICS,", sizeof s);
if (attr & GRID_ATTR_STRIKETHROUGH)
strlcat(s, "STRIKETHROUGH,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE_2)
strlcat(s, "UNDERSCORE_2,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE_3)
strlcat(s, "UNDERSCORE_3,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE_4)
strlcat(s, "UNDERSCORE_4,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE_5)
strlcat(s, "UNDERSCORE_5,", sizeof s);
if (attr & GRID_ATTR_OVERLINE)
strlcat(s, "OVERLINE,", sizeof s);
if (*s == '\0')
return ("NONE");
s[strlen(s) - 1] = '\0';
return (s);
}

View File

@@ -41,6 +41,7 @@
*/ */
#define MAX_HYPERLINKS 5000 #define MAX_HYPERLINKS 5000
#define MAX_HYPERLINK_URI 1024
static long long hyperlinks_next_external_id = 1; static long long hyperlinks_next_external_id = 1;
static u_int global_hyperlinks_count; static u_int global_hyperlinks_count;
@@ -145,9 +146,13 @@ hyperlinks_put(struct hyperlinks *hl, const char *uri_in,
internal_id_in = ""; internal_id_in = "";
utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE); utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE);
utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE); if (strlen(uri) > MAX_HYPERLINK_URI) {
free(uri);
return (0);
}
if (*internal_id_in != '\0') { utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE);
if (*internal_id != '\0') {
find.uri = uri; find.uri = uri;
find.internal_id = internal_id; find.internal_id = internal_id;

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;
@@ -586,7 +586,7 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
len = 8192; len = 8192;
buf = xmalloc(len); buf = xmalloc(len);
tmplen = xsnprintf(tmp, sizeof tmp, "\033P0;%uq", si->p2); tmplen = xsnprintf(tmp, sizeof tmp, "\033P9;%uq", si->p2);
sixel_print_add(&buf, &len, &used, tmp, tmplen); sixel_print_add(&buf, &len, &used, tmp, tmplen);
if (si->set_ra) { if (si->set_ra) {

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;

32
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);
@@ -1439,12 +1439,12 @@ input_esc_dispatch(struct input_ctx *ictx)
static int static int
input_csi_dispatch(struct input_ctx *ictx) 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;
if (ictx->flags & INPUT_DISCARD) if (ictx->flags & INPUT_DISCARD)
return (0); return (0);
@@ -1953,16 +1953,14 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx)
case 2004: case 2004:
screen_write_mode_clear(sctx, MODE_BRACKETPASTE); screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
break; break;
case 2026:
screen_write_stop_sync(ictx->wp);
break;
case 2031: case 2031:
screen_write_mode_clear(sctx, MODE_THEME_UPDATES); screen_write_mode_clear(sctx, MODE_THEME_UPDATES);
if (ictx->wp != NULL) if (ictx->wp != NULL)
ictx->wp->flags &= ~PANE_THEMECHANGED; ictx->wp->flags &= ~PANE_THEMECHANGED;
break; break;
case 2026: /* synchronized output */
screen_write_stop_sync(ictx->wp);
if (ictx->wp != NULL)
ictx->wp->flags |= PANE_REDRAW;
break;
default: default:
log_debug("%s: unknown '%c'", __func__, ictx->ch); log_debug("%s: unknown '%c'", __func__, ictx->ch);
break; break;
@@ -2065,7 +2063,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
ictx->wp->flags &= ~PANE_THEMECHANGED; ictx->wp->flags &= ~PANE_THEMECHANGED;
} }
break; break;
case 2026: /* synchronized output */ case 2026:
screen_write_start_sync(ictx->wp); screen_write_start_sync(ictx->wp);
break; break;
default: default:
@@ -2822,10 +2820,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_NAME_FORBID_EXT); window_set_name(w, "", 1);
} 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_NAME_FORBID_EXT); window_set_name(w, ictx->input_buf, 1);
} }
server_redraw_window_borders(w); server_redraw_window_borders(w);
server_status_window(w); server_status_window(w);
@@ -3055,7 +3053,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 +3223,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}" \
@@ -351,6 +350,8 @@ key_bindings_init(void)
{ {
static const char *const defaults[] = { static const char *const defaults[] = {
/* Prefix keys. */ /* Prefix keys. */
"bind -N 'Hide pane' _ { hide-pane }",
"bind -N 'Send the prefix key' C-b { send-prefix }", "bind -N 'Send the prefix key' C-b { send-prefix }",
"bind -N 'Rotate through the panes' C-o { rotate-window }", "bind -N 'Rotate through the panes' C-o { rotate-window }",
"bind -N 'Suspend the current client' C-z { suspend-client }", "bind -N 'Suspend the current client' C-z { suspend-client }",
@@ -363,6 +364,7 @@ key_bindings_init(void)
"bind -N 'Kill current window' & { confirm-before -p\"kill-window #W? (y/n)\" kill-window }", "bind -N 'Kill current window' & { confirm-before -p\"kill-window #W? (y/n)\" kill-window }",
"bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex { select-window -t ':%%' } }", "bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex { select-window -t ':%%' } }",
"bind -N 'New floating pane' * { new-pane }", "bind -N 'New floating pane' * { new-pane }",
"bind -N 'Toggle pane between floating and tiled' @ { if -F '#{pane_floating_flag}' { join-pane } { break-pane -W } }",
"bind -N 'Switch to previous client' ( { switch-client -p }", "bind -N 'Switch to previous client' ( { switch-client -p }",
"bind -N 'Switch to next client' ) { switch-client -n }", "bind -N 'Switch to next client' ) { switch-client -n }",
"bind -N 'Rename current window' , { command-prompt -I'#W' { rename-window -- '%%' } }", "bind -N 'Rename current window' , { command-prompt -I'#W' { rename-window -- '%%' } }",
@@ -403,11 +405,15 @@ key_bindings_init(void)
"bind -N 'Redraw the current client' r { refresh-client }", "bind -N 'Redraw the current client' r { refresh-client }",
"bind -N 'Choose a session from a list' s { choose-tree -Zs }", "bind -N 'Choose a session from a list' s { choose-tree -Zs }",
"bind -N 'Show a clock' t { clock-mode }", "bind -N 'Show a clock' t { clock-mode }",
"bind -N 'Switch to a window' Tab { new-pane -E -x75% -y30% -X0 -Y0; move-pane -P bottom-centre; switch-mode -wk }",
"bind -N 'Switch to a session' BTab { new-pane -E -x75% -y30% -X0 -Y0; move-pane -P bottom-centre; switch-mode -sk }",
"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 +435,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 +454,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,9 +473,10 @@ 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 { if -F '#{&&:#{pane_active},#{!#{pane_hidden_flag}}}' { hide-pane -t= } { switch-client -t= } }",
"bind -n C-MouseDown1Status { swap-window -t@ }", "bind -n C-MouseDown1Status { swap-window -t@ }",
/* Mouse button 1 down on default pane-border-format */ /* Mouse button 1 down on default pane-border-format */
@@ -482,8 +490,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 +519,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 +528,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 +555,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 +582,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 +604,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 +631,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 +648,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

@@ -265,7 +265,7 @@ layout_parse(struct window *w, const char *layout, char **cause)
window_resize(w, tiled_lc->sx, tiled_lc->sy, -1, -1); window_resize(w, tiled_lc->sx, tiled_lc->sy, -1, -1);
/* Destroy the old layout and swap to the new. */ /* Destroy the old layout and swap to the new. */
layout_free_cell(w->layout_root); layout_free_cell(w->layout_root, 0);
w->layout_root = tiled_lc; w->layout_root = tiled_lc;
/* Assign the panes into the cells. */ /* Assign the panes into the cells. */
@@ -291,7 +291,7 @@ layout_parse(struct window *w, const char *layout, char **cause)
return (0); return (0);
fail: fail:
layout_free_cell(tiled_lc); layout_free_cell(tiled_lc, 0);
return (-1); return (-1);
} }
@@ -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 ',':
@@ -421,6 +423,6 @@ layout_construct(struct layout_cell *lcparent, const char **layout,
return (0); return (0);
fail: fail:
layout_free_cell(*lc); layout_free_cell(*lc, 0);
return (-1); return (-1);
} }

View File

@@ -124,12 +124,12 @@ layout_set_previous(struct window *w)
} }
static struct window_pane * static struct window_pane *
layout_first_tiled(struct window *w) layout_set_first_tiled(struct window *w)
{ {
struct window_pane *wp; struct window_pane *wp;
TAILQ_FOREACH(wp, &w->panes, entry) { TAILQ_FOREACH(wp, &w->panes, entry) {
if (!window_pane_is_floating(wp)) if (wp->layout_cell && layout_cell_is_tiled(wp->layout_cell))
return (wp); return (wp);
} }
return (NULL); return (NULL);
@@ -139,19 +139,15 @@ static void
layout_set_even(struct window *w, enum layout_type type) layout_set_even(struct window *w, enum layout_type type)
{ {
struct window_pane *wp; struct window_pane *wp;
struct layout_cell *lc, *lcnew; struct layout_cell *lcroot, *lcchild;
u_int n, sx, sy; u_int n, sx, sy;
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
/* Get number of panes. */
n = window_count_panes(w, 0); n = window_count_panes(w, 0);
if (n <= 1) if (n <= 1)
return; return;
/* Free the old root and construct a new. */
layout_free(w);
lc = w->layout_root = layout_create_cell(NULL);
if (type == LAYOUT_LEFTRIGHT) { if (type == LAYOUT_LEFTRIGHT) {
sx = (n * (PANE_MINIMUM + 1)) - 1; sx = (n * (PANE_MINIMUM + 1)) - 1;
if (sx < w->sx) if (sx < w->sx)
@@ -163,30 +159,30 @@ layout_set_even(struct window *w, enum layout_type type)
sy = w->sy; sy = w->sy;
sx = w->sx; sx = w->sx;
} }
layout_set_size(lc, sx, sy, 0, 0);
layout_make_node(lc, type);
/* Build new leaf cells. */ layout_free(w, 1);
lcroot = w->layout_root = layout_create_cell(NULL);
layout_set_size(lcroot, sx, sy, 0, 0);
layout_make_node(lcroot, type);
TAILQ_FOREACH(wp, &w->panes, entry) { TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_is_floating(wp)) lcchild = wp->layout_cell;
continue; TAILQ_INSERT_TAIL(&lcroot->cells, lcchild, entry);
lcnew = layout_create_cell(lc); lcchild->parent = lcroot;
layout_make_leaf(lcnew, wp); if (layout_cell_is_tiled(lcchild)) {
lcnew->sx = w->sx; lcchild->sx = w->sx;
lcnew->sy = w->sy; lcchild->sy = w->sy;
TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry); }
} }
/* Spread out cells. */ layout_spread_cell(w, lcroot);
layout_spread_cell(w, lc);
/* Fix cell offsets. */
layout_fix_offsets(w); layout_fix_offsets(w);
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
window_resize(w, lc->sx, lc->sy, -1, -1); window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
notify_window("window-layout-changed", w); notify_window("window-layout-changed", w);
server_redraw_window(w); server_redraw_window(w);
} }
@@ -206,15 +202,14 @@ layout_set_even_v(struct window *w)
static void static void
layout_set_main_h(struct window *w) layout_set_main_h(struct window *w)
{ {
struct window_pane *wp; struct window_pane *wp, *wpmain;
struct layout_cell *lc, *lcmain, *lcother, *lcchild; struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
u_int n, mainh, otherh, sx, sy; u_int n, mainh, otherh, sx, sy;
char *cause; char *cause;
const char *s; const char *s;
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
/* Get number of panes. */
n = window_count_panes(w, 0); n = window_count_panes(w, 0);
if (n <= 1) if (n <= 1)
return; return;
@@ -255,52 +250,48 @@ layout_set_main_h(struct window *w)
if (sx < w->sx) if (sx < w->sx)
sx = w->sx; sx = w->sx;
/* Free old tree and create a new root. */ layout_free(w, 1);
layout_free(w); lcroot = w->layout_root = layout_create_cell(NULL);
lc = w->layout_root = layout_create_cell(NULL); layout_set_size(lcroot, sx, mainh + otherh + 1, 0, 0);
layout_set_size(lc, sx, mainh + otherh + 1, 0, 0); layout_make_node(lcroot, LAYOUT_TOPBOTTOM);
layout_make_node(lc, LAYOUT_TOPBOTTOM);
/* Create the main pane. */ wpmain = layout_set_first_tiled(w);
lcmain = layout_create_cell(lc); lcmain = wpmain->layout_cell;
lcmain->parent = lcroot;
layout_set_size(lcmain, sx, mainh, 0, 0); layout_set_size(lcmain, sx, mainh, 0, 0);
layout_make_leaf(lcmain, layout_first_tiled(w)); TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
/* Create the other pane. */
lcother = layout_create_cell(lc);
layout_set_size(lcother, sx, otherh, 0, 0);
if (n == 1) { if (n == 1) {
wp = TAILQ_NEXT(layout_first_tiled(w), entry); wp = TAILQ_NEXT(wpmain, entry);
while (wp != NULL && window_pane_is_floating(wp)) while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
wp = TAILQ_NEXT(wp, entry); wp = TAILQ_NEXT(wp, entry);
layout_make_leaf(lcother, wp); TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry);
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); wp->layout_cell->parent = lcroot;
} else { } else {
lcother = layout_create_cell(lcroot);
layout_set_size(lcother, sx, otherh, 0, 0);
layout_make_node(lcother, LAYOUT_LEFTRIGHT); layout_make_node(lcother, LAYOUT_LEFTRIGHT);
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); TAILQ_INSERT_TAIL(&lcroot->cells, lcother, entry);
/* Add the remaining panes as children. */
TAILQ_FOREACH(wp, &w->panes, entry) { TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_is_floating(wp)) if (wp == wpmain)
continue; continue;
if (wp == layout_first_tiled(w)) lcchild = wp->layout_cell;
continue;
lcchild = layout_create_cell(lcother);
layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0);
layout_make_leaf(lcchild, wp);
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
lcchild->parent = lcother;
if (layout_cell_is_tiled(lcchild))
layout_set_size(lcchild, PANE_MINIMUM, otherh,
0, 0);
} }
layout_spread_cell(w, lcother); layout_spread_cell(w, lcother);
} }
/* Fix cell offsets. */
layout_fix_offsets(w); layout_fix_offsets(w);
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
window_resize(w, lc->sx, lc->sy, -1, -1); window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
notify_window("window-layout-changed", w); notify_window("window-layout-changed", w);
server_redraw_window(w); server_redraw_window(w);
} }
@@ -308,15 +299,14 @@ layout_set_main_h(struct window *w)
static void static void
layout_set_main_h_mirrored(struct window *w) layout_set_main_h_mirrored(struct window *w)
{ {
struct window_pane *wp; struct window_pane *wp, *wpmain;
struct layout_cell *lc, *lcmain, *lcother, *lcchild; struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
u_int n, mainh, otherh, sx, sy; u_int n, mainh, otherh, sx, sy;
char *cause; char *cause;
const char *s; const char *s;
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
/* Get number of panes. */
n = window_count_panes(w, 0); n = window_count_panes(w, 0);
if (n <= 1) if (n <= 1)
return; return;
@@ -357,52 +347,48 @@ layout_set_main_h_mirrored(struct window *w)
if (sx < w->sx) if (sx < w->sx)
sx = w->sx; sx = w->sx;
/* Free old tree and create a new root. */ layout_free(w, 1);
layout_free(w); lcroot = w->layout_root = layout_create_cell(NULL);
lc = w->layout_root = layout_create_cell(NULL); layout_set_size(lcroot, sx, mainh + otherh + 1, 0, 0);
layout_set_size(lc, sx, mainh + otherh + 1, 0, 0); layout_make_node(lcroot, LAYOUT_TOPBOTTOM);
layout_make_node(lc, LAYOUT_TOPBOTTOM);
wpmain = layout_set_first_tiled(w);
lcmain = wpmain->layout_cell;
lcmain->parent = lcroot;
layout_set_size(lcmain, sx, mainh, 0, 0);
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
/* Create the other pane. */
lcother = layout_create_cell(lc);
layout_set_size(lcother, sx, otherh, 0, 0);
if (n == 1) { if (n == 1) {
wp = TAILQ_NEXT(layout_first_tiled(w), entry); wp = TAILQ_NEXT(wpmain, entry);
while (wp != NULL && window_pane_is_floating(wp)) while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
wp = TAILQ_NEXT(wp, entry); wp = TAILQ_NEXT(wp, entry);
layout_make_leaf(lcother, wp); TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry);
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); wp->layout_cell->parent = lcroot;
} else { } else {
lcother = layout_create_cell(lcroot);
layout_set_size(lcother, sx, otherh, 0, 0);
layout_make_node(lcother, LAYOUT_LEFTRIGHT); layout_make_node(lcother, LAYOUT_LEFTRIGHT);
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); TAILQ_INSERT_HEAD(&lcroot->cells, lcother, entry);
/* Add the remaining panes as children. */
TAILQ_FOREACH(wp, &w->panes, entry) { TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_is_floating(wp)) if (wp == wpmain)
continue; continue;
if (wp == layout_first_tiled(w)) lcchild = wp->layout_cell;
continue;
lcchild = layout_create_cell(lcother);
layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0);
layout_make_leaf(lcchild, wp);
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
lcchild->parent = lcother;
if (layout_cell_is_tiled(lcchild))
layout_set_size(lcchild, PANE_MINIMUM, otherh,
0, 0);
} }
layout_spread_cell(w, lcother); layout_spread_cell(w, lcother);
} }
/* Create the main pane. */
lcmain = layout_create_cell(lc);
layout_set_size(lcmain, sx, mainh, 0, 0);
layout_make_leaf(lcmain, layout_first_tiled(w));
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
/* Fix cell offsets. */
layout_fix_offsets(w); layout_fix_offsets(w);
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
window_resize(w, lc->sx, lc->sy, -1, -1); window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
notify_window("window-layout-changed", w); notify_window("window-layout-changed", w);
server_redraw_window(w); server_redraw_window(w);
} }
@@ -410,21 +396,20 @@ layout_set_main_h_mirrored(struct window *w)
static void static void
layout_set_main_v(struct window *w) layout_set_main_v(struct window *w)
{ {
struct window_pane *wp; struct window_pane *wp, *wpmain;
struct layout_cell *lc, *lcmain, *lcother, *lcchild; struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
u_int n, mainw, otherw, sx, sy; u_int n, mainw, otherw, sx, sy;
char *cause; char *cause;
const char *s; const char *s;
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
/* Get number of panes. */
n = window_count_panes(w, 0); n = window_count_panes(w, 0);
if (n <= 1) if (n <= 1)
return; return;
n--; /* take off main pane */ n--; /* take off main pane */
/* Find available width - take off one line for the border. */ /* Find available width - take off one column for the border. */
sx = w->sx - 1; sx = w->sx - 1;
/* Get the main pane width. */ /* Get the main pane width. */
@@ -459,52 +444,48 @@ layout_set_main_v(struct window *w)
if (sy < w->sy) if (sy < w->sy)
sy = w->sy; sy = w->sy;
/* Free old tree and create a new root. */ layout_free(w, 1);
layout_free(w); lcroot = w->layout_root = layout_create_cell(NULL);
lc = w->layout_root = layout_create_cell(NULL); layout_set_size(lcroot, mainw + otherw + 1, sy, 0, 0);
layout_set_size(lc, mainw + otherw + 1, sy, 0, 0); layout_make_node(lcroot, LAYOUT_LEFTRIGHT);
layout_make_node(lc, LAYOUT_LEFTRIGHT);
/* Create the main pane. */ wpmain = layout_set_first_tiled(w);
lcmain = layout_create_cell(lc); lcmain = wpmain->layout_cell;
lcmain->parent = lcroot;
layout_set_size(lcmain, mainw, sy, 0, 0); layout_set_size(lcmain, mainw, sy, 0, 0);
layout_make_leaf(lcmain, layout_first_tiled(w)); TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
/* Create the other pane. */
lcother = layout_create_cell(lc);
layout_set_size(lcother, otherw, sy, 0, 0);
if (n == 1) { if (n == 1) {
wp = TAILQ_NEXT(layout_first_tiled(w), entry); wp = TAILQ_NEXT(wpmain, entry);
while (wp != NULL && window_pane_is_floating(wp)) while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
wp = TAILQ_NEXT(wp, entry); wp = TAILQ_NEXT(wp, entry);
layout_make_leaf(lcother, wp); TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry);
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); wp->layout_cell->parent = lcroot;
} else { } else {
lcother = layout_create_cell(lcroot);
layout_make_node(lcother, LAYOUT_TOPBOTTOM); layout_make_node(lcother, LAYOUT_TOPBOTTOM);
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); layout_set_size(lcother, otherw, sy, 0, 0);
TAILQ_INSERT_TAIL(&lcroot->cells, lcother, entry);
/* Add the remaining panes as children. */
TAILQ_FOREACH(wp, &w->panes, entry) { TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_is_floating(wp)) if (wp == wpmain)
continue; continue;
if (wp == layout_first_tiled(w)) lcchild = wp->layout_cell;
continue;
lcchild = layout_create_cell(lcother);
layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0);
layout_make_leaf(lcchild, wp);
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
lcchild->parent = lcother;
if (layout_cell_is_tiled(lcchild))
layout_set_size(lcchild, otherw, PANE_MINIMUM,
0, 0);
} }
layout_spread_cell(w, lcother); layout_spread_cell(w, lcother);
} }
/* Fix cell offsets. */
layout_fix_offsets(w); layout_fix_offsets(w);
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
window_resize(w, lc->sx, lc->sy, -1, -1); window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
notify_window("window-layout-changed", w); notify_window("window-layout-changed", w);
server_redraw_window(w); server_redraw_window(w);
} }
@@ -512,8 +493,8 @@ layout_set_main_v(struct window *w)
static void static void
layout_set_main_v_mirrored(struct window *w) layout_set_main_v_mirrored(struct window *w)
{ {
struct window_pane *wp; struct window_pane *wp, *wpmain;
struct layout_cell *lc, *lcmain, *lcother, *lcchild; struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
u_int n, mainw, otherw, sx, sy; u_int n, mainw, otherw, sx, sy;
char *cause; char *cause;
const char *s; const char *s;
@@ -526,7 +507,7 @@ layout_set_main_v_mirrored(struct window *w)
return; return;
n--; /* take off main pane */ n--; /* take off main pane */
/* Find available width - take off one line for the border. */ /* Find available width - take off one column for the border. */
sx = w->sx - 1; sx = w->sx - 1;
/* Get the main pane width. */ /* Get the main pane width. */
@@ -561,62 +542,58 @@ layout_set_main_v_mirrored(struct window *w)
if (sy < w->sy) if (sy < w->sy)
sy = w->sy; sy = w->sy;
/* Free old tree and create a new root. */ layout_free(w, 1);
layout_free(w); lcroot = w->layout_root = layout_create_cell(NULL);
lc = w->layout_root = layout_create_cell(NULL); layout_set_size(lcroot, mainw + otherw + 1, sy, 0, 0);
layout_set_size(lc, mainw + otherw + 1, sy, 0, 0); layout_make_node(lcroot, LAYOUT_LEFTRIGHT);
layout_make_node(lc, LAYOUT_LEFTRIGHT);
wpmain = layout_set_first_tiled(w);
lcmain = wpmain->layout_cell;
lcmain->parent = lcroot;
layout_set_size(lcmain, mainw, sy, 0, 0);
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
/* Create the other pane. */
lcother = layout_create_cell(lc);
layout_set_size(lcother, otherw, sy, 0, 0);
if (n == 1) { if (n == 1) {
wp = TAILQ_NEXT(layout_first_tiled(w), entry); wp = TAILQ_NEXT(wpmain, entry);
while (wp != NULL && window_pane_is_floating(wp)) while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
wp = TAILQ_NEXT(wp, entry); wp = TAILQ_NEXT(wp, entry);
layout_make_leaf(lcother, wp); TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry);
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); wp->layout_cell->parent = lcroot;
} else { } else {
lcother = layout_create_cell(lcroot);
layout_make_node(lcother, LAYOUT_TOPBOTTOM); layout_make_node(lcother, LAYOUT_TOPBOTTOM);
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); layout_set_size(lcother, otherw, sy, 0, 0);
TAILQ_INSERT_HEAD(&lcroot->cells, lcother, entry);
/* Add the remaining panes as children. */
TAILQ_FOREACH(wp, &w->panes, entry) { TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_is_floating(wp)) if (wp == wpmain)
continue; continue;
if (wp == layout_first_tiled(w)) lcchild = wp->layout_cell;
continue;
lcchild = layout_create_cell(lcother);
layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0);
layout_make_leaf(lcchild, wp);
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
lcchild->parent = lcother;
if (layout_cell_is_tiled(lcchild))
layout_set_size(lcchild, otherw, PANE_MINIMUM,
0, 0);
} }
layout_spread_cell(w, lcother); layout_spread_cell(w, lcother);
} }
/* Create the main pane. */
lcmain = layout_create_cell(lc);
layout_set_size(lcmain, mainw, sy, 0, 0);
layout_make_leaf(lcmain, layout_first_tiled(w));
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
/* Fix cell offsets. */
layout_fix_offsets(w); layout_fix_offsets(w);
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
window_resize(w, lc->sx, lc->sy, -1, -1); window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
notify_window("window-layout-changed", w); notify_window("window-layout-changed", w);
server_redraw_window(w); server_redraw_window(w);
} }
void static void
layout_set_tiled(struct window *w) layout_set_tiled(struct window *w)
{ {
struct options *oo = w->options; struct options *oo = w->options;
struct window_pane *wp; struct window_pane *wp;
struct layout_cell *lc, *lcrow, *lcchild; struct layout_cell *lcroot, *lcrow, *lcchild;
u_int n, width, height, used, sx, sy; u_int n, width, height, used, sx, sy;
u_int i, j, columns, rows, max_columns; u_int i, j, columns, rows, max_columns;
@@ -647,56 +624,59 @@ layout_set_tiled(struct window *w)
if (height < PANE_MINIMUM) if (height < PANE_MINIMUM)
height = PANE_MINIMUM; height = PANE_MINIMUM;
/* Free old tree and create a new root. */
layout_free(w);
lc = w->layout_root = layout_create_cell(NULL);
sx = ((width + 1) * columns) - 1; sx = ((width + 1) * columns) - 1;
if (sx < w->sx) if (sx < w->sx)
sx = w->sx; sx = w->sx;
sy = ((height + 1) * rows) - 1; sy = ((height + 1) * rows) - 1;
if (sy < w->sy) if (sy < w->sy)
sy = w->sy; sy = w->sy;
layout_set_size(lc, sx, sy, 0, 0);
layout_make_node(lc, LAYOUT_TOPBOTTOM);
/* Create a grid of the cells, skipping any floating panes. */ layout_free(w, 1);
lcroot = w->layout_root = layout_create_cell(NULL);
layout_set_size(lcroot, sx, sy, 0, 0);
layout_make_node(lcroot, LAYOUT_TOPBOTTOM);
/* Create a grid of the tiled cells. */
wp = TAILQ_FIRST(&w->panes); wp = TAILQ_FIRST(&w->panes);
while (wp != NULL && window_pane_is_floating(wp))
wp = TAILQ_NEXT(wp, entry);
for (j = 0; j < rows; j++) { for (j = 0; j < rows; j++) {
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
wp = TAILQ_NEXT(wp, entry);
/* If this is the last cell, all done. */ /* If this is the last cell, all done. */
if (wp == NULL) if (wp == NULL)
break; break;
/* Create the new row. */ lcchild = wp->layout_cell;
lcrow = layout_create_cell(lc);
layout_set_size(lcrow, w->sx, height, 0, 0);
TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
/* If only one column, just use the row directly. */ /* If only one column, just use the row directly. */
if (n - (j * columns) == 1 || columns == 1) { if (n - (j * columns) == 1 || columns == 1) {
layout_make_leaf(lcrow, wp); lcchild->parent = lcroot;
TAILQ_INSERT_TAIL(&lcroot->cells, lcchild, entry);
layout_set_size(lcchild, w->sx, height, 0, 0);
wp = TAILQ_NEXT(wp, entry); wp = TAILQ_NEXT(wp, entry);
while (wp != NULL && window_pane_is_floating(wp))
wp = TAILQ_NEXT(wp, entry);
continue; continue;
} }
/* Add in the columns. */ /* Create the new row. */
lcrow = layout_create_cell(lcroot);
layout_make_node(lcrow, LAYOUT_LEFTRIGHT); layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
layout_set_size(lcrow, w->sx, height, 0, 0);
TAILQ_INSERT_TAIL(&lcroot->cells, lcrow, entry);
/* Add in the columns. */
for (i = 0; i < columns; i++) { for (i = 0; i < columns; i++) {
/* Create and add a pane cell. */ /* Create and add a pane cell. */
lcchild = layout_create_cell(lcrow); lcchild->parent = lcrow;
layout_set_size(lcchild, width, height, 0, 0);
layout_make_leaf(lcchild, wp);
TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry); TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
layout_set_size(lcchild, width, height, 0, 0);
/* Move to the next non-floating cell. */ /* Move to the next non-floating cell. */
wp = TAILQ_NEXT(wp, entry); wp = TAILQ_NEXT(wp, entry);
while (wp != NULL && window_pane_is_floating(wp)) while (wp != NULL &&
!layout_cell_is_tiled(wp->layout_cell))
wp = TAILQ_NEXT(wp, entry); wp = TAILQ_NEXT(wp, entry);
if (wp == NULL) if (wp == NULL)
break; break;
lcchild = wp->layout_cell;
} }
/* /*
@@ -713,21 +693,19 @@ layout_set_tiled(struct window *w)
w->sx - used); w->sx - used);
} }
/* Adjust the last row height to fit if necessary. */
used = (rows * height) + rows - 1; used = (rows * height) + rows - 1;
if (w->sy > used) { if (w->sy > used) {
lcrow = TAILQ_LAST(&lc->cells, layout_cells); lcrow = TAILQ_LAST(&lcroot->cells, layout_cells);
layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM, layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
w->sy - used); w->sy - used);
} }
/* Fix cell offsets. */
layout_fix_offsets(w); layout_fix_offsets(w);
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);
layout_print_cell(w->layout_root, __func__, 1); layout_print_cell(w->layout_root, __func__, 1);
window_resize(w, lc->sx, lc->sy, -1, -1); window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
notify_window("window-layout-changed", w); notify_window("window-layout-changed", w);
server_redraw_window(w); server_redraw_window(w);
} }

785
layout.c

File diff suppressed because it is too large Load Diff

15
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;
} }

View File

@@ -36,7 +36,26 @@ enum mode_tree_preview {
MODE_TREE_PREVIEW_BIG MODE_TREE_PREVIEW_BIG
}; };
#define MODE_TREE_PREFIX_STYLE \
"#{?mode_tree_selected,#[default]#[noacs]," \
"#[fg=themelightgrey]#[bg=default]#[noacs]}"
#define MODE_TREE_PREFIX_FORMAT \
MODE_TREE_PREFIX_STYLE \
"#{p/#{mode_tree_key_width}:" \
"#{?#{!=:#{mode_tree_key},},(#{mode_tree_key}),}}" \
"#{R:#{?mode_tree_parent_last, ," \
"#[acs]x" MODE_TREE_PREFIX_STYLE " }," \
"#{mode_tree_repeat}}" \
"#{?mode_tree_branch," \
"#[acs]#{?mode_tree_last,mq,tq}" MODE_TREE_PREFIX_STYLE "> ,}" \
"#{?mode_tree_has_children," \
"#{?mode_tree_expanded,#[fg=themered]-" MODE_TREE_PREFIX_STYLE " ," \
"#[fg=themegreen]+" MODE_TREE_PREFIX_STYLE " }," \
"#{?mode_tree_flat,, }}"
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,12 +68,13 @@ 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;
mode_tree_search_cb searchcb; mode_tree_search_cb searchcb;
mode_tree_menu_cb menucb; mode_tree_menu_cb menucb;
mode_tree_height_cb heightcb; mode_tree_height_cb heightcb;
mode_tree_key_cb keycb; mode_tree_key_cb keycb;
mode_tree_swap_cb swapcb; mode_tree_swap_cb swapcb;
mode_tree_sort_cb sortcb; mode_tree_sort_cb sortcb;
@@ -76,6 +96,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 +107,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 +147,25 @@ struct mode_tree_menu {
u_int line; u_int line;
}; };
static void mode_tree_free_items(struct mode_tree_list *); /*
* 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_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 +177,51 @@ 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", "#[fg=themelightgrey]"
"\r\033[1m Down, j \033[0m\016x\017 \033[0mMove cursor down\n", " Up, k #[#{E:tree-mode-border-style},acs]x#[default] Move cursor up",
"\r\033[1m g \033[0m\016x\017 \033[0mGo to top\n", "#[fg=themelightgrey]"
"\r\033[1m G \033[0m\016x\017 \033[0mGo to bottom\n", " Down, j #[#{E:tree-mode-border-style},acs]x#[default] Move cursor down",
"\r\033[1m PPage, C-b \033[0m\016x\017 \033[0mPage up\n", "#[fg=themelightgrey]"
"\r\033[1m NPage, C-f \033[0m\016x\017 \033[0mPage down\n", " g #[#{E:tree-mode-border-style},acs]x#[default] Go to top",
"\r\033[1m Left, h \033[0m\016x\017 \033[0mCollapse %1\n", "#[fg=themelightgrey]"
"\r\033[1m Right, l \033[0m\016x\017 \033[0mExpand %1\n", " G #[#{E:tree-mode-border-style},acs]x#[default] Go to bottom",
"\r\033[1m M-- \033[0m\016x\017 \033[0mCollapse all %1s\n", "#[fg=themelightgrey]"
"\r\033[1m M-+ \033[0m\016x\017 \033[0mExpand all %1s\n", " PPage, C-b #[#{E:tree-mode-border-style},acs]x#[default] Page up",
"\r\033[1m t \033[0m\016x\017 \033[0mToggle %1 tag\n", "#[fg=themelightgrey]"
"\r\033[1m T \033[0m\016x\017 \033[0mUntag all %1s\n", " NPage, C-f #[#{E:tree-mode-border-style},acs]x#[default] Page down",
"\r\033[1m C-t \033[0m\016x\017 \033[0mTag all %1s\n", "#[fg=themelightgrey]"
"\r\033[1m C-s \033[0m\016x\017 \033[0mSearch forward\n", " Left, h #[#{E:tree-mode-border-style},acs]x#[default] Collapse %1",
"\r\033[1m C-r \033[0m\016x\017 \033[0mSearch backward\n", "#[fg=themelightgrey]"
"\r\033[1m n \033[0m\016x\017 \033[0mRepeat search forward\n", " Right, l #[#{E:tree-mode-border-style},acs]x#[default] Expand %1",
"\r\033[1m N \033[0m\016x\017 \033[0mRepeat search backward\n", "#[fg=themelightgrey]"
"\r\033[1m f \033[0m\016x\017 \033[0mFilter %1s\n", " M-- #[#{E:tree-mode-border-style},acs]x#[default] Collapse all %1s",
"\r\033[1m O \033[0m\016x\017 \033[0mChange sort order\n", "#[fg=themelightgrey]"
"\r\033[1m r \033[0m\016x\017 \033[0mReverse sort order\n", " M-+ #[#{E:tree-mode-border-style},acs]x#[default] Expand all %1s",
"\r\033[1m v \033[0m\016x\017 \033[0mToggle preview\n", "#[fg=themelightgrey]"
" t #[#{E:tree-mode-border-style},acs]x#[default] Toggle %1 tag",
"#[fg=themelightgrey]"
" T #[#{E:tree-mode-border-style},acs]x#[default] Untag all %1s",
"#[fg=themelightgrey]"
" C-t #[#{E:tree-mode-border-style},acs]x#[default] Tag all %1s",
"#[fg=themelightgrey]"
" C-s #[#{E:tree-mode-border-style},acs]x#[default] Search forward",
"#[fg=themelightgrey]"
" n #[#{E:tree-mode-border-style},acs]x#[default] Repeat search forward",
"#[fg=themelightgrey]"
" N #[#{E:tree-mode-border-style},acs]x#[default] Repeat search backward",
"#[fg=themelightgrey]"
" f #[#{E:tree-mode-border-style},acs]x#[default] Filter %1s",
"#[fg=themelightgrey]"
" O #[#{E:tree-mode-border-style},acs]x#[default] Change sort order",
"#[fg=themelightgrey]"
" r #[#{E:tree-mode-border-style},acs]x#[default] Reverse sort order",
"#[fg=themelightgrey]"
" v #[#{E:tree-mode-border-style},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", "#[fg=themelightgrey]"
" q, Escape #[#{E:tree-mode-border-style},acs]x#[default] Exit mode",
NULL NULL
}; };
#define MODE_TREE_HELP_DEFAULT_WIDTH 39 #define MODE_TREE_HELP_DEFAULT_WIDTH 39
@@ -378,6 +441,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 +575,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 +709,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 +773,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)
{ {
@@ -713,8 +792,8 @@ mode_tree_no_tag(struct mode_tree_item *mti)
} }
/* /*
* Set the alignment of mti->name: -1 to align left, 0 (default) to not align, * Set the alignment of the item name: -1 to align left, 0 (default) to not
* or 1 to align right. * align, or 1 to align right.
*/ */
void void
mode_tree_align(struct mode_tree_item *mti, int align) mode_tree_align(struct mode_tree_item *mti, int align)
@@ -743,25 +822,34 @@ mode_tree_draw(struct mode_tree_data *mtd)
struct mode_tree_item *mti; struct mode_tree_item *mti;
struct options *oo = wp->window->options; struct options *oo = wp->window->options;
struct screen_write_ctx ctx; struct screen_write_ctx ctx;
struct grid_cell gc0, gc; struct format_tree *ft;
u_int w, h, i, j, sy, box_x, box_y, width; struct grid_cell gc0, gc, box_gc;
char *text, *start, *key; u_int w, h, i, sy, box_x, box_y;
const char *tag, *symbol; u_int width, text_width, prefix_width, left;
size_t size, n; char *text, *prefix;
int keylen, pad, alignlen[mtd->maxdepth + 1]; const char *tag, *separator;
size_t n;
int keylen, alignlen[mtd->maxdepth + 1];
int dfg, dfg0;
if (mtd->line_size == 0) if (mtd->line_size == 0)
return; return;
memcpy(&gc0, &grid_default_cell, sizeof gc0); memcpy(&gc0, &grid_default_cell, sizeof gc0);
memcpy(&gc, &grid_default_cell, sizeof gc); memcpy(&gc, &grid_default_cell, sizeof gc);
style_apply(&gc, oo, "mode-style", NULL); style_apply(&gc, oo, "tree-mode-selection-style", NULL);
memcpy(&box_gc, &grid_default_cell, sizeof box_gc);
style_apply(&box_gc, oo, "tree-mode-border-style", NULL);
dfg = gc.fg;
dfg0 = gc0.fg;
w = mtd->width; w = mtd->width;
h = mtd->height; h = mtd->height;
screen_write_start(&ctx, s); screen_write_start(&ctx, s);
screen_write_clearscreen(&ctx, 8); screen_write_clearscreen(&ctx, 8);
ft = format_create_defaults(NULL, NULL, NULL, NULL, wp);
keylen = 0; keylen = 0;
for (i = 0; i < mtd->line_size; i++) { for (i = 0; i < mtd->line_size; i++) {
@@ -792,81 +880,98 @@ mode_tree_draw(struct mode_tree_data *mtd)
screen_write_cursormove(&ctx, 0, i - mtd->offset, 0); screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
pad = keylen - 2 - mti->keylen;
if (mti->key != KEYC_NONE) if (mti->key != KEYC_NONE)
xasprintf(&key, "(%s)%*s", mti->keystr, pad, ""); format_add(ft, "mode_tree_key", "%s", mti->keystr);
else else
key = xstrdup(""); format_add(ft, "mode_tree_key", "%s", "");
format_add(ft, "mode_tree_key_width", "%d", keylen);
if (line->flat) format_add(ft, "mode_tree_selected", "%d", i == mtd->current);
symbol = ""; if (line->depth == 0) {
else if (TAILQ_EMPTY(&mti->children)) format_add(ft, "mode_tree_repeat", "%u", 0);
symbol = " "; format_add(ft, "mode_tree_branch", "0");
else if (mti->expanded) format_add(ft, "mode_tree_parent_last", "0");
symbol = "- "; } else {
else format_add(ft, "mode_tree_repeat", "%u",
symbol = "+ "; line->depth - 1);
format_add(ft, "mode_tree_branch", "1");
if (line->depth == 0) if (mti->parent != NULL &&
start = xstrdup(symbol); mtd->line_list[mti->parent->line].last)
else { format_add(ft, "mode_tree_parent_last", "1");
size = (4 * line->depth) + 32;
start = xcalloc(1, size);
for (j = 1; j < line->depth; j++) {
if (mti->parent != NULL &&
mtd->line_list[mti->parent->line].last)
strlcat(start, " ", size);
else
strlcat(start, "\001x\001 ", size);
}
if (line->last)
strlcat(start, "\001mq\001> ", size);
else else
strlcat(start, "\001tq\001> ", size); format_add(ft, "mode_tree_parent_last", "0");
strlcat(start, symbol, size);
} }
if (TAILQ_EMPTY(&mti->children))
format_add(ft, "mode_tree_has_children", "0");
else
format_add(ft, "mode_tree_has_children", "1");
format_add(ft, "mode_tree_last", "%d", line->last);
format_add(ft, "mode_tree_expanded", "%d", mti->expanded);
format_add(ft, "mode_tree_flat", "%d", line->flat);
prefix = format_expand(ft, MODE_TREE_PREFIX_FORMAT);
prefix_width = format_width(prefix);
if (prefix_width > w)
prefix_width = w;
if (mti->tagged) if (mti->tagged)
tag = "*"; tag = "*";
else else
tag = ""; tag = "";
xasprintf(&text, "%-*s%s%*s%s%s", keylen, key, start, if (mti->text != NULL)
mti->align * alignlen[line->depth], mti->name, tag, separator = "#[fg=themelightgrey]: #[default]";
(mti->text != NULL) ? ": " : "" ); else
width = utf8_cstrwidth(text); separator = "";
if (width > w) xasprintf(&text, "%*s%s%s",
width = w; mti->align * alignlen[line->depth], mti->name, tag, separator);
free(start); text_width = format_width(text);
left = (prefix_width < w) ? (w - prefix_width) : 0;
if (text_width > left)
text_width = left;
width = prefix_width + text_width;
if (mti->tagged) { if (mti->tagged) {
gc.attr ^= GRID_ATTR_BRIGHT; gc.fg = COLOUR_THEME_CYAN|COLOUR_FLAG_THEME;
gc0.attr ^= GRID_ATTR_BRIGHT; gc0.fg = COLOUR_THEME_CYAN|COLOUR_FLAG_THEME;
} }
if (i != mtd->current) { if (i != mtd->current) {
screen_write_clearendofline(&ctx, 8); screen_write_clearendofline(&ctx, 8);
screen_write_nputs(&ctx, w, &gc0, "%s", text); format_draw(&ctx, &grid_default_cell, prefix_width,
if (mti->text != NULL) { prefix, NULL, 0);
format_draw(&ctx, &gc0, w - width, mti->text, if (left != 0) {
NULL, 0); screen_write_cursormove(&ctx, prefix_width,
i - mtd->offset, 0);
format_draw(&ctx, &gc0, left, text, NULL, 0);
if (mti->text != NULL && width < w) {
screen_write_cursormove(&ctx, width,
i - mtd->offset, 0);
format_draw(&ctx, &gc0, w - width,
mti->text, NULL, 0);
}
} }
} else { } else {
screen_write_clearendofline(&ctx, gc.bg); screen_write_clearendofline(&ctx, gc.bg);
screen_write_nputs(&ctx, w, &gc, "%s", text); format_draw(&ctx, &gc, prefix_width, prefix, NULL, 0);
if (mti->text != NULL) { if (left != 0) {
format_draw(&ctx, &gc, w - width, mti->text, screen_write_cursormove(&ctx, prefix_width,
NULL, 0); i - mtd->offset, 0);
format_draw(&ctx, &gc, left, text, NULL, 0);
if (mti->text != NULL && width < w) {
screen_write_cursormove(&ctx, width,
i - mtd->offset, 0);
format_draw(&ctx, &gc, w - width,
mti->text, NULL, 0);
}
} }
} }
free(text); free(text);
free(key); free(prefix);
if (mti->tagged) { if (mti->tagged) {
gc.attr ^= GRID_ATTR_BRIGHT; gc.fg = dfg;
gc0.attr ^= GRID_ATTR_BRIGHT; gc0.fg = dfg0;
} }
} }
format_free(ft);
if (mtd->preview == MODE_TREE_PREVIEW_OFF) if (mtd->preview == MODE_TREE_PREVIEW_OFF)
goto done; goto done;
@@ -881,31 +986,34 @@ mode_tree_draw(struct mode_tree_data *mtd)
mti = mti->parent; mti = mti->parent;
screen_write_cursormove(&ctx, 0, h, 0); screen_write_cursormove(&ctx, 0, h, 0);
screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL); screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, &box_gc, 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)) {
screen_write_cursormove(&ctx, 1, h, 0); screen_write_cursormove(&ctx, 1, h, 0);
screen_write_puts(&ctx, &gc0, "%s", text); screen_write_puts(&ctx, &box_gc, "%s", text);
if (mtd->no_matches) if (mtd->no_matches)
n = (sizeof "no matches") - 1; n = (sizeof "no matches") - 1;
else else
n = (sizeof "active") - 1; n = (sizeof "active") - 1;
if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) { if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
screen_write_puts(&ctx, &gc0, " (filter: "); screen_write_puts(&ctx, &box_gc, " (filter: ");
if (mtd->no_matches) if (mtd->no_matches)
screen_write_puts(&ctx, &gc, "no matches"); screen_write_puts(&ctx, &box_gc, "no matches");
else else
screen_write_puts(&ctx, &gc0, "active"); screen_write_puts(&ctx, &box_gc, "active");
screen_write_puts(&ctx, &gc0, ") "); screen_write_puts(&ctx, &box_gc, ") ");
} else } else
screen_write_puts(&ctx, &gc0, " "); screen_write_puts(&ctx, &box_gc, " ");
} }
free(text); free(text);
@@ -918,10 +1026,151 @@ mode_tree_draw(struct mode_tree_data *mtd)
} }
done: done:
screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0); 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_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->c = c;
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 +1291,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_icase = mode_tree_is_lowercase(s);
mode_tree_search_set(mtd);
} }
mtd->search = xstrdup(s);
mtd->search_icase = mode_tree_is_lowercase(s);
mode_tree_search_set(mtd);
return (0); if (key == PROMPT_KEY_HANDLED)
return (PROMPT_CONTINUE);
return (PROMPT_CLOSE);
} }
static void static enum prompt_result
mode_tree_search_free(void *data)
{
mode_tree_remove_ref(data);
}
static int
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 +1334,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 +1417,32 @@ 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, struct format_tree *ft, const char *line,
const char *item, u_int x, u_int y, u_int w)
{ {
struct session *s = c->session; char *expanded, *replaced;
u_int px, py, w, h = 0;
const char **line, **lines = NULL, *item = "item"; replaced = cmd_template_replace(line, item, 1);
char *new_line; expanded = format_expand(ft, replaced);
free(replaced);
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 options *oo = mtd->wp->window->options;
struct grid_cell box_gc, gc;
struct format_tree *ft;
const char **line, **lines = NULL, *item = "item";
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 +1458,36 @@ 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(&box_gc, &grid_default_cell, sizeof box_gc);
NULL, px, py, w, h, NULL, NULL, 0, NULL, NULL, NULL, c, s, NULL, style_apply(&box_gc, oo, "tree-mode-border-style", NULL);
NULL, NULL, NULL) != 0) memcpy(&gc, &grid_default_cell, sizeof gc);
return; ft = format_create_defaults(NULL, NULL, NULL, NULL, mtd->wp);
screen_write_cursormove(ctx, x, y, 0);
screen_write_box(ctx, box_w, box_h, BOX_LINES_DEFAULT, &box_gc, NULL);
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, ft, *line, item, x, y, w);
free(new_line); for (line = lines; line != NULL && *line != NULL; line++, y++)
} mode_tree_draw_help_line(ctx, &gc, ft, *line, item, x, y, w);
for (line = lines; line != NULL && *line != NULL; line++) { for (line = mode_tree_help_end; *line != NULL; line++, y++)
new_line = cmd_template_replace(*line, item, 1); mode_tree_draw_help_line(ctx, &gc, ft, *line, item, x, y, w);
popup_write(c, new_line, strlen(new_line)); format_free(ft);
free(new_line); }
}
for (line = mode_tree_help_end; *line != NULL; line++) { static void
new_line = cmd_template_replace(*line, item, 1); mode_tree_display_help(struct mode_tree_data *mtd)
popup_write(c, new_line, strlen(new_line)); {
free(new_line); mtd->help = 1;
} mode_tree_draw(mtd);
popup_write(c, "\033[H", 3);
} }
int int
@@ -1221,14 +1496,81 @@ 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);
}
if (*key == KEYC_FOCUS_IN || *key == KEYC_FOCUS_OUT) {
*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 +1636,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 +1766,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 +1780,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_NAME_FORBID_EXT); window_set_name(w, name, 1);
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, WINDOW_NAME_FORBID); name = clean_name(name, 0);
free(copy); free(copy);
if (name == NULL) if (name == NULL)
return (xstrdup("")); return (xstrdup(""));

View File

@@ -64,19 +64,19 @@ static const char *options_table_cursor_style_list[] = {
"blinking-bar", "bar", NULL "blinking-bar", "bar", NULL
}; };
static const char *options_table_pane_scrollbars_list[] = { static const char *options_table_pane_scrollbars_list[] = {
"off", "modal", "on", NULL "off", "modal", "on", "auto-hide", NULL
}; };
static const char *options_table_pane_scrollbars_position_list[] = { 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
@@ -108,6 +108,9 @@ static const char *options_table_extended_keys_format_list[] = {
static const char *options_table_allow_passthrough_list[] = { static const char *options_table_allow_passthrough_list[] = {
"off", "on", "all", NULL "off", "on", "all", NULL
}; };
static const char *options_table_theme_list[] = {
"detect", "terminal", "light", "dark", NULL
};
static const char *options_table_copy_mode_line_numbers_list[] = { static const char *options_table_copy_mode_line_numbers_list[] = {
"off", "default", "absolute", "relative", "hybrid", NULL "off", "default", "absolute", "relative", "hybrid", NULL
}; };
@@ -270,6 +273,7 @@ const struct options_name_map options_other_names[] = {
{ "clock-mode-color", "clock-mode-colour" }, { "clock-mode-color", "clock-mode-colour" },
{ "cursor-color", "cursor-colour" }, { "cursor-color", "cursor-colour" },
{ "prompt-cursor-color", "prompt-cursor-colour" }, { "prompt-cursor-color", "prompt-cursor-colour" },
{ "prompt-command-cursor-color", "prompt-command-cursor-colour" },
{ "pane-colors", "pane-colours" }, { "pane-colors", "pane-colours" },
{ NULL, NULL } { NULL, NULL }
}; };
@@ -327,9 +331,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."
}, },
@@ -444,7 +449,7 @@ const struct options_table_entry options_table[] = {
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.default_str = "default", .default_str = "bg=themedarkgrey,fg=themewhite",
.separator = ",", .separator = ",",
.text = "Default style of menu." .text = "Default style of menu."
}, },
@@ -453,7 +458,7 @@ const struct options_table_entry options_table[] = {
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.default_str = "bg=yellow,fg=black", .default_str = "bg=themeyellow,fg=themeblack",
.separator = ",", .separator = ",",
.text = "Default style of selected menu item." .text = "Default style of selected menu item."
}, },
@@ -461,7 +466,7 @@ const struct options_table_entry options_table[] = {
{ .name = "menu-border-style", { .name = "menu-border-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "default", .default_str = "bg=themedarkgrey,fg=themelightgrey",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Default style of menu borders." .text = "Default style of menu borders."
@@ -536,6 +541,175 @@ const struct options_table_entry options_table[] = {
"automatically detected." "automatically detected."
}, },
{ .name = "theme",
.type = OPTIONS_TABLE_CHOICE,
.scope = OPTIONS_TABLE_SERVER,
.choices = options_table_theme_list,
.default_num = 0,
.text = "Whether tmux should detect the terminal theme, use terminal "
"ANSI colours, or force the light or dark theme."
},
{ .name = "dark-theme-black",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},gray5,black}",
.text = "Dark theme colour for black."
},
{ .name = "dark-theme-white",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},gray90,white}",
.text = "Dark theme colour for white."
},
{ .name = "dark-theme-light-grey",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},gray70,white}",
.text = "Dark theme colour for light grey."
},
{ .name = "dark-theme-dark-grey",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},gray15,black}",
.text = "Dark theme colour for dark grey."
},
{ .name = "dark-theme-green",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},yellowgreen,green}",
.text = "Dark theme colour for green."
},
{ .name = "dark-theme-yellow",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},darkgoldenrod,yellow}",
.text = "Dark theme colour for yellow."
},
{ .name = "dark-theme-red",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},indianred,red}",
.text = "Dark theme colour for red."
},
{ .name = "dark-theme-blue",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},skyblue3,blue}",
.text = "Dark theme colour for blue."
},
{ .name = "dark-theme-cyan",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},cadetblue,cyan}",
.text = "Dark theme colour for cyan."
},
{ .name = "dark-theme-magenta",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},mediumpurple,magenta}",
.text = "Dark theme colour for magenta."
},
{ .name = "light-theme-black",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},gray10,black}",
.text = "Light theme colour for black."
},
{ .name = "light-theme-white",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},gray95,white}",
.text = "Light theme colour for white."
},
{ .name = "light-theme-light-grey",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},gray80,white}",
.text = "Light theme colour for light grey."
},
{ .name = "light-theme-dark-grey",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},gray45,black}",
.text = "Light theme colour for dark grey."
},
{ .name = "light-theme-green",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},seagreen,green}",
.text = "Light theme colour for green."
},
{ .name = "light-theme-yellow",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},darkgoldenrod,yellow}",
.text = "Light theme colour for yellow."
},
{ .name = "light-theme-red",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},indianred4,red}",
.text = "Light theme colour for red."
},
{ .name = "light-theme-blue",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},steelblue,blue}",
.text = "Light theme colour for blue."
},
{ .name = "light-theme-cyan",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},darkcyan,cyan}",
.text = "Light theme colour for cyan."
},
{ .name = "light-theme-magenta",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "#{?#{e|>=:#{client_colours},256},purple4,magenta}",
.text = "Light theme colour for magenta."
},
{ .name = "user-keys", { .name = "user-keys",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER, .scope = OPTIONS_TABLE_SERVER,
@@ -634,19 +808,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 = "themered",
.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 = "themeblue",
.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,
@@ -726,11 +910,13 @@ const struct options_table_entry options_table[] = {
{ .name = "message-command-style", { .name = "message-command-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
.default_str = "bg=black,fg=yellow,fill=black", .default_str = "bg=themeblack,fg=themeyellow,"
"#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},,"
"fill=themeblack}",
.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 +941,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=themeyellow,fg=themeblack,"
"#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},,"
"fill=themeyellow}",
.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. "
@@ -954,7 +1142,7 @@ const struct options_table_entry options_table[] = {
{ .name = "status-style", { .name = "status-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
.default_str = "bg=green,fg=black", .default_str = "bg=themegreen,fg=themeblack",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the status line." .text = "Style of the status line."
@@ -963,7 +1151,7 @@ const struct options_table_entry options_table[] = {
{ .name = "pane-status-current-style", { .name = "pane-status-current-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "default", .default_str = "underscore",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the current pane in the status line." .text = "Style of the current pane in the status line."
@@ -980,12 +1168,22 @@ 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."
}, },
{ .name = "prompt-command-cursor-colour",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION,
.flags = OPTIONS_TABLE_IS_COLOUR,
.default_str = "",
.text = "Colour of the cursor in the command prompt when in command "
"mode, if 'status-keys' is set to 'vi'."
},
{ .name = "prompt-cursor-style", { .name = "prompt-cursor-style",
.type = OPTIONS_TABLE_CHOICE, .type = OPTIONS_TABLE_CHOICE,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
@@ -1006,7 +1204,7 @@ const struct options_table_entry options_table[] = {
{ .name = "session-status-current-style", { .name = "session-status-current-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "default", .default_str = "underscore",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the current session in the status line." .text = "Style of the current session in the status line."
@@ -1134,9 +1332,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 = "themeblue",
.text = "Colour of the clock in clock mode." .text = "Colour of the clock in clock mode."
}, },
@@ -1151,7 +1350,7 @@ const struct options_table_entry options_table[] = {
{ .name = "copy-mode-match-style", { .name = "copy-mode-match-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "bg=cyan,fg=black", .default_str = "bg=themecyan,fg=themeblack",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of search matches in copy mode." .text = "Style of search matches in copy mode."
@@ -1160,7 +1359,7 @@ const struct options_table_entry options_table[] = {
{ .name = "copy-mode-current-match-style", { .name = "copy-mode-current-match-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "bg=magenta,fg=black", .default_str = "bg=thememagenta,fg=themeblack",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the current search match in copy mode." .text = "Style of the current search match in copy mode."
@@ -1169,7 +1368,7 @@ const struct options_table_entry options_table[] = {
{ .name = "copy-mode-mark-style", { .name = "copy-mode-mark-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "bg=red,fg=black", .default_str = "bg=themeyellow,fg=themeblack",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the marked line in copy mode." .text = "Style of the marked line in copy mode."
@@ -1208,7 +1407,7 @@ const struct options_table_entry options_table[] = {
{ .name = "copy-mode-current-line-number-style", { .name = "copy-mode-current-line-number-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "fg=yellow", .default_str = "fg=themeyellow",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of current line number in copy mode." .text = "Style of current line number in copy mode."
@@ -1217,7 +1416,7 @@ const struct options_table_entry options_table[] = {
{ .name = "copy-mode-line-number-style", { .name = "copy-mode-line-number-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "fg=white,dim", .default_str = "fg=themelightgrey,dim",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of line numbers in copy mode." .text = "Style of line numbers in copy mode."
@@ -1238,6 +1437,28 @@ const struct options_table_entry options_table[] = {
.text = "Character used to fill unused parts of window." .text = "Character used to fill unused parts of window."
}, },
{ .name = "floating-pane-border-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_str = "default",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Default border style for floating panes. "
"Overrides pane-border-style for floating panes unless "
"a per-pane style is set."
},
{ .name = "floating-pane-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_str = "default",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Default content style for floating panes. "
"Overrides window-style for floating panes unless "
"a per-pane style is set."
},
{ .name = "main-pane-height", { .name = "main-pane-height",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
@@ -1266,7 +1487,7 @@ const struct options_table_entry options_table[] = {
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.default_str = "noattr,bg=yellow,fg=black", .default_str = "noattr,bg=themeyellow,fg=themeblack",
.separator = ",", .separator = ",",
.text = "Style of indicators and highlighting in modes." .text = "Style of indicators and highlighting in modes."
}, },
@@ -1315,7 +1536,9 @@ const struct options_table_entry options_table[] = {
{ .name = "pane-active-border-style", { .name = "pane-active-border-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}", .default_str = "fg=#{?pane_marked,thememagenta,"
"#{?synchronize-panes,themered,"
"#{?pane_in_mode,themeyellow,themegreen}}}",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the active pane border." .text = "Style of the active pane border."
@@ -1356,7 +1579,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 +1588,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."
@@ -1374,7 +1597,7 @@ const struct options_table_entry options_table[] = {
{ .name = "pane-border-style", { .name = "pane-border-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_str = "default", .default_str = "fg=themelightgrey",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the pane status lines." .text = "Style of the pane status lines."
@@ -1393,13 +1616,23 @@ const struct options_table_entry options_table[] = {
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.choices = options_table_pane_scrollbars_list, .choices = options_table_pane_scrollbars_list,
.default_num = PANE_SCROLLBARS_OFF, .default_num = PANE_SCROLLBARS_OFF,
.text = "Pane scrollbar state." .text = "Pane scrollbar state: off, on, modal, or auto-hide."
},
{ .name = "pane-scrollbars-timeout",
.type = OPTIONS_TABLE_NUMBER,
.scope = OPTIONS_TABLE_WINDOW,
.minimum = 0,
.maximum = INT_MAX,
.default_num = 500,
.unit = "milliseconds",
.text = "Time before modal and auto-hide pane scrollbars disappear."
}, },
{ .name = "pane-scrollbars-style", { .name = "pane-scrollbars-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_str = "bg=black,fg=white,width=1,pad=0", .default_str = "bg=themedarkgrey,fg=themelightgrey,width=1,pad=0",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the pane scrollbar." .text = "Style of the pane scrollbar."
@@ -1416,7 +1649,7 @@ const struct options_table_entry options_table[] = {
{ .name = "popup-style", { .name = "popup-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "default", .default_str = "bg=themedarkgrey,fg=themewhite",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Default style of popups." .text = "Default style of popups."
@@ -1425,7 +1658,7 @@ const struct options_table_entry options_table[] = {
{ .name = "popup-border-style", { .name = "popup-border-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "default", .default_str = "bg=themedarkgrey,fg=themelightgrey",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Default style of popup borders." .text = "Default style of popup borders."
@@ -1471,6 +1704,15 @@ const struct options_table_entry options_table[] = {
"history when clearing the whole screen." "history when clearing the whole screen."
}, },
{ .name = "switch-mode-match-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_str = "bg=cyan fg=black",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Style of matched characters in switch mode."
},
{ .name = "synchronize-panes", { .name = "synchronize-panes",
.type = OPTIONS_TABLE_FLAG, .type = OPTIONS_TABLE_FLAG,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
@@ -1488,6 +1730,15 @@ const struct options_table_entry options_table[] = {
"A value of 0 means no limit." "A value of 0 means no limit."
}, },
{ .name = "tree-mode-border-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW,
.default_str = "bg=themedarkgrey,fg=themelightgrey",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Style of borders in tree mode."
},
{ .name = "tree-mode-preview-format", { .name = "tree-mode-preview-format",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
@@ -1503,13 +1754,22 @@ const struct options_table_entry options_table[] = {
.default_str = "fg=#{?#{||:" .default_str = "fg=#{?#{||:"
"#{&&:#{pane_format},#{pane_active}}," "#{&&:#{pane_format},#{pane_active}},"
"#{&&:#{window_format},#{window_active}}}," "#{&&:#{window_format},#{window_active}}},"
"#{display-panes-active-colour}," "themered,"
"#{display-panes-colour}}", "themeblue}",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of preview indicator in tree mode." .text = "Style of preview indicator in tree mode."
}, },
{ .name = "tree-mode-selection-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW,
.default_str = "#{E:mode-style}",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Style of the selected line in tree mode."
},
{ .name = "window-active-style", { .name = "window-active-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
@@ -1583,7 +1843,7 @@ const struct options_table_entry options_table[] = {
{ .name = "window-status-current-style", { .name = "window-status-current-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW, .scope = OPTIONS_TABLE_WINDOW,
.default_str = "default", .default_str = "underscore",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the current window in the status line." .text = "Style of the current window in the status line."

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)
{ {
@@ -977,9 +989,12 @@ struct style *
options_string_to_style(struct options *oo, const char *name, 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 char *s; const struct options_table_entry *oe;
char *expanded; const struct grid_cell *dgc = &grid_default_cell;
const char *s;
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))
free(expanded); failed = style_parse_colour(&o->style, dgc, expanded);
return (NULL); else
} failed = style_parse(&o->style, dgc, expanded);
free(expanded); free(expanded);
if (failed != 0)
return (NULL);
} 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);
} }
@@ -1186,6 +1214,17 @@ options_push_changes(const char *name)
log_debug("%s: %s", __func__, name); log_debug("%s: %s", __func__, name);
if (strcmp(name, "theme") == 0 ||
strncmp(name, "dark-theme-", 11) == 0 ||
strncmp(name, "light-theme-", 12) == 0) {
TAILQ_FOREACH(loop, &clients, entry) {
server_client_update_theme_colours(loop);
if (loop->tty.flags & TTY_OPENED)
tty_invalidate(&loop->tty);
server_redraw_client(loop);
}
}
if (strcmp(name, "automatic-rename") == 0) { if (strcmp(name, "automatic-rename") == 0) {
RB_FOREACH(w, windows, &windows) { RB_FOREACH(w, windows, &windows) {
if (w->active == NULL) if (w->active == NULL)
@@ -1219,6 +1258,16 @@ 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-timeout") == 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 ||
@@ -1237,8 +1286,17 @@ options_push_changes(const char *name)
if (strcmp(name, "pane-border-status") == 0 || if (strcmp(name, "pane-border-status") == 0 ||
strcmp(name, "pane-scrollbars") == 0 || strcmp(name, "pane-scrollbars") == 0 ||
strcmp(name, "pane-scrollbars-position") == 0) { strcmp(name, "pane-scrollbars-position") == 0) {
RB_FOREACH(w, windows, &windows) RB_FOREACH(w, windows, &windows) {
w->sb = options_get_number(w->options,
"pane-scrollbars");
w->sb_pos = options_get_number(w->options,
"pane-scrollbars-position");
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);
}
}
if (strcmp(name, "pane-scrollbars") == 0) {
RB_FOREACH(wp, window_pane_tree, &all_window_panes)
window_pane_scrollbar_hide(wp);
} }
if (strcmp(name, "pane-scrollbars-style") == 0) { if (strcmp(name, "pane-scrollbars-style") == 0) {
RB_FOREACH(wp, window_pane_tree, &all_window_panes) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) {

View File

@@ -219,7 +219,7 @@ paste_rename(const char *oldname, const char *newname, char **cause)
return (-1); return (-1);
} }
name = clean_name(newname, ""); name = clean_name(newname, 0);
if (name == NULL) { if (name == NULL) {
if (cause != NULL) if (cause != NULL)
xasprintf(cause, "invalid buffer name: %s", newname); xasprintf(cause, "invalid buffer name: %s", newname);
@@ -287,7 +287,7 @@ paste_set(char *data, size_t size, const char *name, char **cause)
return (-1); return (-1);
} }
newname = clean_name(name, ""); newname = clean_name(name, 0);
if (newname == NULL) { if (newname == NULL) {
if (cause != NULL) if (cause != NULL)
xasprintf(cause, "invalid buffer name: %s", name); xasprintf(cause, "invalid buffer name: %s", name);

169
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;
@@ -647,11 +635,7 @@ 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_menu_items, NULL, c, NULL);
menu_add_items(pd->menu, popup_internal_menu_items, NULL, c,
NULL);
} else
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;
else else
@@ -858,127 +842,16 @@ 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->job = job_run(shellcmd, argc, argv, env, s, cwd,
pd->ictx = input_init(NULL, NULL, &pd->palette, NULL); popup_job_update_cb, popup_job_complete_cb, NULL, pd,
else { JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
pd->job = job_run(shellcmd, argc, argv, env, s, cwd, if (pd->job == NULL) {
popup_job_update_cb, popup_job_complete_cb, NULL, pd, popup_free(pd);
JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy); return (-1);
if (pd->job == NULL) {
popup_free(pd);
return (-1);
}
pd->ictx = input_init(NULL, job_get_event(pd->job),
&pd->palette, c);
} }
pd->ictx = input_init(NULL, job_get_event(pd->job), &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;
}

1605
prompt.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,31 @@ TESTS!= echo *.sh
.PHONY: all $(TESTS) .PHONY: all $(TESTS)
.NOTPARALLEL: all $(TESTS) .NOTPARALLEL: all $(TESTS)
all: $(TESTS) all:
@failed=0; failures=; \
for test in $(TESTS); do \
printf '%-40s ' "$$test"; \
start=$$(date +%s); \
if sh "$$test" >/dev/null 2>&1; then \
end=$$(date +%s); \
echo "PASS ($$((end - start))s)"; \
else \
end=$$(date +%s); \
echo "FAIL ($$((end - start))s)"; \
failed=1; \
failures="$$failures $$test"; \
fi; \
sleep 1; \
done; \
if [ "$$failed" -ne 0 ]; then \
echo; \
echo "failures:"; \
for test in $$failures; do \
echo " $$test"; \
done; \
fi; \
exit $$failed
$(TESTS): $(TESTS):
sh $*.sh sh $@
sleep 1 sleep 1

Binary file not shown.

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

@@ -0,0 +1,137 @@
#!/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 allow empty names, ':', '.', '#' and '#('.
$TMUX rename-session '' || fail "empty session name rejected"
must_equal "$($TMUX display-message -p '#{session_name}')" ''
$TMUX rename-session 'session:.##(ok)' || \
fail "session name with : . or #( rejected"
must_equal "$($TMUX display-message -p '#{session_name}')" 'session:.#(ok)'
$TMUX rename-window '' || fail "empty window name rejected"
must_equal "$($TMUX display-message -p '#{window_name}')" ''
$TMUX rename-window 'window:.##(ok)' || \
fail "window name with : . or #( rejected"
must_equal "$($TMUX display-message -p '#{window_name}')" 'window:.#(ok)'
$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)'
$TMUX set-option -q @name 'format:.#(ok)' || exit 1
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 : . or #( 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"
created_window=$($TMUX new-window -dP -F '#{window_id}' \
-n 'created-window:.##(ok)') || \
fail "new-window name with : . or #( rejected"
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
'created-window:.#(ok)'
created=$($TMUX new-session -dP -F '#{session_id}:#{window_id}' \
-s 'new-session-#{pid}:.##(ok)' -n 'new-window-#{pid}:.##(ok)') || \
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:.#(ok)"
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
"new-window-$pid:.#(ok)"
$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)'
# 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'
$TMUX send-keys "printf '\\033]2;title#[fg=red]ok\\007'" Enter || exit 1
sleep 1
must_equal "$($TMUX display-message -p '#{pane_title}')" 'title#[fg=red]ok'
$TMUX send-keys "printf '\\033]2;title#(bad)\\007'" Enter || exit 1
sleep 1
must_equal "$($TMUX display-message -p '#{pane_title}')" 'title_(bad)'
# 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 allow '#' except in '#('.
$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 allow '#' except in '#('.
$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

126
regress/environ-update.sh Normal file
View File

@@ -0,0 +1,126 @@
#!/bin/sh
# Tests of update-environment handling (environ_update() in environ.c), which
# runs when a client attaches to a session: for each pattern in the session's
# update-environment option, a matching variable in the attaching client's
# environment is copied into the session environment, and a pattern that
# matches nothing clears that name in the session (a NULL-valued entry).
#
# This needs a real attached client with a controllable environment, so - as in
# format-variables.sh - a second server provides one: an inner "tmux attach"
# runs inside a pane of the second server, and the variables to import are set
# in that inner command's own environment.
#
# environ.sh covers the set-environment/show-environment commands themselves.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
# A second server on its own socket hosts the pane that runs the inner client.
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
cleanup()
{
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
}
fail()
{
echo "$1"
cleanup
exit 1
}
# check_value $var $expected
#
# Compare show-environment of $var on the session with $expected.
check_value()
{
out=$($TMUX show-environment -t main "$1" 2>&1)
if [ "$out" != "$2" ]; then
echo "show-environment $1 failed."
echo "Expected: '$2'"
echo "But got: '$out'"
cleanup
exit 1
fi
}
# wait_clients $n
#
# Wait (up to ~10s) until the test server has exactly $n clients attached.
wait_clients()
{
i=0
while [ "$i" -lt 10 ]; do
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
[ "$c" -eq "$1" ] && return 0
sleep 1
i=$((i + 1))
done
return 1
}
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
# The session imports MYVAR and ABSENTVAR by exact name and anything matching
# the glob TEST_*; nothing else is imported.
$TMUX set -g update-environment "MYVAR ABSENTVAR TEST_*" || exit 1
# Seed the session so the effect of attaching is visible: MYVAR will be
# overwritten by the client's value and ABSENTVAR will be cleared.
$TMUX set-environment -t main MYVAR oldvalue || exit 1
$TMUX set-environment -t main ABSENTVAR pre-existing || exit 1
# --- attach a client whose environment carries the imported variables ------
#
# MYVAR and TEST_GLOB are present in the inner client's environment; ABSENTVAR
# is deliberately absent; OTHER is present but not named by update-environment.
$TMUX2 new-session -d -x 90 -y 30 \
"MYVAR=fromclient TEST_GLOB=globval OTHER=nope $TMUX attach -t main" \
|| fail "could not start inner client"
wait_clients 1 || fail "no client attached to test server"
# MYVAR matched by name and present in the client -> imported (overwrites).
check_value MYVAR "MYVAR=fromclient"
# TEST_GLOB matched by the TEST_* glob and present -> imported.
check_value TEST_GLOB "TEST_GLOB=globval"
# ABSENTVAR named but not in the client environment -> cleared (NULL value,
# printed as -NAME).
check_value ABSENTVAR "-ABSENTVAR"
# OTHER is in the client environment but not named by update-environment, so it
# is not imported at all.
out=$($TMUX show-environment -t main OTHER 2>&1)
[ "$out" = "unknown variable: OTHER" ] || \
fail "OTHER was imported but should not have been: '$out'"
# --- -E disables the update-environment import -----------------------------
#
# Detach the client (kill its host server), reset the session variables, then
# reattach with -E: the session values must be left untouched.
$TMUX2 kill-server 2>/dev/null
wait_clients 0 || fail "client did not detach"
$TMUX set-environment -t main MYVAR oldvalue2 || exit 1
$TMUX set-environment -t main ABSENTVAR pre2 || exit 1
$TMUX2 new-session -d -x 90 -y 30 \
"MYVAR=fromclientE $TMUX attach -E -t main" \
|| fail "could not start inner -E client"
wait_clients 1 || fail "no -E client attached to test server"
# With -E neither variable is touched by the attach.
check_value MYVAR "MYVAR=oldvalue2"
check_value ABSENTVAR "ABSENTVAR=pre2"
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
fail "server died after update-environment tests"
fi
cleanup
exit 0

187
regress/environ.sh Normal file
View File

@@ -0,0 +1,187 @@
#!/bin/sh
# Tests of the environment engine (environ.c) and its two commands,
# set-environment/setenv (cmd-set-environment.c) and show-environment/showenv
# (cmd-show-environment.c).
#
# The environment is a red-black tree of name/value entries held at two
# scopes: the global environment and each session's environment. An entry
# may be marked hidden (ENVIRON_HIDDEN) or "cleared" (a NULL value, which
# masks an inherited variable rather than removing the entry). This
# exercises: set and show at global and session scope; the shell (-s) output
# form and its escaping of $ ` " and \; hidden variables (-h) and their
# filtering; -r cleared entries printed as -NAME / "unset NAME;"; -u removal;
# -F expansion of the value at set time; the plain "NAME=value" and "%hidden"
# config-file assignment forms (environ_put); and the full set of argument and
# target errors from both commands.
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
# check_value $args $expected
#
# Run show-environment with $args and compare the single-line output.
check_value()
{
out=$($TMUX show-environment $1 2>&1)
if [ "$out" != "$2" ]; then
echo "show-environment $1 failed."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
check_ok()
{
if ! $TMUX "$@"; then
echo "Command failed (expected success): $*"
exit 1
fi
}
# check_fail $expected_error $cmd...
check_fail()
{
exp="$1"
shift
out=$($TMUX "$@" 2>&1)
if [ $? -eq 0 ]; then
echo "Command succeeded (expected failure): $*"
exit 1
fi
if [ "$out" != "$exp" ]; then
echo "Wrong error for: $*"
echo "Expected: '$exp'"
echo "But got: '$out'"
exit 1
fi
}
assert_alive()
{
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
echo "Server died: $1"
exit 1
fi
}
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
# --- set and show at session scope ----------------------------------------
check_ok set-environment FOO bar
check_value "FOO" "FOO=bar"
# setenv is an alias for set-environment; showenv for show-environment.
check_ok setenv FOO2 bar2
out=$($TMUX showenv FOO2 2>&1)
[ "$out" = "FOO2=bar2" ] || { echo "setenv/showenv alias failed: '$out'"; exit 1; }
# --- set and show at global scope -----------------------------------------
#
# The global scope is separate from the session scope: a session variable is
# not visible in the global environment.
check_ok set-environment -g GVAR gval
check_value "-g GVAR" "GVAR=gval"
check_fail "unknown variable: FOO" show-environment -g FOO
# --- overwrite replaces the value -----------------------------------------
check_ok set-environment FOO baz
check_value "FOO" "FOO=baz"
# --- shell (-s) output form and escaping ----------------------------------
#
# With -s the value is printed as a shell assignment with export, and the
# characters $ ` " and \ are backslash-escaped (POSIX double-quote rules).
check_ok set-environment ESC 'a$b`c"d\e'
check_value "-s ESC" 'ESC="a\$b\`c\"d\\e"; export ESC;'
# --- -F expands the value as a format at set time -------------------------
#
# With a resolvable target the value is expanded once when set; the stored
# value is the result, not the format.
check_ok set-environment -t main -F EXP '#{session_name}'
check_value "EXP" "EXP=main"
# --- hidden variables (-h) ------------------------------------------------
#
# set-environment -h marks a variable hidden. show-environment hides it by
# default and only prints it when -h is given; conversely a normal variable is
# omitted when -h is given.
check_ok set-environment -h SECRET s3cr
check_value "SECRET" ""
check_value "-h SECRET" "SECRET=s3cr"
check_value "-h FOO" ""
# --- -r clears a variable (NULL value, masks inheritance) -----------------
#
# A cleared entry still exists but has no value: normal form prints "-NAME"
# and shell form prints "unset NAME;".
check_ok set-environment -r FOO
check_value "FOO" "-FOO"
check_value "-s FOO" "unset FOO;"
# --- -u removes a variable entirely ---------------------------------------
check_ok set-environment -u FOO
check_fail "unknown variable: FOO" show-environment FOO
# --- show with no name lists every (non-hidden) variable ------------------
check_ok set-environment -g LISTA 1
check_ok set-environment -g LISTB 2
check_ok set-environment -gh LISTHID 3
out=$($TMUX show-environment -g 2>&1)
echo "$out" | grep -q '^LISTA=1$' || { echo "list missing LISTA"; exit 1; }
echo "$out" | grep -q '^LISTB=2$' || { echo "list missing LISTB"; exit 1; }
# A hidden variable is not listed without -h.
echo "$out" | grep -q '^LISTHID' && { echo "list showed hidden var without -h"; exit 1; }
# With -h only hidden variables are listed.
$TMUX show-environment -gh 2>&1 | grep -q '^LISTHID=3$' || \
{ echo "list -h missing LISTHID"; exit 1; }
# --- config-file assignment forms (environ_put) ---------------------------
#
# A bare NAME=value line in a config file sets a global variable; a "%hidden"
# NAME=value line sets a hidden one. Start a second server from such a config
# and read the values back.
CONF=$(mktemp)
cat > "$CONF" <<EOF
CFGVAR=fromconfig
%hidden CFGHID=hiddencfg
EOF
CTMUX="$TEST_TMUX -Ltest2 -f$CONF"
$CTMUX kill-server 2>/dev/null
$CTMUX new-session -d -s c -x 80 -y 24 || { rm -f "$CONF"; exit 1; }
out=$($CTMUX show-environment -g CFGVAR 2>&1)
[ "$out" = "CFGVAR=fromconfig" ] || \
{ echo "config assignment failed: '$out'"; $CTMUX kill-server; rm -f "$CONF"; exit 1; }
out=$($CTMUX show-environment -gh CFGHID 2>&1)
[ "$out" = "CFGHID=hiddencfg" ] || \
{ echo "config %hidden failed: '$out'"; $CTMUX kill-server; rm -f "$CONF"; exit 1; }
# The %hidden variable is hidden from a plain show.
out=$($CTMUX show-environment -g CFGHID 2>&1)
[ "$out" = "" ] || \
{ echo "config %hidden not hidden: '$out'"; $CTMUX kill-server; rm -f "$CONF"; exit 1; }
$CTMUX kill-server 2>/dev/null
rm -f "$CONF"
# --- set-environment argument errors --------------------------------------
check_fail "empty variable name" set-environment "" x
check_fail "variable name contains =" set-environment "A=B" x
check_fail "can't specify a value with -u" set-environment -u FOO val
check_fail "can't specify a value with -r" set-environment -r FOO val
check_fail "no value specified" set-environment NOVAL
# --- show-environment errors ----------------------------------------------
check_fail "unknown variable: MISSING" show-environment MISSING
# --- unresolvable target errors -------------------------------------------
check_fail "no such session: nosuch" show-environment -t nosuch FOO
check_fail "no such session: nosuch" set-environment -t nosuch FOO bar
assert_alive "after environ tests"
$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

106
regress/format-fuzzy.sh Normal file
View File

@@ -0,0 +1,106 @@
#!/bin/sh
# Tests of fuzzy format matching.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
# test_format $format $expected_result
test_format()
{
fmt="$1"
exp="$2"
out=$($TMUX display-message -p "$fmt")
if [ "$out" != "$exp" ]; then
echo "Fuzzy format test failed for '$fmt'."
echo "Expected: '$exp'"
echo "But got '$out'"
exit 1
fi
}
$TMUX kill-server 2>/dev/null
$TMUX -f/dev/null new-session -d || exit 1
# Empty pattern matches everything but has no highlighted positions.
test_format '#{m/z:,abc}' '1'
test_format '#{m/p:,abc}' ''
# Plain fuzzy matching and positions.
test_format '#{m/z:abc,a_b_c}' '1'
test_format '#{m/p:abc,a_b_c}' '0,2,4'
test_format '#{m/z:abc,acb}' '0'
test_format '#{m/p:abc,acb}' ''
# Multiple terms are ANDed. Positions are for all positive matched terms.
test_format '#{m/z:dev bash,dev:1 bash}' '1'
test_format '#{m/p:dev bash,dev:1 bash}' '0,1,2,6,7,8,9'
test_format '#{m/z:dev bash,dev:1 sh}' '0'
test_format '#{m/p:dev bash,dev:1 sh}' ''
# Lowercase patterns are case-insensitive for ASCII; uppercase makes the
# pattern case-sensitive.
test_format '#{m/z:abc,ABC}' '1'
test_format '#{m/p:abc,ABC}' '0,1,2'
test_format '#{m/z:ABC,abc}' '0'
test_format '#{m/p:ABC,abc}' ''
# Exact substring matching with a leading quote.
test_format "#{m/z:'bash,dev bash}" '1'
test_format "#{m/p:'bash,dev bash}" '4,5,6,7'
test_format "#{m/z:'bash,b-a-s-h}" '0'
test_format "#{m/p:'bash,b-a-s-h}" ''
# Prefix and suffix matching.
test_format '#{m/z:^dev,dev bash}' '1'
test_format '#{m/p:^dev,dev bash}' '0,1,2'
test_format '#{m/z:^dev,prod dev}' '0'
test_format '#{m/p:^dev,prod dev}' ''
test_format '#{m/z:bash$,dev bash}' '1'
test_format '#{m/p:bash$,dev bash}' '4,5,6,7'
test_format '#{m/z:bash$,bash dev}' '0'
test_format '#{m/p:bash$,bash dev}' ''
# Inverse terms. Plain inverse terms are exact substring tests, not fuzzy.
test_format '#{m/z:!ssh,dev bash}' '1'
test_format '#{m/z:!ssh,dev ssh}' '0'
test_format '#{m/z:!ssh,s_s_h}' '1'
test_format '#{m/z:dev !ssh,dev bash}' '1'
test_format '#{m/p:dev !ssh,dev bash}' '0,1,2'
test_format '#{m/z:dev !ssh,dev ssh}' '0'
test_format '#{m/p:dev !ssh,dev ssh}' ''
# OR separates groups; each group is an AND of its terms.
test_format '#{m/z:prod | dev,dev bash}' '1'
test_format '#{m/p:prod | dev,dev bash}' '0,1,2'
test_format '#{m/z:prod | dev,prod bash}' '1'
test_format '#{m/p:prod | dev,prod bash}' '0,1,2,3'
test_format '#{m/z:prod | dev,test bash}' '0'
test_format '#{m/p:prod | dev,test bash}' ''
# Style sequences do not count towards display positions.
test_format '#{m/z:dev,#[bold]dev#[default]}' '1'
test_format '#{m/p:dev,#[bold]dev#[default]}' '0,1,2'
test_format '#{m/p:dev,#[fg=red#,bg=blue]dev#[default]}' '0,1,2'
test_format '#{m/p:dev,#[bold]d#[default]e#[underscore]v#[default]}' '0,1,2'
test_format '#{m/p:bash,#[bold]dev#[default] bash}' '4,5,6,7'
# UTF-8. Non-ASCII matching is exact, even when ASCII would fold case.
test_format '#{m/z:é,café}' '1'
test_format '#{m/p:é,café}' '3'
test_format '#{m/z:é,É}' '0'
test_format '#{m/p:é,É}' ''
test_format '#{m/z:éx,éx}' '1'
test_format '#{m/p:éx,éx}' '0,1'
# Wide UTF-8 characters occupy two display cells and both should be marked for
# highlighting.
test_format '#{m/z:界,a界b}' '1'
test_format '#{m/p:界,a界b}' '1,2'
exit 0

563
regress/format-modifiers.sh Normal file
View File

@@ -0,0 +1,563 @@
#!/bin/sh
# Tests of format modifiers as described in tmux(1) FORMATS.
#
# This complements format-strings.sh (which covers escapes, conditionals,
# boolean operators and the l: literal modifier). Here we exercise the
# remaining modifiers: comparisons/matching (m, C, <, >, ==, ...), numeric
# operations (e|op|), width/padding/truncation (=, p, n, w, a, R), basename
# and dirname (b, d), time conversion (t), loops (S, W, P), colour (c) and
# modifier nesting/limits.
PATH=/bin:/usr/bin
TERM=screen
TZ=UTC
export TZ
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
ESC=$(printf '\033')
# test_format $format $expected [$target]
#
# Expand $format with display-message and compare with $expected. If $target
# is given it is passed to display-message with -t.
test_format()
{
fmt="$1"
exp="$2"
target="$3"
if [ -n "$target" ]; then
out=$($TMUX display-message -t "$target" -p "$fmt")
else
out=$($TMUX display-message -p "$fmt")
fi
if [ "$out" != "$exp" ]; then
echo "Format test failed for '$fmt'."
echo "Expected: '$exp'"
echo "But got '$out'"
exit 1
fi
}
# test_expand $format $expected
#
# Expand $format in a plain format_expand context (list-windows -F on the
# single-window "tf" session) rather than the format_expand_time context of
# display-message. This matters for t/f: display-message runs the whole format
# through strftime(3), so a strftime specifier there must be doubled (%%H); in a
# format_expand context a single specifier (%H) is applied directly to the
# variable's time.
test_expand()
{
fmt="$1"
exp="$2"
out=$($TMUX list-windows -t tf -F "$fmt")
if [ "$out" != "$exp" ]; then
echo "Format test failed for '$fmt'."
echo "Expected: '$exp'"
echo "But got '$out'"
exit 1
fi
}
# assert_alive
#
# Check that the server is still responding (used after operations that could
# in principle crash it, such as recursion and division by zero).
assert_alive()
{
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
echo "Server did not survive: $1"
exit 1
fi
}
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
# Single-window session used by test_expand for format_expand-context tests.
$TMUX new-session -d -s tf || exit 1
# User options used as inputs. Modifiers operate on variable names, so plain
# literals must be provided via options (or a nested #{l:...}). They are set
# globally (-g) so they are visible from every session, including the "tf"
# session used by test_expand.
$TMUX set -g @s 'abcdefghij' || exit 1
$TMUX set -g @path '/usr/local/bin/foo' || exit 1
$TMUX set -g @name 'window-name' || exit 1
$TMUX set -g @greek 'αβγ' || exit 1 # 6 bytes, 3 columns wide
$TMUX set -g @cjk '中文' || exit 1 # 6 bytes, 4 columns wide
$TMUX set -g @host 'myhost' || exit 1
$TMUX set -g @ts '1000000000' || exit 1 # 2001-09-09 01:46:40 UTC
$TMUX set -g @sp 'a b$c' || exit 1 # shell-special characters for q:
$TMUX set -g @hash 'a#b' || exit 1 # a "#" for q/e:
# --- Comparisons and matching --------------------------------------------
# m: glob match, first argument is the pattern.
test_format "#{m:*foo*,barfoobar}" "1"
test_format "#{m:*foo*,barbar}" "0"
test_format "#{m:abc,abc}" "1"
# m/i: ignore case.
test_format "#{m/i:*FOO*,barfoobar}" "1"
test_format "#{m/i:*FOO*,barbar}" "0"
# m/r: regular expression.
test_format "#{m/r:^[0-9]+\$,12345}" "1"
test_format "#{m/r:^[0-9]+\$,12a45}" "0"
# m/ri: regular expression, ignore case.
test_format "#{m/ri:^ab+\$,ABBB}" "1"
test_format "#{m/ri:^ab+\$,ACCC}" "0"
# m/z: fuzzy match, returns a boolean.
test_format "#{m/z:foo,foobar}" "1"
test_format "#{m/z:xyz,foobar}" "0"
# m/p: fuzzy match, returns the matched (0-based) column positions.
test_format "#{m/p:ac,abc}" "0,2"
test_format "#{m/p:xyz,abc}" ""
# Fuzzy match against empty text.
test_format "#{m/p:x,}" ""
test_format "#{m/z:x,}" "0"
# String comparisons.
test_format "#{==:#{@host},myhost}" "1"
test_format "#{==:#{@host},other}" "0"
test_format "#{!=:abc,xyz}" "1"
test_format "#{!=:abc,abc}" "0"
test_format "#{<:3,5}" "1"
test_format "#{<:5,3}" "0"
test_format "#{>:5,3}" "1"
test_format "#{>:3,5}" "0"
test_format "#{<=:5,5}" "1"
test_format "#{<=:6,5}" "0"
test_format "#{>=:5,5}" "1"
test_format "#{>=:4,5}" "0"
# Negation and canonical boolean.
test_format "#{!:0}" "1"
test_format "#{!:1}" "0"
test_format "#{!!:}" "0"
test_format "#{!!:0}" "0"
test_format "#{!!:non-empty}" "1"
# --- Quoting (q) ---------------------------------------------------------
# q: escapes shell special characters with a backslash.
test_format "#{q:@sp}" 'a\ b\$c'
# q/e and q/h escape "#" for the format/style parser by doubling it.
test_format "#{q/e:@hash}" 'a##b'
test_format "#{q/h:@hash}" 'a##b'
# q/a quotes the value as a single shell argument.
test_format "#{q/a:@sp}" '"a b\$c"'
# --- Name existence (N) --------------------------------------------------
# N/w is true if a window with the (expanded) name exists in the session, N/s
# if a session with that name exists. The default (no argument) is /w.
$TMUX rename-window -t main:0 knownwin
test_format "#{N/s:main}" "1"
test_format "#{N/s:nosuchsession}" "0"
test_format "#{N/w:knownwin}" "1" "main:"
test_format "#{N/w:nosuchwindow}" "0" "main:"
test_format "#{N:nosuchwindow}" "0" "main:"
# --- Numeric operations (e) ----------------------------------------------
# Integer operators.
test_format "#{e|+|:2,3}" "5"
test_format "#{e|-|:10,4}" "6"
test_format "#{e|-|:2,5}" "-3"
test_format "#{e|*|:6,7}" "42"
test_format "#{e|/|:20,4}" "5"
# Modulus - both spellings (% must be doubled as it is a strftime specifier).
test_format "#{e|m|:7,3}" "1"
test_format "#{e|%%|:7,3}" "1"
# Numeric comparison operators.
test_format "#{e|==|:5,5}" "1"
test_format "#{e|!=|:5,5}" "0"
test_format "#{e|<|:2,5}" "1"
test_format "#{e|>|:9,2}" "1"
test_format "#{e|<=|:5,5}" "1"
test_format "#{e|>=|:5,5}" "1"
# Floating point with a decimal-place count.
test_format "#{e|*|f|4:5.5,3}" "16.5000"
test_format "#{e|/|f|3:1,3}" "0.333"
test_format "#{e|/|f|2:10,3}" "3.33"
# Default number of decimal places for float is two.
test_format "#{e|*|f:2.5,2}" "5.00"
# Division by zero must not crash the server (result is unspecified).
$TMUX display-message -p "#{e|/|:5,0}" >/dev/null 2>&1
$TMUX display-message -p "#{e|/|f:5,0}" >/dev/null 2>&1
assert_alive "division by zero"
# --- ASCII and repeat ----------------------------------------------------
# a: numeric value to its ASCII character.
test_format "#{a:98}" "b"
test_format "#{a:65}" "A"
# a: out-of-range or non-numeric input yields an empty string.
test_format "#{a:200}" ""
test_format "#{a:notanumber}" ""
# R: repeat first argument second-argument times.
test_format "#{R:a,3}" "aaa"
test_format "#{R:ab,2}" "abab"
# A long repeat exercises output-buffer growth during expansion.
test_format "#{n:#{R:x,300}}" "300"
# --- Width, padding and truncation ---------------------------------------
# =N truncates from the start, =-N from the end.
test_format "#{=5:@s}" "abcde"
test_format "#{=-5:@s}" "fghij"
# = with no width, or a non-numeric width, does not truncate.
test_format "#{=:@s}" "abcdefghij"
test_format "#{=/x:@s}" "abcdefghij"
# A marker is appended/prepended only when trimming actually occurs.
test_format "#{=/5/...:@s}" "abcde..."
test_format "#{=/5/...:@name}" "windo..."
test_format "#{=/20/...:@s}" "abcdefghij"
# Truncation is display-width (UTF-8) aware: a wide (2-column) character is only
# included if it fits entirely within the limit.
test_format "#{=3:@greek}" "αβγ"
test_format "#{=2:@greek}" "αβ"
test_format "#{=2:@cjk}" "中"
test_format "#{=1:@cjk}" ""
# Markers with wide characters: the marker is added when trimming occurs, and a
# limit that splits a wide character drops it entirely.
test_format "#{=/2/x:@cjk}" "中x"
test_format "#{=/1/x:@cjk}" "x"
# p pads to a width: a positive width left-aligns (pads on the right), a
# negative width right-aligns (pads on the left).
test_format "#{p12:@name}" "window-name "
test_format "#{p-12:@name}" " window-name"
# No padding once the value already meets the width.
test_format "#{p3:@name}" "window-name"
# p with no width does nothing.
test_format "#{p:@name}" "window-name"
# Padding is display-width aware: @cjk is 4 columns wide, so p6/p-6 add two
# spaces (not four).
test_format "#{p6:@cjk}" "中文 "
test_format "#{p-6:@cjk}" " 中文"
# n is byte length, w is display width.
test_format "#{n:@s}" "10"
test_format "#{w:@s}" "10"
test_format "#{n:@greek}" "6"
test_format "#{w:@greek}" "3"
test_format "#{n:@cjk}" "6"
test_format "#{w:@cjk}" "4"
# --- basename and dirname ------------------------------------------------
test_format "#{b:@path}" "foo"
test_format "#{d:@path}" "/usr/local/bin"
# --- Time conversion -----------------------------------------------------
# t: converts an integer time to a ctime(3) string.
test_format "#{t:@ts}" "Sun Sep 9 01:46:40 2001"
# t/p: shorter format for times in the past.
test_format "#{t/p:@ts}" "Sep01"
# t/r: relative time depends on the current time, just check it is non-empty.
if [ -z "$($TMUX display-message -p '#{t/r:@ts}')" ]; then
echo "Format test failed for '#{t/r:@ts}': empty result"
exit 1
fi
# t/f: custom strftime format applied to the variable's time. Tested in a
# format_expand context (list-windows -F), where a single strftime specifier is
# applied directly. (In display-message, which additionally expands the format
# through strftime, these would need to be doubled - %%Y etc.) The colon in the
# format is escaped as '#:' because it is otherwise the modifier separator.
test_expand "#{t/f/%Y:@ts}" "2001"
test_expand "#{t/f/%Y-%m-%d:@ts}" "2001-09-09"
test_expand "#{t/f/%H#:%M#:%S:@ts}" "01:46:40"
# An escaped comma in the custom format is unescaped before strftime.
test_expand "#{t/f/%Y#,end:@ts}" "2001,end"
# T: expands its argument and then runs the result through strftime with the
# current time. A value with no strftime specifier is returned unchanged.
test_format "#{T:@ts}" "1000000000"
# t/p (pretty) and t/r (relative) format times by age relative to now, with a
# different branch per age band. Build options a known number of seconds in the
# past and check each yields a non-empty result (the exact text depends on the
# wall clock, so only non-emptiness is asserted).
now=$(date +%s)
for age in 30 300 4000 90000 200000 3000000 40000000; do
$TMUX set -g @age "$((now - age))"
if [ -z "$($TMUX display-message -p '#{t/r:@age}')" ]; then
echo "Empty #{t/r:@age} for age ${age}s"
exit 1
fi
if [ -z "$($TMUX display-message -p '#{t/p:@age}')" ]; then
echo "Empty #{t/p:@age} for age ${age}s"
exit 1
fi
done
# A time in the future has no relative form.
$TMUX set -g @future "$((now + 100000))"
test_format "#{t/r:@future}" ""
# --- Content search (C) --------------------------------------------------
# Use a window running cat so the content is deterministic (no shell prompt).
$TMUX new-session -d -s search -x 80 -y 10 'cat'
sleep 1
$TMUX send-keys -t search: 'Zebra_Marker_42' Enter
sleep 1
# C: returns the (1-based) line number of a match or 0 if not found.
test_format "#{C:Zebra_Marker_42}" "1" "search:"
test_format "#{C:Absent_String_999}" "0" "search:"
test_format "#{C/r:Zebra_.*_42}" "1" "search:"
test_format "#{C/i:zebra_marker_42}" "1" "search:"
$TMUX kill-session -t search 2>/dev/null
# --- Colour (c) ----------------------------------------------------------
# c: converts a colour to its six-digit hexadecimal RGB value.
test_format "#{c:red}" "800000"
test_format "#{c:colour4}" "000080"
test_format "#{c:#7f7f7f}" "7f7f7f"
# c/f and c/b produce the SGR escape sequence for fg/bg respectively.
test_format "#{c/f:red}" "${ESC}[31m"
test_format "#{c/b:red}" "${ESC}[41m"
test_format "#{c/b:colour4}" "${ESC}[48;5;4m"
# "none" gives a reset; an unknown colour gives an empty string.
test_format "#{c/f:none}" "${ESC}[0m"
test_format "#{c:notacolour}" ""
test_format "#{c/f:notacolour}" ""
# --- Nesting and limits --------------------------------------------------
# Modifier chaining: inner b: then outer truncation/padding/length.
test_format "#{=5:#{b:@path}}" "foo"
test_format "#{=2:#{b:@path}}" "fo"
test_format "#{p6:#{b:@path}}" "foo "
test_format "#{n:#{b:@path}}" "3"
# Nested l: literal expanded then truncated.
test_format "#{=5:#{l:abcdefghij}}" "abcde"
# Deeper nesting: basename -> pad to 10 -> truncate to 5.
test_format "#{=5:#{p10:#{b:@path}}}" "foo "
# A substitution applied to a nested basename.
test_format "#{s/o/O/:#{b:@path}}" "fOO"
# Unbounded self-recursion must hit the loop limit rather than crash.
$TMUX set @rec '#{E:@rec}'
$TMUX display-message -p '#{E:@rec}' >/dev/null 2>&1
assert_alive "recursive expansion"
# --- Missing, malformed and limit inputs ---------------------------------
# An undefined variable expands to empty; modifiers on it behave sensibly.
test_format "#{@undefined}" ""
test_format "#{=5:@undefined}" ""
test_format "#{b:@undefined}" ""
test_format "#{n:@undefined}" "0"
# Malformed numeric expressions expand to empty rather than erroring out.
test_format "#{e|+|:notanumber,2}" "" # invalid left operand
test_format "#{e|+|:2,notanumber}" "" # invalid right operand
test_format "#{e|badop|:1,2}" "" # unknown operator
test_format "#{e|+|f|x:1,2}" "" # invalid precision
test_format "#{e|+|:1}" "" # too few operands
test_format "#{e|+|f|2|extra:1,2}" "" # too many arguments (limit is 3)
# Repeat with a non-numeric or zero count yields an empty string.
test_format "#{R:a,notanumber}" ""
test_format "#{R:a,0}" ""
# Comparisons with too few arguments expand to empty.
test_format "#{==:a}" ""
test_format "#{<:a}" ""
# A substitution with fewer than two arguments is a no-op.
test_format "#{s/onlyone:@s}" "abcdefghij"
# A non-numeric width for = or p is treated as no width (no change).
test_format "#{=/x:@s}" "abcdefghij"
test_format "#{p/x:@s}" "abcdefghij"
# The I (client terminal) modifier with no attached client is empty; this also
# exercises its argument parsing (/c termcap, /f feature, default). The
# non-empty terminal cases are covered with a real client in format-variables.sh.
test_format "#{I/c:RGB}" ""
test_format "#{I/f:overline}" ""
test_format "#{I:x}" ""
# --- Escaping inside modifiers -------------------------------------------
# A "," or "#" inside a modifier argument is escaped with "#".
test_format "#{s/#,/-/:#{l:a,b,c}}" "a-b-c" # escaped comma in the pattern
test_format "#{=/3/#,:@s}" "abc," # escaped comma in the marker
# The truncation marker is itself expanded as a format.
test_format "#{=/3/#{l:>}:@s}" "abc>"
# Substitution flags: a third argument of "i" is case-insensitive; an invalid
# regular expression leaves the text unchanged.
test_format "#{s/A/X/i:@s}" "Xbcdefghij"
test_format "#{s/[/X/:@s}" "abcdefghij"
# --- Unicode in modifier arguments ---------------------------------------
# Wide (CJK) and emoji text: matching, substitution, repeat and markers all
# operate on characters, and n/w report bytes/columns.
$TMUX set -g @emoji '😀😀' || exit 1 # 8 bytes, 4 columns
test_format "#{m:*中*,#{@cjk}}" "1"
test_format "#{s/文/X/:@cjk}" "中X"
test_format "#{R:中,3}" "中中中"
test_format "#{=/1/中:@s}" "a中"
test_format "#{w:@emoji}" "4"
test_format "#{n:@emoji}" "8"
test_format "#{=2:@emoji}" "😀"
# --- Server messages (show-messages) -------------------------------------
# show-messages formats each logged message (this exercises the message-time
# formatting path); just check the server survives producing it.
$TMUX show-messages >/dev/null 2>&1
assert_alive "show-messages"
# --- Verbose expansion (logging) -----------------------------------------
# display-message -v turns on format logging, so re-expanding a representative
# set of formats with -v exercises the logging code paths. Only survival is
# checked; the log text itself is not asserted.
for f in \
'#{=3:@s}' \
'#{e|+|:2,3}' \
'#{e|*|f|2:2.5,2}' \
'#{m:*a*,abc}' \
'#{<:3,5}' \
'#{s/a/X/:@s}' \
'#{b:@path}' \
'#{t:@ts}' \
'#{p6:@name}' \
'#{=3:#{b:@path}}'; do
$TMUX display-message -v -p "$f" >/dev/null 2>&1
done
assert_alive "verbose expansion"
# --- Loops and sorting (S, W, P, L) --------------------------------------
#
# These need a fully controlled server so the set of sessions, windows and
# panes (and their order) is known, so start from a clean server. This must be
# the last section as it discards the setup above.
$TMUX kill-server 2>/dev/null
# Sessions, created in this order, so session ids (and hence creation order)
# are zeta=$0, alpha=$1, mike=$2.
$TMUX new-session -d -s zeta -x 80 -y 24 || exit 1
$TMUX new-session -d -s alpha || exit 1
$TMUX new-session -d -s mike || exit 1
$TMUX set -g automatic-rename off
# S loops over every session. The default order is by session id (SORT_INDEX),
# /i is the same, /n is by name, and the r suffix reverses.
test_format "#{S:#{session_name} }" "zeta alpha mike "
test_format "#{S/i:#{session_name} }" "zeta alpha mike "
test_format "#{S/n:#{session_name} }" "alpha mike zeta "
test_format "#{S/nr:#{session_name} }" "zeta mike alpha "
test_format "#{S/ir:#{session_name} }" "mike alpha zeta "
# /t sorts by activity time; the exact order is timing-dependent, so just check
# every session is still iterated (this exercises the activity-sort branch).
test_format "#{S/t:x}" "xxx"
# An unrecognised sort letter falls back to the default order; /r on its own
# reverses that default (this covers the fall-through branch).
test_format "#{S/r:#{session_name} }" "mike alpha zeta "
# Windows in session zeta: window 0 renamed charlie, then alpha at 1, bravo at
# 2. The default order is by index (SORT_ORDER), /n is by name, r reverses.
$TMUX rename-window -t zeta:0 charlie
$TMUX new-window -d -t zeta:1 -n alpha
$TMUX new-window -d -t zeta:2 -n bravo
test_format "#{W:#{window_name} }" "charlie alpha bravo " "zeta:"
test_format "#{W/n:#{window_name} }" "alpha bravo charlie " "zeta:"
test_format "#{W/nr:#{window_name} }" "charlie bravo alpha " "zeta:"
test_format "#{W/ir:#{window_index}}" "210" "zeta:"
# /i (by index) and /t (by activity); /i matches the default order here.
test_format "#{W/i:#{window_name} }" "charlie alpha bravo " "zeta:"
test_format "#{W/t:x}" "xxx" "zeta:"
# An unrecognised sort letter falls back to the default order; /r reverses it.
test_format "#{W/r:#{window_name} }" "bravo alpha charlie " "zeta:"
# Panes in window zeta:charlie. Splitting the active (newest) pane each time
# makes pane index match creation order (0,1,2 left to right). The default
# order is by creation (SORT_CREATION), r reverses.
$TMUX split-window -h -t zeta:charlie
$TMUX split-window -h -t zeta:charlie
test_format "#{P:#{pane_index}}" "012" "zeta:charlie"
test_format "#{P/r:#{pane_index}}" "210" "zeta:charlie"
# A pane-sort argument is accepted; for panes only the r (reverse) suffix has an
# effect, so these all keep the count and exercise the argument branch.
test_format "#{P/i:x}" "xxx" "zeta:charlie"
test_format "#{P/n:x}" "xxx" "zeta:charlie"
test_format "#{P/t:x}" "xxx" "zeta:charlie"
# Verbose expansion of the loops, to exercise their logging paths.
$TMUX display-message -v -p "#{S:#{session_name}}" >/dev/null 2>&1
$TMUX display-message -v -t zeta: -p "#{W:#{window_name}}" >/dev/null 2>&1
$TMUX display-message -v -t zeta:charlie -p "#{P:#{pane_index}}" >/dev/null 2>&1
assert_alive "verbose loop expansion"
# L loops over attached clients. Attach two control-mode clients, each held
# open by a background process keeping a FIFO's write end open.
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
rm -f "$FIFO1" "$FIFO2"
mkfifo "$FIFO1" "$FIFO2" || exit 1
# Hold the write ends open so the control clients stay attached.
sleep 30 >"$FIFO1" &
HOLD1=$!
sleep 30 >"$FIFO2" &
HOLD2=$!
$TMUX -C attach -t zeta <"$FIFO1" >/dev/null 2>&1 &
CC1=$!
$TMUX -C attach -t alpha <"$FIFO2" >/dev/null 2>&1 &
CC2=$!
sleep 1
# Two clients attached: L emits one item per client.
test_format "#{L:x}" "xx"
# The client sort orders (default, index, name, activity, reversed) are all
# accepted; assert only the count so the test does not depend on client names or
# timing.
test_format "#{L/i:x}" "xx"
test_format "#{L/n:x}" "xx"
test_format "#{L/t:x}" "xx"
test_format "#{L/nr:x}" "xx"
test_format "#{L/r:x}" "xx"
# Now detach one and confirm the count drops to one.
kill $HOLD2 2>/dev/null
sleep 1
test_format "#{L:x}" "x"
kill $HOLD1 $CC1 $CC2 2>/dev/null
rm -f "$FIFO1" "$FIFO2"
exit 0

143
regress/format-mouse.sh Normal file
View File

@@ -0,0 +1,143 @@
#!/bin/sh
# Tests of the mouse format variables (mouse_x, mouse_y, mouse_word,
# mouse_line, ...). These are only populated while a mouse key binding is being
# dispatched, so the test drives a real mouse event:
#
# - an inner client is attached inside a pane of a second ("outer") tmux
# server, giving the inner server a genuine terminal;
# - mouse mode is on and a MouseDown1Pane binding records the mouse format
# variables into an option;
# - an SGR mouse sequence is written to the outer pane, so the inner client
# receives it as a real mouse click.
#
# This exercises the mouse callbacks and the grid word/line lookup code that
# display-message cannot otherwise reach.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
cleanup()
{
$TMUX kill-server >/dev/null 2>&1
$TMUX2 kill-server >/dev/null 2>&1
}
fail()
{
echo "$1"
cleanup
exit 1
}
# click COL ROW
#
# Write an SGR mouse press then release (button 0) at 1-based COL/ROW to the
# outer pane holding the inner client.
click()
{
col="$1"
row="$2"
seq=$(printf '\033[<0;%s;%sM\033[<0;%s;%sm' "$col" "$row" "$col" "$row")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 1
}
cleanup
# Inner session with a single pane running cat, so its content is exactly what
# we send it.
$TMUX new-session -d -s cov -x 80 -y 24 'cat' || exit 1
$TMUX set -g mouse on
sleep 1
$TMUX send-keys -t cov:0.0 'alpha beta gamma' Enter
sleep 1
# Record every pane mouse variable when the pane is clicked.
$TMUX bind -n MouseDown1Pane run-shell \
"$TMUX set -g @m 'x=#{mouse_x} y=#{mouse_y} word=#{mouse_word} line=#{mouse_line} pane=#{mouse_pane} hl=[#{mouse_hyperlink}]'"
# Attach a real client inside an outer tmux pane. Clicks all target the first
# row, which lines up with the inner client regardless of the outer status line.
$TMUX2 new-session -d -x 80 -y 24 "$TMUX attach -t cov" || exit 1
sleep 1
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
[ -n "$OUTER" ] || fail "No outer pane."
# Click column 3, row 1: over the first word ("alpha") of the first line.
click 3 1
M=$($TMUX show -gv @m 2>/dev/null)
[ -n "$M" ] || fail "Mouse binding did not fire (no @m)."
# mouse_x is 0-based column (SGR column 3 -> x 2); mouse_y is 0-based row 0.
case "$M" in
*"x=2 "*) ;;
*) fail "Unexpected mouse_x in: $M" ;;
esac
case "$M" in
*"y=0 "*) ;;
*) fail "Unexpected mouse_y in: $M" ;;
esac
# mouse_word is the word under the cursor, mouse_line the whole line.
case "$M" in
*"word=alpha "*) ;;
*) fail "Unexpected mouse_word in: $M" ;;
esac
case "$M" in
*"line=alpha beta gamma "*) ;;
*) fail "Unexpected mouse_line in: $M" ;;
esac
# A click in a different column selects a different word.
click 8 1
M=$($TMUX show -gv @m 2>/dev/null)
case "$M" in
*"word=beta "*) ;;
*) fail "Unexpected mouse_word for second click in: $M" ;;
esac
# The same variables have a separate path when the pane is in a mode (the word
# and line come from the mode, not the live grid). A binding in the copy-mode
# key table fires while copy mode is active.
$TMUX bind -T copy-mode MouseDown1Pane run-shell \
"$TMUX set -g @cm 'x=#{mouse_x} word=#{mouse_word} line=#{mouse_line}'"
$TMUX copy-mode -t cov:0.0
sleep 1
click 8 1
CM=$($TMUX show -gv @cm 2>/dev/null)
case "$CM" in
*"word=beta"*) ;;
*) fail "Unexpected copy-mode mouse_word in: $CM" ;;
esac
$TMUX send-keys -t cov:0.0 -X cancel
sleep 1
# Hyperlinks: a new window whose pane emits an OSC 8 hyperlink over the text
# "LINKED". Clicking it reports the target URL via mouse_hyperlink (this drives
# the grid hyperlink lookup). The emitter is written to a small script to keep
# the escape sequence readable.
LINKSH="${TMPDIR:-/tmp}/fmt-mouse-link-$$.sh"
cat >"$LINKSH" <<'EOF'
#!/bin/sh
printf '\033]8;;http://example.com\033\\LINKED\033]8;;\033\\\n'
exec cat
EOF
chmod +x "$LINKSH"
$TMUX neww -t cov: -n link "$LINKSH"
sleep 1
$TMUX select-window -t cov:link
sleep 1
click 3 1
M=$($TMUX show -gv @m 2>/dev/null)
rm -f "$LINKSH"
case "$M" in
*"hl=[http://example.com]"*) ;;
*) fail "Unexpected mouse_hyperlink in: $M" ;;
esac
cleanup
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 "#}" "}"

388
regress/format-variables.sh Normal file
View File

@@ -0,0 +1,388 @@
#!/bin/sh
# Tests that every format variable listed in tmux(1) (the format_table in
# format.c) can be expanded without crashing the server, and checks the value
# of a stable subset.
#
# The main point is coverage and crash-safety: each variable is expanded in a
# rich context - a real attached client (from a nested tmux), a control-mode
# client, a grouped session, two windows with a bell alert, a window with two
# panes running cat, a paste buffer and options - so the per-variable callbacks
# actually run. format-modifiers.sh covers the modifier machinery; this covers
# the variable callbacks.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
# A second server on its own socket provides a real terminal (an inner client
# attached inside one of its panes) so client terminal variables are populated.
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
# Every variable name in format_table[]. Kept as a plain word list so it can be
# iterated with normal shell word splitting.
NAMES="
active_window_index
alternate_on
alternate_saved_x
alternate_saved_y
bracket_paste_flag
buffer_created
buffer_full
buffer_mode_format
buffer_name
buffer_sample
buffer_size
client_activity
client_cell_height
client_cell_width
client_colours
client_control_mode
client_created
client_discarded
client_flags
client_height
client_key_table
client_last_session
client_mode_format
client_name
client_pid
client_prefix
client_readonly
client_session
client_termfeatures
client_termname
client_termtype
client_theme
client_tty
client_uid
client_user
client_utf8
client_width
client_written
config_files
cursor_blinking
cursor_character
cursor_colour
cursor_flag
cursor_shape
cursor_very_visible
cursor_x
cursor_y
history_all_bytes
history_bytes
history_limit
history_size
host
host_short
insert_flag
keypad_cursor_flag
keypad_flag
last_window_index
mouse_all_flag
mouse_any_flag
mouse_button_flag
mouse_hyperlink
mouse_line
mouse_pane
mouse_sgr_flag
mouse_standard_flag
mouse_status_line
mouse_status_range
mouse_utf8_flag
mouse_word
mouse_x
mouse_y
next_session_id
origin_flag
pane_active
pane_at_bottom
pane_at_left
pane_at_right
pane_at_top
pane_bg
pane_bottom
pane_current_command
pane_current_path
pane_dead
pane_dead_signal
pane_dead_status
pane_dead_time
pane_fg
pane_flags
pane_floating_flag
pane_format
pane_height
pane_id
pane_in_mode
pane_index
pane_input_off
pane_key_mode
pane_last
pane_left
pane_marked
pane_marked_set
pane_mode
pane_path
pane_pb_progress
pane_pb_state
pane_pid
pane_pipe
pane_pipe_pid
pane_right
pane_search_string
pane_start_command
pane_start_path
pane_synchronized
pane_tabs
pane_title
pane_top
pane_tty
pane_unseen_changes
pane_width
pane_x
pane_y
pane_z
pane_zoomed_flag
pid
scroll_region_lower
scroll_region_upper
server_sessions
session_active
session_activity
session_activity_flag
session_alert
session_alerts
session_attached
session_attached_list
session_bell_flag
session_created
session_format
session_group
session_group_attached
session_group_attached_list
session_group_list
session_group_many_attached
session_group_size
session_grouped
session_id
session_last_attached
session_many_attached
session_marked
session_name
session_path
session_silence_flag
session_stack
session_windows
sixel_support
socket_path
start_time
synchronized_output_flag
tree_mode_format
uid
user
version
window_active
window_active_clients
window_active_clients_list
window_active_sessions
window_active_sessions_list
window_activity
window_activity_flag
window_bell_flag
window_bigger
window_cell_height
window_cell_width
window_end_flag
window_flags
window_format
window_height
window_id
window_index
window_last_flag
window_layout
window_linked
window_linked_sessions
window_linked_sessions_list
window_marked_flag
window_name
window_offset_x
window_offset_y
window_panes
window_raw_flags
window_silence_flag
window_stack_index
window_start_flag
window_visible_layout
window_width
window_zoomed_flag
wrap_flag
"
# test_var $name $expected [$extra_args...]
#
# Expand a single #{name} and compare against $expected. Any extra arguments
# are passed straight to display-message (e.g. -c or -t).
test_var()
{
name="$1"
exp="$2"
shift 2
out=$($TMUX display-message "$@" -p "#{$name}")
if [ "$out" != "$exp" ]; then
echo "Variable test failed for '#{$name}'."
echo "Expected: '$exp'"
echo "But got '$out'"
exit 1
fi
}
assert_alive()
{
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
echo "Server did not survive: $1"
exit 1
fi
}
FIFO="${TMPDIR:-/tmp}/fmt-vars-$$"
HOLD=""
CC=""
cleanup()
{
[ -n "$HOLD" ] && kill $HOLD 2>/dev/null
[ -n "$CC" ] && kill $CC 2>/dev/null
rm -f "$FIFO"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
}
fail()
{
echo "$1"
cleanup
exit 1
}
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
# A session "cov" with a window "win0" holding two panes running cat, plus a
# second window, an option and a paste buffer.
$TMUX new-session -d -s cov -x 80 -y 24 -n win0 'cat' || exit 1
$TMUX set -g automatic-rename off
$TMUX set -g monitor-bell on
$TMUX set -g monitor-activity on
$TMUX split-window -t cov:win0 -d 'cat' || exit 1
$TMUX new-window -d -t cov:1 -n win1 'cat' || exit 1
$TMUX set -g @opt 'optionvalue' || exit 1
$TMUX set-buffer -b buf0 'somebuffer' || exit 1
# A second session grouped with cov, so the session_group_* variables have real
# data to report.
$TMUX new-session -d -s cov2 -t cov || exit 1
sleep 1
$TMUX send-keys -t cov:win0.0 'some pane content' Enter
# Ring the bell in the non-current window so a bell alert is raised on the
# session (this populates session_alert/session_alerts and window_bell_flag).
$TMUX send-keys -t cov:win1.0 C-g
sleep 1
# Attach a control-mode client, held open by a background process keeping the
# write end of a FIFO open, so client_* variables have a client to read.
rm -f "$FIFO"
mkfifo "$FIFO" || exit 1
sleep 30 >"$FIFO" &
HOLD=$!
$TMUX -C attach -t cov <"$FIFO" >/dev/null 2>&1 &
CC=$!
# Attach a real client too: an inner tmux running inside a pane of the second
# server gets a genuine terminal, which populates the terminal-dependent client
# variables (client_termname, cursor_shape, the I modifier, ...).
$TMUX2 new-session -d -x 90 -y 30 "$TMUX attach -t cov" || exit 1
sleep 1
# The real (terminal) client, identified by not being in control mode.
RC=$($TMUX list-clients -F '#{client_control_mode} #{client_name}' |
awk '$1==0 { print $2; exit }')
# The control client.
CLIENT=$($TMUX list-clients -F '#{client_control_mode} #{client_name}' |
awk '$1==1 { print $2; exit }')
[ -n "$RC" ] || fail "No real client attached."
[ -n "$CLIENT" ] || fail "No control client attached."
# Expand every variable at once, with the real terminal client and a target
# pane in context, and confirm the server survives. This runs every callback.
FMT=""
for n in $NAMES; do
FMT="$FMT#{$n}"
done
$TMUX display-message -c "$RC" -t cov:win0.0 -p "$FMT" >/dev/null 2>&1
assert_alive "expanding all variables together"
# Expand each variable on its own too, so a crash can be pinned to one name.
for n in $NAMES; do
$TMUX display-message -c "$RC" -t cov:win0.0 -p "#{$n}" >/dev/null 2>&1
assert_alive "expanding #{$n}"
done
# Deterministic checks on stable variables (targeting pane 0 of window 0).
TGT="cov:win0.0"
test_var session_name "cov" -t "$TGT"
test_var window_name "win0" -t "$TGT"
test_var window_index "0" -t "$TGT"
test_var window_panes "2" -t "$TGT"
test_var session_windows "2" -t "$TGT"
test_var pane_index "0" -t "$TGT"
test_var pane_in_mode "0" -t "$TGT"
test_var pane_at_top "1" -t "$TGT"
test_var pane_at_left "1" -t "$TGT"
test_var last_window_index "1" -t "$TGT"
test_var pid "$($TMUX display-message -p '#{pid}')" -t "$TGT"
# The grouped session is reported as such.
test_var session_grouped "1" -t "cov:"
test_var session_group_size "2" -t "cov:"
# list-buffers -F formats each paste buffer (this fills in the paste-buffer
# format defaults).
if [ "$($TMUX list-buffers -F '#{buffer_name}=#{buffer_sample}')" != \
"buf0=somebuffer" ]; then
fail "Unexpected list-buffers format output."
fi
# Version reported by the variable matches tmux -V.
VER=$($TMUX -V | sed 's/^tmux //')
test_var version "$VER" -t "$TGT"
# Client variables from each kind of client.
test_var client_name "$CLIENT" -c "$CLIENT"
test_var client_control_mode "1" -c "$CLIENT"
test_var client_control_mode "0" -c "$RC"
test_var socket_path "$($TMUX display-message -p '#{socket_path}')" -c "$CLIENT"
# The real client has a terminal, so termcap/feature/environ queries work.
test_var "I/e:TERM" "$($TMUX display-message -c "$RC" -p '#{client_termname}')" \
-c "$RC"
# Termcap and feature queries against a real terminal return a boolean.
case "$($TMUX display-message -c "$RC" -p '#{I/c:colors}')" in
0|1) ;;
*) fail "Unexpected #{I/c:colors} for real client." ;;
esac
case "$($TMUX display-message -c "$RC" -p '#{I/f:256}')" in
0|1) ;;
*) fail "Unexpected #{I/f:256} for real client." ;;
esac
# Time variables through the pretty and relative modifiers: start_time is the
# recent server start, exercising the "last 24 hours" and "just now" paths.
[ -n "$($TMUX display-message -p '#{t/p:start_time}')" ] ||
fail "Empty #{t/p:start_time}."
[ -n "$($TMUX display-message -p '#{t/r:start_time}')" ] ||
fail "Empty #{t/r:start_time}."
cleanup
exit 0

176
regress/input-common.inc Normal file
View File

@@ -0,0 +1,176 @@
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
TMP=$(mktemp)
EXP=$(mktemp)
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
exit_status=0
fail()
{
echo "FAIL: $1"
diff -u "$EXP" "$TMP"
exit_status=1
}
start_pane()
{
start_pane_hlimit "$1" "$2" "$3" "$4" 0
}
start_pane_history()
{
start_pane_hlimit "$1" "$2" "$3" "$4" 2000
}
start_pane_hlimit()
{
name=$1
sx=$2
sy=$3
seq=$4
hlimit=$5
$TMUX kill-server 2>/dev/null
sleep 0.1
$TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1
$TMUX set-option -g history-limit "$hlimit" || exit 1
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \
"printf '$seq'; sleep 2" || exit 1
$TMUX kill-session -t test-setup
sleep 0.3
}
start_cmd()
{
name=$1
sx=$2
sy=$3
cmd=$4
$TMUX kill-server 2>/dev/null
sleep 0.1
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" "$cmd" || exit 1
sleep 0.3
}
normalize_capture()
{
sed 's/[ ]*$//' |
awk '{ line[NR] = $0; if ($0 != "") last = NR }
END { for (i = 1; i <= last; i++) print line[i] }'
}
capture_grid()
{
$TMUX capture-pane -pN -t "$1:" -S 0 -E - | normalize_capture
}
check_capture()
{
name=$1
expected=$2
capture_grid "$name" >"$TMP"
printf "%s\n" "$expected" >"$EXP"
cmp "$TMP" "$EXP" || fail "$name"
}
check_cursor()
{
name=$1
expected=$2
actual=$($TMUX display-message -p -t "$name:" '#{cursor_x},#{cursor_y}')
if [ "$actual" != "$expected" ]; then
printf "%s\n" "$expected" >"$EXP"
printf "%s\n" "$actual" >"$TMP"
fail "$name cursor"
fi
}
check_flags()
{
name=$1
expected=$2
$TMUX capture-pane -pNF -t "$name:" -S 0 -E - |
normalize_capture |
awk '$0 != "-"' >"$TMP"
printf "%s\n" "$expected" >"$EXP"
cmp "$TMP" "$EXP" || fail "$name flags"
}
check_joined()
{
name=$1
expected=$2
$TMUX capture-pane -pNJ -t "$name:" -S 0 -E - |
normalize_capture >"$TMP"
printf "%s\n" "$expected" >"$EXP"
cmp "$TMP" "$EXP" || fail "$name joined"
}
capture_raw()
{
$TMUX capture-pane -pR -t "$1:"
}
capture_raw_used()
{
capture_raw "$1" |
awk '/^(G| L)/ || /^ C/ && $3 !~ /^data=\(1,1, \)$/'
}
check_raw()
{
name=$1
expected=$2
capture_raw "$name" >"$TMP"
printf "%s\n" "$expected" >"$EXP"
cmp "$TMP" "$EXP" || fail "$name raw"
}
check_raw_used()
{
name=$1
expected=$2
capture_raw_used "$name" >"$TMP"
printf "%s\n" "$expected" >"$EXP"
cmp "$TMP" "$EXP" || fail "$name raw used"
}
check_raw_has()
{
name=$1
shift
capture_raw "$name" >"$TMP"
for expected in "$@"; do
if ! grep -Fqx "$expected" "$TMP"; then
printf "%s\n" "$expected" >"$EXP"
fail "$name raw missing"
fi
done
}
check_raw_matches()
{
name=$1
shift
capture_raw "$name" >"$TMP"
for expected in "$@"; do
if ! grep -Eq "$expected" "$TMP"; then
printf "%s\n" "$expected" >"$EXP"
fail "$name raw missing"
fi
done
}

39
regress/input-cursor.sh Normal file
View File

@@ -0,0 +1,39 @@
#!/bin/sh
. ./input-common.inc
start_pane cursor 10 3 'ABCDE\r\033[2Cxy\033[1D!\033[4GZ\n'
check_capture cursor 'ABxZE'
check_cursor cursor '0,1'
start_pane saverc 10 3 'abc\0337\033[2;5HXY\0338Z\n'
check_capture saverc 'abcZ
XY'
check_cursor saverc '0,1'
start_pane hvp 10 4 'A\033[3dB\033[5GC\033[2;2fD\n'
check_capture hvp 'A
D
B C'
check_cursor hvp '0,2'
start_pane cursorlines 8 4 'A\033[2BB\033[1FC\033[1AD\n'
check_capture cursorlines 'AD
C
B'
check_cursor cursorlines '0,1'
start_pane tabs 12 3 'a\tb\n'
check_capture tabs 'a b'
check_cursor tabs '0,1'
start_pane tabclear 12 3 '\033H\ta\033[3g\r\tb\n'
check_capture tabclear ' a b'
check_cursor tabclear '0,1'
start_pane cbt 16 3 '0123456789\r\033[10C\033[Zx\n'
check_capture cbt '01234567x9'
check_cursor cbt '0,1'
$TMUX kill-server 2>/dev/null
exit $exit_status

54
regress/input-edit.sh Normal file
View File

@@ -0,0 +1,54 @@
#!/bin/sh
. ./input-common.inc
start_pane dch 10 3 'abcdef\r\033[3C\033[2PXY\n'
check_capture dch 'abcXY'
start_pane ich 10 3 'abcdef\r\033[3C\033[2@XY\n'
check_capture ich 'abcXYdef'
start_pane erase 10 3 'abcdef\r\033[3C\033[KZ\n'
check_capture erase 'abcZ'
start_pane el1 10 3 'abcdef\r\033[3C\033[1KZ\n'
check_capture el1 ' Zef'
start_pane ech 10 3 'abcdef\r\033[3C\033[2XX\n'
check_capture ech 'abcX f'
start_pane ed 10 3 'one\ntwo\033[2;2H\033[JX\n'
check_capture ed 'one
tX'
start_pane ed1 10 3 'one\ntwo\033[2;2H\033[1JX\n'
check_capture ed1 '
Xo'
start_pane ed2 10 3 'one\ntwo\033[2JZ\n'
check_capture ed2 '
Z'
start_pane il 8 4 '111\n222\n333\033[2;1H\033[LAAA\n'
check_capture il '111
AAA
222
333'
start_pane dl 8 4 '111\n222\n333\033[2;1H\033[MZZZ\n'
check_capture dl '111
ZZZ'
start_pane irm 10 3 'abcdef\r\033[4h\033[3CXY\033[4lZ\n'
check_capture irm 'abcXYZef'
start_pane rep 10 3 'A\033[4bB\n'
check_capture rep 'AAAAAB'
start_pane decaln 6 3 '\033#8'
check_capture decaln 'EEEEEE
EEEEEE
EEEEEE'
$TMUX kill-server 2>/dev/null
exit $exit_status

View File

@@ -0,0 +1,42 @@
#!/bin/sh
. ./input-common.inc
start_cmd csi-param-discard 8 3 \
"perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2"
check_capture csi-param-discard 'OK'
start_cmd csi-interm-discard 8 3 \
"perl -e 'print qq{\e[ \030OK}'; sleep 2"
check_capture csi-interm-discard 'OK'
start_cmd osc-discard 8 3 \
"perl -e 'print qq{\e]2;}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
check_capture osc-discard 'OK'
start_cmd apc-discard 8 3 \
"perl -e 'print qq{\e_}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
check_capture apc-discard 'OK'
start_pane unknown-csi 8 3 '\033[?9999zOK'
check_capture unknown-csi 'OK'
start_pane unknown-osc 8 3 '\033]999;bad\aOK'
check_capture unknown-osc 'OK'
start_pane malformed-osc 8 3 '\033]8;id=a:id=b;http://bad\aX\033]8;id=no-separator\aY\033]9;4;5;200\a\033]9;4;z\a\033]10;notacolour\a\033]11;notacolour\a\033]12;notacolour\a\033]4;999;red\a\033]104;999\a\033]52bad\a\033]52;c;@@@\aOK'
check_capture malformed-osc 'XYOK'
check_raw_matches malformed-osc \
'C 0,0 data=\(1,1,X\).* link=NONE linkid=NONE' \
'C 0,1 data=\(1,1,Y\).* link=NONE linkid=NONE' \
'C 0,2 data=\(1,1,O\).* link=NONE linkid=NONE' \
'C 0,3 data=\(1,1,K\).* link=NONE linkid=NONE'
start_pane malformed-dcs 8 3 '\033P$qBAD\033\\OK'
check_capture malformed-dcs 'OK^[P0$r
^[\'
start_pane malformed-utf8 8 3 '\360\200\200\200A\355\240\200B'
check_capture malformed-utf8 '<27>A<EFBFBD>B'
exit $exit_status

15
regress/input-modes.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/bin/sh
. ./input-common.inc
start_pane alternate 10 3 'MAIN\033[?1049hALT\033[?1049lZ\n'
check_capture alternate 'MAINZ'
start_pane osc133 10 4 '\033]133;A\007prompt\n\033]133;C\007output\n'
check_capture osc133 'prompt
output'
check_flags osc133 'P prompt
O output'
$TMUX kill-server 2>/dev/null
exit $exit_status

37
regress/input-osc.sh Normal file
View File

@@ -0,0 +1,37 @@
#!/bin/sh
. ./input-common.inc
start_pane hyperlink 20 3 '\033]8;id=1;https://example.com\033\\link\033]8;;\033\\ plain\n'
check_capture hyperlink 'link plain'
check_flags hyperlink 'HX link plain'
$TMUX capture-pane -peH -t hyperlink: -S 0 -E - >/dev/null || exit 1
start_pane palette 20 3 '\033]4;1;rgb:11/22/33;2;red\007\033]104;1;2\007X\n'
check_capture palette 'X'
start_pane osc-colours 20 3 '\033]10;rgb:11/22/33\007\033]11;rgb:44/55/66\007\033]12;rgb:77/88/99\007\033]110\007\033]111\007\033]112\007X\n'
check_capture osc-colours 'X'
start_pane progress 20 3 '\033]9;4;1;25\007\033]9;4;0\007\033]9;4;5;200\007X\n'
check_capture progress 'X'
start_pane rename 20 3 '\033krenamed\033\\X\n'
check_capture rename 'X'
start_pane apc-title 20 3 '\033_test-title\033\\X\n'
check_capture apc-title 'X'
$TMUX kill-server 2>/dev/null
sleep 0.1
$TMUX new-session -d -x 20 -y 3 -s osc52 "sleep 2" || exit 1
$TMUX set-option -s set-clipboard on || exit 1
$TMUX respawn-pane -k -t osc52: \
"printf '\033]52;c;SGVsbG8=\007'; sleep 2" || exit 1
sleep 0.3
$TMUX save-buffer -b buffer0 - >"$TMP"
printf "Hello" >"$EXP"
cmp "$TMP" "$EXP" || fail "osc52"
$TMUX kill-server 2>/dev/null
exit $exit_status

View File

@@ -0,0 +1,84 @@
#!/bin/sh
. ./input-common.inc
start_pane bs 8 3 'abc\bd'
check_capture bs 'abd'
check_raw_matches bs \
'C 0,0 data=\(1,1,a\) flags=NONE\[0\]' \
'C 0,1 data=\(1,1,b\) flags=NONE\[0\]' \
'C 0,2 data=\(1,1,d\) flags=NONE\[0\]'
start_pane nel 8 3 'A\033EB'
check_capture nel 'A
B'
check_raw_matches nel \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 1,0 data=\(1,1,B\) flags=NONE\[0\]'
start_pane tabstops 16 3 '\033H1\t2\033[3g\r\t3'
check_raw_matches tabstops \
'C 0,0 data=\(1,1,1\) flags=NONE\[0\]' \
'C 0,8 data=\(1,1,2\) flags=NONE\[0\]' \
'C 0,15 data=\(1,1,3\) flags=NONE\[0\]'
start_pane decaln 6 3 '\033#8'
check_raw_matches decaln \
'C 0,0 data=\(1,1,E\) flags=NONE\[0\]' \
'C 1,5 data=\(1,1,E\) flags=NONE\[0\]' \
'C 2,5 data=\(1,1,E\) flags=NONE\[0\]'
start_pane charset 8 3 '\033(0qxl\033(BZ'
check_raw_matches charset \
'C 0,0 data=\(1,1,q\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
'C 0,1 data=\(1,1,x\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
'C 0,2 data=\(1,1,l\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
'C 0,3 data=\(1,1,Z\) flags=NONE\[0\] attr=NONE\[0\]'
start_pane g1charset 8 3 '\033)0\016q\017Z'
check_raw_matches g1charset \
'C 0,0 data=\(1,1,q\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
'C 0,1 data=\(1,1,Z\) flags=NONE\[0\] attr=NONE\[0\]'
start_pane csisave 8 3 '\033[3;3HS\033[s\033[1;1HA\033[uR'
check_raw_matches csisave \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 2,2 data=\(1,1,S\) flags=NONE\[0\]' \
'C 2,3 data=\(1,1,R\) flags=NONE\[0\]'
start_pane alternate 8 3 'main\033[?1049halt\033[?1049lback'
check_capture alternate 'mainback'
check_raw_matches alternate \
'C 0,0 data=\(1,1,m\) flags=NONE\[0\]' \
'C 0,4 data=\(1,1,b\) flags=NONE\[0\]' \
'C 0,7 data=\(1,1,k\) flags=NONE\[0\]'
start_pane sync 8 3 '\033P=1signored\033\\A\033P=2s\033\\B'
check_raw_matches sync \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]'
start_pane private 8 3 '\033[?25lA\033[?25hB\033[?1000hC\033[?1000lD'
check_raw_matches private \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]' \
'C 0,2 data=\(1,1,C\) flags=NONE\[0\]' \
'C 0,3 data=\(1,1,D\) flags=NONE\[0\]'
start_pane ris 8 3 'A\033cB'
check_capture ris 'B'
check_raw_matches ris \
'C 0,0 data=\(1,1,B\) flags=NONE\[0\]' \
'C 0,1 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\]'
start_pane keypad 8 3 '\033=A\033>B'
check_raw_matches keypad \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]'
start_pane cursorstyle 8 3 '\033[5 qA\033[0 qB'
check_raw_matches cursorstyle \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]'
exit $exit_status

View File

@@ -0,0 +1,33 @@
#!/bin/sh
. ./input-common.inc
start_pane absolute 8 4 'A\033[3;5HB\033[2GC\033[2D!'
check_capture absolute 'A
!C B'
check_cursor absolute '1,2'
check_raw_matches absolute \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 2,4 data=\(1,1,B\) flags=NONE\[0\]' \
'C 2,0 data=\(1,1,!\) flags=NONE\[0\]' \
'C 2,1 data=\(1,1,C\) flags=NONE\[0\]'
start_pane savecursor 8 4 '\033[4;4HS\0337\033[1;1HA\0338R'
check_capture savecursor 'A
SR'
check_cursor savecursor '5,3'
check_raw_matches savecursor \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 3,3 data=\(1,1,S\) flags=NONE\[0\]' \
'C 3,4 data=\(1,1,R\) flags=NONE\[0\]'
start_pane origin 8 5 '\033[2;4r\033[?6h\033[1;1HO\033[3;1HP\033[?6lQ'
check_raw_matches origin \
'C 1,0 data=\(1,1,O\) flags=NONE\[0\]' \
'C 3,0 data=\(1,1,P\) flags=NONE\[0\]' \
'C 0,0 data=\(1,1,Q\) flags=NONE\[0\]'
exit $exit_status

47
regress/input-raw-edit.sh Normal file
View File

@@ -0,0 +1,47 @@
#!/bin/sh
. ./input-common.inc
start_pane erasechars 8 3 'ABCDEFGH\r\033[3C\033[2X'
check_capture erasechars 'ABC FGH'
check_raw_matches erasechars \
'C 0,3 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]' \
'C 0,4 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]' \
'C 0,5 data=\(1,1,F\) flags=NONE\[0\]'
start_pane deletechars 8 3 'ABCDEFGH\r\033[3C\033[3P'
check_capture deletechars 'ABCGH'
check_raw_matches deletechars \
'C 0,3 data=\(1,1,G\) flags=NONE\[0\]' \
'C 0,4 data=\(1,1,H\) flags=NONE\[0\]' \
'C 0,5 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]'
start_pane insertchars 8 3 'ABCDEF\r\033[3C\033[2@xy'
check_capture insertchars 'ABCxyDEF'
check_raw_matches insertchars \
'C 0,3 data=\(1,1,x\) flags=NONE\[0\]' \
'C 0,4 data=\(1,1,y\) flags=NONE\[0\]' \
'C 0,5 data=\(1,1,D\) flags=NONE\[0\]'
start_pane eraseline 8 3 'ABCDEFGH\r\033[4C\033[K'
check_capture eraseline 'ABCD'
check_raw_matches eraseline \
'C 0,4 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]' \
'C 0,7 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]'
start_pane erasescreen 8 3 '1111111\033[2;1H2222222\033[H\033[JZ'
check_capture erasescreen 'Z'
check_raw_matches erasescreen \
'^G 8x3 \(0/0\)$' \
'C [0-9]+,0 data=\(1,1,Z\) flags=NONE\[0\]' \
'C [0-9]+,1 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]'
start_pane tabs 12 3 'A\tB\033[2g\r\033[IC'
check_raw_matches tabs \
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
'C 0,1 data=\(7,7, \) flags=TAB\[[0-9a-f]+\]' \
'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\]' \
'C 0,8 data=\(1,1,B\) flags=NONE\[0\]' \
'C 0,0 data=\(1,1,C\) flags=NONE\[0\]'
exit $exit_status

View File

@@ -0,0 +1,23 @@
#!/bin/sh
. ./input-common.inc
start_pane_hlimit trim 6 3 'one\ntwo\nthree\nfour\nfive\nsix' 2
check_raw_matches trim \
'^G 6x3 \(2/2\)$' \
'L 0 \(-\) flags=NONE\[0\]' \
'L 1 \(-\) flags=NONE\[0\]'
$TMUX clear-history -t trim:
check_raw_matches trim \
'^G 6x3 \(0/2\)$' \
'C 0,0 data=\(1,1,f\) flags=NONE\[0\]' \
'C 2,0 data=\(1,1,s\) flags=NONE\[0\]'
start_pane_hlimit edhistory 6 3 'one\ntwo\nthree\033[H\033[JZ' 5
check_raw_matches edhistory \
'^G 6x3 \([1-9][0-9]*/5\)$' \
'L [0-9]+ \(-\) flags=NONE\[0\]' \
'C [0-9]+,0 data=\(1,1,Z\) flags=NONE\[0\]'
exit $exit_status

View File

@@ -0,0 +1,21 @@
#!/bin/sh
. ./input-common.inc
start_pane_history reflow 8 4 'abcdefgh\nijklmnop\nqrstuvwx\nyz'
$TMUX resize-window -t reflow: -x 4 -y 4
sleep 0.2
check_raw_matches reflow \
'^G 4x4 \([0-9]+/2000\)$' \
'L [0-9]+ \([0-9-]+\) flags=WRAPPED\[[0-9a-f]+\]' \
'C [0-9]+,0 data=\(1,1,a\) flags=NONE\[0\]' \
'C [0-9]+,3 data=\(1,1,d\) flags=NONE\[0\]'
$TMUX resize-window -t reflow: -x 12 -y 4
sleep 0.2
check_raw_matches reflow \
'^G 12x4 \([0-9]+/2000\)$' \
'C [0-9]+,0 data=\(1,1,a\) flags=NONE\[0\]' \
'C [0-9]+,7 data=\(1,1,h\) flags=NONE\[0\]'
exit $exit_status

View File

@@ -0,0 +1,39 @@
#!/bin/sh
. ./input-common.inc
start_pane_history history 6 3 'one\ntwo\nthree\nfour\nfive'
check_raw_matches history \
'^G 6x3 \([1-9][0-9]*/2000\)$' \
'L [0-9]+ \(-\) flags=NONE\[0\]' \
'C [0-9]+,0 data=\(1,1,o\) flags=NONE\[0\]'
start_pane_history index 6 4 'A\nB\nC\033[2;3r\033[2;1HX\033D\033DY'
check_raw_matches index \
'C [0-9]+,0 data=\(1,1,X\) flags=NONE\[0\]' \
'C [0-9]+,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C [0-9]+,1 data=\(1,1,Y\) flags=NONE\[0\]'
start_pane reverse 6 4 'A\nB\nC\033[2;3r\033[2;1H\033MY'
check_raw_matches reverse \
'C 1,0 data=\(1,1,Y\) flags=NONE\[0\]' \
'C 2,0 data=\(1,1,B\) flags=NONE\[0\]'
start_pane insertline 6 4 'A\nB\nC\033[2;3r\033[2;1H\033[LY'
check_raw_matches insertline \
'C 1,0 data=\(1,1,Y\) flags=NONE\[0\]' \
'C 2,0 data=\(1,1,B\) flags=NONE\[0\]'
start_pane deleteline 6 4 'A\nB\nC\033[2;3r\033[2;1H\033[MY'
check_raw_matches deleteline \
'C 1,0 data=\(1,1,Y\) flags=NONE\[0\]' \
'C 2,0 data=\(1,1, \) flags=NONE\[0\]'
start_pane region-edge 6 4 'top\033[2;3rmid\033[2;1H\033D\033Mbot'
check_raw_matches region-edge \
'C 0,0 data=\(1,1,m\) flags=NONE\[0\]' \
'C 1,0 data=\(1,1,b\) flags=NONE\[0\]' \
'C 2,0 data=\(1,1, \) flags=NONE\[0\]' \
'C 3,0 data=\(1,1, \) flags=NONE\[0\]'
exit $exit_status

39
regress/input-raw-sgr.sh Normal file
View File

@@ -0,0 +1,39 @@
#!/bin/sh
. ./input-common.inc
start_pane attrs 16 3 '\033[1mB\033[2mD\033[3mI\033[4mU\033[5mK\033[7mR\033[8mH\033[9mS\033[53mO'
check_raw_matches attrs \
'C 0,0 data=\(1,1,B\) flags=NONE\[0\] attr=BRIGHT\[[0-9a-f]+\]' \
'C 0,1 data=\(1,1,D\) flags=NONE\[0\] attr=BRIGHT,DIM\[[0-9a-f]+\]' \
'C 0,2 data=\(1,1,I\) flags=NONE\[0\] attr=BRIGHT,DIM,ITALICS\[[0-9a-f]+\]' \
'C 0,3 data=\(1,1,U\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,ITALICS\[[0-9a-f]+\]' \
'C 0,5 data=\(1,1,R\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,BLINK,REVERSE,ITALICS\[[0-9a-f]+\]' \
'C 0,7 data=\(1,1,S\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,BLINK,REVERSE,HIDDEN,ITALICS,STRIKETHROUGH\[[0-9a-f]+\]' \
'C 0,8 data=\(1,1,O\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,BLINK,REVERSE,HIDDEN,ITALICS,STRIKETHROUGH,OVERLINE\[[0-9a-f]+\]'
start_pane colours 12 3 '\033[38;5;196;48;5;17mX\033[58;5;45mY'
check_raw_matches colours \
'C 0,0 data=\(1,1,X\) flags=FG256,BG256\[[0-9a-f]+\] attr=NONE\[0\] fg=colour196\[10000c4\] bg=colour17\[1000011\]' \
'C 0,1 data=\(1,1,Y\) flags=FG256,BG256\[[0-9a-f]+\] attr=NONE\[0\] fg=colour196\[10000c4\] bg=colour17\[1000011\] us=colour45\[100002d\]'
start_pane bce 8 3 '\033[44mA\033[K'
check_raw_matches bce \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\] attr=NONE\[0\].* bg=blue\[4\]' \
'C 0,1 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\].* bg=blue\[4\]' \
'C 0,7 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\].* bg=blue\[4\]'
start_pane underlines 12 3 '\033[4:2m2\033[4:3m3\033[4:4m4\033[4:5m5'
check_raw_matches underlines \
'C 0,0 data=\(1,1,2\) flags=NONE\[0\] attr=UNDERSCORE_2\[[0-9a-f]+\]' \
'C 0,1 data=\(1,1,3\) flags=NONE\[0\] attr=UNDERSCORE_3\[[0-9a-f]+\]' \
'C 0,2 data=\(1,1,4\) flags=NONE\[0\] attr=UNDERSCORE_4\[[0-9a-f]+\]' \
'C 0,3 data=\(1,1,5\) flags=NONE\[0\] attr=UNDERSCORE_5\[[0-9a-f]+\]'
start_pane hyperlink 12 3 '\033]8;id=id1;https://example.com/a\033\\A\033]8;;\033\\B'
check_raw_matches hyperlink \
'L 0 \(0\) flags=EXTENDED,HYPERLINK\[[0-9a-f]+\]' \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\].* link=https://example.com/a linkid=id1' \
'C 0,1 data=\(1,1,B\) flags=NONE\[0\].* link=NONE linkid=NONE'
exit $exit_status

View File

@@ -0,0 +1,82 @@
#!/bin/sh
. ./input-common.inc
start_pane wide 8 3 'A\343\201\202B'
check_capture wide 'AあB'
check_raw_matches wide \
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
'C 0,1 data=\(2,3,あ\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
start_pane combining 8 3 'e\314\201x'
check_raw_matches combining \
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
'C 0,0 data=\(1,[0-9]+,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 0,1 data=\(1,1,x\) flags=NONE\[0\]'
start_pane emoji 10 3 '\360\237\230\200Z'
check_raw_matches emoji \
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
'C 0,0 data=\(2,4,😀\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
'C 0,2 data=\(1,1,Z\) flags=NONE\[0\]'
start_pane flag 10 3 '\360\237\207\254\360\237\207\247!'
check_raw_matches flag \
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
'C 0,0 data=\(2,8,🇬🇧\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
'C 0,2 data=\(1,1,!\) flags=NONE\[0\]'
start_pane variation 10 3 '*\357\270\217!'
check_raw_matches variation \
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
'C 0,0 data=\(2,[0-9]+,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
'C 0,2 data=\(1,1,!\) flags=NONE\[0\]'
start_pane invalid 10 3 '\377A'
check_raw_matches invalid \
'C 0,0 data=\(1,3,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 0,1 data=\(1,1,A\) flags=NONE\[0\]'
start_pane trunc2 10 3 '\303A'
check_raw_matches trunc2 \
'C 0,0 data=\(1,3,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 0,1 data=\(1,1,A\) flags=NONE\[0\]'
start_pane trunc3 10 3 '\342\202A'
check_raw_matches trunc3 \
'C 0,0 data=\(1,3,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 0,1 data=\(1,1,A\) flags=NONE\[0\]'
start_pane overwrite-wide-left 10 3 'A\343\201\202B\r\033[1CX'
check_raw_matches overwrite-wide-left \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 0,1 data=\(1,1,X\) flags=NONE\[0\]' \
'C 0,2 data=\(1,1, \) flags=NONE\[0\]' \
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
start_pane overwrite-wide-pad 10 3 'A\343\201\202B\r\033[2CX'
check_raw_matches overwrite-wide-pad \
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
'C 0,1 data=\(1,1, \) flags=NONE\[0\]' \
'C 0,2 data=\(1,1,X\) flags=NONE\[0\]' \
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
start_pane overwrite-wide-with-wide 10 3 'A\343\201\202B\r\033[1C\347\225\214'
check_raw_matches overwrite-wide-with-wide \
'C 0,1 data=\(2,3,界\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
start_pane wide-right-edge 4 3 'ABC\343\201\202Z'
check_raw_matches wide-right-edge \
'L 0 \(0\) flags=WRAPPED\[[0-9a-f]+\]' \
'C 1,0 data=\(2,3,あ\) flags=NONE\[0\] attr=NONE\[0\]' \
'C 1,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
'C 1,2 data=\(1,1,Z\) flags=NONE\[0\]'
exit $exit_status

28
regress/input-raw-wrap.sh Normal file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
. ./input-common.inc
start_pane wrap 5 3 'ABCDEZ'
check_capture wrap 'ABCDE
Z'
check_raw_matches wrap \
'^G 5x3 \(0/0\)$' \
'L 0 \(0\) flags=WRAPPED\[[0-9a-f]+\]' \
'C 0,4 data=\(1,1,E\) flags=NONE\[0\]' \
'C 1,0 data=\(1,1,Z\) flags=NONE\[0\]'
start_pane nowrap 5 3 '\033[?7lABCDEZ'
check_capture nowrap 'ABCDZ'
check_raw_matches nowrap \
'^G 5x3 \(0/0\)$' \
'L 0 \(0\) flags=NONE\[0\]' \
'C 0,4 data=\(1,1,Z\) flags=NONE\[0\]'
start_pane pending 5 3 'ABCD\r\033[4CZ'
check_capture pending 'ABCDZ'
check_cursor pending '5,0'
check_raw_matches pending \
'L 0 \(0\) flags=NONE\[0\]' \
'C 0,4 data=\(1,1,Z\) flags=NONE\[0\]'
exit $exit_status

94
regress/input-replies.sh Normal file
View File

@@ -0,0 +1,94 @@
#!/bin/sh
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
sleep 0.1
TMP=$(mktemp)
EXP=$(mktemp)
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
$TMUX new-session -d -x 80 -y 24 -s replies \; \
set-window-option -t replies:0 remain-on-exit on || exit 1
$TMUX set-option -s set-clipboard on || exit 1
$TMUX set-option -s get-clipboard buffer || exit 1
printf Hello | $TMUX load-buffer -
sleep 0.3
exit_status=0
fail()
{
echo "FAIL: $1"
diff -u "$EXP" "$TMP"
exit_status=1
}
query()
{
name=$1
expected=$2
seq=$3
count=$4
setup=$5
$TMUX respawn-window -k -t replies:0 \
"stty raw -echo min 1 time 20; printf '$setup'; printf '$seq'; dd bs=1 count=$count 2>/dev/null | cat -v >$TMP"
sleep 0.5
printf "%s" "$expected" >"$EXP"
cmp "$TMP" "$EXP" || fail "$name"
}
query_timeout()
{
name=$1
expected=$2
seq=$3
setup=$4
$TMUX respawn-window -k -t replies:0 \
"stty raw -echo min 0 time 5; printf '$setup'; printf '$seq'; sleep 0.1; dd bs=1 count=128 2>/dev/null | cat -v >$TMP"
sleep 0.7
printf "%s" "$expected" >"$EXP"
cmp "$TMP" "$EXP" || fail "$name"
}
query "dsr-ok" '^[[0n' '\033[5n' 4 ''
query "dsr-cursor" '^[[1;1R' '\033[6n' 6 ''
query "da-primary" '^[[?1;2c' '\033[c' 7 ''
query "da-secondary" '^[[>84;0;0c' '\033[>c' 10 ''
query "decrqm-irm-reset" '^[[4;2$y' '\033[4$p' 8 ''
query "decrqm-irm-set" '^[[4;1$y' '\033[4$p' 8 '\033[4h'
query "decrqm-cursor-keys-reset" '^[[?1;2$y' '\033[?1$p' 9 ''
query "decrqm-cursor-keys-set" '^[[?1;1$y' '\033[?1$p' 9 '\033[?1h'
query "decrqm-columns" '^[[?3;4$y' '\033[?3$p' 9 ''
query "decrqm-origin-reset" '^[[?6;2$y' '\033[?6$p' 9 ''
query "decrqm-origin-set" '^[[?6;1$y' '\033[?6$p' 9 '\033[?6h'
query "decrqm-wrap-set" '^[[?7;1$y' '\033[?7$p' 9 ''
query "decrqm-wrap-reset" '^[[?7;2$y' '\033[?7$p' 9 '\033[?7l'
query "decrqm-cursor-visible-set" '^[[?25;1$y' '\033[?25$p' 10 ''
query "decrqm-cursor-visible-reset" '^[[?25;2$y' '\033[?25$p' 10 '\033[?25l'
query "decrqm-mouse-standard-set" '^[[?1000;1$y' '\033[?1000$p' 12 '\033[?1000h'
query "decrqm-mouse-button-set" '^[[?1002;1$y' '\033[?1002$p' 12 '\033[?1002h'
query "decrqm-mouse-all-set" '^[[?1003;1$y' '\033[?1003$p' 12 '\033[?1003h'
query "decrqm-focus-set" '^[[?1004;1$y' '\033[?1004$p' 12 '\033[?1004h'
query "decrqm-mouse-utf8-set" '^[[?1005;1$y' '\033[?1005$p' 12 '\033[?1005h'
query "decrqm-mouse-sgr-set" '^[[?1006;1$y' '\033[?1006$p' 12 '\033[?1006h'
query "decrqm-bracket-paste-set" '^[[?2004;1$y' '\033[?2004$p' 12 '\033[?2004h'
query "decrqm-theme-updates-set" '^[[?2031;1$y' '\033[?2031$p' 12 '\033[?2031h'
query "decrqss-cursor-style" '^[P1$r q0 q' '\033P$q q\033\\' 10 ''
query_timeout "osc-10-query" '^[]10;rgb:ffff/0000/0000^G' '\033]10;?\007' '\033]10;red\007'
query_timeout "osc-11-query" '^[]11;rgb:0000/0000/ffff^G' '\033]11;?\007' '\033]11;blue\007'
query_timeout "osc-12-query" '^[]12;rgb:0000/ffff/0000^G' '\033]12;?\007' '\033]12;green\007'
query_timeout "osc-4-query" '^[]4;1;rgb:ffff/0000/0000^G' '\033]4;1;?\007' '\033]4;1;red\007'
query_timeout "osc-104-reset-query" '' '\033]4;1;?\007' '\033]4;1;red\007\033]104;1\007'
query_timeout "osc-52-query" '^[]52;c;SGVsbG8=^G' '\033]52;c;?\007' ''
$TMUX kill-server 2>/dev/null
exit $exit_status

117
regress/input-requests.sh Normal file
View File

@@ -0,0 +1,117 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
python3 - "$TEST_TMUX" <<'PY'
import os
import select
import signal
import subprocess
import sys
import tempfile
import time
tmux = sys.argv[1]
server = [tmux, "-Ltest", "-f/dev/null"]
def run(*args, check=True):
return subprocess.run(server + list(args), check=check,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def attach():
pid, fd = os.forkpty()
if pid == 0:
os.environ["TERM"] = "xterm-256color"
os.execl(tmux, tmux, "-Ltest", "-f/dev/null", "attach-session",
"-t", "requests")
os.set_blocking(fd, False)
return pid, fd
def read_until(fd, needle, timeout=5):
end = time.time() + timeout
data = b""
while time.time() < end:
r, _, _ = select.select([fd], [], [], 0.05)
if fd in r:
try:
chunk = os.read(fd, 4096)
except BlockingIOError:
chunk = b""
if chunk == b"":
continue
data += chunk
if needle in data:
return data
raise RuntimeError("did not see terminal request %r in %r" %
(needle, data))
def wait_file(path, timeout=5):
end = time.time() + timeout
while time.time() < end:
try:
with open(path, "rb") as f:
data = f.read()
if data:
return data
except FileNotFoundError:
pass
time.sleep(0.05)
return b""
def respawn(command):
run("respawn-window", "-k", "-t", "requests:0", command)
time.sleep(0.2)
def cleanup(pid=None):
if pid is not None:
try:
os.kill(pid, signal.SIGHUP)
except ProcessLookupError:
pass
run("kill-server", check=False)
run("kill-server", check=False)
run("new-session", "-d", "-x", "80", "-y", "24", "-s", "requests",
"sleep 60")
pid, fd = attach()
try:
time.sleep(0.5)
with tempfile.NamedTemporaryFile(delete=False) as f:
palette_out = f.name
respawn("stty raw -echo min 1 time 50; "
"printf '\\033]4;99;?\\033\\\\'; "
"dd bs=1 count=64 2>/dev/null | cat -v >%s; sleep 1" %
palette_out)
read_until(fd, b"\033]4;99;?\033\\")
os.write(fd, b"\033]4;99;rgb:0101/0202/0303\033\\")
got = wait_file(palette_out)
expected = b"^[]4;99;rgb:0101/0202/0303^[\\"
if got != expected:
raise AssertionError("palette reply: expected %r got %r" %
(expected, got))
run("set-option", "-s", "set-clipboard", "on")
run("set-option", "-s", "get-clipboard", "request")
with tempfile.NamedTemporaryFile(delete=False) as f:
clip_out = f.name
respawn("stty raw -echo min 1 time 50; "
"printf '\\033]52;c;?\\033\\\\'; "
"dd bs=1 count=64 2>/dev/null | cat -v >%s; sleep 1" %
clip_out)
data = read_until(fd, b"]52;")
if b"?" not in data:
raise RuntimeError("clipboard request missing query in %r" % data)
os.write(fd, b"\033]52;c;UmVxdWVzdA==\033\\")
got = wait_file(clip_out)
expected = b"^[]52;c;UmVxdWVzdA==^[\\"
if got != expected:
raise AssertionError("clipboard reply: expected %r got %r" %
(expected, got))
finally:
cleanup(pid)
PY

73
regress/input-scroll.sh Normal file
View File

@@ -0,0 +1,73 @@
#!/bin/sh
. ./input-common.inc
start_pane wrap 5 3 'abcdeF'
check_capture wrap 'abcde
F'
check_cursor wrap '1,1'
check_flags wrap 'W abcde
- F'
check_joined wrap 'abcdeF'
start_pane wraplast 5 3 'abcd\033[5GZQ'
check_capture wraplast 'abcdZ
Q'
check_cursor wraplast '1,1'
start_pane nowrap 5 3 '\033[?7labcdeF'
check_capture nowrap 'abcdF'
check_cursor nowrap '4,0'
start_pane origin 6 4 '111111\n222222\n333333\n444444\033[2;3r\033[?6h\033[1;1HAA\033[?6l\033[r'
check_capture origin '111111
AA2222
333333
444444'
start_pane scrollup 5 4 '11111\n22222\n33333\n44444\033[2;3r\033[3;1HAAAAA\nBBBBB\033[r'
check_capture scrollup '11111
AAAAA
BBBBB
44444'
start_pane scrolldown 5 4 '11111\n22222\n33333\n44444\033[2;3r\033[2;1H\033[TZZZZZ\033[r'
check_capture scrolldown '11111
ZZZZZ
22222
44444'
start_pane ri 5 4 '11111\n22222\n33333\n44444\033[2;3r\033[2;1H\033MZZZZZ\033[r'
check_capture ri '11111
ZZZZZ
22222
44444'
start_pane nel 5 3 'AA\033EBC\n'
check_capture nel 'AA
BC'
$TMUX kill-server 2>/dev/null
sleep 0.1
$TMUX new-session -d -x 5 -y 3 -s history \; \
set-option -g history-limit 3 \; \
respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; sleep 2" || exit 1
sleep 0.3
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
printf "%s\n" '01
02
03
04
05
06' >"$EXP"
cmp "$TMP" "$EXP" || fail "history limit"
$TMUX clear-history -t history:
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
printf "%s\n" '04
05
06' >"$EXP"
cmp "$TMP" "$EXP" || fail "clear-history"
$TMUX kill-server 2>/dev/null
exit $exit_status

26
regress/input-sgr.sh Normal file
View File

@@ -0,0 +1,26 @@
#!/bin/sh
. ./input-common.inc
start_pane sgr-basic 20 3 '\033[1;2;3;4;5;7;8;9mA\033[22;23;24;25;27;28;29mB\n'
check_capture sgr-basic 'AB'
$TMUX capture-pane -peN -t sgr-basic: -S 0 -E - >/dev/null || exit 1
start_pane sgr-colour 20 3 '\033[31;42mA\033[38;5;196;48;5;22mB\033[38;2;1;2;3;48;2;4;5;6mC\033[39;49mD\n'
check_capture sgr-colour 'ABCD'
$TMUX capture-pane -peN -t sgr-colour: -S 0 -E - >/dev/null || exit 1
start_pane sgr-underline 20 3 '\033[4:1mA\033[4:2mB\033[4:3mC\033[4:4mD\033[4:5mE\033[4:0mF\n'
check_capture sgr-underline 'ABCDEF'
$TMUX capture-pane -peN -t sgr-underline: -S 0 -E - >/dev/null || exit 1
start_pane sgr-uscolour 20 3 '\033[58;5;45;4mA\033[58:2::10:20:30mB\033[59mC\n'
check_capture sgr-uscolour 'ABC'
$TMUX capture-pane -peN -t sgr-uscolour: -S 0 -E - >/dev/null || exit 1
start_pane sgr-reset 20 3 '\033[90;100mA\033[0mB\033[91;101mC\033[39;49mD\n'
check_capture sgr-reset 'ABCD'
$TMUX capture-pane -peN -t sgr-reset: -S 0 -E - >/dev/null || exit 1
$TMUX kill-server 2>/dev/null
exit $exit_status

45
regress/input-unicode.sh Normal file
View File

@@ -0,0 +1,45 @@
#!/bin/sh
. ./input-common.inc
start_pane wide 10 3 '\343\201\202B\rX\n'
check_capture wide 'X B'
check_flags wide 'X X B'
start_pane widepad 10 3 'A\343\201\202B\r\033[2CX\n'
check_capture widepad 'A XB'
check_flags widepad 'X A XB'
start_pane wideedge 5 3 'abc\343\201\202Z\n'
check_capture wideedge 'abcあ
Z'
check_cursor wideedge '0,2'
check_joined wideedge 'abcあZ'
start_pane wideeol 5 3 'abcd\343\201\202Z\n'
check_capture wideeol 'abcd
あZ'
check_cursor wideeol '0,2'
start_pane combine 10 3 'e\314\201\n'
check_capture combine 'é'
check_cursor combine '0,1'
start_pane combinewide 10 3 '\343\201\202\314\201X\n'
check_capture combinewide 'あ́X'
check_cursor combinewide '0,1'
start_pane variation 10 3 '\342\234\224\357\270\217X\n'
check_capture variation '✔X'
check_cursor variation '0,1'
start_pane flag 10 3 '\360\237\207\254\360\237\207\247X\n'
check_capture flag '🇬🇧X'
check_cursor flag '0,1'
start_pane combining-left 10 3 '\314\201A\n'
check_capture combining-left 'A'
check_cursor combining-left '0,1'
$TMUX kill-server 2>/dev/null
exit $exit_status

161
regress/options-array.sh Normal file
View File

@@ -0,0 +1,161 @@
#!/bin/sh
# Tests of array options in the options engine (options_array_* in options.c
# and the array handling in cmd-set-option.c / cmd-show-options.c).
#
# Array options are indexed by integer. This exercises: setting a whole array
# from a separator-delimited string; per-index set with option[N]; -a append
# (which lands at the next free index); show ordering by ascending index and
# preservation of gaps; per-index unset with -u; show -v of a single index and
# of a missing index; and per-option separators (user-keys splits only on
# comma, update-environment on space or comma).
#
# update-environment (session), status-format (session), user-keys (server)
# and command-alias (server) are used as representative array options.
#
# options-scope.sh covers scoping/inheritance and options-values.sh covers
# value validation.
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
check_value()
{
out=$($TMUX show $1 2>&1)
if [ "$out" != "$2" ]; then
echo "show $1 failed."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
# check_array $args $expected
#
# Compare the full (multi-line) show output for an array option with a
# newline-separated $expected string.
check_array()
{
out=$($TMUX show $1 2>&1)
if [ "$out" != "$(printf '%s' "$2")" ]; then
echo "show $1 (array) failed."
echo "Expected:"; printf '%s\n' "$2"
echo "But got:"; printf '%s\n' "$out"
exit 1
fi
}
check_ok()
{
if ! $TMUX "$@"; then
echo "Command failed (expected success): $*"
exit 1
fi
}
check_fail()
{
exp="$1"
shift
out=$($TMUX "$@" 2>&1)
if [ $? -eq 0 ]; then
echo "Command succeeded (expected failure): $*"
exit 1
fi
if [ "$out" != "$exp" ]; then
echo "Wrong error for: $*"
echo "Expected: '$exp'"
echo "But got: '$out'"
exit 1
fi
}
assert_alive()
{
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
echo "Server died: $1"
exit 1
fi
}
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
# --- whole-array assignment splits on the separator -----------------------
#
# update-environment has the default " ," separator, so a single string value
# is split into consecutive indices starting at 0.
check_ok set -g update-environment "AAA BBB,CCC"
check_array "-g update-environment" "update-environment[0] AAA
update-environment[1] BBB
update-environment[2] CCC"
# --- -a append goes to the next free index --------------------------------
check_ok set -ga update-environment "DDD"
check_array "-g update-environment" "update-environment[0] AAA
update-environment[1] BBB
update-environment[2] CCC
update-environment[3] DDD"
# --- per-index unset leaves a gap; show preserves order and gaps ----------
check_ok set -gu update-environment[1]
check_array "-g update-environment" "update-environment[0] AAA
update-environment[2] CCC
update-environment[3] DDD"
# show -v of an existing index returns its value; a missing index is empty.
check_value "-gv update-environment[0]" "AAA"
check_value "-gv update-environment[1]" ""
# --- explicit indexed set, including out-of-order and gaps ----------------
#
# status-format is a session array; assigning an empty string first clears its
# multi-index default, then set specific indices out of order and confirm show
# sorts by ascending index and keeps the gap at [1].
check_ok set -g status-format ""
check_array "-g status-format" "status-format"
check_ok set -g status-format[5] "five"
check_ok set -g status-format[0] "zero"
check_ok set -g status-format[2] "two"
check_array "-g status-format" "status-format[0] zero
status-format[2] two
status-format[5] five"
# --- comma-only separator (user-keys) -------------------------------------
#
# user-keys splits only on comma, so an embedded space stays within one entry
# (and show quotes a value containing a space).
check_ok set -g user-keys "One,Two Three"
check_array "-g user-keys" 'user-keys[0] One
user-keys[1] "Two Three"'
# --- command-type array (a hook) ------------------------------------------
#
# Hooks are command arrays: an indexed value is parsed as a command when set
# and re-printed from the parsed command list; a syntax error is reported.
check_ok set -g alert-bell[0] "display-message hi"
check_value "-gv alert-bell[0]" "display-message hi"
check_fail "syntax error" set -g alert-bell[0] "if -x {"
# --- colour-type array ----------------------------------------------------
#
# pane-colours is a colour array; an indexed value is validated as a colour.
check_ok set -w pane-colours[0] red
check_value "-wv pane-colours[0]" "red"
check_fail "bad colour: xxxyyy" set -w pane-colours[1] xxxyyy
# --- -o refuses to overwrite an already-set index -------------------------
check_ok set -g command-alias[9] "x=list-keys"
check_fail "already set: command-alias[9]" set -go command-alias[9] "y=list-keys"
# --- non-array option rejects index syntax --------------------------------
#
# status-left is a plain string; indexing it is an error.
check_fail "not an array: status-left[0]" set -g status-left[0] "x"
assert_alive "after options-array tests"
$TMUX kill-server 2>/dev/null
exit 0

208
regress/options-scope.sh Normal file
View File

@@ -0,0 +1,208 @@
#!/bin/sh
# Tests of the options engine scoping and inheritance, as described in the
# OPTIONS section of tmux(1) and implemented in options.c, cmd-set-option.c and
# cmd-show-options.c.
#
# This exercises: global vs session vs window vs pane precedence; -u to remove
# an option (revealing the inherited value); -gu to restore a global option to
# its compiled default; scope inference from the option name (-w/-p and the
# set-window-option alias); show -v (which does NOT walk parents) versus show -A
# (which does, marking inherited values with a trailing *); unknown/ambiguous
# option errors and -q suppression; and user options (@foo) at every scope.
#
# options-values.sh covers value validation and options-array.sh covers arrays.
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
# check_value $args $expected
#
# Run show-option with $args and compare the single-line output with $expected.
check_value()
{
out=$($TMUX show $1 2>&1)
if [ "$out" != "$2" ]; then
echo "show $1 failed."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
# check_ok $cmd...
#
# Run a command and require that it succeeds.
check_ok()
{
if ! $TMUX "$@"; then
echo "Command failed (expected success): $*"
exit 1
fi
}
# check_fail $expected_error $cmd...
#
# Run a command and require that it fails with the given error message.
check_fail()
{
exp="$1"
shift
out=$($TMUX "$@" 2>&1)
if [ $? -eq 0 ]; then
echo "Command succeeded (expected failure): $*"
exit 1
fi
if [ "$out" != "$exp" ]; then
echo "Wrong error for: $*"
echo "Expected: '$exp'"
echo "But got: '$out'"
exit 1
fi
}
assert_alive()
{
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
echo "Server died: $1"
exit 1
fi
}
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
# --- global vs session precedence -----------------------------------------
#
# status-left is a session option. A value set at the session scope shadows
# the global one; show -v at each scope reports that scope's own value.
check_ok set -g status-left "GLOBAL"
check_ok set status-left "SESSION"
check_value "-v status-left" "SESSION"
check_value "-gv status-left" "GLOBAL"
# show -v does NOT inherit: -u removes the session entry, after which the
# session-scope show -v is empty even though the global value still exists.
check_ok set -u status-left
check_value "-v status-left" ""
check_value "-gv status-left" "GLOBAL"
# show -A walks the parent scopes and marks an inherited value with a "*".
out=$($TMUX show -A 2>/dev/null | grep '^status-left\*')
if [ "$out" != "status-left* GLOBAL" ]; then
echo "show -A did not mark inherited status-left."
echo "But got: '$out'"
exit 1
fi
# --- -gu restores the compiled default ------------------------------------
#
# Removing a global option with -u restores its built-in default rather than
# deleting it; status-left's default is the format "[#{session_name}] ".
check_ok set -g status-left "GLOBAL2"
check_value "-gv status-left" "GLOBAL2"
check_ok set -gu status-left
check_value "-gv status-left" "[#{session_name}] "
# --- scope inference from the option name ---------------------------------
#
# mode-keys is a window option, so a bare set-option infers the window scope;
# set-window-option (setw) is an explicit alias for the same thing, and -g w
# targets the global window options.
check_ok set mode-keys vi
check_value "-wv mode-keys" "vi"
check_ok setw mode-keys emacs
check_value "-wv mode-keys" "emacs"
check_ok set -gw mode-keys vi
check_value "-gwv mode-keys" "vi"
# cursor-colour is a window-and-pane option. A pane-scope value overrides a
# window-scope one for that pane.
check_ok set -w cursor-colour blue
check_ok set -p cursor-colour red
check_value "-pv cursor-colour" "red"
out=$($TMUX show -Ap 2>/dev/null | grep '^cursor-colour ')
if [ "$out" != "cursor-colour red" ]; then
echo "pane cursor-colour did not override window value."
echo "But got: '$out'"
exit 1
fi
# --- -U unsets a window option and clears pane copies ----------------------
#
# When a window option also has per-pane copies, -u on the window scope leaves
# those pane copies in place; -U additionally removes the option from every
# pane in the window, so all panes fall back to inheritance.
$TMUX split-window -t main || exit 1
panes=$($TMUX list-panes -t main -F '#{pane_id}')
set -- $panes
pa=$1
pb=$2
check_ok set -p -t "$pa" cursor-colour red
check_ok set -p -t "$pb" cursor-colour blue
check_ok set -w -t main cursor-colour green
check_value "-pv -t $pa cursor-colour" "red"
check_value "-pv -t $pb cursor-colour" "blue"
check_value "-wv -t main cursor-colour" "green"
check_ok set -Uw -t main cursor-colour
check_value "-pv -t $pa cursor-colour" ""
check_value "-pv -t $pb cursor-colour" ""
check_value "-wv -t main cursor-colour" ""
# --- unknown, ambiguous and -q --------------------------------------------
check_fail "invalid option: no-such-option" set -g no-such-option x
check_fail "ambiguous option: status-l" set -g status-l x
# A unique prefix resolves to the full option name.
check_ok set -g status-inte 5
check_value "-gv status-interval" "5"
# -q suppresses the error and exits successfully.
check_ok set -gq no-such-option x
check_ok show -gqv no-such-option
# --- errors from unresolvable targets -------------------------------------
#
# A -t target that does not resolve produces a scope-specific error from
# options_scope_from_name()/options_scope_from_flags().
check_fail "no such session: nosuch" show -t nosuch status-left
check_fail "no such window: nosuch" show -w -t nosuch mode-keys
check_fail "no such pane: nosuch" set -p -t nosuch cursor-colour red
# --- show with no option name lists every option --------------------------
#
# show without a specific option walks the whole table (cmd_show_options_all).
# Hooks are hidden unless -H is given.
$TMUX set -g @listme "here" || exit 1
if ! $TMUX show -g | grep -q '^@listme here$'; then
echo "show -g did not list @listme."
exit 1
fi
# alert-bell is a hook: only shown with -H.
if $TMUX show -g | grep -q '^alert-bell'; then
echo "show -g listed a hook without -H."
exit 1
fi
if ! $TMUX show -gH | grep -q '^alert-bell'; then
echo "show -gH did not list the alert-bell hook."
exit 1
fi
# --- user options at every scope ------------------------------------------
#
# @-prefixed user options can be created freely at any scope and do not
# inherit type checking.
check_ok set -g @u "global-user"
check_ok set @u "session-user"
check_ok set -w @u "window-user"
check_ok set -p @u "pane-user"
check_value "-gv @u" "global-user"
check_value "-v @u" "session-user"
check_value "-wv @u" "window-user"
check_value "-pv @u" "pane-user"
assert_alive "after options-scope tests"
$TMUX kill-server 2>/dev/null
exit 0

193
regress/options-values.sh Normal file
View File

@@ -0,0 +1,193 @@
#!/bin/sh
# Tests of options engine value validation, as implemented by
# options_from_string() and friends in options.c.
#
# Each option table entry has a type (string, number, key, colour, flag,
# choice, command) with type-specific parsing and validation. This exercises:
# number range limits; choice options rejecting unknown values; flag options
# toggling with no value and rejecting garbage; colour and key options
# rejecting invalid input; string append with -a; -F expansion at set time;
# and -o refusing to overwrite an option that is already set.
#
# options-scope.sh covers scoping/inheritance and options-array.sh covers
# arrays.
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
check_value()
{
out=$($TMUX show $1 2>&1)
if [ "$out" != "$2" ]; then
echo "show $1 failed."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
check_ok()
{
if ! $TMUX "$@"; then
echo "Command failed (expected success): $*"
exit 1
fi
}
check_fail()
{
exp="$1"
shift
out=$($TMUX "$@" 2>&1)
if [ $? -eq 0 ]; then
echo "Command succeeded (expected failure): $*"
exit 1
fi
if [ "$out" != "$exp" ]; then
echo "Wrong error for: $*"
echo "Expected: '$exp'"
echo "But got: '$out'"
exit 1
fi
}
assert_alive()
{
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
echo "Server died: $1"
exit 1
fi
}
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
# --- number options -------------------------------------------------------
#
# display-time is a number with a minimum of 0; a negative value and a
# non-numeric value are both rejected via strtonum(3).
check_ok set -g display-time 4000
check_value "-gv display-time" "4000"
check_fail "value is too small: -5" set -g display-time -5
check_fail "value is invalid: abc" set -g display-time abc
# A missing value is rejected for a non-flag, non-choice option.
check_fail "empty value" set -g display-time
# --- choice options -------------------------------------------------------
#
# status-keys accepts only its listed choices (vi/emacs); anything else is an
# "unknown value" error and the option keeps its previous value.
check_ok set -g status-keys vi
check_value "-gv status-keys" "vi"
check_fail "unknown value: bogus" set -g status-keys bogus
check_value "-gv status-keys" "vi"
# --- flag options ---------------------------------------------------------
#
# focus-events is an on/off flag. Setting with no value toggles it; explicit
# on/off/yes/no/1/0 are accepted (case-insensitively); anything else fails.
check_ok set -g focus-events off
check_value "-gv focus-events" "off"
check_ok set -g focus-events # toggle
check_value "-gv focus-events" "on"
check_ok set -g focus-events # toggle back
check_value "-gv focus-events" "off"
check_ok set -g focus-events yes
check_value "-gv focus-events" "on"
check_ok set -g focus-events NO
check_value "-gv focus-events" "off"
check_fail "bad value: maybe" set -g focus-events maybe
# --- colour options -------------------------------------------------------
#
# status-bg is a colour; named colours, numbers and #rrggbb are accepted,
# garbage is rejected.
check_ok set -g status-bg red
check_value "-gv status-bg" "red"
check_ok set -g status-bg colour123
check_value "-gv status-bg" "colour123"
check_ok set -g status-bg "#00ff00"
check_value "-gv status-bg" "#00ff00"
check_fail "bad colour: xxxyyy" set -g status-bg xxxyyy
# --- style options --------------------------------------------------------
#
# status-style is a style string, validated when set; a bogus style keyword is
# rejected and the old value is retained.
check_ok set -g status-style "fg=red,bg=black"
check_value "-gv status-style" "fg=red,bg=black"
check_fail "invalid style: bg=xxxyyy" set -g status-style "bg=xxxyyy"
check_value "-gv status-style" "fg=red,bg=black"
# --- key options ----------------------------------------------------------
#
# prefix is a key; a valid key name is stored in canonical form, a bad one is
# rejected.
check_ok set -g prefix C-a
check_value "-gv prefix" "C-a"
check_fail "bad key: boguskey" set -g prefix boguskey
# --- string options with extra validation ---------------------------------
#
# default-shell is a string but is checked to be an executable shell; a bogus
# path is rejected and the old value kept.
old=$($TMUX show -gv default-shell)
check_fail "not a suitable shell: /not/a/shell" set -g default-shell /not/a/shell
check_value "-gv default-shell" "$old"
# --- user options require a value ------------------------------------------
#
# A user option set with no value at all is an error.
check_fail "empty value" set -g @novalue
# --- command options ------------------------------------------------------
#
# default-client-command is a command option: the value is parsed as a tmux
# command when set and re-printed from the parsed command list. A syntax
# error is reported and the option is left unchanged.
check_ok set -g default-client-command "new-window"
check_value "-gv default-client-command" "new-window"
check_fail "syntax error" set -g default-client-command "if -x {"
check_value "-gv default-client-command" "new-window"
# --- renamed option aliases -----------------------------------------------
#
# Historical option names are mapped to their current spelling, so setting
# cursor-color updates cursor-colour.
check_ok set -w cursor-color red
check_value "-wv cursor-colour" "red"
# --- string append (-a) ---------------------------------------------------
#
# -a appends to the current string value rather than replacing it.
check_ok set -g @str "foo"
check_ok set -ga @str "bar"
check_value "-gv @str" "foobar"
# --- -F expands at set time -----------------------------------------------
#
# With -F the value is expanded as a format once, at set time; without -F it is
# stored literally.
check_ok set -gF @expanded "#{session_name}"
check_value "-gv @expanded" "main"
check_ok set -g @literal "#{session_name}"
check_value "-gv @literal" "#{session_name}"
# --- -o refuses to overwrite ----------------------------------------------
#
# -o makes set-option fail if the option is already set, leaving it unchanged;
# it succeeds for an option that is not yet set.
check_ok set -g @once "first"
check_fail "already set: @once" set -go @once "second"
check_value "-gv @once" "first"
check_ok set -go @fresh "value"
check_value "-gv @fresh" "value"
assert_alive "after options-values tests"
$TMUX kill-server 2>/dev/null
exit 0

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

@@ -0,0 +1,303 @@
#!/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. The mode prompt no longer fills the rest
# of the row, so insert a marker to distinguish prompt input from tree content
# that may remain visible after the prompt.
$IN send-keys C-a || exit 1
$IN send-keys C-k || exit 1
$IN send-keys -l "X" || exit 1
settle
search_is "X" "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

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