mirror of
https://github.com/tmux/tmux.git
synced 2026-09-25 06:06:29 +00:00
Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c624de9f3c | ||
|
|
cf3ab3cad9 | ||
|
|
c6f62289b0 | ||
|
|
b114b7e089 | ||
|
|
f85b015dc1 | ||
|
|
899b4b5471 | ||
|
|
16064a410e | ||
|
|
fcc7366e86 | ||
|
|
5d2b6542b6 | ||
|
|
66e6b2e1c3 | ||
|
|
baf71480d0 | ||
|
|
5838b45315 | ||
|
|
cfa755a961 | ||
|
|
82683707bc | ||
|
|
3ee24bde1a | ||
|
|
4f7bef5083 | ||
|
|
80b1f20e7d | ||
|
|
111a11ac6a | ||
|
|
1853c220a4 | ||
|
|
115002748b | ||
|
|
db8f9b4c52 | ||
|
|
4951eac7fa | ||
|
|
185ae656e0 | ||
|
|
ab3da84e34 | ||
|
|
bce30eefd3 | ||
|
|
1302bbde07 | ||
|
|
e935342702 | ||
|
|
6d56dd9e7b | ||
|
|
8ca7434d65 | ||
|
|
a9cff084c0 | ||
|
|
186f51b7ee | ||
|
|
298b35c8a9 | ||
|
|
7a1cc1d443 | ||
|
|
fefb926291 | ||
|
|
36273fde4f | ||
|
|
8ac29f6587 | ||
|
|
db9b54f232 | ||
|
|
54f5c82500 | ||
|
|
b3bb8bbd9f | ||
|
|
c92720e3b0 | ||
|
|
08500f844d | ||
|
|
a97912a4f8 | ||
|
|
91ea9e4beb | ||
|
|
b30f5f82e0 | ||
|
|
d4eca8d586 | ||
|
|
8908f50524 | ||
|
|
eb2e26f4cd | ||
|
|
9569c57fc3 | ||
|
|
b48e2ea8e9 | ||
|
|
f7cd6ab547 | ||
|
|
401793eba6 | ||
|
|
fcfa0b6db4 | ||
|
|
5f7a4b0f52 | ||
|
|
bcc2b7ad1e | ||
|
|
9e8cf6f0e2 | ||
|
|
b8a62979ac | ||
|
|
45f4000017 | ||
|
|
572b32766c | ||
|
|
e6ad44c8a3 | ||
|
|
96cd98560b | ||
|
|
ac23a47a5f | ||
|
|
22b6ad6fce | ||
|
|
efe2453eac |
@@ -27,10 +27,10 @@ jobs:
|
|||||||
runner: ubuntu-24.04
|
runner: ubuntu-24.04
|
||||||
make: make
|
make: make
|
||||||
configure: --enable-utf8proc --enable-asan
|
configure: --enable-utf8proc --enable-asan
|
||||||
- name: ubuntu-24.04-arm64
|
#- name: ubuntu-24.04-arm64
|
||||||
runner: ubuntu-24.04-arm
|
# runner: ubuntu-24.04-arm
|
||||||
make: make
|
# make: make
|
||||||
configure: --enable-utf8proc --enable-asan
|
# configure: --enable-utf8proc --enable-asan
|
||||||
- name: macos-26-arm64
|
- name: macos-26-arm64
|
||||||
runner: macos-26
|
runner: macos-26
|
||||||
make: gmake
|
make: gmake
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
- name: test
|
- name: test
|
||||||
run: |
|
run: |
|
||||||
cd regress
|
cd regress
|
||||||
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
|
${{ matrix.make }}
|
||||||
|
|
||||||
- name: logs
|
- name: logs
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|||||||
@@ -9,12 +9,7 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
- modal panes may be created with new-pane -O; a window can have one modal
|
- modal panes may be created with new-pane -O; a window can have one modal
|
||||||
pane and it prevents interaction with the other panes while it is active.
|
pane and it prevents interaction with the other panes while it is active.
|
||||||
-C closes the pane when the mouse is clicked outside it and -K sends all
|
A modal pane is now used for the editor in buffer mode;
|
||||||
keys, including the prefix key, to the pane. A modal pane is now used for
|
|
||||||
the editor in buffer mode;
|
|
||||||
|
|
||||||
- new-pane -A creates a floating pane which remains above a zoomed pane;
|
|
||||||
modal panes do this by default;
|
|
||||||
|
|
||||||
- move-pane and resize-pane can now move and resize floating panes, including
|
- move-pane and resize-pane can now move and resize floating panes, including
|
||||||
mouse dragging;
|
mouse dragging;
|
||||||
@@ -29,15 +24,10 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
-k for choose commands kills the pane when the mode exits, which is useful
|
-k for choose commands kills the pane when the mode exits, which is useful
|
||||||
for modes in floating panes;
|
for modes in floating panes;
|
||||||
|
|
||||||
- command-prompt -P opens a prompt inside a pane rather than on the status
|
|
||||||
line;
|
|
||||||
|
|
||||||
- pane-border-status has top-floating and bottom-floating, and there are new
|
- pane-border-status has top-floating and bottom-floating, and there are new
|
||||||
default bindings under C-b g for common move and resize operations.
|
default bindings under C-b g for common move and resize operations;
|
||||||
|
|
||||||
* Layout strings now use a JSON subset format which includes floating panes.
|
- floating panes are unzoomed before creation to avoid a crash.
|
||||||
The old format is still accepted; control mode clients receive old layouts
|
|
||||||
unless they set the new-layouts flag.
|
|
||||||
|
|
||||||
* Add support for themes and improve default colours:
|
* Add support for themes and improve default colours:
|
||||||
|
|
||||||
@@ -84,7 +74,7 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
- new hooks to cover areas including client create and destroy, pane
|
- new hooks to cover areas including client create and destroy, pane
|
||||||
activity, pane mode and prompt changes, pane movement and resizing, session
|
activity, pane mode and prompt changes, pane movement and resizing, session
|
||||||
group changes and window creation, close, swap and zoom;
|
group changes and window creation, close and zoom;
|
||||||
|
|
||||||
- OSC 133 escape sequences now trigger events: pane-command-started,
|
- OSC 133 escape sequences now trigger events: pane-command-started,
|
||||||
pane-command-finished and pane-shell-prompt.
|
pane-command-finished and pane-shell-prompt.
|
||||||
@@ -97,23 +87,10 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
while scrolling or while hovered and disappears after pane-scrollbars-timeout
|
while scrolling or while hovered and disappears after pane-scrollbars-timeout
|
||||||
(Michael Grant).
|
(Michael Grant).
|
||||||
|
|
||||||
* Add a clear-on-attach server option. When disabled, tmux does not enter the
|
|
||||||
alternate screen on attach and instead scrolls the existing terminal content
|
|
||||||
into the scrollback buffer so it remains accessible (issue 5508).
|
|
||||||
|
|
||||||
* Copy mode can now automatically refresh as pane content changes with the
|
|
||||||
refresh-on, refresh-off and refresh-toggle commands. Automatic refresh is off
|
|
||||||
by default; refresh-now refreshes once and is bound to r. These replace
|
|
||||||
refresh-from-pane (issue 5165).
|
|
||||||
|
|
||||||
* Extend the fill-character option so both inside and outside the window can be
|
* Extend the fill-character option so both inside and outside the window can be
|
||||||
changed, with a new default for unused areas inside the window.
|
changed.
|
||||||
|
|
||||||
* Change show-options and show-hooks to use formats and add a -F flag to each.
|
* Change set-option and set-hooks to use formats and add a -F flag to each.
|
||||||
|
|
||||||
* Allow individual terminal features to be disabled by adding @ to their names
|
|
||||||
in terminal-features, and add a utf8 feature for terminals which support
|
|
||||||
UTF-8 output.
|
|
||||||
|
|
||||||
* Add a #{A/count:frames} modifier to show a series of frames as an animation
|
* Add a #{A/count:frames} modifier to show a series of frames as an animation
|
||||||
in the status line (issue 5412 from Fernando Daciuk).
|
in the status line (issue 5412 from Fernando Daciuk).
|
||||||
@@ -128,12 +105,6 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
* Add new-window -E, respawn-pane -E and respawn-window -E as more convenient
|
* Add new-window -E, respawn-pane -E and respawn-window -E as more convenient
|
||||||
methods to create an empty pane (rather than using '' for the command).
|
methods to create an empty pane (rather than using '' for the command).
|
||||||
|
|
||||||
* Add capture-pane -I to include the time each line entered history.
|
|
||||||
|
|
||||||
* Various memory leak fixes from Heon Jeong.
|
|
||||||
|
|
||||||
* Add a default C-b T binding to change the current pane title.
|
|
||||||
|
|
||||||
* Menus now belong to the window, so appear on all clients.
|
* Menus now belong to the window, so appear on all clients.
|
||||||
|
|
||||||
* Dragging over an existing copy mode selection now adjusts it (Michael Grant).
|
* Dragging over an existing copy mode selection now adjusts it (Michael Grant).
|
||||||
@@ -147,13 +118,6 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
* Copy mode no longer exits at the bottom while a selection is in progress
|
* Copy mode no longer exits at the bottom while a selection is in progress
|
||||||
(issue 5349).
|
(issue 5349).
|
||||||
|
|
||||||
* Add copy-mode-current-line-style to set the style of the line containing the
|
|
||||||
cursor in copy mode (issue 5391).
|
|
||||||
|
|
||||||
* Copy mode word commands now recognise all Unicode whitespace characters; a
|
|
||||||
space in word-separators matches any Unicode whitespace character (issue
|
|
||||||
5562).
|
|
||||||
|
|
||||||
* Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
|
* Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
|
||||||
nolink) (issues 4842 and 4280 from Moritz Angermann).
|
nolink) (issues 4842 and 4280 from Moritz Angermann).
|
||||||
|
|
||||||
@@ -164,9 +128,7 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
* Add or improve format variables including client_colours,
|
* Add or improve format variables including client_colours,
|
||||||
pane_start_command_list, window_manual_width, window_manual_height,
|
pane_start_command_list, window_manual_width, window_manual_height,
|
||||||
pane_last_output_time, pane_modal_flag, window_modal_pane, pane_private_modes,
|
pane_last_output_time, pane_modal_flag and window_modal_pane.
|
||||||
pane_output_generation, history_added, history_collected, history_generation,
|
|
||||||
hook_fire_count and hook_fire_time.
|
|
||||||
|
|
||||||
* Add additional pane sort orders, and a z sort order for choose-tree so
|
* Add additional pane sort orders, and a z sort order for choose-tree so
|
||||||
floating panes can be sorted by z-index.
|
floating panes can be sorted by z-index.
|
||||||
@@ -178,18 +140,12 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
* The mouse option now defaults to on.
|
* The mouse option now defaults to on.
|
||||||
|
|
||||||
* The active-pane client flag has been removed.
|
|
||||||
|
|
||||||
* Fix send-keys -K so keys are inserted in the correct place in the input
|
* Fix send-keys -K so keys are inserted in the correct place in the input
|
||||||
queue, like keys from key bindings (issue 3476).
|
queue, like keys from key bindings (issue 3476).
|
||||||
|
|
||||||
* Fix control mode clients hanging on exit if pty data was still queued (Ben
|
* Fix control mode clients hanging on exit if pty data was still queued (Ben
|
||||||
Maurer, issue 5356), and avoid sending notifications to clients which are
|
Maurer, issue 5356), and avoid sending notifications to clients which are
|
||||||
already exiting (Ben Maurer, issue 5357). Queue notifications so they are not
|
already exiting (Ben Maurer, issue 5357).
|
||||||
sent inside %begin/%end (issue 5458), do not let a stuck client prevent the
|
|
||||||
server from exiting (issue 5444), and reset control mode offsets when a pane
|
|
||||||
is respawned (issue 5498). Bound buffered command replies so a control mode
|
|
||||||
client that stops reading cannot consume unlimited memory (issue 5565).
|
|
||||||
|
|
||||||
* Fix grouped sessions sometimes being left as unusable command targets while
|
* Fix grouped sessions sometimes being left as unusable command targets while
|
||||||
they are being killed (Bryce Miller, issue 5180).
|
they are being killed (Bryce Miller, issue 5180).
|
||||||
|
|||||||
+3
-5
@@ -104,6 +104,7 @@ dist_tmux_SOURCES = \
|
|||||||
cmd-find-window.c \
|
cmd-find-window.c \
|
||||||
cmd-find.c \
|
cmd-find.c \
|
||||||
cmd-if-shell.c \
|
cmd-if-shell.c \
|
||||||
|
cmd-invoke.c \
|
||||||
cmd-join-pane.c \
|
cmd-join-pane.c \
|
||||||
cmd-kill-pane.c \
|
cmd-kill-pane.c \
|
||||||
cmd-kill-server.c \
|
cmd-kill-server.c \
|
||||||
@@ -174,7 +175,6 @@ dist_tmux_SOURCES = \
|
|||||||
input-keys.c \
|
input-keys.c \
|
||||||
input.c \
|
input.c \
|
||||||
job.c \
|
job.c \
|
||||||
json.c \
|
|
||||||
key-bindings.c \
|
key-bindings.c \
|
||||||
key-string.c \
|
key-string.c \
|
||||||
layout-custom.c \
|
layout-custom.c \
|
||||||
@@ -188,6 +188,7 @@ dist_tmux_SOURCES = \
|
|||||||
options-table.c \
|
options-table.c \
|
||||||
options.c \
|
options.c \
|
||||||
paste.c \
|
paste.c \
|
||||||
|
popup.c \
|
||||||
proc.c \
|
proc.c \
|
||||||
prompt-history.c \
|
prompt-history.c \
|
||||||
prompt.c \
|
prompt.c \
|
||||||
@@ -257,8 +258,7 @@ check_PROGRAMS = \
|
|||||||
fuzz/input-fuzzer \
|
fuzz/input-fuzzer \
|
||||||
fuzz/cmd-parse-fuzzer \
|
fuzz/cmd-parse-fuzzer \
|
||||||
fuzz/format-fuzzer \
|
fuzz/format-fuzzer \
|
||||||
fuzz/style-fuzzer \
|
fuzz/style-fuzzer
|
||||||
fuzz/layout-fuzzer
|
|
||||||
fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS)
|
fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS)
|
||||||
fuzz_input_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
|
fuzz_input_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
|
||||||
fuzz_cmd_parse_fuzzer_LDFLAGS = $(FUZZING_LIBS)
|
fuzz_cmd_parse_fuzzer_LDFLAGS = $(FUZZING_LIBS)
|
||||||
@@ -267,8 +267,6 @@ fuzz_format_fuzzer_LDFLAGS = $(FUZZING_LIBS)
|
|||||||
fuzz_format_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
|
fuzz_format_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
|
||||||
fuzz_style_fuzzer_LDFLAGS = $(FUZZING_LIBS)
|
fuzz_style_fuzzer_LDFLAGS = $(FUZZING_LIBS)
|
||||||
fuzz_style_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
|
fuzz_style_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
|
||||||
fuzz_layout_fuzzer_LDFLAGS = $(FUZZING_LIBS)
|
|
||||||
fuzz_layout_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Install tmux.1 in the right format.
|
# Install tmux.1 in the right format.
|
||||||
|
|||||||
+8
-71
@@ -35,7 +35,7 @@ The usual local layout is:
|
|||||||
```sh
|
```sh
|
||||||
cd /some/where/useful
|
cd /some/where/useful
|
||||||
git clone https://github.com/tmux/tmux.git tmux-portable
|
git clone https://github.com/tmux/tmux.git tmux-portable
|
||||||
git clone https://github.com/tmux/tmux-openbsd-cutover.git tmux-openbsd-cutover
|
git clone https://github.com/ThomasAdam/tmux-obsd.git tmux-openbsd-cutover
|
||||||
```
|
```
|
||||||
|
|
||||||
The exact directory names do not matter, but the examples below use:
|
The exact directory names do not matter, but the examples below use:
|
||||||
@@ -56,19 +56,18 @@ The cutover repository has three important branches:
|
|||||||
|
|
||||||
# Adding the OpenBSD remote to portable
|
# Adding the OpenBSD remote to portable
|
||||||
|
|
||||||
In the portable repository, add the published cutover repository as a remote.
|
In the portable repository, add the cutover repository as a remote:
|
||||||
This works regardless of which branch is checked out in a local cutover clone:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /path/to/tmux-portable
|
cd /path/to/tmux-portable
|
||||||
git remote add tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
git remote add tmux-openbsd /path/to/tmux-openbsd-cutover
|
||||||
git config remote.tmux-openbsd.tagOpt --no-tags
|
git config remote.tmux-openbsd.tagOpt --no-tags
|
||||||
```
|
```
|
||||||
|
|
||||||
If the remote already exists, update it instead:
|
If the remote already exists, update it instead:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git remote set-url tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
git remote set-url tmux-openbsd /path/to/tmux-openbsd-cutover
|
||||||
git config remote.tmux-openbsd.tagOpt --no-tags
|
git config remote.tmux-openbsd.tagOpt --no-tags
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -78,32 +77,6 @@ Fetch the cutover master branch explicitly:
|
|||||||
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
||||||
```
|
```
|
||||||
|
|
||||||
To merge unpublished changes from a local cutover clone instead, first ensure
|
|
||||||
it has an up-to-date local `master` branch. A normal clone may check out
|
|
||||||
`automation` and have only `origin/master`; fetching `master` from that clone
|
|
||||||
will then fail with `couldn't find remote ref master`.
|
|
||||||
|
|
||||||
With a clean cutover working tree:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd /path/to/tmux-openbsd-cutover
|
|
||||||
git fetch --no-tags origin
|
|
||||||
git switch master
|
|
||||||
git merge --ff-only origin/master
|
|
||||||
```
|
|
||||||
|
|
||||||
`git switch master` creates a tracking branch from `origin/master` if there is
|
|
||||||
no local `master` yet. If the fast-forward fails, reconcile the local cutover
|
|
||||||
changes before continuing; do not reset them away.
|
|
||||||
|
|
||||||
Then, in portable, point the remote at that clone and fetch its local `master`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd /path/to/tmux-portable
|
|
||||||
git remote set-url tmux-openbsd /path/to/tmux-openbsd-cutover
|
|
||||||
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
|
||||||
```
|
|
||||||
|
|
||||||
# Automated syncing
|
# Automated syncing
|
||||||
|
|
||||||
The normal sync is performed by the GitHub Actions workflow in the
|
The normal sync is performed by the GitHub Actions workflow in the
|
||||||
@@ -126,9 +99,7 @@ OpenBSD changes.
|
|||||||
If the workflow fails while merging into portable, do the merge locally and
|
If the workflow fails while merging into portable, do the merge locally and
|
||||||
push the result.
|
push the result.
|
||||||
|
|
||||||
Start with a clean working tree and an up-to-date portable master. If a merge
|
Start from an up-to-date portable master:
|
||||||
is already in progress, skip to resolving conflicts, or abort it before
|
|
||||||
starting again:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /path/to/tmux-portable
|
cd /path/to/tmux-portable
|
||||||
@@ -137,15 +108,10 @@ git checkout master
|
|||||||
git pull --ff-only origin master
|
git pull --ff-only origin master
|
||||||
```
|
```
|
||||||
|
|
||||||
Fetch the published cutover branch. Update an existing remote as well, since
|
Fetch the cutover branch:
|
||||||
it may point at a local clone without a `master` branch or with a stale one:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
if git remote get-url tmux-openbsd >/dev/null 2>&1; then
|
git remote add tmux-openbsd /path/to/tmux-openbsd-cutover 2>/dev/null || true
|
||||||
git remote set-url tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
|
||||||
else
|
|
||||||
git remote add tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
|
||||||
fi
|
|
||||||
git config remote.tmux-openbsd.tagOpt --no-tags
|
git config remote.tmux-openbsd.tagOpt --no-tags
|
||||||
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
||||||
```
|
```
|
||||||
@@ -156,22 +122,7 @@ Merge it:
|
|||||||
git merge --no-ff --log refs/remotes/tmux-openbsd/master
|
git merge --no-ff --log refs/remotes/tmux-openbsd/master
|
||||||
```
|
```
|
||||||
|
|
||||||
If merging a local cutover branch instead, use the local-clone preparation
|
Resolve conflicts by deciding whether portable or OpenBSD owns the file.
|
||||||
and fetch commands above in place of this fetch block.
|
|
||||||
|
|
||||||
When the merge reports conflicts, it leaves the merge in progress. List the
|
|
||||||
unresolved files, edit the conflict markers to combine the required portable
|
|
||||||
and OpenBSD changes, then stage each resolved file:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git diff --name-only --diff-filter=U
|
|
||||||
git diff -- path/to/file
|
|
||||||
git add path/to/file
|
|
||||||
```
|
|
||||||
|
|
||||||
For files that should come entirely from one side, decide whether portable or
|
|
||||||
OpenBSD owns the file before using the commands below. They replace the whole
|
|
||||||
file, including changes outside the conflicting hunks.
|
|
||||||
|
|
||||||
Useful commands:
|
Useful commands:
|
||||||
|
|
||||||
@@ -189,25 +140,11 @@ git add path/to/file
|
|||||||
|
|
||||||
This takes the OpenBSD/cutover version of a conflicted file.
|
This takes the OpenBSD/cutover version of a conflicted file.
|
||||||
|
|
||||||
For a modify/delete conflict, the side that deleted the file has no version
|
|
||||||
to check out. For example, portable generates `Makefile` using autotools and
|
|
||||||
does not track OpenBSD's `Makefile`. If Git reports that `Makefile` was deleted
|
|
||||||
in HEAD and modified in cutover, keep the portable deletion with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git rm -- Makefile
|
|
||||||
```
|
|
||||||
|
|
||||||
This removes the OpenBSD file left by the merge; regenerate the portable
|
|
||||||
`Makefile` with your usual configure command before building. Use this only
|
|
||||||
for the unmerged OpenBSD file, not an existing generated build file.
|
|
||||||
|
|
||||||
Before committing, inspect the result:
|
Before committing, inspect the result:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git status
|
git status
|
||||||
git diff --check
|
git diff --check
|
||||||
git diff --cached --check
|
|
||||||
git diff --cached --stat
|
git diff --cached --stat
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+41
-140
@@ -51,13 +51,6 @@ struct args {
|
|||||||
struct args_value *values;
|
struct args_value *values;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Prepared command state. */
|
|
||||||
struct args_command_state {
|
|
||||||
struct cmd_list *cmdlist;
|
|
||||||
char *cmd;
|
|
||||||
struct cmd_parse_input pi;
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct args_entry *args_find(struct args *, u_char);
|
static struct args_entry *args_find(struct args *, u_char);
|
||||||
|
|
||||||
static int args_cmp(struct args_entry *, struct args_entry *);
|
static int args_cmp(struct args_entry *, struct args_entry *);
|
||||||
@@ -89,8 +82,7 @@ args_copy_value(struct args_value *to, struct args_value *from)
|
|||||||
case ARGS_NONE:
|
case ARGS_NONE:
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
to->cmdlist = from->cmdlist;
|
to->cmd = cmd_parse_add_ref(from->cmd);
|
||||||
to->cmdlist->references++;
|
|
||||||
break;
|
break;
|
||||||
case ARGS_STRING:
|
case ARGS_STRING:
|
||||||
to->string = xstrdup(from->string);
|
to->string = xstrdup(from->string);
|
||||||
@@ -123,7 +115,7 @@ args_value_as_string(struct args_value *value)
|
|||||||
return ("");
|
return ("");
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
if (value->cached == NULL)
|
if (value->cached == NULL)
|
||||||
value->cached = cmd_list_print(value->cmdlist, 0);
|
value->cached = cmd_parse_print(value->cmd, 0);
|
||||||
return (value->cached);
|
return (value->cached);
|
||||||
case ARGS_STRING:
|
case ARGS_STRING:
|
||||||
return (value->string);
|
return (value->string);
|
||||||
@@ -367,7 +359,7 @@ args_copy_copy_value(struct args_value *to, struct args_value *from, int argc,
|
|||||||
to->string = expanded;
|
to->string = expanded;
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv);
|
to->cmd = cmd_parse_add_ref(from->cmd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,7 +410,7 @@ args_free_value(struct args_value *value)
|
|||||||
free(value->string);
|
free(value->string);
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
cmd_list_free(value->cmdlist);
|
cmd_parse_free(value->cmd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
free(value->cached);
|
free(value->cached);
|
||||||
@@ -477,7 +469,7 @@ args_to_vector(struct args *args, int *argc, char ***argv)
|
|||||||
cmd_append_argv(argc, argv, args->values[i].string);
|
cmd_append_argv(argc, argv, args->values[i].string);
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
s = cmd_list_print(args->values[i].cmdlist, 0);
|
s = cmd_parse_print(args->values[i].cmd, 0);
|
||||||
cmd_append_argv(argc, argv, s);
|
cmd_append_argv(argc, argv, s);
|
||||||
free(s);
|
free(s);
|
||||||
break;
|
break;
|
||||||
@@ -532,7 +524,7 @@ args_print_add_value(char **buf, size_t *len, struct args_value *value)
|
|||||||
case ARGS_NONE:
|
case ARGS_NONE:
|
||||||
break;
|
break;
|
||||||
case ARGS_COMMANDS:
|
case ARGS_COMMANDS:
|
||||||
expanded = cmd_list_print(value->cmdlist, 0);
|
expanded = cmd_parse_print(value->cmd, 0);
|
||||||
args_print_add(buf, len, "{ %s }", expanded);
|
args_print_add(buf, len, "{ %s }", expanded);
|
||||||
break;
|
break;
|
||||||
case ARGS_STRING:
|
case ARGS_STRING:
|
||||||
@@ -747,144 +739,53 @@ args_string(struct args *args, u_int idx)
|
|||||||
return (args_value_as_string(&args->values[idx]));
|
return (args_value_as_string(&args->values[idx]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make a command now. */
|
/*
|
||||||
struct cmd_list *
|
* Convert one command argument into a parse tree. A string value is parsed;
|
||||||
args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
|
* existing commands are referenced.
|
||||||
int expand)
|
*/
|
||||||
{
|
struct cmd_parse_tree *
|
||||||
struct args_command_state *state;
|
args_to_commands(struct cmd *self, struct cmdq_item *item, u_int idx,
|
||||||
char *error;
|
const char *default_command, int expand, char **cause)
|
||||||
struct cmd_list *cmdlist;
|
|
||||||
|
|
||||||
state = args_make_commands_prepare(self, item, idx, NULL, 0, expand);
|
|
||||||
cmdlist = args_make_commands(state, 0, NULL, &error);
|
|
||||||
if (cmdlist == NULL) {
|
|
||||||
cmdq_error(item, "%s", error);
|
|
||||||
free(error);
|
|
||||||
}
|
|
||||||
args_make_commands_free(state);
|
|
||||||
return (cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save bits to make a command later. */
|
|
||||||
struct args_command_state *
|
|
||||||
args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx,
|
|
||||||
const char *default_command, int wait, int expand)
|
|
||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
|
||||||
struct args_value *value;
|
struct args_value *value;
|
||||||
struct args_command_state *state;
|
struct cmd_parse_input pi = { 0 };
|
||||||
const char *cmd;
|
const char *cmd = NULL, *file;
|
||||||
const char *file;
|
char *expanded;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
|
||||||
state = xcalloc(1, sizeof *state);
|
*cause = NULL;
|
||||||
|
|
||||||
if (idx < args->count) {
|
if (idx >= args->count)
|
||||||
value = &args->values[idx];
|
|
||||||
if (value->type == ARGS_COMMANDS) {
|
|
||||||
state->cmdlist = value->cmdlist;
|
|
||||||
state->cmdlist->references++;
|
|
||||||
return (state);
|
|
||||||
}
|
|
||||||
cmd = value->string;
|
|
||||||
} else {
|
|
||||||
if (default_command == NULL)
|
|
||||||
fatalx("argument out of range");
|
|
||||||
cmd = default_command;
|
cmd = default_command;
|
||||||
|
else {
|
||||||
|
value = &args->values[idx];
|
||||||
|
switch (value->type) {
|
||||||
|
case ARGS_COMMANDS:
|
||||||
|
return (cmd_parse_add_ref(value->cmd));
|
||||||
|
case ARGS_STRING:
|
||||||
|
cmd = value->string;
|
||||||
|
break;
|
||||||
|
case ARGS_NONE:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (cmd == NULL)
|
||||||
|
fatalx("argument out of range");
|
||||||
|
|
||||||
if (expand)
|
if (expand)
|
||||||
state->cmd = format_single_from_target(item, cmd);
|
expanded = format_single_from_target(item, cmd);
|
||||||
else
|
else
|
||||||
state->cmd = xstrdup(cmd);
|
expanded = xstrdup(cmd);
|
||||||
log_debug("%s: %s", __func__, state->cmd);
|
log_debug("%s: %s", __func__, expanded);
|
||||||
|
|
||||||
if (wait)
|
cmd_get_source(self, &file, &pi.line);
|
||||||
state->pi.item = item;
|
|
||||||
cmd_get_source(self, &file, &state->pi.line);
|
|
||||||
if (file != NULL)
|
if (file != NULL)
|
||||||
state->pi.file = xstrdup(file);
|
pi.file = file;
|
||||||
state->pi.c = tc;
|
pi.flags = cmd_get_parse_flags(self);
|
||||||
if (state->pi.c != NULL)
|
tree = cmd_parse_from_string(expanded, &pi, cause);
|
||||||
state->pi.c->references++;
|
free(expanded);
|
||||||
cmd_find_copy_state(&state->pi.fs, target);
|
return (tree);
|
||||||
|
|
||||||
return (state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return argument as command. */
|
|
||||||
struct cmd_list *
|
|
||||||
args_make_commands(struct args_command_state *state, int argc, char **argv,
|
|
||||||
char **error)
|
|
||||||
{
|
|
||||||
struct cmd_parse_result *pr;
|
|
||||||
char *cmd, *new_cmd;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (state->cmdlist != NULL) {
|
|
||||||
if (argc == 0) {
|
|
||||||
state->cmdlist->references++;
|
|
||||||
return (state->cmdlist);
|
|
||||||
}
|
|
||||||
return (cmd_list_copy(state->cmdlist, argc, argv));
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd = xstrdup(state->cmd);
|
|
||||||
log_debug("%s: %s", __func__, cmd);
|
|
||||||
cmd_log_argv(argc, argv, __func__);
|
|
||||||
for (i = 0; i < argc; i++) {
|
|
||||||
new_cmd = cmd_template_replace(cmd, argv[i], i + 1);
|
|
||||||
log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd);
|
|
||||||
free(cmd);
|
|
||||||
cmd = new_cmd;
|
|
||||||
}
|
|
||||||
log_debug("%s: %s", __func__, cmd);
|
|
||||||
|
|
||||||
pr = cmd_parse_from_string(cmd, &state->pi);
|
|
||||||
free(cmd);
|
|
||||||
switch (pr->status) {
|
|
||||||
case CMD_PARSE_ERROR:
|
|
||||||
*error = pr->error;
|
|
||||||
return (NULL);
|
|
||||||
case CMD_PARSE_SUCCESS:
|
|
||||||
return (pr->cmdlist);
|
|
||||||
}
|
|
||||||
fatalx("invalid parse return state");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free commands state. */
|
|
||||||
void
|
|
||||||
args_make_commands_free(struct args_command_state *state)
|
|
||||||
{
|
|
||||||
if (state->cmdlist != NULL)
|
|
||||||
cmd_list_free(state->cmdlist);
|
|
||||||
if (state->pi.c != NULL)
|
|
||||||
server_client_unref(state->pi.c);
|
|
||||||
free((void *)state->pi.file);
|
|
||||||
free(state->cmd);
|
|
||||||
free(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get prepared command. */
|
|
||||||
char *
|
|
||||||
args_make_commands_get_command(struct args_command_state *state)
|
|
||||||
{
|
|
||||||
struct cmd *first;
|
|
||||||
int n;
|
|
||||||
char *s;
|
|
||||||
|
|
||||||
if (state->cmdlist != NULL) {
|
|
||||||
first = cmd_list_first(state->cmdlist);
|
|
||||||
if (first == NULL)
|
|
||||||
return (xstrdup(""));
|
|
||||||
return (xstrdup(cmd_get_entry(first)->name));
|
|
||||||
}
|
|
||||||
n = strcspn(state->cmd, " ,");
|
|
||||||
xasprintf(&s, "%.*s", n, state->cmd);
|
|
||||||
return (s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get first value in argument. */
|
/* Get first value in argument. */
|
||||||
|
|||||||
@@ -87,20 +87,21 @@ start_cfg(void)
|
|||||||
if (cfg_quiet)
|
if (cfg_quiet)
|
||||||
flags = CMD_PARSE_QUIET;
|
flags = CMD_PARSE_QUIET;
|
||||||
for (i = 0; i < cfg_nfiles; i++)
|
for (i = 0; i < cfg_nfiles; i++)
|
||||||
load_cfg(cfg_files[i], c, NULL, NULL, flags, NULL);
|
load_cfg(cfg_files[i], NULL, NULL, flags, NULL);
|
||||||
|
|
||||||
cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
|
cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
load_cfg(const char *path, struct client *c, struct cmdq_item *item,
|
load_cfg(const char *path, struct cmdq_item *item,
|
||||||
struct cmd_find_state *current, int flags, struct cmdq_item **new_item)
|
struct cmd_find_state *current, int flags, struct cmdq_item **new_item)
|
||||||
{
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
struct cmd_parse_input pi;
|
struct cmd_parse_input pi;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
struct cmdq_item *new_item0;
|
struct cmdq_item *new_item0;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
|
char *cause = NULL;
|
||||||
|
|
||||||
if (new_item != NULL)
|
if (new_item != NULL)
|
||||||
*new_item = NULL;
|
*new_item = NULL;
|
||||||
@@ -117,18 +118,16 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
|
|||||||
pi.flags = flags;
|
pi.flags = flags;
|
||||||
pi.file = path;
|
pi.file = path;
|
||||||
pi.line = 1;
|
pi.line = 1;
|
||||||
pi.item = item;
|
tree = cmd_parse_from_file(f, &pi, &cause);
|
||||||
pi.c = c;
|
|
||||||
|
|
||||||
pr = cmd_parse_from_file(f, &pi);
|
|
||||||
fclose(f);
|
fclose(f);
|
||||||
if (pr->status == CMD_PARSE_ERROR) {
|
if (tree == NULL) {
|
||||||
cfg_add_cause("%s", pr->error);
|
cfg_add_cause("%s", cause);
|
||||||
free(pr->error);
|
free(cause);
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
cmd_parse_log(__func__, tree);
|
||||||
if (flags & CMD_PARSE_PARSEONLY) {
|
if (flags & CMD_PARSE_PARSEONLY) {
|
||||||
cmd_list_free(pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +137,12 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
|
|||||||
state = cmdq_new_state(NULL, NULL, 0);
|
state = cmdq_new_state(NULL, NULL, 0);
|
||||||
cmdq_add_format(state, "current_file", "%s", pi.file);
|
cmdq_add_format(state, "current_file", "%s", pi.file);
|
||||||
|
|
||||||
new_item0 = cmdq_get_command(pr->cmdlist, state);
|
new_item0 = cmd_invoke_get(tree, state, NULL);
|
||||||
if (item != NULL)
|
if (item != NULL)
|
||||||
new_item0 = cmdq_insert_after(item, new_item0);
|
new_item0 = cmdq_insert_after(item, new_item0);
|
||||||
else
|
else
|
||||||
new_item0 = cmdq_append(NULL, new_item0);
|
new_item0 = cmdq_append(NULL, new_item0);
|
||||||
cmd_list_free(pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
|
|
||||||
if (new_item != NULL)
|
if (new_item != NULL)
|
||||||
@@ -153,13 +152,14 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
|
|||||||
|
|
||||||
int
|
int
|
||||||
load_cfg_from_buffer(const void *buf, size_t len, const char *path,
|
load_cfg_from_buffer(const void *buf, size_t len, const char *path,
|
||||||
struct client *c, struct cmdq_item *item, struct cmd_find_state *current,
|
struct cmdq_item *item, struct cmd_find_state *current, int flags,
|
||||||
int flags, struct cmdq_item **new_item)
|
struct cmdq_item **new_item)
|
||||||
{
|
{
|
||||||
struct cmd_parse_input pi;
|
struct cmd_parse_input pi;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
struct cmdq_item *new_item0;
|
struct cmdq_item *new_item0;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
|
char *cause = NULL;
|
||||||
|
|
||||||
if (new_item != NULL)
|
if (new_item != NULL)
|
||||||
*new_item = NULL;
|
*new_item = NULL;
|
||||||
@@ -170,17 +170,15 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
|
|||||||
pi.flags = flags;
|
pi.flags = flags;
|
||||||
pi.file = path;
|
pi.file = path;
|
||||||
pi.line = 1;
|
pi.line = 1;
|
||||||
pi.item = item;
|
tree = cmd_parse_from_buffer(buf, len, &pi, &cause);
|
||||||
pi.c = c;
|
if (tree == NULL) {
|
||||||
|
cfg_add_cause("%s", cause);
|
||||||
pr = cmd_parse_from_buffer(buf, len, &pi);
|
free(cause);
|
||||||
if (pr->status == CMD_PARSE_ERROR) {
|
|
||||||
cfg_add_cause("%s", pr->error);
|
|
||||||
free(pr->error);
|
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
cmd_parse_log(__func__, tree);
|
||||||
if (flags & CMD_PARSE_PARSEONLY) {
|
if (flags & CMD_PARSE_PARSEONLY) {
|
||||||
cmd_list_free(pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,12 +188,12 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
|
|||||||
state = cmdq_new_state(NULL, NULL, 0);
|
state = cmdq_new_state(NULL, NULL, 0);
|
||||||
cmdq_add_format(state, "current_file", "%s", pi.file);
|
cmdq_add_format(state, "current_file", "%s", pi.file);
|
||||||
|
|
||||||
new_item0 = cmdq_get_command(pr->cmdlist, state);
|
new_item0 = cmd_invoke_get(tree, state, NULL);
|
||||||
if (item != NULL)
|
if (item != NULL)
|
||||||
new_item0 = cmdq_insert_after(item, new_item0);
|
new_item0 = cmdq_insert_after(item, new_item0);
|
||||||
else
|
else
|
||||||
new_item0 = cmdq_append(NULL, new_item0);
|
new_item0 = cmdq_append(NULL, new_item0);
|
||||||
cmd_list_free(pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
|
|
||||||
if (new_item != NULL)
|
if (new_item != NULL)
|
||||||
|
|||||||
@@ -69,6 +69,25 @@ static void client_dispatch_attached(struct imsg *);
|
|||||||
static void client_dispatch_wait(struct imsg *);
|
static void client_dispatch_wait(struct imsg *);
|
||||||
static const char *client_exit_message(void);
|
static const char *client_exit_message(void);
|
||||||
|
|
||||||
|
/* Does any command have a flag? */
|
||||||
|
static int
|
||||||
|
client_command_has_flag(int argc, char **argv, int flag)
|
||||||
|
{
|
||||||
|
struct args_value *values;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
int found;
|
||||||
|
|
||||||
|
values = args_from_vector(argc, argv);
|
||||||
|
tree = cmd_parse_from_arguments(values, argc, NULL);
|
||||||
|
found = cmd_parse_any_have(tree, flag);
|
||||||
|
|
||||||
|
cmd_parse_free(tree);
|
||||||
|
args_free_values(values, argc);
|
||||||
|
free(values);
|
||||||
|
|
||||||
|
return (found);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get server create lock. If already held then server start is happening in
|
* Get server create lock. If already held then server start is happening in
|
||||||
* another client, so block until the lock is released and return -2 to
|
* another client, so block until the lock is released and return -2 to
|
||||||
@@ -232,7 +251,6 @@ int
|
|||||||
client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
||||||
int feat)
|
int feat)
|
||||||
{
|
{
|
||||||
struct cmd_parse_result *pr;
|
|
||||||
struct msg_command *data;
|
struct msg_command *data;
|
||||||
int fd, i;
|
int fd, i;
|
||||||
const char *ttynam, *termname, *cwd;
|
const char *ttynam, *termname, *cwd;
|
||||||
@@ -242,7 +260,6 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
|||||||
size_t size;
|
size_t size;
|
||||||
char **caps = NULL, *cause;
|
char **caps = NULL, *cause;
|
||||||
u_int ncaps = 0;
|
u_int ncaps = 0;
|
||||||
struct args_value *values;
|
|
||||||
|
|
||||||
/* Set up the initial command. */
|
/* Set up the initial command. */
|
||||||
if (shell_command != NULL) {
|
if (shell_command != NULL) {
|
||||||
@@ -253,22 +270,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
|||||||
flags |= CLIENT_STARTSERVER;
|
flags |= CLIENT_STARTSERVER;
|
||||||
} else {
|
} else {
|
||||||
msg = MSG_COMMAND;
|
msg = MSG_COMMAND;
|
||||||
|
if (client_command_has_flag(argc, argv, CMD_STARTSERVER))
|
||||||
/*
|
|
||||||
* It's annoying parsing the command string twice (in client
|
|
||||||
* and later in server) but it is necessary to get the start
|
|
||||||
* server flag.
|
|
||||||
*/
|
|
||||||
values = args_from_vector(argc, argv);
|
|
||||||
pr = cmd_parse_from_arguments(values, argc, NULL);
|
|
||||||
if (pr->status == CMD_PARSE_SUCCESS) {
|
|
||||||
if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER))
|
|
||||||
flags |= CLIENT_STARTSERVER;
|
flags |= CLIENT_STARTSERVER;
|
||||||
cmd_list_free(pr->cmdlist);
|
|
||||||
} else
|
|
||||||
free(pr->error);
|
|
||||||
args_free_values(values, argc);
|
|
||||||
free(values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create client process structure (starts logging). */
|
/* Create client process structure (starts logging). */
|
||||||
|
|||||||
@@ -91,10 +91,8 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
|
|||||||
wp = target.wp;
|
wp = target.wp;
|
||||||
|
|
||||||
if (wl != NULL) {
|
if (wl != NULL) {
|
||||||
if (wp != NULL) {
|
if (wp != NULL)
|
||||||
window_redraw_active_switch(wp->window, wp);
|
|
||||||
window_set_active_pane(wp->window, wp, 1);
|
window_set_active_pane(wp->window, wp, 1);
|
||||||
}
|
|
||||||
session_set_current(s, wl);
|
session_set_current(s, wl);
|
||||||
if (wp != NULL)
|
if (wp != NULL)
|
||||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||||
|
|||||||
+13
-19
@@ -56,11 +56,12 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
key_code key;
|
key_code key;
|
||||||
const char *tablename, *note = args_get(args, 'N');
|
const char *tablename, *note = args_get(args, 'N'), *file;
|
||||||
struct cmd_parse_result *pr;
|
|
||||||
int repeat;
|
int repeat;
|
||||||
struct args_value *value;
|
struct args_value *value;
|
||||||
u_int count = args_count(args);
|
u_int count = args_count(args);
|
||||||
|
struct cmd_parse_tree *cmd;
|
||||||
|
struct cmd_parse_input pi;
|
||||||
|
|
||||||
key = key_string_lookup_string(args_string(args, 0));
|
key = key_string_lookup_string(args_string(args, 0));
|
||||||
if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
|
if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
|
||||||
@@ -83,25 +84,18 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
|
|
||||||
value = args_value(args, 1);
|
value = args_value(args, 1);
|
||||||
if (count == 2 && value->type == ARGS_COMMANDS) {
|
if (count == 2 && value->type == ARGS_COMMANDS) {
|
||||||
key_bindings_add(tablename, key, note, repeat, value->cmdlist);
|
cmd = cmd_parse_add_ref(value->cmd);
|
||||||
value->cmdlist->references++;
|
key_bindings_add(tablename, key, note, repeat, cmd);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count == 2)
|
memset(&pi, 0, sizeof pi);
|
||||||
pr = cmd_parse_from_string(args_string(args, 1), NULL);
|
cmd_get_source(self, &file, &pi.line);
|
||||||
else {
|
if (file != NULL)
|
||||||
pr = cmd_parse_from_arguments(args_values(args) + 1, count - 1,
|
pi.file = file;
|
||||||
NULL);
|
pi.flags = cmd_get_parse_flags(self);
|
||||||
}
|
|
||||||
switch (pr->status) {
|
cmd = cmd_parse_from_arguments(args_values(args) + 1, count - 1, &pi);
|
||||||
case CMD_PARSE_ERROR:
|
key_bindings_add(tablename, key, note, repeat, cmd);
|
||||||
cmdq_error(item, "%s", pr->error);
|
|
||||||
free(pr->error);
|
|
||||||
return (CMD_RETURN_ERROR);
|
|
||||||
case CMD_PARSE_SUCCESS:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
key_bindings_add(tablename, key, note, repeat, pr->cmdlist);
|
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-13
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-capture-pane.c,v 1.69 2026/09/08 07:31:59 nicm Exp $ */
|
/* $OpenBSD: cmd-capture-pane.c,v 1.68 2026/07/20 11:16:33 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
|
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
|
||||||
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
|
|||||||
.name = "capture-pane",
|
.name = "capture-pane",
|
||||||
.alias = "capturep",
|
.alias = "capturep",
|
||||||
|
|
||||||
.args = { "ab:CeE:FHIJLMNpPqRS:Tt:", 0, 0, NULL },
|
.args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL },
|
||||||
.usage = "[-aCeFHIJLMNpPqRT] " 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 },
|
||||||
@@ -200,7 +200,7 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
|
|||||||
tmp[0] = line[i];
|
tmp[0] = line[i];
|
||||||
tmp[1] = '\0';
|
tmp[1] = '\0';
|
||||||
} else
|
} else
|
||||||
snprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
|
xsnprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
|
||||||
buf = cmd_capture_pane_append(buf, len, tmp,
|
buf = cmd_capture_pane_append(buf, len, tmp,
|
||||||
strlen(tmp));
|
strlen(tmp));
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
struct grid_cell *gc = NULL;
|
struct grid_cell *gc = NULL;
|
||||||
struct window_mode_entry *wme;
|
struct window_mode_entry *wme;
|
||||||
int n, join_lines, number_lines, flags = 0;
|
int n, join_lines, number_lines, flags = 0;
|
||||||
int show_flags, show_time, hyperlinks;
|
int show_flags, hyperlinks;
|
||||||
u_int *links = NULL, nlinks = 0;
|
u_int *links = NULL, nlinks = 0;
|
||||||
u_int i, sx, top, bottom, tmp;
|
u_int i, sx, top, bottom, tmp;
|
||||||
char *cause, *buf = NULL, *line, b[64], *cp;
|
char *cause, *buf = NULL, *line, b[64], *cp;
|
||||||
@@ -342,7 +342,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
flags |= GRID_STRING_TRIM_SPACES;
|
flags |= GRID_STRING_TRIM_SPACES;
|
||||||
number_lines = args_has(args, 'L');
|
number_lines = args_has(args, 'L');
|
||||||
show_flags = args_has(args, 'F');
|
show_flags = args_has(args, 'F');
|
||||||
show_time = args_has(args, 'I');
|
|
||||||
hyperlinks = args_has(args, 'H');
|
hyperlinks = args_has(args, 'H');
|
||||||
if (hyperlinks)
|
if (hyperlinks)
|
||||||
links = xreallocarray(NULL, gd->sx, sizeof *links);
|
links = xreallocarray(NULL, gd->sx, sizeof *links);
|
||||||
@@ -359,7 +358,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
free(line);
|
free(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
gl = grid_peek_line(gd, i);
|
|
||||||
|
|
||||||
if (number_lines) {
|
if (number_lines) {
|
||||||
if (i >= gd->hsize)
|
if (i >= gd->hsize)
|
||||||
@@ -370,16 +368,11 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
if (n >= 0)
|
if (n >= 0)
|
||||||
buf = cmd_capture_pane_append(buf, len, b, n);
|
buf = cmd_capture_pane_append(buf, len, b, n);
|
||||||
}
|
}
|
||||||
if (show_time) {
|
|
||||||
n = snprintf(b, sizeof b, "%llu ",
|
|
||||||
(unsigned long long)grid_line_time(gl));
|
|
||||||
if (n >= 0)
|
|
||||||
buf = cmd_capture_pane_append(buf, len, b, n);
|
|
||||||
}
|
|
||||||
if (show_flags) {
|
if (show_flags) {
|
||||||
cp = b;
|
cp = b;
|
||||||
*cp = '\0';
|
*cp = '\0';
|
||||||
|
|
||||||
|
gl = grid_peek_line(gd, i);
|
||||||
if (gl->flags & GRID_LINE_DEAD)
|
if (gl->flags & GRID_LINE_DEAD)
|
||||||
*cp++ = 'D';
|
*cp++ = 'D';
|
||||||
if (gl->flags & GRID_LINE_HYPERLINK)
|
if (gl->flags & GRID_LINE_HYPERLINK)
|
||||||
@@ -400,6 +393,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
|||||||
}
|
}
|
||||||
buf = cmd_capture_pane_append(buf, len, line, linelen);
|
buf = cmd_capture_pane_append(buf, len, line, linelen);
|
||||||
|
|
||||||
|
gl = grid_peek_line(gd, i);
|
||||||
if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
|
if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
|
||||||
buf[(*len)++] = '\n';
|
buf[(*len)++] = '\n';
|
||||||
|
|
||||||
|
|||||||
+49
-21
@@ -57,7 +57,7 @@ struct cmd_command_prompt_prompt {
|
|||||||
|
|
||||||
struct cmd_command_prompt_cdata {
|
struct cmd_command_prompt_cdata {
|
||||||
struct cmdq_item *item;
|
struct cmdq_item *item;
|
||||||
struct args_command_state *state;
|
struct cmd_parse_tree *tree;
|
||||||
|
|
||||||
int flags;
|
int flags;
|
||||||
enum prompt_type prompt_type;
|
enum prompt_type prompt_type;
|
||||||
@@ -85,13 +85,13 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
const char *type, *s, *input;
|
const char *type, *s, *input, *cmd;
|
||||||
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, *cause = NULL;
|
||||||
struct window_pane *wp = target->wp;
|
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, n;
|
||||||
int pane = args_has(args, 'P');
|
int pane = args_has(args, 'P');
|
||||||
|
|
||||||
if (pane) {
|
if (pane) {
|
||||||
@@ -107,14 +107,22 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cdata->item = item;
|
cdata->item = item;
|
||||||
if (pane)
|
if (pane)
|
||||||
cdata->wp = wp;
|
cdata->wp = wp;
|
||||||
cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait,
|
if (count != 0) {
|
||||||
args_has(args, 'F'));
|
cdata->tree = args_to_commands(self, item, 0, NULL,
|
||||||
|
args_has(args, 'F'), &cause);
|
||||||
|
if (cdata->tree == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
|
free(cdata);
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((s = args_get(args, 'p')) == NULL) {
|
if ((s = args_get(args, 'p')) == NULL) {
|
||||||
if (count != 0) {
|
if (count != 0) {
|
||||||
tmp = args_make_commands_get_command(cdata->state);
|
cmd = args_string(args, 0);
|
||||||
xasprintf(&prompts, "(%s)", tmp);
|
n = strcspn(cmd, " ,");
|
||||||
free(tmp);
|
xasprintf(&prompts, "(%.*s)", n, cmd);
|
||||||
} else {
|
} else {
|
||||||
prompts = xstrdup(":");
|
prompts = xstrdup(":");
|
||||||
space = 0;
|
space = 0;
|
||||||
@@ -201,8 +209,11 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
|||||||
struct cmd_command_prompt_cdata *cdata = data;
|
struct cmd_command_prompt_cdata *cdata = data;
|
||||||
char *error;
|
char *error;
|
||||||
struct cmdq_item *item = cdata->item, *new_item;
|
struct cmdq_item *item = cdata->item, *new_item;
|
||||||
struct cmd_list *cmdlist;
|
struct cmdq_state *cs = NULL;
|
||||||
struct cmd_command_prompt_prompt *prompt;
|
struct cmd_command_prompt_prompt *prompt;
|
||||||
|
struct cmd_invoke_input ci = { 0 };
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmd_parse_input pi = { 0 };
|
||||||
int argc = 0;
|
int argc = 0;
|
||||||
char **argv = NULL;
|
char **argv = NULL;
|
||||||
|
|
||||||
@@ -235,18 +246,35 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
|||||||
cdata->argv = cmd_copy_argv(argc, argv);
|
cdata->argv = cmd_copy_argv(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdlist = args_make_commands(cdata->state, argc, argv, &error);
|
if (item != NULL)
|
||||||
if (cmdlist == NULL) {
|
cs = cmdq_get_state(item);
|
||||||
cmdq_append(c, cmdq_get_error(error));
|
|
||||||
free(error);
|
if (cdata->tree != NULL) {
|
||||||
} else if (item == NULL) {
|
/* Explicit body: prompt inputs become the template argv. */
|
||||||
new_item = cmdq_get_command(cmdlist, NULL);
|
ci.argc = argc;
|
||||||
|
ci.argv = argv;
|
||||||
|
new_item = cmd_invoke_get(cdata->tree, cs, &ci);
|
||||||
|
if (item == NULL)
|
||||||
cmdq_append(c, new_item);
|
cmdq_append(c, new_item);
|
||||||
cmd_list_free(cmdlist);
|
else
|
||||||
} else {
|
|
||||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
cmd_list_free(cmdlist);
|
} else if (argc > 0 && argv[0] != NULL) {
|
||||||
|
/* No body: parse the submitted text as command language. */
|
||||||
|
tree = cmd_parse_from_string(argv[0], &pi, &error);
|
||||||
|
if (tree == NULL) {
|
||||||
|
if (item == NULL)
|
||||||
|
cmdq_append(c, cmdq_get_error(error));
|
||||||
|
else
|
||||||
|
cmdq_error(item, "%s", error);
|
||||||
|
free(error);
|
||||||
|
} else {
|
||||||
|
new_item = cmd_invoke_get(tree, cs, NULL);
|
||||||
|
cmd_parse_free(tree);
|
||||||
|
if (item == NULL)
|
||||||
|
cmdq_append(c, new_item);
|
||||||
|
else
|
||||||
|
cmdq_insert_after(item, new_item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cmd_free_argv(argc, argv);
|
cmd_free_argv(argc, argv);
|
||||||
|
|
||||||
@@ -283,6 +311,6 @@ cmd_command_prompt_free(void *data)
|
|||||||
}
|
}
|
||||||
free(cdata->prompts);
|
free(cdata->prompts);
|
||||||
cmd_free_argv(cdata->argc, cdata->argv);
|
cmd_free_argv(cdata->argc, cdata->argv);
|
||||||
args_make_commands_free(cdata->state);
|
cmd_parse_free(cdata->tree);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-13
@@ -51,7 +51,7 @@ const struct cmd_entry cmd_confirm_before_entry = {
|
|||||||
|
|
||||||
struct cmd_confirm_before_data {
|
struct cmd_confirm_before_data {
|
||||||
struct cmdq_item *item;
|
struct cmdq_item *item;
|
||||||
struct cmd_list *cmdlist;
|
struct cmd_parse_tree *tree;
|
||||||
u_char confirm_key;
|
u_char confirm_key;
|
||||||
int default_yes;
|
int default_yes;
|
||||||
};
|
};
|
||||||
@@ -70,13 +70,15 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct cmd_confirm_before_data *cdata;
|
struct cmd_confirm_before_data *cdata;
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
char *new_prompt;
|
char *new_prompt, *cause = NULL;
|
||||||
const char *confirm_key, *prompt, *cmd;
|
const char *confirm_key, *prompt, *cmd;
|
||||||
int wait = !args_has(args, 'b');
|
int wait = !args_has(args, 'b'), n;
|
||||||
|
|
||||||
cdata = xcalloc(1, sizeof *cdata);
|
cdata = xcalloc(1, sizeof *cdata);
|
||||||
cdata->cmdlist = args_make_commands_now(self, item, 0, 1);
|
cdata->tree = args_to_commands(self, item, 0, NULL, 1, &cause);
|
||||||
if (cdata->cmdlist == NULL) {
|
if (cdata->tree == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
@@ -92,8 +94,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cdata->confirm_key = confirm_key[0];
|
cdata->confirm_key = confirm_key[0];
|
||||||
else {
|
else {
|
||||||
cmdq_error(item, "invalid confirm key");
|
cmdq_error(item, "invalid confirm key");
|
||||||
cmd_list_free(cdata->cmdlist);
|
cmd_confirm_before_free(cdata);
|
||||||
free(cdata);
|
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,8 +104,9 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
if ((prompt = args_get(args, 'p')) != NULL)
|
if ((prompt = args_get(args, 'p')) != NULL)
|
||||||
xasprintf(&new_prompt, "%s ", prompt);
|
xasprintf(&new_prompt, "%s ", prompt);
|
||||||
else {
|
else {
|
||||||
cmd = cmd_get_entry(cmd_list_first(cdata->cmdlist))->name;
|
cmd = args_string(args, 0);
|
||||||
xasprintf(&new_prompt, "Confirm '%s'? (%c/n) ", cmd,
|
n = strcspn(cmd, " ,");
|
||||||
|
xasprintf(&new_prompt, "Confirm '%.*s'? (%c/n) ", n, cmd,
|
||||||
cdata->confirm_key);
|
cdata->confirm_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +126,7 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s,
|
|||||||
{
|
{
|
||||||
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;
|
||||||
|
struct cmdq_state *cs;
|
||||||
int retcode = 1;
|
int retcode = 1;
|
||||||
|
|
||||||
if (c->flags & CLIENT_DEAD)
|
if (c->flags & CLIENT_DEAD)
|
||||||
@@ -136,11 +139,11 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s,
|
|||||||
retcode = 0;
|
retcode = 0;
|
||||||
|
|
||||||
if (item == NULL) {
|
if (item == NULL) {
|
||||||
new_item = cmdq_get_command(cdata->cmdlist, NULL);
|
new_item = cmd_invoke_get(cdata->tree, NULL, NULL);
|
||||||
cmdq_append(c, new_item);
|
cmdq_append(c, new_item);
|
||||||
} else {
|
} else {
|
||||||
new_item = cmdq_get_command(cdata->cmdlist,
|
cs = cmdq_get_state(item);
|
||||||
cmdq_get_state(item));
|
new_item = cmd_invoke_get(cdata->tree, cs, NULL);
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +162,6 @@ cmd_confirm_before_free(void *data)
|
|||||||
{
|
{
|
||||||
struct cmd_confirm_before_data *cdata = data;
|
struct cmd_confirm_before_data *cdata = data;
|
||||||
|
|
||||||
cmd_list_free(cdata->cmdlist);
|
cmd_parse_free(cdata->tree);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
}
|
}
|
||||||
|
|||||||
+300
-171
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-display-menu.c,v 1.55 2026/09/21 12:14:32 nicm Exp $ */
|
/* $OpenBSD: cmd-display-menu.c,v 1.53 2026/08/31 07:46:55 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -54,8 +54,8 @@ const struct cmd_entry cmd_display_popup_entry = {
|
|||||||
.name = "display-popup",
|
.name = "display-popup",
|
||||||
.alias = "popup",
|
.alias = "popup",
|
||||||
|
|
||||||
.args = { "Bb:Cc:d:e:Eh:ks:S:t:T:w:x:y:", 0, -1, NULL },
|
.args = { "Bb:Cc:d:e:Eh:kNs:S:t:T:w:x:y:", 0, -1, NULL },
|
||||||
.usage = "[-BCEk] [-b border-lines] [-c target-client] "
|
.usage = "[-BCEkN] [-b border-lines] [-c target-client] "
|
||||||
"[-d start-directory] [-e environment] [-h height] "
|
"[-d start-directory] [-e environment] [-h height] "
|
||||||
"[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
|
"[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
|
||||||
" [-T title] [-w width] [-x position] [-y position] "
|
" [-T title] [-w width] [-x position] [-y position] "
|
||||||
@@ -91,6 +91,203 @@ cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause)
|
|||||||
return (type);
|
return (type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
cmd_display_menu_get_popup_pos(struct client *tc, struct cmdq_item *item,
|
||||||
|
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
|
||||||
|
{
|
||||||
|
struct tty *tty = &tc->tty;
|
||||||
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
|
struct key_event *event = cmdq_get_event(item);
|
||||||
|
struct session *s = tc->session;
|
||||||
|
struct winlink *wl = target->wl;
|
||||||
|
struct window_pane *wp = target->wp;
|
||||||
|
struct style_ranges *ranges = NULL;
|
||||||
|
struct style_range *sr = NULL;
|
||||||
|
const char *xp, *yp;
|
||||||
|
char *p;
|
||||||
|
int top;
|
||||||
|
u_int line, ox, oy, sx, sy, lines, position;
|
||||||
|
long n;
|
||||||
|
struct format_tree *ft;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Work out the position from the -x and -y arguments. This is the
|
||||||
|
* bottom-left position.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* If the popup is too big, stop now. */
|
||||||
|
if (w > tty->sx || h > tty->sy)
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
/* Create format with mouse position if any. */
|
||||||
|
ft = format_create_from_target(item);
|
||||||
|
if (event->m.valid) {
|
||||||
|
format_add(ft, "popup_mouse_x", "%u", event->m.x);
|
||||||
|
format_add(ft, "popup_mouse_y", "%u", event->m.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Position of the previous menu, for -x/-y L. */
|
||||||
|
format_add(ft, "popup_last_x", "%u", target->w->menu_last_px);
|
||||||
|
format_add(ft, "popup_last_y", "%u", target->w->menu_last_py + h);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there are any status lines, add this window position and the
|
||||||
|
* status line position.
|
||||||
|
*/
|
||||||
|
top = status_at_line(tc);
|
||||||
|
if (top != -1) {
|
||||||
|
lines = status_line_size(tc);
|
||||||
|
if (top == 0)
|
||||||
|
top = lines;
|
||||||
|
else
|
||||||
|
top = 0;
|
||||||
|
position = options_get_number(s->options, "status-position");
|
||||||
|
|
||||||
|
for (line = 0; line < lines; line++) {
|
||||||
|
ranges = &tc->status.entries[line].ranges;
|
||||||
|
TAILQ_FOREACH(sr, ranges, entry) {
|
||||||
|
if (sr->type != STYLE_RANGE_WINDOW)
|
||||||
|
continue;
|
||||||
|
if (sr->argument == (u_int)wl->idx)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (sr != NULL)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sr != NULL) {
|
||||||
|
format_add(ft, "popup_window_status_line_x", "%u",
|
||||||
|
sr->start);
|
||||||
|
if (position == 0) {
|
||||||
|
format_add(ft, "popup_window_status_line_y",
|
||||||
|
"%u", line + 1 + h);
|
||||||
|
} else {
|
||||||
|
format_add(ft, "popup_window_status_line_y",
|
||||||
|
"%u", tty->sy - lines + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position == 0)
|
||||||
|
format_add(ft, "popup_status_line_y", "%u", lines + h);
|
||||||
|
else {
|
||||||
|
format_add(ft, "popup_status_line_y", "%u",
|
||||||
|
tty->sy - lines);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
top = 0;
|
||||||
|
|
||||||
|
/* Popup width and height. */
|
||||||
|
format_add(ft, "popup_width", "%u", w);
|
||||||
|
format_add(ft, "popup_height", "%u", h);
|
||||||
|
|
||||||
|
/* Position so popup is in the centre. */
|
||||||
|
n = (long)(tty->sx - 1) / 2 - w / 2;
|
||||||
|
if (n < 0)
|
||||||
|
format_add(ft, "popup_centre_x", "%u", 0);
|
||||||
|
else
|
||||||
|
format_add(ft, "popup_centre_x", "%ld", n);
|
||||||
|
n = (tty->sy - 1) / 2 + h / 2;
|
||||||
|
if (n >= tty->sy)
|
||||||
|
format_add(ft, "popup_centre_y", "%u", tty->sy - h);
|
||||||
|
else
|
||||||
|
format_add(ft, "popup_centre_y", "%ld", n);
|
||||||
|
|
||||||
|
/* Position of popup relative to mouse. */
|
||||||
|
if (event->m.valid) {
|
||||||
|
n = (long)event->m.x - w / 2;
|
||||||
|
if (n < 0)
|
||||||
|
format_add(ft, "popup_mouse_centre_x", "%u", 0);
|
||||||
|
else
|
||||||
|
format_add(ft, "popup_mouse_centre_x", "%ld", n);
|
||||||
|
n = event->m.y - h / 2;
|
||||||
|
if (n + h >= tty->sy) {
|
||||||
|
format_add(ft, "popup_mouse_centre_y", "%u",
|
||||||
|
tty->sy - h);
|
||||||
|
} else
|
||||||
|
format_add(ft, "popup_mouse_centre_y", "%ld", n);
|
||||||
|
n = (long)event->m.y + h;
|
||||||
|
if (n >= tty->sy)
|
||||||
|
format_add(ft, "popup_mouse_top", "%u", tty->sy - 1);
|
||||||
|
else
|
||||||
|
format_add(ft, "popup_mouse_top", "%ld", n);
|
||||||
|
n = event->m.y - h;
|
||||||
|
if (n < 0)
|
||||||
|
format_add(ft, "popup_mouse_bottom", "%u", 0);
|
||||||
|
else
|
||||||
|
format_add(ft, "popup_mouse_bottom", "%ld", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Position in pane. */
|
||||||
|
tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy);
|
||||||
|
n = top + wp->yoff - oy + h;
|
||||||
|
if (n >= tty->sy)
|
||||||
|
format_add(ft, "popup_pane_top", "%u", tty->sy - h);
|
||||||
|
else
|
||||||
|
format_add(ft, "popup_pane_top", "%ld", n);
|
||||||
|
format_add(ft, "popup_pane_bottom", "%u", top + wp->yoff + wp->sy - oy);
|
||||||
|
format_add(ft, "popup_pane_left", "%u", wp->xoff - ox);
|
||||||
|
n = (long)wp->xoff + wp->sx - ox - w;
|
||||||
|
if (n < 0)
|
||||||
|
format_add(ft, "popup_pane_right", "%u", 0);
|
||||||
|
else
|
||||||
|
format_add(ft, "popup_pane_right", "%ld", n);
|
||||||
|
|
||||||
|
/* Expand horizontal position. */
|
||||||
|
xp = args_get(args, 'x');
|
||||||
|
if (xp == NULL || strcmp(xp, "C") == 0)
|
||||||
|
xp = "#{popup_centre_x}";
|
||||||
|
else if (strcmp(xp, "R") == 0)
|
||||||
|
xp = "#{popup_pane_right}";
|
||||||
|
else if (strcmp(xp, "P") == 0)
|
||||||
|
xp = "#{popup_pane_left}";
|
||||||
|
else if (strcmp(xp, "M") == 0)
|
||||||
|
xp = "#{popup_mouse_centre_x}";
|
||||||
|
else if (strcmp(xp, "L") == 0)
|
||||||
|
xp = "#{popup_last_x}";
|
||||||
|
else if (strcmp(xp, "W") == 0)
|
||||||
|
xp = "#{popup_window_status_line_x}";
|
||||||
|
p = format_expand(ft, xp);
|
||||||
|
n = strtol(p, NULL, 10);
|
||||||
|
if (n + w >= tty->sx)
|
||||||
|
n = tty->sx - w;
|
||||||
|
else if (n < 0)
|
||||||
|
n = 0;
|
||||||
|
*px = n;
|
||||||
|
log_debug("%s: -x: %s = %s = %u (-w %u)", __func__, xp, p, *px, w);
|
||||||
|
free(p);
|
||||||
|
|
||||||
|
/* Expand vertical position */
|
||||||
|
yp = args_get(args, 'y');
|
||||||
|
if (yp == NULL || strcmp(yp, "C") == 0)
|
||||||
|
yp = "#{popup_centre_y}";
|
||||||
|
else if (strcmp(yp, "P") == 0)
|
||||||
|
yp = "#{popup_pane_bottom}";
|
||||||
|
else if (strcmp(yp, "M") == 0)
|
||||||
|
yp = "#{popup_mouse_top}";
|
||||||
|
else if (strcmp(yp, "L") == 0)
|
||||||
|
yp = "#{popup_last_y}";
|
||||||
|
else if (strcmp(yp, "S") == 0)
|
||||||
|
yp = "#{popup_status_line_y}";
|
||||||
|
else if (strcmp(yp, "W") == 0)
|
||||||
|
yp = "#{popup_window_status_line_y}";
|
||||||
|
p = format_expand(ft, yp);
|
||||||
|
n = strtol(p, NULL, 10);
|
||||||
|
if (n < h)
|
||||||
|
n = 0;
|
||||||
|
else
|
||||||
|
n -= h;
|
||||||
|
if (n + h >= tty->sy)
|
||||||
|
n = tty->sy - h;
|
||||||
|
else if (n < 0)
|
||||||
|
n = 0;
|
||||||
|
*py = n;
|
||||||
|
log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h);
|
||||||
|
free(p);
|
||||||
|
|
||||||
|
format_free(ft);
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
cmd_display_menu_get_menu_pos(struct client *tc, struct cmdq_item *item,
|
cmd_display_menu_get_menu_pos(struct client *tc, struct cmdq_item *item,
|
||||||
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
|
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
|
||||||
@@ -285,8 +482,8 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
enum box_lines lines = BOX_LINES_DEFAULT;
|
enum box_lines lines = BOX_LINES_DEFAULT;
|
||||||
char *title, *cause = NULL;
|
char *title, *cause = NULL;
|
||||||
int flags = 0, starting_choice = 0;
|
int flags = 0, starting_choice = 0;
|
||||||
u_int px, py, sx, sy, i, count = args_count(args);
|
u_int px, py, i, count = args_count(args);
|
||||||
struct options *o = target->w->options;
|
struct options *o = target->s->curw->window->options;
|
||||||
struct options_entry *oe;
|
struct options_entry *oe;
|
||||||
|
|
||||||
if (args_has(args, 'C')) {
|
if (args_has(args, 'C')) {
|
||||||
@@ -334,6 +531,9 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
}
|
}
|
||||||
if (menu->count == 0)
|
if (menu->count == 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py,
|
||||||
|
menu->width + 4, menu->count + 2))
|
||||||
|
goto out;
|
||||||
|
|
||||||
value = args_get(args, 'b');
|
value = args_get(args, 'b');
|
||||||
if (value != NULL) {
|
if (value != NULL) {
|
||||||
@@ -344,11 +544,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cmdq_error(item, "menu-border-lines %s", cause);
|
cmdq_error(item, "menu-border-lines %s", cause);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
} else
|
}
|
||||||
lines = options_get_number(o, "menu-border-lines");
|
|
||||||
menu_get_size(menu, lines, &sx, &sy);
|
|
||||||
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py, sx, sy))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
if (args_has(args, 'O'))
|
if (args_has(args, 'O'))
|
||||||
flags |= MENU_STAYOPEN;
|
flags |= MENU_STAYOPEN;
|
||||||
@@ -369,22 +565,6 @@ fail:
|
|||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum pane_lines
|
|
||||||
cmd_display_popup_get_lines(const char *value, char **cause)
|
|
||||||
{
|
|
||||||
const struct options_table_entry *oe;
|
|
||||||
|
|
||||||
if (value == NULL)
|
|
||||||
value = "single";
|
|
||||||
else if (strcmp(value, "rounded") == 0)
|
|
||||||
value = "single";
|
|
||||||
else if (strcmp(value, "padded") == 0)
|
|
||||||
value = "spaces";
|
|
||||||
|
|
||||||
oe = options_search("pane-border-lines");
|
|
||||||
return (options_find_choice(oe, value, cause));
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum cmd_retval
|
static enum cmd_retval
|
||||||
cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
||||||
{
|
{
|
||||||
@@ -392,199 +572,148 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
struct session *s = target->s;
|
struct session *s = target->s;
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct winlink *wl = target->wl;
|
struct tty *tty = &tc->tty;
|
||||||
struct window *w = wl->window;
|
const char *value, *shell, *shellcmd = NULL;
|
||||||
struct window_pane *wp = target->wp, *new_wp = NULL;
|
const char *style = args_get(args, 's');
|
||||||
struct spawn_context sc = { 0 };
|
|
||||||
struct layout_cell *lc = NULL;
|
|
||||||
struct layout_geometry lg;
|
|
||||||
struct event_payload *ep;
|
|
||||||
struct cmd_find_state fs;
|
|
||||||
const char *value, *style = args_get(args, 's');
|
|
||||||
const char *border_style = args_get(args, 'S');
|
const char *border_style = args_get(args, 'S');
|
||||||
char *cause = NULL, *title = NULL;
|
char *cwd = NULL, *cause = NULL, **argv = NULL;
|
||||||
enum pane_lines lines = PANE_LINES_SINGLE;
|
char *title = NULL;
|
||||||
u_int px, py, sx, sy, count = args_count(args);
|
int modify = popup_present(tc);
|
||||||
|
int flags = -1, argc = 0;
|
||||||
|
enum box_lines lines = BOX_LINES_DEFAULT;
|
||||||
|
u_int px, py, w, h, count = args_count(args);
|
||||||
struct args_value *av;
|
struct args_value *av;
|
||||||
long long ll;
|
struct environ *env = NULL;
|
||||||
|
struct options *o = s->curw->window->options;
|
||||||
|
struct options_entry *oe;
|
||||||
|
|
||||||
if (args_has(args, 'C')) {
|
if (args_has(args, 'C')) {
|
||||||
if (w->modal != NULL)
|
server_client_clear_overlay(tc);
|
||||||
server_kill_pane(w->modal);
|
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
if (tc->flags & CLIENT_CONTROL)
|
if (tc->flags & CLIENT_CONTROL)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
if (w->modal != NULL)
|
if (!modify && tc->overlay_draw != NULL)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
|
|
||||||
value = args_get(args, 'b');
|
if (!modify) {
|
||||||
if (args_has(args, 'B'))
|
h = tty->sy / 2;
|
||||||
lines = PANE_LINES_NONE;
|
|
||||||
else if (value != NULL) {
|
|
||||||
lines = cmd_display_popup_get_lines(value, &cause);
|
|
||||||
if (cause != NULL) {
|
|
||||||
cmdq_error(item, "pane-border-lines %s", cause);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sy = w->sy / 2;
|
|
||||||
if (args_has(args, 'h')) {
|
if (args_has(args, 'h')) {
|
||||||
ll = args_percentage(args, 'h', 1, w->sy, w->sy, &cause);
|
h = args_percentage(args, 'h', 1, tty->sy, tty->sy,
|
||||||
|
&cause);
|
||||||
if (cause != NULL) {
|
if (cause != NULL) {
|
||||||
cmdq_error(item, "height %s", cause);
|
cmdq_error(item, "height %s", cause);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
sy = ll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sx = w->sx / 2;
|
w = tty->sx / 2;
|
||||||
if (args_has(args, 'w')) {
|
if (args_has(args, 'w')) {
|
||||||
ll = args_percentage(args, 'w', 1, w->sx, w->sx, &cause);
|
w = args_percentage(args, 'w', 1, tty->sx, tty->sx,
|
||||||
|
&cause);
|
||||||
if (cause != NULL) {
|
if (cause != NULL) {
|
||||||
cmdq_error(item, "width %s", cause);
|
cmdq_error(item, "width %s", cause);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
sx = ll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sx > w->sx)
|
if (w > tty->sx)
|
||||||
sx = w->sx;
|
w = tty->sx;
|
||||||
if (sy > w->sy)
|
if (h > tty->sy)
|
||||||
sy = w->sy;
|
h = tty->sy;
|
||||||
if ((lines == PANE_LINES_NONE && (sx < 1 || sy < 1)) ||
|
if (!cmd_display_menu_get_popup_pos(tc, item, args, &px, &py,
|
||||||
(lines != PANE_LINES_NONE && (sx < 3 || sy < 3)))
|
w, h))
|
||||||
goto out;
|
|
||||||
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py, sx, sy))
|
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
lg.sx = sx;
|
value = args_get(args, 'd');
|
||||||
lg.sy = sy;
|
if (value != NULL)
|
||||||
lg.xoff = px;
|
cwd = format_single_from_target(item, value);
|
||||||
lg.yoff = py;
|
else
|
||||||
if (lines != PANE_LINES_NONE) {
|
cwd = xstrdup(server_client_get_cwd(tc, s));
|
||||||
lg.sx -= 2;
|
if (count == 0) {
|
||||||
lg.sy -= 2;
|
shellcmd = options_get_string(s->options,
|
||||||
lg.xoff++;
|
"default-command");
|
||||||
lg.yoff++;
|
} else if (count == 1)
|
||||||
}
|
shellcmd = args_string(args, 0);
|
||||||
|
if (count <= 1 && (shellcmd == NULL || *shellcmd == '\0')) {
|
||||||
|
shellcmd = NULL;
|
||||||
|
shell = options_get_string(s->options, "default-shell");
|
||||||
|
if (!checkshell(shell))
|
||||||
|
shell = _PATH_BSHELL;
|
||||||
|
cmd_append_argv(&argc, &argv, shell);
|
||||||
|
} else
|
||||||
|
args_to_vector(args, &argc, &argv);
|
||||||
|
|
||||||
window_push_zoom(w, 0, 1);
|
if (args_has(args, 'e') >= 1) {
|
||||||
lc = layout_floating_pane(w, wp, &lg);
|
env = environ_create();
|
||||||
if (lc == NULL) {
|
|
||||||
window_pop_zoom(w);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc.item = item;
|
|
||||||
sc.s = s;
|
|
||||||
sc.wl = wl;
|
|
||||||
sc.tc = tc;
|
|
||||||
sc.wp0 = wp;
|
|
||||||
sc.lc = lc;
|
|
||||||
sc.idx = -1;
|
|
||||||
sc.cwd = args_get(args, 'd');
|
|
||||||
sc.flags = (SPAWN_FLOATING|SPAWN_MODAL|SPAWN_FLOATOVERZOOM);
|
|
||||||
|
|
||||||
if (count != 1 || *args_string(args, 0) != '\0')
|
|
||||||
args_to_vector(args, &sc.argc, &sc.argv);
|
|
||||||
sc.environ = environ_create();
|
|
||||||
av = args_first_value(args, 'e');
|
av = args_first_value(args, 'e');
|
||||||
while (av != NULL) {
|
while (av != NULL) {
|
||||||
environ_put(sc.environ, av->string, 0);
|
environ_put(env, av->string, 0);
|
||||||
av = args_next_value(av);
|
av = args_next_value(av);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
new_wp = spawn_pane(&sc, &cause);
|
value = args_get(args, 'b');
|
||||||
if (new_wp == NULL) {
|
if (args_has(args, 'B'))
|
||||||
cmdq_error(item, "create pane failed: %s", cause);
|
lines = BOX_LINES_NONE;
|
||||||
free(cause);
|
else if (value != NULL) {
|
||||||
cause = NULL;
|
oe = options_get(o, "popup-border-lines");
|
||||||
window_pop_zoom(w);
|
lines = options_find_choice(options_table_entry(oe), value,
|
||||||
|
&cause);
|
||||||
|
if (cause != NULL) {
|
||||||
|
cmdq_error(item, "popup-border-lines %s", cause);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
window_pop_zoom(w);
|
}
|
||||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
|
||||||
if (!args_has(args, 'E'))
|
|
||||||
new_wp->flags |= PANE_CLOSEONCANCEL;
|
|
||||||
|
|
||||||
options_set_number(new_wp->options, "pane-border-lines", lines);
|
|
||||||
if (args_has(args, 'E') > 1) {
|
|
||||||
if (args_has(args, 'k'))
|
|
||||||
options_set_number(new_wp->options, "remain-on-exit", 4);
|
|
||||||
else
|
|
||||||
options_set_number(new_wp->options, "remain-on-exit", 2);
|
|
||||||
} else if (args_has(args, 'E'))
|
|
||||||
options_set_number(new_wp->options, "remain-on-exit", 0);
|
|
||||||
else if (args_has(args, 'k'))
|
|
||||||
options_set_number(new_wp->options, "remain-on-exit", 3);
|
|
||||||
else
|
|
||||||
options_set_number(new_wp->options, "remain-on-exit", 1);
|
|
||||||
options_set_string(new_wp->options, "remain-on-exit-format", 0, "%s", "");
|
|
||||||
|
|
||||||
if (style != NULL) {
|
|
||||||
if (options_set_string(new_wp->options, "window-style", 0,
|
|
||||||
"%s", style) == NULL) {
|
|
||||||
cmdq_error(item, "bad style: %s", style);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
options_set_string(new_wp->options, "window-active-style", 0,
|
|
||||||
"%s", style);
|
|
||||||
new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|
|
|
||||||
PANE_THEMECHANGED);
|
|
||||||
}
|
|
||||||
if (border_style != NULL) {
|
|
||||||
if (options_set_string(new_wp->options, "pane-border-style", 0,
|
|
||||||
"%s", border_style) == NULL) {
|
|
||||||
cmdq_error(item, "bad border style: %s", border_style);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
options_set_string(new_wp->options, "pane-active-border-style",
|
|
||||||
0, "%s", border_style);
|
|
||||||
}
|
|
||||||
if (args_has(args, 'T'))
|
if (args_has(args, 'T'))
|
||||||
title = format_single_from_target(item, args_get(args, 'T'));
|
title = format_single_from_target(item, args_get(args, 'T'));
|
||||||
if (title != NULL) {
|
else
|
||||||
options_set_number(new_wp->options, "pane-border-status",
|
title = xstrdup("");
|
||||||
PANE_STATUS_TOP);
|
|
||||||
options_set_string(new_wp->options, "pane-border-format", 0,
|
if (args_has(args, 'N') || !modify)
|
||||||
"%s", "#{pane_title}");
|
flags = 0;
|
||||||
screen_set_title(&new_wp->base, title, 0);
|
if (args_has(args, 'E') > 1) {
|
||||||
ep = event_payload_create();
|
if (flags == -1)
|
||||||
cmd_find_from_pane(&fs, new_wp, 0);
|
flags = 0;
|
||||||
event_payload_set_target(ep, &fs);
|
flags |= POPUP_CLOSEEXITZERO;
|
||||||
event_payload_set_pane(ep, "pane", new_wp);
|
} else if (args_has(args, 'E')) {
|
||||||
event_payload_set_window(ep, "window", new_wp->window);
|
if (flags == -1)
|
||||||
event_payload_set_string(ep, "new_title", "%s", title);
|
flags = 0;
|
||||||
events_fire("pane-title-changed", ep);
|
flags |= POPUP_CLOSEEXIT;
|
||||||
|
}
|
||||||
|
if (args_has(args, 'k')) {
|
||||||
|
if (flags == -1)
|
||||||
|
flags = 0;
|
||||||
|
flags |= POPUP_CLOSEANYKEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_wp->wait_item = item;
|
if (modify) {
|
||||||
server_redraw_session(s);
|
popup_modify(tc, title, style, border_style, lines, flags);
|
||||||
if (sc.argv != NULL)
|
goto out;
|
||||||
cmd_free_argv(sc.argc, sc.argv);
|
}
|
||||||
environ_free(sc.environ);
|
if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
|
||||||
|
argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0)
|
||||||
|
goto out;
|
||||||
|
environ_free(env);
|
||||||
|
free(cwd);
|
||||||
free(title);
|
free(title);
|
||||||
|
cmd_free_argv(argc, argv);
|
||||||
return (CMD_RETURN_WAIT);
|
return (CMD_RETURN_WAIT);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (sc.argv != NULL)
|
cmd_free_argv(argc, argv);
|
||||||
cmd_free_argv(sc.argc, sc.argv);
|
environ_free(env);
|
||||||
environ_free(sc.environ);
|
free(cwd);
|
||||||
free(title);
|
free(title);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
free(cause);
|
free(cause);
|
||||||
if (new_wp != NULL) {
|
cmd_free_argv(argc, argv);
|
||||||
server_client_remove_pane(new_wp);
|
environ_free(env);
|
||||||
layout_close_pane(new_wp);
|
free(cwd);
|
||||||
window_remove_pane(new_wp->window, new_wp);
|
|
||||||
}
|
|
||||||
if (sc.argv != NULL)
|
|
||||||
cmd_free_argv(sc.argc, sc.argv);
|
|
||||||
environ_free(sc.environ);
|
|
||||||
free(title);
|
free(title);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-23
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-display-message.c,v 1.66 2026/09/08 08:33:10 nicm Exp $ */
|
/* $OpenBSD: cmd-display-message.c,v 1.65 2026/02/23 08:46:57 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||||
@@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = {
|
|||||||
.name = "display-message",
|
.name = "display-message",
|
||||||
.alias = "display",
|
.alias = "display",
|
||||||
|
|
||||||
.args = { "aCc:d:jlINpt:F:v", 0, 1, NULL },
|
.args = { "aCc:d:lINpt:F:v", 0, 1, NULL },
|
||||||
.usage = "[-aCIjlNpv] [-c target-client] [-d delay] [-F format] "
|
.usage = "[-aCIlNpv] [-c target-client] [-d delay] [-F format] "
|
||||||
CMD_TARGET_PANE_USAGE " [message]",
|
CMD_TARGET_PANE_USAGE " [message]",
|
||||||
|
|
||||||
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
||||||
@@ -67,15 +67,14 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct winlink *wl = target->wl;
|
struct winlink *wl = target->wl;
|
||||||
struct window_pane *wp = target->wp;
|
struct window_pane *wp = target->wp;
|
||||||
const char *template;
|
const char *template;
|
||||||
char *msg, *cause = NULL;
|
char *msg, *cause;
|
||||||
int delay = -1, flags, Nflag = args_has(args, 'N');
|
int delay = -1, flags, Nflag = args_has(args, 'N');
|
||||||
int Cflag = args_has(args, 'C');
|
int Cflag = args_has(args, 'C');
|
||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
u_int count = args_count(args);
|
u_int count = args_count(args);
|
||||||
struct evbuffer *evb;
|
struct evbuffer *evb;
|
||||||
struct json_node *jn;
|
|
||||||
|
|
||||||
if (args_has(args, 'I') && !args_has(args, 'j')) {
|
if (args_has(args, 'I')) {
|
||||||
if (wp == NULL)
|
if (wp == NULL)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
switch (window_pane_start_input(wp, item, &cause)) {
|
switch (window_pane_start_input(wp, item, &cause)) {
|
||||||
@@ -108,9 +107,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
template = args_string(args, 0);
|
template = args_string(args, 0);
|
||||||
else
|
else
|
||||||
template = args_get(args, 'F');
|
template = args_get(args, 'F');
|
||||||
if (args_has(args, 'j') && template == NULL)
|
if (template == NULL)
|
||||||
template = "";
|
|
||||||
else if (template == NULL)
|
|
||||||
template = DISPLAY_MESSAGE_TEMPLATE;
|
template = DISPLAY_MESSAGE_TEMPLATE;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -132,7 +129,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
|
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
|
||||||
format_defaults(ft, c, s, wl, wp);
|
format_defaults(ft, c, s, wl, wp);
|
||||||
|
|
||||||
if (args_has(args, 'a') && !args_has(args, 'j')) {
|
if (args_has(args, 'a')) {
|
||||||
format_each(ft, cmd_display_message_each, item);
|
format_each(ft, cmd_display_message_each, item);
|
||||||
format_free(ft);
|
format_free(ft);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
@@ -142,19 +139,6 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
msg = xstrdup(template);
|
msg = xstrdup(template);
|
||||||
else
|
else
|
||||||
msg = format_expand_time(ft, template);
|
msg = format_expand_time(ft, template);
|
||||||
if (args_has(args, 'j')) {
|
|
||||||
jn = json_parse(msg, &cause);
|
|
||||||
if (jn == NULL) {
|
|
||||||
cmdq_error(item, "%s", cause);
|
|
||||||
free(cause);
|
|
||||||
free(msg);
|
|
||||||
format_free(ft);
|
|
||||||
return (CMD_RETURN_ERROR);
|
|
||||||
}
|
|
||||||
free(msg);
|
|
||||||
msg = json_to_string(jn);
|
|
||||||
json_destroy_node(jn);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmdq_get_client(item) == NULL)
|
if (cmdq_get_client(item) == NULL)
|
||||||
cmdq_error(item, "%s", msg);
|
cmdq_error(item, "%s", msg);
|
||||||
|
|||||||
+7
-11
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-find.c,v 1.88 2026/09/07 12:05:12 nicm Exp $ */
|
/* $OpenBSD: cmd-find.c,v 1.87 2026/07/17 12:42:51 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -388,11 +388,9 @@ cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window)
|
|||||||
|
|
||||||
/* Try as an offset. */
|
/* Try as an offset. */
|
||||||
if (!exact && (window[0] == '+' || window[0] == '-')) {
|
if (!exact && (window[0] == '+' || window[0] == '-')) {
|
||||||
if (window[1] != '\0') {
|
if (window[1] != '\0')
|
||||||
n = strtonum(window + 1, 1, INT_MAX, &errstr);
|
n = strtonum(window + 1, 1, INT_MAX, NULL);
|
||||||
if (errstr != NULL)
|
else
|
||||||
return (-1);
|
|
||||||
} else
|
|
||||||
n = 1;
|
n = 1;
|
||||||
s = fs->s;
|
s = fs->s;
|
||||||
if (fs->flags & CMD_FIND_WINDOW_INDEX) {
|
if (fs->flags & CMD_FIND_WINDOW_INDEX) {
|
||||||
@@ -628,11 +626,9 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane)
|
|||||||
|
|
||||||
/* Try as an offset. */
|
/* Try as an offset. */
|
||||||
if (pane[0] == '+' || pane[0] == '-') {
|
if (pane[0] == '+' || pane[0] == '-') {
|
||||||
if (pane[1] != '\0') {
|
if (pane[1] != '\0')
|
||||||
n = strtonum(pane + 1, 1, INT_MAX, &errstr);
|
n = strtonum(pane + 1, 1, INT_MAX, NULL);
|
||||||
if (errstr != NULL)
|
else
|
||||||
return (-1);
|
|
||||||
} else
|
|
||||||
n = 1;
|
n = 1;
|
||||||
wp = fs->w->active;
|
wp = fs->w->active;
|
||||||
if (pane[0] == '+')
|
if (pane[0] == '+')
|
||||||
|
|||||||
+48
-43
@@ -53,8 +53,9 @@ const struct cmd_entry cmd_if_shell_entry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct cmd_if_shell_data {
|
struct cmd_if_shell_data {
|
||||||
struct args_command_state *cmd_if;
|
struct cmd_parse_tree *cmd_if;
|
||||||
struct args_command_state *cmd_else;
|
struct cmd_parse_tree *cmd_else;
|
||||||
|
|
||||||
|
|
||||||
struct client *client;
|
struct client *client;
|
||||||
struct cmdq_item *item;
|
struct cmdq_item *item;
|
||||||
@@ -76,46 +77,58 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
struct cmd_if_shell_data *cdata;
|
struct cmd_if_shell_data *cdata;
|
||||||
struct cmdq_item *new_item;
|
struct cmdq_item *new_item;
|
||||||
char *shellcmd;
|
struct cmd_parse_tree *tree;
|
||||||
|
char *shellcmd, *cause = NULL;
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct session *s = target->s;
|
struct session *s = target->s;
|
||||||
struct cmd_list *cmdlist;
|
u_int count = args_count(args), idx;
|
||||||
u_int count = args_count(args);
|
|
||||||
int wait = !args_has(args, 'b');
|
int wait = !args_has(args, 'b');
|
||||||
|
|
||||||
shellcmd = format_single_from_target(item, args_string(args, 0));
|
shellcmd = format_single_from_target(item, args_string(args, 0));
|
||||||
if (args_has(args, 'F')) {
|
if (args_has(args, 'F')) {
|
||||||
if (*shellcmd != '0' && *shellcmd != '\0')
|
if (*shellcmd != '0' && *shellcmd != '\0')
|
||||||
cmdlist = args_make_commands_now(self, item, 1, 0);
|
idx = 1;
|
||||||
else if (count == 3)
|
else if (count == 3)
|
||||||
cmdlist = args_make_commands_now(self, item, 2, 0);
|
idx = 2;
|
||||||
else {
|
else {
|
||||||
free(shellcmd);
|
free(shellcmd);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
free(shellcmd);
|
free(shellcmd);
|
||||||
if (cmdlist == NULL)
|
|
||||||
|
tree = args_to_commands(self, item, idx, NULL, 0, &cause);
|
||||||
|
if (tree == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
}
|
||||||
|
new_item = cmd_invoke_get(tree, cmdq_get_state(item), NULL);
|
||||||
|
cmd_parse_free(tree);
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
cmd_list_free(cmdlist);
|
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
cdata = xcalloc(1, sizeof *cdata);
|
cdata = xcalloc(1, sizeof *cdata);
|
||||||
|
cdata->cmd_if = args_to_commands(self, item, 1, NULL, 0, &cause);
|
||||||
cdata->cmd_if = args_make_commands_prepare(self, item, 1, NULL, wait,
|
if (cdata->cmd_if == NULL) {
|
||||||
0);
|
cmdq_error(item, "%s", cause);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
if (count == 3) {
|
if (count == 3) {
|
||||||
cdata->cmd_else = args_make_commands_prepare(self, item, 2,
|
cdata->cmd_else = args_to_commands(self, item, 2, NULL, 0,
|
||||||
NULL, wait, 0);
|
&cause);
|
||||||
|
if (cdata->cmd_else == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wait) {
|
if (!wait)
|
||||||
|
cdata->client = tc;
|
||||||
|
else {
|
||||||
cdata->client = cmdq_get_client(item);
|
cdata->client = cmdq_get_client(item);
|
||||||
cdata->item = item;
|
cdata->item = item;
|
||||||
} else
|
}
|
||||||
cdata->client = tc;
|
|
||||||
if (cdata->client != NULL)
|
if (cdata->client != NULL)
|
||||||
cdata->client->references++;
|
cdata->client->references++;
|
||||||
|
|
||||||
@@ -124,15 +137,19 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1,
|
cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1,
|
||||||
-1) == NULL) {
|
-1) == NULL) {
|
||||||
cmdq_error(item, "failed to run command: %s", shellcmd);
|
cmdq_error(item, "failed to run command: %s", shellcmd);
|
||||||
free(shellcmd);
|
goto fail;
|
||||||
cmd_if_shell_free(cdata);
|
|
||||||
return (CMD_RETURN_ERROR);
|
|
||||||
}
|
}
|
||||||
free(shellcmd);
|
free(shellcmd);
|
||||||
|
|
||||||
if (!wait)
|
if (!wait)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
return (CMD_RETURN_WAIT);
|
return (CMD_RETURN_WAIT);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
free(cause);
|
||||||
|
free(shellcmd);
|
||||||
|
cmd_if_shell_free(cdata);
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -141,35 +158,23 @@ cmd_if_shell_callback(struct job *job)
|
|||||||
struct cmd_if_shell_data *cdata = job_get_data(job);
|
struct cmd_if_shell_data *cdata = job_get_data(job);
|
||||||
struct client *c = cdata->client;
|
struct client *c = cdata->client;
|
||||||
struct cmdq_item *item = cdata->item, *new_item;
|
struct cmdq_item *item = cdata->item, *new_item;
|
||||||
struct args_command_state *state;
|
struct cmd_parse_tree *tree;
|
||||||
struct cmd_list *cmdlist;
|
|
||||||
char *error;
|
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
status = job_get_status(job);
|
status = job_get_status(job);
|
||||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
||||||
state = cdata->cmd_else;
|
tree = cdata->cmd_else;
|
||||||
else
|
else
|
||||||
state = cdata->cmd_if;
|
tree = cdata->cmd_if;
|
||||||
if (state == NULL)
|
if (tree == NULL)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
cmdlist = args_make_commands(state, 0, NULL, &error);
|
if (item == NULL) {
|
||||||
if (cmdlist == NULL) {
|
new_item = cmd_invoke_get(tree, NULL, NULL);
|
||||||
if (cdata->item == NULL) {
|
|
||||||
*error = toupper((u_char)*error);
|
|
||||||
status_message_set(c, -1, 1, 0, 0, "%s", error);
|
|
||||||
} else
|
|
||||||
cmdq_error(cdata->item, "%s", error);
|
|
||||||
free(error);
|
|
||||||
} else if (item == NULL) {
|
|
||||||
new_item = cmdq_get_command(cmdlist, NULL);
|
|
||||||
cmdq_append(c, new_item);
|
cmdq_append(c, new_item);
|
||||||
cmd_list_free(cmdlist);
|
|
||||||
} else {
|
} else {
|
||||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
new_item = cmd_invoke_get(tree, cmdq_get_state(item), NULL);
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
cmd_list_free(cmdlist);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@@ -186,8 +191,8 @@ cmd_if_shell_free(void *data)
|
|||||||
server_client_unref(cdata->client);
|
server_client_unref(cdata->client);
|
||||||
|
|
||||||
if (cdata->cmd_else != NULL)
|
if (cdata->cmd_else != NULL)
|
||||||
args_make_commands_free(cdata->cmd_else);
|
cmd_parse_free(cdata->cmd_else);
|
||||||
args_make_commands_free(cdata->cmd_if);
|
if (cdata->cmd_if != NULL)
|
||||||
|
cmd_parse_free(cdata->cmd_if);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
}
|
}
|
||||||
|
|||||||
+594
@@ -0,0 +1,594 @@
|
|||||||
|
/* $OpenBSD$ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Nicholas Marriott <nicm@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* 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 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 <pwd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "tmux.h"
|
||||||
|
#include "tmux-parser.h"
|
||||||
|
|
||||||
|
/* One frame in the parse tree stack. */
|
||||||
|
struct cmd_invoke_frame {
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmd_parse_node *node;
|
||||||
|
struct cmd_parse_node *next;
|
||||||
|
struct cmd_parse_node *end;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Shared state for a parsed command tree invocation. */
|
||||||
|
struct cmd_invoke_state {
|
||||||
|
u_int references;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmd_parse_tree *current_tree;
|
||||||
|
|
||||||
|
struct cmd_invoke_frame *stack;
|
||||||
|
u_int nstack;
|
||||||
|
|
||||||
|
enum cmd_retval last;
|
||||||
|
int have_last;
|
||||||
|
|
||||||
|
int argc;
|
||||||
|
char **argv;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void cmd_invoke_push(struct cmd_invoke_state *,
|
||||||
|
struct cmd_parse_tree *, struct cmd_parse_node *,
|
||||||
|
struct cmd_parse_node *, struct cmd_parse_node *);
|
||||||
|
static struct cmd_parse_tree *cmd_invoke_tree(struct cmd_invoke_state *);
|
||||||
|
static struct cmd_parse_node *cmd_invoke_next(struct cmd_invoke_state *);
|
||||||
|
static void cmd_invoke_skip_sequence(struct cmd_invoke_state *);
|
||||||
|
static int cmd_invoke_expand_string(struct cmdq_item *,
|
||||||
|
struct cmd_invoke_state *, struct cmd_parse_node *,
|
||||||
|
char **);
|
||||||
|
static int cmd_invoke_assignment(struct cmdq_item *,
|
||||||
|
struct cmd_invoke_state *, struct cmd_parse_node *);
|
||||||
|
static int cmd_invoke_if(struct cmdq_item *, struct cmd_invoke_state *,
|
||||||
|
struct cmd_parse_node *);
|
||||||
|
static struct cmd *cmd_invoke_build_command(struct cmdq_item *,
|
||||||
|
struct cmd_invoke_state *, struct cmd_parse_node *);
|
||||||
|
static int cmd_invoke_read_only(struct cmdq_item *, struct cmd *);
|
||||||
|
|
||||||
|
/* Push a node's child range onto the traversal stack. */
|
||||||
|
static void
|
||||||
|
cmd_invoke_push(struct cmd_invoke_state *is, struct cmd_parse_tree *tree,
|
||||||
|
struct cmd_parse_node *node, struct cmd_parse_node *first,
|
||||||
|
struct cmd_parse_node *end)
|
||||||
|
{
|
||||||
|
u_int n = is->nstack + 1;
|
||||||
|
|
||||||
|
is->stack = xreallocarray(is->stack, n, sizeof *is->stack);
|
||||||
|
is->stack[is->nstack].tree = cmd_parse_add_ref(tree);
|
||||||
|
is->stack[is->nstack].node = node;
|
||||||
|
is->stack[is->nstack].next = first;
|
||||||
|
is->stack[is->nstack].end = end;
|
||||||
|
is->nstack = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct cmd_parse_tree *
|
||||||
|
cmd_invoke_tree(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
if (is->current_tree != NULL)
|
||||||
|
return (is->current_tree);
|
||||||
|
return (is->tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the next node to execute from the traversal stack. */
|
||||||
|
static struct cmd_parse_node *
|
||||||
|
cmd_invoke_next(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
struct cmd_invoke_frame *frame;
|
||||||
|
struct cmd_parse_node *node;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (is->nstack == 0)
|
||||||
|
return (NULL);
|
||||||
|
frame = &is->stack[is->nstack - 1];
|
||||||
|
if (frame->next != NULL && frame->next != frame->end)
|
||||||
|
break;
|
||||||
|
cmd_parse_free(frame->tree);
|
||||||
|
is->nstack--;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = frame->next;
|
||||||
|
frame->next = cmd_parse_node_next(node);
|
||||||
|
is->current_tree = frame->tree;
|
||||||
|
return (node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip the rest of the active sequence after a command failure. Failure scope
|
||||||
|
* is the active CMD_PARSE_SEQUENCE. Discard nested frames and advance only as
|
||||||
|
* far as the end of that sequence.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
cmd_invoke_skip_sequence(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
enum cmd_parse_node_type type;
|
||||||
|
u_int i;
|
||||||
|
|
||||||
|
for (i = is->nstack; i > 0; i--) {
|
||||||
|
type = cmd_parse_node_type(is->stack[i - 1].node);
|
||||||
|
if (type == CMD_PARSE_SEQUENCE) {
|
||||||
|
is->stack[i - 1].next = is->stack[i - 1].end;
|
||||||
|
while (is->nstack > i)
|
||||||
|
cmd_parse_free(is->stack[--is->nstack].tree);
|
||||||
|
is->nstack = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is->have_last = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append a string to a dynamically allocated buffer. */
|
||||||
|
static void
|
||||||
|
cmd_invoke_append(char **buf, size_t *len, const char *s)
|
||||||
|
{
|
||||||
|
size_t slen;
|
||||||
|
|
||||||
|
if (s != NULL) {
|
||||||
|
slen = strlen(s);
|
||||||
|
*buf = xrealloc(*buf, *len + slen + 1);
|
||||||
|
memcpy(*buf + *len, s, slen + 1);
|
||||||
|
*len += slen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look up an environment variable. */
|
||||||
|
static const char *
|
||||||
|
cmd_invoke_getenv(struct cmdq_item *item, const char *name)
|
||||||
|
{
|
||||||
|
struct client *c = cmdq_get_client(item);
|
||||||
|
struct environ_entry *envent;
|
||||||
|
|
||||||
|
if (c != NULL && c->environ != NULL) {
|
||||||
|
envent = environ_find(c->environ, name);
|
||||||
|
if (envent != NULL && envent->value != NULL)
|
||||||
|
return (envent->value);
|
||||||
|
}
|
||||||
|
envent = environ_find(global_environ, name);
|
||||||
|
if (envent != NULL && envent->value != NULL)
|
||||||
|
return (envent->value);
|
||||||
|
return ("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolve a tilde expansion to a home directory. */
|
||||||
|
static const char *
|
||||||
|
cmd_invoke_tilde(const char *name)
|
||||||
|
{
|
||||||
|
struct passwd *pw;
|
||||||
|
|
||||||
|
if (name == NULL || *name == '\0')
|
||||||
|
return (find_home());
|
||||||
|
if ((pw = getpwnam(name)) == NULL)
|
||||||
|
return ("");
|
||||||
|
return (pw->pw_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expand a parsed string node into an argv string. */
|
||||||
|
static int
|
||||||
|
cmd_invoke_expand_string(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node, char **out)
|
||||||
|
{
|
||||||
|
struct cmd_parse_node *child;
|
||||||
|
const char *s, *value;
|
||||||
|
char *buf = NULL, *new;
|
||||||
|
size_t len = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
child = cmd_parse_node_first_child(node);
|
||||||
|
while (child != NULL) {
|
||||||
|
value = cmd_parse_node_value(child);
|
||||||
|
switch (cmd_parse_node_type(child)) {
|
||||||
|
case CMD_PARSE_TEXT:
|
||||||
|
s = value;
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_ENVIRONMENT:
|
||||||
|
s = cmd_invoke_getenv(item, value);
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_TILDE:
|
||||||
|
s = cmd_invoke_tilde(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalx("unexpected node type in string");
|
||||||
|
}
|
||||||
|
cmd_invoke_append(&buf, &len, s);
|
||||||
|
child = cmd_parse_node_next(child);
|
||||||
|
}
|
||||||
|
if (buf == NULL)
|
||||||
|
buf = xstrdup("");
|
||||||
|
for (i = 0; i < is->argc; i++) {
|
||||||
|
new = cmd_template_replace(buf, is->argv[i], i + 1);
|
||||||
|
free(buf);
|
||||||
|
buf = new;
|
||||||
|
}
|
||||||
|
*out = buf;
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Execute an assignment node by updating the environment. */
|
||||||
|
static int
|
||||||
|
cmd_invoke_assignment(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node)
|
||||||
|
{
|
||||||
|
struct cmd_parse_node *value;
|
||||||
|
const char *name;
|
||||||
|
char *expanded;
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
|
value = cmd_parse_node_first_child(node);
|
||||||
|
if (value == NULL)
|
||||||
|
return (-1);
|
||||||
|
if (cmd_invoke_expand_string(item, is, value, &expanded) != 0)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
name = cmd_parse_node_value(node);
|
||||||
|
if (cmd_parse_node_type(node) == CMD_PARSE_HIDDEN_ASSIGN)
|
||||||
|
flags |= ENVIRON_HIDDEN;
|
||||||
|
environ_set(global_environ, name, flags, "%s", expanded);
|
||||||
|
free(expanded);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expand and evaluate a conditional expression. */
|
||||||
|
static int
|
||||||
|
cmd_invoke_is_true(struct cmdq_item *item,
|
||||||
|
struct cmd_invoke_state *is, struct cmd_parse_node *node, int *result)
|
||||||
|
{
|
||||||
|
struct format_tree *ft;
|
||||||
|
char *s, *expanded;
|
||||||
|
|
||||||
|
if (cmd_invoke_expand_string(item, is, node, &s) != 0)
|
||||||
|
return (-1);
|
||||||
|
ft = format_create_from_target(item);
|
||||||
|
|
||||||
|
expanded = format_expand(ft, s);
|
||||||
|
*result = format_true(expanded);
|
||||||
|
free(expanded);
|
||||||
|
|
||||||
|
format_free(ft);
|
||||||
|
free(s);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip else branches. */
|
||||||
|
static struct cmd_parse_node *
|
||||||
|
cmd_invoke_if_branch_end(struct cmd_parse_node *first)
|
||||||
|
{
|
||||||
|
struct cmd_parse_node *node;
|
||||||
|
|
||||||
|
for (node = first; node != NULL; node = cmd_parse_node_next(node)) {
|
||||||
|
switch (cmd_parse_node_type(node)) {
|
||||||
|
case CMD_PARSE_ELIF:
|
||||||
|
case CMD_PARSE_ELSE:
|
||||||
|
return (node);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select and queue the branch for a conditional node. */
|
||||||
|
static int
|
||||||
|
cmd_invoke_if(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node)
|
||||||
|
{
|
||||||
|
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
|
||||||
|
struct cmd_parse_node *child, *first, *next;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
next = cmd_parse_node_first_child(node);
|
||||||
|
if (next == NULL)
|
||||||
|
return (0);
|
||||||
|
first = cmd_parse_node_next(next);
|
||||||
|
if (cmd_invoke_is_true(item, is, next, &r) != 0)
|
||||||
|
return (-1);
|
||||||
|
if (r) {
|
||||||
|
next = cmd_invoke_if_branch_end(first);
|
||||||
|
cmd_invoke_push(is, tree, node, first, next);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (child = first; child != NULL; child = cmd_parse_node_next(child)) {
|
||||||
|
switch (cmd_parse_node_type(child)) {
|
||||||
|
case CMD_PARSE_ELIF:
|
||||||
|
next = cmd_parse_node_first_child(child);
|
||||||
|
if (next == NULL)
|
||||||
|
break;
|
||||||
|
if (cmd_invoke_is_true(item, is, next, &r) != 0)
|
||||||
|
return (-1);
|
||||||
|
if (r) {
|
||||||
|
next = cmd_parse_node_next(next);
|
||||||
|
cmd_invoke_push(is, tree, child, next, NULL);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_ELSE:
|
||||||
|
next = cmd_parse_node_first_child(child);
|
||||||
|
cmd_invoke_push(is, tree, child, next, NULL);
|
||||||
|
return (0);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Report an error from an invoke item. */
|
||||||
|
static void
|
||||||
|
cmd_invoke_error(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node, const char *cause)
|
||||||
|
{
|
||||||
|
struct client *c = cmdq_get_client(item);
|
||||||
|
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
|
||||||
|
const char *file = cmd_parse_file(tree);
|
||||||
|
u_int line = cmd_parse_node_line(node);
|
||||||
|
|
||||||
|
if (c != NULL && (~c->flags & CLIENT_CONTROL || file == NULL)) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file != NULL)
|
||||||
|
cfg_add_cause("%s:%u: %s", file, line, cause);
|
||||||
|
else
|
||||||
|
cfg_add_cause("%s", cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build one command from a parsed command node. */
|
||||||
|
static struct cmd *
|
||||||
|
cmd_invoke_build_command(struct cmdq_item *item, struct cmd_invoke_state *is,
|
||||||
|
struct cmd_parse_node *node)
|
||||||
|
{
|
||||||
|
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
|
||||||
|
struct cmd_parse_node *child;
|
||||||
|
struct args_value *values = NULL;
|
||||||
|
struct cmd *cmd;
|
||||||
|
const char *file = cmd_parse_file(tree);
|
||||||
|
u_int line = cmd_parse_node_line(node);
|
||||||
|
int flags = cmd_parse_flags(tree);
|
||||||
|
char *cause = NULL;
|
||||||
|
u_int count = 0;
|
||||||
|
|
||||||
|
child = cmd_parse_node_first_child(node);
|
||||||
|
while (child != NULL) {
|
||||||
|
values = xreallocarray(values, count + 1, sizeof *values);
|
||||||
|
memset(&values[count], 0, sizeof values[count]);
|
||||||
|
switch (cmd_parse_node_type(child)) {
|
||||||
|
case CMD_PARSE_STRING:
|
||||||
|
values[count].type = ARGS_STRING;
|
||||||
|
if (cmd_invoke_expand_string(item, is, child,
|
||||||
|
&values[count].string) != 0)
|
||||||
|
goto fail;
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_COMMANDS:
|
||||||
|
values[count].type = ARGS_COMMANDS;
|
||||||
|
values[count].cmd = cmd_parse_from_node(tree, child);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalx("unexpected node type in command");
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
child = cmd_parse_node_next(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = cmd_parse(values, count, file, line, flags, &cause);
|
||||||
|
if (cmd == NULL) {
|
||||||
|
cmd_invoke_error(item, is, node, cause);
|
||||||
|
free(cause);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
args_free_values(values, count);
|
||||||
|
free(values);
|
||||||
|
return (cmd);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
args_free_values(values, count);
|
||||||
|
free(values);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
cmd_invoke_read_only(struct cmdq_item *item, struct cmd *cmd)
|
||||||
|
{
|
||||||
|
struct client *c = cmdq_get_client(item);
|
||||||
|
const struct cmd_entry *entry = cmd_get_entry(cmd);
|
||||||
|
|
||||||
|
if (c == NULL || (~c->flags & CLIENT_READONLY))
|
||||||
|
return (0);
|
||||||
|
if (entry->flags & CMD_READONLY)
|
||||||
|
return (0);
|
||||||
|
cmdq_error(item, "client is read-only");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the first invoke queue item for a parsed tree. */
|
||||||
|
struct cmdq_item *
|
||||||
|
cmd_invoke_get(struct cmd_parse_tree *tree, struct cmdq_state *state,
|
||||||
|
const struct cmd_invoke_input *input)
|
||||||
|
{
|
||||||
|
struct cmd_invoke_state *is;
|
||||||
|
struct cmd_parse_node *root = cmd_parse_root(tree), *first;
|
||||||
|
struct cmdq_item *item;
|
||||||
|
struct cmd_invoke_input new_input = { 0 };
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (input == NULL)
|
||||||
|
input = &new_input;
|
||||||
|
|
||||||
|
is = xcalloc(1, sizeof *is);
|
||||||
|
is->references = 1;
|
||||||
|
is->tree = cmd_parse_add_ref(tree);
|
||||||
|
|
||||||
|
is->argc = input->argc;
|
||||||
|
if (input->argc != 0) {
|
||||||
|
is->argv = xreallocarray(NULL, input->argc, sizeof *is->argv);
|
||||||
|
for (i = 0; i < input->argc; i++)
|
||||||
|
is->argv[i] = xstrdup(input->argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
first = cmd_parse_node_first_child(root);
|
||||||
|
cmd_invoke_push(is, tree, root, first, NULL);
|
||||||
|
|
||||||
|
item = cmdq_get_invoke(is, state);
|
||||||
|
cmd_invoke_state_free(is);
|
||||||
|
return (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a reference to shared invoke state. */
|
||||||
|
struct cmd_invoke_state *
|
||||||
|
cmd_invoke_state_add_ref(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
is->references++;
|
||||||
|
return (is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release a reference to shared invoke state. */
|
||||||
|
void
|
||||||
|
cmd_invoke_state_free(struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
u_int j;
|
||||||
|
|
||||||
|
if (is == NULL)
|
||||||
|
return;
|
||||||
|
if (--is->references != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < is->argc; i++)
|
||||||
|
free(is->argv[i]);
|
||||||
|
free(is->argv);
|
||||||
|
for (j = 0; j < is->nstack; j++)
|
||||||
|
cmd_parse_free(is->stack[j].tree);
|
||||||
|
|
||||||
|
cmd_parse_free(is->tree);
|
||||||
|
free(is->stack);
|
||||||
|
free(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Record the result from the last command item. */
|
||||||
|
void
|
||||||
|
cmd_invoke_result(struct cmd_invoke_state *is, enum cmd_retval retval)
|
||||||
|
{
|
||||||
|
is->last = retval;
|
||||||
|
is->have_last = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fire an invoke item and queue the next command plus continuation. */
|
||||||
|
enum cmd_retval
|
||||||
|
cmd_invoke_fire(struct cmdq_item *item, struct cmd_invoke_state *is)
|
||||||
|
{
|
||||||
|
struct cmd_parse_node *node, *first;
|
||||||
|
struct cmd_parse_tree *tree, *alias;
|
||||||
|
struct cmdq_item *new_item, *next;
|
||||||
|
struct cmdq_state *state;
|
||||||
|
struct client *c;
|
||||||
|
struct cmd *cmd;
|
||||||
|
const char *file;
|
||||||
|
char *cause;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (is->have_last && is->last == CMD_RETURN_ERROR)
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
else
|
||||||
|
is->have_last = 0;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
node = cmd_invoke_next(is);
|
||||||
|
if (node == NULL)
|
||||||
|
return (CMD_RETURN_NORMAL);
|
||||||
|
cmd_parse_log_node(__func__, node);
|
||||||
|
|
||||||
|
tree = cmd_invoke_tree(is);
|
||||||
|
switch (cmd_parse_node_type(node)) {
|
||||||
|
case CMD_PARSE_ROOT:
|
||||||
|
case CMD_PARSE_SEQUENCE:
|
||||||
|
first = cmd_parse_node_first_child(node);
|
||||||
|
cmd_invoke_push(is, tree, node, first, NULL);
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_ASSIGN:
|
||||||
|
case CMD_PARSE_HIDDEN_ASSIGN:
|
||||||
|
if (cmd_invoke_assignment(item, is, node) == 0)
|
||||||
|
break;
|
||||||
|
cmd_invoke_error(item, is, node, "bad assignment");
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_IF:
|
||||||
|
if (cmd_invoke_if(item, is, node) == 0)
|
||||||
|
break;
|
||||||
|
cmd_invoke_error(item, is, node, "bad condition");
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_ELIF:
|
||||||
|
case CMD_PARSE_ELSE:
|
||||||
|
break;
|
||||||
|
case CMD_PARSE_COMMAND:
|
||||||
|
r = cmd_parse_expand_alias(tree, node, &alias, &cause);
|
||||||
|
if (r == 1) {
|
||||||
|
node = cmd_parse_root(alias);
|
||||||
|
first = cmd_parse_node_first_child(node);
|
||||||
|
cmd_invoke_push(is, alias, node, first, NULL);
|
||||||
|
cmd_parse_free(alias);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (r == -1) {
|
||||||
|
c = cmdq_get_client(item);
|
||||||
|
file = cmd_parse_file(tree);
|
||||||
|
if (c != NULL &&
|
||||||
|
(~c->flags & CLIENT_CONTROL || file == NULL))
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
else
|
||||||
|
cfg_add_cause("%s", cause);
|
||||||
|
free(cause);
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = cmd_invoke_build_command(item, is, node);
|
||||||
|
if (cmd == NULL) {
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cmd_invoke_read_only(item, cmd) != 0) {
|
||||||
|
cmd_free(cmd);
|
||||||
|
cmd_invoke_skip_sequence(is);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Queue one command followed by this walker. WAIT and
|
||||||
|
* command-inserted items therefore run before resume.
|
||||||
|
*/
|
||||||
|
state = cmdq_get_state(item);
|
||||||
|
new_item = cmdq_get_one_command(cmd, state, is);
|
||||||
|
next = cmdq_get_invoke(is, state);
|
||||||
|
cmdq_insert_after(item, next);
|
||||||
|
cmdq_insert_after(item, new_item);
|
||||||
|
return (CMD_RETURN_NORMAL);
|
||||||
|
case CMD_PARSE_STRING:
|
||||||
|
case CMD_PARSE_COMMANDS:
|
||||||
|
case CMD_PARSE_TEXT:
|
||||||
|
case CMD_PARSE_ENVIRONMENT:
|
||||||
|
case CMD_PARSE_TILDE:
|
||||||
|
fatalx("unexpected node type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-22
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-join-pane.c,v 1.75 2026/09/21 10:33:16 nicm Exp $ */
|
/* $OpenBSD: cmd-join-pane.c,v 1.74 2026/08/03 20:29:52 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 George Nachman <tmux@georgester.com>
|
* Copyright (c) 2011 George Nachman <tmux@georgester.com>
|
||||||
@@ -133,7 +133,7 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
|||||||
} else if (strcmp(position, "back") == 0) {
|
} else if (strcmp(position, "back") == 0) {
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||||
if (!window_pane_is_floating_with_hidden(owp))
|
if (!window_pane_is_floating(owp))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (owp != NULL)
|
if (owp != NULL)
|
||||||
@@ -142,30 +142,24 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
|||||||
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
||||||
} else if (strcmp(position, "forward") == 0) {
|
} else if (strcmp(position, "forward") == 0) {
|
||||||
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
||||||
while (owp != NULL && owp->layout_cell == NULL)
|
|
||||||
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
|
|
||||||
if (owp != NULL) {
|
if (owp != NULL) {
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||||
}
|
}
|
||||||
} else if (strcmp(position, "backward") == 0) {
|
} else if (strcmp(position, "backward") == 0) {
|
||||||
owp = TAILQ_NEXT(wp, zentry);
|
owp = TAILQ_NEXT(wp, zentry);
|
||||||
while (owp != NULL && owp->layout_cell == NULL)
|
|
||||||
owp = TAILQ_NEXT(owp, zentry);
|
|
||||||
if (owp != NULL && window_pane_is_floating(owp)) {
|
if (owp != NULL && window_pane_is_floating(owp)) {
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
||||||
}
|
}
|
||||||
} else if (strcmp(position, "forward-loop") == 0) {
|
} else if (strcmp(position, "forward-loop") == 0) {
|
||||||
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
||||||
while (owp != NULL && owp->layout_cell == NULL)
|
|
||||||
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
|
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
if (owp != NULL)
|
if (owp != NULL)
|
||||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||||
else {
|
else {
|
||||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||||
if (!window_pane_is_floating_with_hidden(owp))
|
if (!window_pane_is_floating(owp))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (owp != NULL)
|
if (owp != NULL)
|
||||||
@@ -175,8 +169,6 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
|||||||
}
|
}
|
||||||
} else if (strcmp(position, "backward-loop") == 0) {
|
} else if (strcmp(position, "backward-loop") == 0) {
|
||||||
owp = TAILQ_NEXT(wp, zentry);
|
owp = TAILQ_NEXT(wp, zentry);
|
||||||
while (owp != NULL && owp->layout_cell == NULL)
|
|
||||||
owp = TAILQ_NEXT(owp, zentry);
|
|
||||||
if (owp != NULL && window_pane_is_floating(owp)) {
|
if (owp != NULL && window_pane_is_floating(owp)) {
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
||||||
@@ -307,7 +299,7 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
|||||||
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, oxoff, oyoff, osx, osy;
|
int y, ly, x, lx;
|
||||||
|
|
||||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
@@ -329,16 +321,10 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
|||||||
ly = m->statusat - 1;
|
ly = m->statusat - 1;
|
||||||
|
|
||||||
if (x != lx || y != ly) {
|
if (x != lx || y != ly) {
|
||||||
oxoff = wp->xoff;
|
|
||||||
oyoff = wp->yoff;
|
|
||||||
osx = wp->sx;
|
|
||||||
osy = wp->sy;
|
|
||||||
|
|
||||||
lc->g.xoff += x - lx;
|
lc->g.xoff += x - lx;
|
||||||
lc->g.yoff += y - ly;
|
lc->g.yoff += y - ly;
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
|
server_redraw_window(w);
|
||||||
window_redraw_floating_pane(wp, oxoff, oyoff, osx, osy);
|
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,10 +347,8 @@ cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl,
|
|||||||
|
|
||||||
n = 0;
|
n = 0;
|
||||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||||
if (!window_pane_is_floating_with_hidden(owp))
|
if (!window_pane_is_floating(owp))
|
||||||
break;
|
break;
|
||||||
if (owp->layout_cell == NULL)
|
|
||||||
continue;
|
|
||||||
if (n >= z)
|
if (n >= z)
|
||||||
break;
|
break;
|
||||||
n++;
|
n++;
|
||||||
@@ -459,6 +443,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cmdq_error(item, "pane is not floating");
|
cmdq_error(item, "pane is not floating");
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
|
server_unzoom_window(dst_w);
|
||||||
if ((s = args_get(args, 'P')) != NULL)
|
if ((s = args_get(args, 'P')) != NULL)
|
||||||
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
|
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
|
||||||
if ((s = args_get(args, 'z')) != NULL)
|
if ((s = args_get(args, 'z')) != NULL)
|
||||||
|
|||||||
+27
-13
@@ -37,6 +37,16 @@
|
|||||||
"-T #{p|#{key_table_width}:key_table} " \
|
"-T #{p|#{key_table_width}:key_table} " \
|
||||||
"#{p|#{key_string_width}:#{q|a:key_string}} " \
|
"#{p|#{key_string_width}:#{q|a:key_string}} " \
|
||||||
"#{key_command}}"
|
"#{key_command}}"
|
||||||
|
#define LIST_KEYS_TEMPLATE_PRETTY \
|
||||||
|
"#{?notes_only," \
|
||||||
|
"#{key_prefix} " \
|
||||||
|
"#{key_string} " \
|
||||||
|
"#{?key_note,#{key_note},#{key_command}}" \
|
||||||
|
"," \
|
||||||
|
"bind-key #{?key_repeat,-r ,}" \
|
||||||
|
"-T #{key_table} " \
|
||||||
|
"#{q|a:key_string} " \
|
||||||
|
"#{key_command}}"
|
||||||
|
|
||||||
static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *);
|
static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *);
|
||||||
|
|
||||||
@@ -44,8 +54,8 @@ const struct cmd_entry cmd_list_keys_entry = {
|
|||||||
.name = "list-keys",
|
.name = "list-keys",
|
||||||
.alias = "lsk",
|
.alias = "lsk",
|
||||||
|
|
||||||
.args = { "1aF:NO:P:rT:", 0, 1, NULL },
|
.args = { "1aF:NO:P:prT:", 0, 1, NULL },
|
||||||
.usage = "[-1aNr] [-F format] [-O order] [-P prefix-string]"
|
.usage = "[-1aNpr] [-F format] [-O order] [-P prefix-string]"
|
||||||
"[-T key-table] [key]",
|
"[-T key-table] [key]",
|
||||||
|
|
||||||
.flags = CMD_STARTSERVER|CMD_AFTERHOOK,
|
.flags = CMD_STARTSERVER|CMD_AFTERHOOK,
|
||||||
@@ -119,7 +129,7 @@ cmd_list_keys_get_root_and_prefix(u_int *n, struct sort_criteria *sort_crit)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cmd_list_keys_filter_key_list(int filter_notes, int filter_key, key_code only,
|
cmd_list_keys_do_filter(int filter_notes, int filter_key, key_code only,
|
||||||
struct key_binding **l, u_int *n)
|
struct key_binding **l, u_int *n)
|
||||||
{
|
{
|
||||||
key_code key;
|
key_code key;
|
||||||
@@ -137,8 +147,8 @@ cmd_list_keys_filter_key_list(int filter_notes, int filter_key, key_code only,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cmd_list_keys_format_add_key_binding(struct format_tree *ft,
|
cmd_list_keys_format_key_binding(struct format_tree *ft,
|
||||||
const struct key_binding *bd, const char *prefix)
|
const struct key_binding *bd, const char *prefix, int flags)
|
||||||
{
|
{
|
||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
@@ -157,8 +167,7 @@ cmd_list_keys_format_add_key_binding(struct format_tree *ft,
|
|||||||
|
|
||||||
format_add(ft, "key_string", "%s", key_string_lookup_key(bd->key, 0));
|
format_add(ft, "key_string", "%s", key_string_lookup_key(bd->key, 0));
|
||||||
|
|
||||||
s = cmd_list_print(bd->cmdlist, CMD_LIST_PRINT_ESCAPED|
|
s = cmd_parse_print(bd->cmd, flags);
|
||||||
CMD_LIST_PRINT_NO_GROUPS);
|
|
||||||
format_add(ft, "key_command", "%s", s);
|
format_add(ft, "key_command", "%s", s);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
@@ -177,6 +186,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
char *prefix = NULL;
|
char *prefix = NULL;
|
||||||
u_int i, n;
|
u_int i, n;
|
||||||
int single, notes_only, filter_notes, filter_key;
|
int single, notes_only, filter_notes, filter_key;
|
||||||
|
int print_flags = 0;
|
||||||
struct sort_criteria sort_crit;
|
struct sort_criteria sort_crit;
|
||||||
|
|
||||||
if ((keystr = args_string(args, 0)) != NULL) {
|
if ((keystr = args_string(args, 0)) != NULL) {
|
||||||
@@ -206,9 +216,15 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
prefix = cmd_list_keys_get_prefix(args);
|
prefix = cmd_list_keys_get_prefix(args);
|
||||||
single = args_has(args, '1');
|
single = args_has(args, '1');
|
||||||
notes_only = args_has(args, 'N');
|
notes_only = args_has(args, 'N');
|
||||||
|
if (args_has(args, 'p'))
|
||||||
|
print_flags |= CMD_PARSE_PRINT_MULTILINE;
|
||||||
|
|
||||||
if ((template = args_get(args, 'F')) == NULL)
|
if ((template = args_get(args, 'F')) == NULL) {
|
||||||
|
if (print_flags & CMD_PARSE_PRINT_MULTILINE)
|
||||||
|
template = LIST_KEYS_TEMPLATE_PRETTY;
|
||||||
|
else
|
||||||
template = LIST_KEYS_TEMPLATE;
|
template = LIST_KEYS_TEMPLATE;
|
||||||
|
}
|
||||||
|
|
||||||
if (table)
|
if (table)
|
||||||
l = sort_get_key_bindings_table(table, &n, &sort_crit);
|
l = sort_get_key_bindings_table(table, &n, &sort_crit);
|
||||||
@@ -219,10 +235,8 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
|
|
||||||
filter_notes = notes_only && !args_has(args, 'a');
|
filter_notes = notes_only && !args_has(args, 'a');
|
||||||
filter_key = only != KEYC_UNKNOWN;
|
filter_key = only != KEYC_UNKNOWN;
|
||||||
if (filter_notes || filter_key) {
|
if (filter_notes || filter_key)
|
||||||
cmd_list_keys_filter_key_list(filter_notes, filter_key, only, l,
|
cmd_list_keys_do_filter(filter_notes, filter_key, only, l, &n);
|
||||||
&n);
|
|
||||||
}
|
|
||||||
if (filter_key && n == 0) {
|
if (filter_key && n == 0) {
|
||||||
cmdq_error(item, "unknown key: %s", keystr);
|
cmdq_error(item, "unknown key: %s", keystr);
|
||||||
free(prefix);
|
free(prefix);
|
||||||
@@ -239,7 +253,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
format_add(ft, "key_table_width", "%u",
|
format_add(ft, "key_table_width", "%u",
|
||||||
cmd_list_keys_get_table_width(l, n));
|
cmd_list_keys_get_table_width(l, n));
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
cmd_list_keys_format_add_key_binding(ft, l[i], prefix);
|
cmd_list_keys_format_key_binding(ft, l[i], prefix, print_flags);
|
||||||
|
|
||||||
line = format_expand(ft, template);
|
line = format_expand(ft, template);
|
||||||
if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL))
|
if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL))
|
||||||
|
|||||||
+2
-3
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-list-panes.c,v 1.41 2026/09/08 10:20:08 nicm Exp $ */
|
/* $OpenBSD: cmd-list-panes.c,v 1.40 2026/06/01 14:01:09 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -97,7 +97,6 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
|
|||||||
struct cmdq_item *item, int type)
|
struct cmdq_item *item, int type)
|
||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct client *c = cmdq_get_client(item);
|
|
||||||
struct window_pane *wp, **l;
|
struct window_pane *wp, **l;
|
||||||
u_int i, n;
|
u_int i, n;
|
||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
@@ -148,7 +147,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
|
|||||||
wp = l[i];
|
wp = l[i];
|
||||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||||
format_add(ft, "line", "%u", n);
|
format_add(ft, "line", "%u", n);
|
||||||
format_defaults(ft, c, s, wl, wp);
|
format_defaults(ft, NULL, s, wl, wp);
|
||||||
|
|
||||||
if (filter != NULL) {
|
if (filter != NULL) {
|
||||||
expanded = format_expand(ft, filter);
|
expanded = format_expand(ft, filter);
|
||||||
|
|||||||
+2
-3
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-list-sessions.c,v 1.37 2026/09/08 10:20:08 nicm Exp $ */
|
/* $OpenBSD: cmd-list-sessions.c,v 1.36 2026/02/27 08:25:12 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -53,7 +53,6 @@ static enum cmd_retval
|
|||||||
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct client *c = cmdq_get_client(item);
|
|
||||||
struct session **l;
|
struct session **l;
|
||||||
u_int n, i;
|
u_int n, i;
|
||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
@@ -77,7 +76,7 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||||
format_add(ft, "line", "%u", i);
|
format_add(ft, "line", "%u", i);
|
||||||
format_defaults(ft, c, l[i], NULL, NULL);
|
format_defaults(ft, NULL, l[i], NULL, NULL);
|
||||||
|
|
||||||
if (filter != NULL) {
|
if (filter != NULL) {
|
||||||
expanded = format_expand(ft, filter);
|
expanded = format_expand(ft, filter);
|
||||||
|
|||||||
+2
-3
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-list-windows.c,v 1.51 2026/09/08 10:20:08 nicm Exp $ */
|
/* $OpenBSD: cmd-list-windows.c,v 1.50 2026/02/27 08:25:12 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -60,7 +60,6 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
struct client *c = cmdq_get_client(item);
|
|
||||||
struct winlink *wl, **l;
|
struct winlink *wl, **l;
|
||||||
struct session *s;
|
struct session *s;
|
||||||
u_int i, n;
|
u_int i, n;
|
||||||
@@ -95,7 +94,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
s = wl->session;
|
s = wl->session;
|
||||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||||
format_add(ft, "line", "%u", n);
|
format_add(ft, "line", "%u", n);
|
||||||
format_defaults(ft, c, s, wl, NULL);
|
format_defaults(ft, NULL, s, wl, NULL);
|
||||||
|
|
||||||
if (filter != NULL) {
|
if (filter != NULL) {
|
||||||
expanded = format_expand(ft, filter);
|
expanded = format_expand(ft, filter);
|
||||||
|
|||||||
+7
-17
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-new-window.c,v 1.104 2026/09/03 21:04:11 nicm Exp $ */
|
/* $OpenBSD: cmd-new-window.c,v 1.103 2026/07/08 08:07:42 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -60,8 +60,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct session *s = target->s;
|
struct session *s = target->s;
|
||||||
struct winlink *wl = target->wl, *new_wl = NULL;
|
struct winlink *wl = target->wl, *new_wl = NULL;
|
||||||
int idx = target->idx, before;
|
int idx = target->idx, before, count = args_count(args);
|
||||||
int count = args_count(args);
|
|
||||||
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
||||||
const char *template, *name;
|
const char *template, *name;
|
||||||
struct cmd_find_state fs;
|
struct cmd_find_state fs;
|
||||||
@@ -75,10 +74,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If -S is given, select an existing window instead of creating a
|
* If -S and -n are given and -t is not and a single window with this
|
||||||
* new one: preferring -t if it already points at a window, or
|
* name already exists, select it.
|
||||||
* otherwise -n if one exists with that name. If neither matches,
|
|
||||||
* fall through and create a window as normal.
|
|
||||||
*/
|
*/
|
||||||
name = args_get(args, 'n');
|
name = args_get(args, 'n');
|
||||||
if (name != NULL) {
|
if (name != NULL) {
|
||||||
@@ -91,10 +88,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
wname = clean_name(expanded, 0);
|
wname = clean_name(expanded, 0);
|
||||||
free(expanded);
|
free(expanded);
|
||||||
}
|
}
|
||||||
if (args_has(args, 'S')) {
|
if (args_has(args, 'S') && wname != NULL && target->idx == -1) {
|
||||||
if (idx != -1)
|
|
||||||
new_wl = winlink_find_by_index(&s->windows, idx);
|
|
||||||
else if (wname != NULL) {
|
|
||||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||||
if (strcmp(wl->window->name, expanded) != 0)
|
if (strcmp(wl->window->name, expanded) != 0)
|
||||||
@@ -103,17 +97,12 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
new_wl = wl;
|
new_wl = wl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
cmdq_error(item, "multiple windows named %s",
|
cmdq_error(item, "multiple windows named %s", wname);
|
||||||
wname);
|
|
||||||
free(wname);
|
free(wname);
|
||||||
free(expanded);
|
free(expanded);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
free(expanded);
|
free(expanded);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Found an existing window to select instead of creating a new one. */
|
|
||||||
if (new_wl != NULL) {
|
if (new_wl != NULL) {
|
||||||
free(wname);
|
free(wname);
|
||||||
if (args_has(args, 'd'))
|
if (args_has(args, 'd'))
|
||||||
@@ -125,6 +114,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
recalculate_sizes();
|
recalculate_sizes();
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
before = args_has(args, 'b');
|
before = args_has(args, 'b');
|
||||||
if (args_has(args, 'a') || before) {
|
if (args_has(args, 'a') || before) {
|
||||||
|
|||||||
+1587
-1174
File diff suppressed because it is too large
Load Diff
+41
-73
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-queue.c,v 1.124 2026/09/22 06:46:50 nicm Exp $ */
|
/* $OpenBSD: cmd-queue.c,v 1.123 2026/08/24 20:34:26 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
enum cmdq_type {
|
enum cmdq_type {
|
||||||
CMDQ_COMMAND,
|
CMDQ_COMMAND,
|
||||||
CMDQ_CALLBACK,
|
CMDQ_CALLBACK,
|
||||||
|
CMDQ_INVOKE,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Command queue item. */
|
/* Command queue item. */
|
||||||
@@ -47,7 +48,6 @@ struct cmdq_item {
|
|||||||
struct client *target_client;
|
struct client *target_client;
|
||||||
|
|
||||||
enum cmdq_type type;
|
enum cmdq_type type;
|
||||||
u_int group;
|
|
||||||
|
|
||||||
u_int number;
|
u_int number;
|
||||||
time_t time;
|
time_t time;
|
||||||
@@ -58,8 +58,8 @@ struct cmdq_item {
|
|||||||
struct cmd_find_state source;
|
struct cmd_find_state source;
|
||||||
struct cmd_find_state target;
|
struct cmd_find_state target;
|
||||||
|
|
||||||
struct cmd_list *cmdlist;
|
|
||||||
struct cmd *cmd;
|
struct cmd *cmd;
|
||||||
|
struct cmd_invoke_state *invoke_state;
|
||||||
|
|
||||||
cmdq_cb cb;
|
cmdq_cb cb;
|
||||||
void *data;
|
void *data;
|
||||||
@@ -428,8 +428,10 @@ cmdq_remove(struct cmdq_item *item)
|
|||||||
{
|
{
|
||||||
if (item->client != NULL)
|
if (item->client != NULL)
|
||||||
server_client_unref(item->client);
|
server_client_unref(item->client);
|
||||||
if (item->cmdlist != NULL)
|
if (item->cmd != NULL)
|
||||||
cmd_list_free(item->cmdlist);
|
cmd_free(item->cmd);
|
||||||
|
if (item->invoke_state != NULL)
|
||||||
|
cmd_invoke_state_free(item->invoke_state);
|
||||||
cmdq_free_state(item->state);
|
cmdq_free_state(item->state);
|
||||||
|
|
||||||
TAILQ_REMOVE(&item->queue->list, item, entry);
|
TAILQ_REMOVE(&item->queue->list, item, entry);
|
||||||
@@ -438,75 +440,46 @@ cmdq_remove(struct cmdq_item *item)
|
|||||||
free(item);
|
free(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove all subsequent items that match this item's group. */
|
/* Get a single command for command queue with invoke state. */
|
||||||
static void
|
|
||||||
cmdq_remove_group(struct cmdq_item *item)
|
|
||||||
{
|
|
||||||
struct cmdq_item *this, *next;
|
|
||||||
|
|
||||||
if (item->group == 0)
|
|
||||||
return;
|
|
||||||
this = TAILQ_NEXT(item, entry);
|
|
||||||
while (this != NULL) {
|
|
||||||
next = TAILQ_NEXT(this, entry);
|
|
||||||
if (this->group == item->group)
|
|
||||||
cmdq_remove(this);
|
|
||||||
this = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Empty command callback. */
|
|
||||||
static enum cmd_retval
|
|
||||||
cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data)
|
|
||||||
{
|
|
||||||
return (CMD_RETURN_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get a command for the command queue. */
|
|
||||||
struct cmdq_item *
|
struct cmdq_item *
|
||||||
cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state)
|
cmdq_get_one_command(struct cmd *cmd, struct cmdq_state *state,
|
||||||
|
struct cmd_invoke_state *invoke_state)
|
||||||
{
|
{
|
||||||
struct cmdq_item *item, *first = NULL, *last = NULL;
|
struct cmdq_item *item;
|
||||||
struct cmd *cmd;
|
const struct cmd_entry *entry = cmd_get_entry(cmd);
|
||||||
const struct cmd_entry *entry;
|
|
||||||
int created = 0;
|
|
||||||
|
|
||||||
if ((cmd = cmd_list_first(cmdlist)) == NULL)
|
|
||||||
return (cmdq_get_callback(cmdq_empty_command, NULL));
|
|
||||||
|
|
||||||
if (state == NULL) {
|
|
||||||
state = cmdq_new_state(NULL, NULL, 0);
|
|
||||||
created = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (cmd != NULL) {
|
|
||||||
entry = cmd_get_entry(cmd);
|
|
||||||
|
|
||||||
item = xcalloc(1, sizeof *item);
|
item = xcalloc(1, sizeof *item);
|
||||||
xasprintf(&item->name, "[%s/%p]", entry->name, item);
|
xasprintf(&item->name, "[%s/%p]", entry->name, item);
|
||||||
item->type = CMDQ_COMMAND;
|
item->type = CMDQ_COMMAND;
|
||||||
|
|
||||||
item->group = cmd_get_group(cmd);
|
|
||||||
item->state = cmdq_link_state(state);
|
|
||||||
|
|
||||||
item->cmdlist = cmdlist;
|
|
||||||
item->cmd = cmd;
|
item->cmd = cmd;
|
||||||
|
|
||||||
cmdlist->references++;
|
if (state == NULL)
|
||||||
log_debug("%s: %s group %u", __func__, item->name, item->group);
|
item->state = cmdq_new_state(NULL, NULL, 0);
|
||||||
|
else
|
||||||
|
item->state = cmdq_link_state(state);
|
||||||
|
|
||||||
if (first == NULL)
|
if (invoke_state != NULL)
|
||||||
first = item;
|
item->invoke_state = cmd_invoke_state_add_ref(invoke_state);
|
||||||
if (last != NULL)
|
|
||||||
last->next = item;
|
|
||||||
last = item;
|
|
||||||
|
|
||||||
cmd = cmd_list_next(cmd);
|
log_debug("%s: %s", __func__, item->name);
|
||||||
|
return (item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (created)
|
/* Get an invoke continuation for command queue. */
|
||||||
cmdq_free_state(state);
|
struct cmdq_item *
|
||||||
return (first);
|
cmdq_get_invoke(struct cmd_invoke_state *invoke_state, struct cmdq_state *state)
|
||||||
|
{
|
||||||
|
struct cmdq_item *item;
|
||||||
|
|
||||||
|
item = xcalloc(1, sizeof *item);
|
||||||
|
xasprintf(&item->name, "[invoke/%p]", item);
|
||||||
|
item->type = CMDQ_INVOKE;
|
||||||
|
if (state == NULL)
|
||||||
|
item->state = cmdq_new_state(NULL, NULL, 0);
|
||||||
|
else
|
||||||
|
item->state = cmdq_link_state(state);
|
||||||
|
item->invoke_state = cmd_invoke_state_add_ref(invoke_state);
|
||||||
|
return (item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fill in flag for a command. */
|
/* Fill in flag for a command. */
|
||||||
@@ -580,14 +553,11 @@ cmdq_fire_command(struct cmdq_item *item)
|
|||||||
int flags, quiet = 0;
|
int flags, quiet = 0;
|
||||||
char *tmp;
|
char *tmp;
|
||||||
|
|
||||||
if (item->client != NULL && (item->client->flags & CLIENT_DEAD))
|
|
||||||
return (CMD_RETURN_ERROR);
|
|
||||||
|
|
||||||
if (cfg_finished)
|
if (cfg_finished)
|
||||||
cmdq_add_message(item);
|
cmdq_add_message(item);
|
||||||
if (log_get_level() > 1) {
|
if (log_get_level() > 1) {
|
||||||
tmp = cmd_print(cmd);
|
tmp = cmd_print(cmd);
|
||||||
log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
|
log_debug("%s %s: %s", __func__, name, tmp);
|
||||||
free(tmp);
|
free(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,7 +636,6 @@ cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
|
|||||||
xasprintf(&item->name, "[%s/%p]", name, item);
|
xasprintf(&item->name, "[%s/%p]", name, item);
|
||||||
item->type = CMDQ_CALLBACK;
|
item->type = CMDQ_CALLBACK;
|
||||||
|
|
||||||
item->group = 0;
|
|
||||||
item->state = cmdq_new_state(NULL, NULL, 0);
|
item->state = cmdq_new_state(NULL, NULL, 0);
|
||||||
|
|
||||||
item->cb = cb;
|
item->cb = cb;
|
||||||
@@ -748,17 +717,16 @@ cmdq_next(struct client *c)
|
|||||||
switch (item->type) {
|
switch (item->type) {
|
||||||
case CMDQ_COMMAND:
|
case CMDQ_COMMAND:
|
||||||
retval = cmdq_fire_command(item);
|
retval = cmdq_fire_command(item);
|
||||||
|
if (item->invoke_state != NULL)
|
||||||
|
cmd_invoke_result(item->invoke_state, retval);
|
||||||
|
|
||||||
/*
|
|
||||||
* If a command returns an error, remove any
|
|
||||||
* subsequent commands in the same group.
|
|
||||||
*/
|
|
||||||
if (retval == CMD_RETURN_ERROR)
|
|
||||||
cmdq_remove_group(item);
|
|
||||||
break;
|
break;
|
||||||
case CMDQ_CALLBACK:
|
case CMDQ_CALLBACK:
|
||||||
retval = cmdq_fire_callback(item);
|
retval = cmdq_fire_callback(item);
|
||||||
break;
|
break;
|
||||||
|
case CMDQ_INVOKE:
|
||||||
|
retval = cmd_invoke_fire(item, item->invoke_state);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
retval = CMD_RETURN_ERROR;
|
retval = CMD_RETURN_ERROR;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+4
-7
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-resize-pane.c,v 1.69 2026/09/21 10:33:16 nicm Exp $ */
|
/* $OpenBSD: cmd-resize-pane.c,v 1.68 2026/08/31 07:44:39 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -91,7 +91,6 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
server_redraw_window(w);
|
server_redraw_window(w);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
if (!window_pane_is_floating(wp))
|
|
||||||
server_unzoom_window(w);
|
server_unzoom_window(w);
|
||||||
lc = wp->layout_cell; /* may have been replaced by unzoom */
|
lc = wp->layout_cell; /* may have been replaced by unzoom */
|
||||||
|
|
||||||
@@ -237,8 +236,8 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
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 left, right, resizes = 0;
|
int left, right;
|
||||||
int new_xoff, new_yoff, old_xoff, old_yoff;
|
int new_xoff, new_yoff, resizes = 0;
|
||||||
|
|
||||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
@@ -249,8 +248,6 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
lc = wp->layout_cell;
|
lc = wp->layout_cell;
|
||||||
sx = wp->sx;
|
sx = wp->sx;
|
||||||
sy = wp->sy;
|
sy = wp->sy;
|
||||||
old_xoff = wp->xoff;
|
|
||||||
old_yoff = wp->yoff;
|
|
||||||
left = wp->xoff - 1;
|
left = wp->xoff - 1;
|
||||||
right = wp->xoff + sx;
|
right = wp->xoff + sx;
|
||||||
if (window_pane_scrollbar_reserve(wp) &&
|
if (window_pane_scrollbar_reserve(wp) &&
|
||||||
@@ -350,7 +347,7 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
}
|
}
|
||||||
if (resizes != 0) {
|
if (resizes != 0) {
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
window_redraw_floating_pane(wp, old_xoff, old_yoff, sx, sy);
|
server_redraw_window(w);
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-31
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-run-shell.c,v 1.95 2026/09/20 07:59:55 nicm Exp $ */
|
/* $OpenBSD: cmd-run-shell.c,v 1.94 2026/08/25 06:04:33 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||||
@@ -57,7 +57,7 @@ const struct cmd_entry cmd_run_shell_entry = {
|
|||||||
struct cmd_run_shell_data {
|
struct cmd_run_shell_data {
|
||||||
struct client *client;
|
struct client *client;
|
||||||
char *cmd;
|
char *cmd;
|
||||||
struct args_command_state *state;
|
struct cmd_parse_tree *tree;
|
||||||
char *cwd;
|
char *cwd;
|
||||||
struct cmdq_item *item;
|
struct cmdq_item *item;
|
||||||
struct session *s;
|
struct session *s;
|
||||||
@@ -119,7 +119,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
double d;
|
double d;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
char *end, key[16];
|
char *end, key[16], *cause = NULL;
|
||||||
u_int i;
|
u_int i;
|
||||||
int wait = !args_has(args, 'b');
|
int wait = !args_has(args, 'b');
|
||||||
|
|
||||||
@@ -129,8 +129,10 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cmdq_error(item, "invalid delay time: %s", delay);
|
cmdq_error(item, "invalid delay time: %s", delay);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
} else if (args_count(args) == 0)
|
} else {
|
||||||
|
if (args_count(args) == 0)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
cdata = xcalloc(1, sizeof *cdata);
|
cdata = xcalloc(1, sizeof *cdata);
|
||||||
if (!args_has(args, 'C')) {
|
if (!args_has(args, 'C')) {
|
||||||
@@ -145,8 +147,13 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
format_free(ft);
|
format_free(ft);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cdata->state = args_make_commands_prepare(self, item, 0, NULL,
|
cdata->tree = args_to_commands(self, item, 0, NULL, 1, &cause);
|
||||||
wait, 1);
|
if (cdata->tree == NULL) {
|
||||||
|
cmdq_error(item, "%s", cause);
|
||||||
|
free(cause);
|
||||||
|
cmd_run_shell_free(cdata);
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args_has(args, 't') && wp != NULL)
|
if (args_has(args, 't') && wp != NULL)
|
||||||
@@ -163,26 +170,29 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
}
|
}
|
||||||
if (cdata->client != NULL)
|
if (cdata->client != NULL)
|
||||||
cdata->client->references++;
|
cdata->client->references++;
|
||||||
|
|
||||||
if (args_has(args, 'c'))
|
if (args_has(args, 'c'))
|
||||||
cdata->cwd = format_single_from_target(item, args_get(args, 'c'));
|
cdata->cwd = xstrdup(args_get(args, 'c'));
|
||||||
else
|
else
|
||||||
cdata->cwd = xstrdup(server_client_get_cwd(c, s));
|
cdata->cwd = xstrdup(server_client_get_cwd(c, s));
|
||||||
|
|
||||||
if (args_has(args, 'E'))
|
if (args_has(args, 'E'))
|
||||||
cdata->flags |= JOB_SHOWSTDERR;
|
cdata->flags |= JOB_SHOWSTDERR;
|
||||||
|
|
||||||
|
if (s != NULL) {
|
||||||
cdata->s = s;
|
cdata->s = s;
|
||||||
if (s != NULL)
|
|
||||||
session_add_ref(s, __func__);
|
session_add_ref(s, __func__);
|
||||||
|
}
|
||||||
|
|
||||||
evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
|
evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
|
||||||
if (delay != NULL) {
|
if (delay == NULL)
|
||||||
|
event_active(&cdata->timer, EV_TIMEOUT, 1);
|
||||||
|
else {
|
||||||
timerclear(&tv);
|
timerclear(&tv);
|
||||||
tv.tv_sec = (time_t)d;
|
tv.tv_sec = (time_t)d;
|
||||||
tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
|
tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
|
||||||
evtimer_add(&cdata->timer, &tv);
|
evtimer_add(&cdata->timer, &tv);
|
||||||
} else
|
}
|
||||||
event_active(&cdata->timer, EV_TIMEOUT, 1);
|
|
||||||
|
|
||||||
if (!wait)
|
if (!wait)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
@@ -196,10 +206,9 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
|
|||||||
struct client *c = cdata->client;
|
struct client *c = cdata->client;
|
||||||
const char *cmd = cdata->cmd;
|
const char *cmd = cdata->cmd;
|
||||||
struct cmdq_item *item = cdata->item, *new_item;
|
struct cmdq_item *item = cdata->item, *new_item;
|
||||||
struct cmd_list *cmdlist;
|
struct cmdq_state *cs = NULL;
|
||||||
char *error;
|
|
||||||
|
|
||||||
if (cdata->state == NULL) {
|
if (cdata->tree == NULL) {
|
||||||
if (cmd == NULL) {
|
if (cmd == NULL) {
|
||||||
if (cdata->item != NULL)
|
if (cdata->item != NULL)
|
||||||
cmdq_continue(cdata->item);
|
cmdq_continue(cdata->item);
|
||||||
@@ -222,23 +231,13 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdlist = args_make_commands(cdata->state, 0, NULL, &error);
|
if (item != NULL)
|
||||||
if (cmdlist == NULL) {
|
cs = cmdq_get_state(item);
|
||||||
if (cdata->item == NULL) {
|
new_item = cmd_invoke_get(cdata->tree, cs, NULL);
|
||||||
*error = toupper((u_char)*error);
|
if (item == NULL)
|
||||||
status_message_set(c, -1, 1, 0, 0, "%s", error);
|
|
||||||
} else
|
|
||||||
cmdq_error(cdata->item, "%s", error);
|
|
||||||
free(error);
|
|
||||||
} else if (item == NULL) {
|
|
||||||
new_item = cmdq_get_command(cmdlist, NULL);
|
|
||||||
cmdq_append(c, new_item);
|
cmdq_append(c, new_item);
|
||||||
cmd_list_free(cmdlist);
|
else
|
||||||
} else {
|
|
||||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
|
||||||
cmdq_insert_after(item, new_item);
|
cmdq_insert_after(item, new_item);
|
||||||
cmd_list_free(cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cdata->item != NULL)
|
if (cdata->item != NULL)
|
||||||
cmdq_continue(cdata->item);
|
cmdq_continue(cdata->item);
|
||||||
@@ -306,8 +305,7 @@ cmd_run_shell_free(void *data)
|
|||||||
session_remove_ref(cdata->s, __func__);
|
session_remove_ref(cdata->s, __func__);
|
||||||
if (cdata->client != NULL)
|
if (cdata->client != NULL)
|
||||||
server_client_unref(cdata->client);
|
server_client_unref(cdata->client);
|
||||||
if (cdata->state != NULL)
|
cmd_parse_free(cdata->tree);
|
||||||
args_make_commands_free(cdata->state);
|
|
||||||
free(cdata->cwd);
|
free(cdata->cwd);
|
||||||
free(cdata->cmd);
|
free(cdata->cmd);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
|
|||||||
+4
-9
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-select-layout.c,v 1.45 2026/09/09 07:03:39 nicm Exp $ */
|
/* $OpenBSD: cmd-select-layout.c,v 1.43 2026/07/10 13:38:45 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -73,13 +73,12 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
{
|
{
|
||||||
struct args *args = cmd_get_args(self);
|
struct args *args = cmd_get_args(self);
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
struct client *c = cmdq_get_target_client(item);
|
|
||||||
struct winlink *wl = target->wl;
|
struct winlink *wl = target->wl;
|
||||||
struct window *w = wl->window;
|
struct window *w = wl->window;
|
||||||
struct window_pane *wp = target->wp;
|
struct window_pane *wp = target->wp;
|
||||||
const char *layoutname;
|
const char *layoutname;
|
||||||
char *oldlayout, *cause = NULL;
|
char *oldlayout, *cause;
|
||||||
int next, previous, layout, flags = 0;
|
int next, previous, layout;
|
||||||
|
|
||||||
server_unzoom_window(w);
|
server_unzoom_window(w);
|
||||||
|
|
||||||
@@ -90,12 +89,8 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
if (args_has(args, 'p'))
|
if (args_has(args, 'p'))
|
||||||
previous = 1;
|
previous = 1;
|
||||||
|
|
||||||
if (c != NULL &&
|
|
||||||
(c->flags & CLIENT_CONTROL) &&
|
|
||||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
|
||||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
|
||||||
oldlayout = w->old_layout;
|
oldlayout = w->old_layout;
|
||||||
w->old_layout = layout_dump(w, w->layout_root, flags);
|
w->old_layout = layout_dump(w, w->layout_root);
|
||||||
|
|
||||||
if (next || previous) {
|
if (next || previous) {
|
||||||
if (next)
|
if (next)
|
||||||
|
|||||||
+1
-1
@@ -131,7 +131,7 @@ cmd_source_file_done(__unused struct client *oc, const char *path,
|
|||||||
if (error != 0)
|
if (error != 0)
|
||||||
cmdq_error(item, "%s: %s", strerror(error), path);
|
cmdq_error(item, "%s: %s", strerror(error), path);
|
||||||
else if (bsize != 0) {
|
else if (bsize != 0) {
|
||||||
if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after,
|
if (load_cfg_from_buffer(bdata, bsize, path, cdata->after,
|
||||||
target, cdata->flags, &new_item) < 0)
|
target, cdata->flags, &new_item) < 0)
|
||||||
cdata->retval = CMD_RETURN_ERROR;
|
cdata->retval = CMD_RETURN_ERROR;
|
||||||
else if (new_item != NULL)
|
else if (new_item != NULL)
|
||||||
|
|||||||
+4
-13
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */
|
/* $OpenBSD: cmd-split-window.c,v 1.150 2026/08/20 09:19:24 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
|
|||||||
.name = "new-pane",
|
.name = "new-pane",
|
||||||
.alias = "newp",
|
.alias = "newp",
|
||||||
|
|
||||||
.args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
.args = { "AbB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||||
.usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] "
|
.usage = "[-AbCdefhIkKLMOPvWZ] [-B border-lines] "
|
||||||
"[-c start-directory] [-e environment] "
|
"[-c start-directory] [-e environment] "
|
||||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||||
"[-s style] [-S active-border-style] "
|
"[-s style] [-S active-border-style] "
|
||||||
@@ -219,8 +219,6 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||||
if (args_has(args, 'C') && args_has(args, 'O'))
|
if (args_has(args, 'C') && args_has(args, 'O'))
|
||||||
new_wp->flags |= PANE_CLOSEONCLICK;
|
new_wp->flags |= PANE_CLOSEONCLICK;
|
||||||
if (args_has(args, 'D') && args_has(args, 'O'))
|
|
||||||
new_wp->flags |= PANE_CLOSEONCANCEL;
|
|
||||||
|
|
||||||
style = args_get(args, 's');
|
style = args_get(args, 's');
|
||||||
if (style != NULL) {
|
if (style != NULL) {
|
||||||
@@ -362,7 +360,6 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
|||||||
enum pane_lines lines;
|
enum pane_lines lines;
|
||||||
u_int sx, sy;
|
u_int sx, sy;
|
||||||
int x, y, xoff, yoff, border;
|
int x, y, xoff, yoff, border;
|
||||||
int oxoff, oyoff, osx, osy;
|
|
||||||
|
|
||||||
if (c->tty.mouse_last_pane == -1)
|
if (c->tty.mouse_last_pane == -1)
|
||||||
return;
|
return;
|
||||||
@@ -418,14 +415,8 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
|||||||
if (sy < PANE_MINIMUM)
|
if (sy < PANE_MINIMUM)
|
||||||
sy = PANE_MINIMUM;
|
sy = PANE_MINIMUM;
|
||||||
|
|
||||||
oxoff = wp->xoff;
|
|
||||||
oyoff = wp->yoff;
|
|
||||||
osx = wp->sx;
|
|
||||||
osy = wp->sy;
|
|
||||||
|
|
||||||
layout_set_size(lc, sx, sy, xoff, yoff);
|
layout_set_size(lc, sx, sy, xoff, yoff);
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
|
server_redraw_window(w);
|
||||||
window_redraw_floating_pane(wp, oxoff, oyoff, osx, osy);
|
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-35
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-wait-for.c,v 1.24 2026/09/22 06:46:50 nicm Exp $ */
|
/* $OpenBSD: cmd-wait-for.c,v 1.23 2026/07/10 13:38:45 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -480,40 +480,6 @@ cmd_wait_for_unlock(struct cmdq_item *item, const char *name,
|
|||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
cmd_wait_for_client_lost(struct client *c)
|
|
||||||
{
|
|
||||||
struct wait_channel *wc, *wc1;
|
|
||||||
struct wait_item *wi, *wi1;
|
|
||||||
struct wait_event_item *wei, *wei1;
|
|
||||||
|
|
||||||
TAILQ_FOREACH_SAFE(wei, &wait_event_items, entry, wei1) {
|
|
||||||
if (cmdq_get_client(wei->item) == c) {
|
|
||||||
TAILQ_REMOVE(&wait_event_items, wei, entry);
|
|
||||||
cmdq_continue(wei->item);
|
|
||||||
cmd_wait_for_event_free(wei);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
|
|
||||||
TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
|
|
||||||
if (cmdq_get_client(wi->item) == c) {
|
|
||||||
cmdq_continue(wi->item);
|
|
||||||
TAILQ_REMOVE(&wc->waiters, wi, entry);
|
|
||||||
free(wi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
|
|
||||||
if (cmdq_get_client(wi->item) == c) {
|
|
||||||
cmdq_continue(wi->item);
|
|
||||||
TAILQ_REMOVE(&wc->lockers, wi, entry);
|
|
||||||
free(wi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd_wait_for_remove_empty(wc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
cmd_wait_for_flush(void)
|
cmd_wait_for_flush(void)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -220,18 +220,11 @@ const struct cmd_entry *cmd_table[] = {
|
|||||||
struct cmd {
|
struct cmd {
|
||||||
const struct cmd_entry *entry;
|
const struct cmd_entry *entry;
|
||||||
struct args *args;
|
struct args *args;
|
||||||
u_int group;
|
|
||||||
|
|
||||||
char *file;
|
char *file;
|
||||||
u_int line;
|
u_int line;
|
||||||
int parse_flags;
|
int parse_flags;
|
||||||
|
|
||||||
TAILQ_ENTRY(cmd) qentry;
|
|
||||||
};
|
};
|
||||||
TAILQ_HEAD(cmds, cmd);
|
|
||||||
|
|
||||||
/* Next group number for new command list. */
|
|
||||||
static u_int cmd_list_next_group = 1;
|
|
||||||
|
|
||||||
/* Log an argument vector. */
|
/* Log an argument vector. */
|
||||||
void printflike(3, 4)
|
void printflike(3, 4)
|
||||||
@@ -402,13 +395,6 @@ cmd_get_args(struct cmd *cmd)
|
|||||||
return (cmd->args);
|
return (cmd->args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get group for command. */
|
|
||||||
u_int
|
|
||||||
cmd_get_group(struct cmd *cmd)
|
|
||||||
{
|
|
||||||
return (cmd->group);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get file and line for command. */
|
/* Get file and line for command. */
|
||||||
void
|
void
|
||||||
cmd_get_source(struct cmd *cmd, const char **file, u_int *line)
|
cmd_get_source(struct cmd *cmd, const char **file, u_int *line)
|
||||||
@@ -558,23 +544,6 @@ cmd_free(struct cmd *cmd)
|
|||||||
free(cmd);
|
free(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy a command. */
|
|
||||||
struct cmd *
|
|
||||||
cmd_copy(struct cmd *cmd, int argc, char **argv)
|
|
||||||
{
|
|
||||||
struct cmd *new_cmd;
|
|
||||||
|
|
||||||
new_cmd = xcalloc(1, sizeof *new_cmd);
|
|
||||||
new_cmd->entry = cmd->entry;
|
|
||||||
new_cmd->args = args_copy(cmd->args, argc, argv);
|
|
||||||
|
|
||||||
if (cmd->file != NULL)
|
|
||||||
new_cmd->file = xstrdup(cmd->file);
|
|
||||||
new_cmd->line = cmd->line;
|
|
||||||
|
|
||||||
return (new_cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get a command as a string. */
|
/* Get a command as a string. */
|
||||||
char *
|
char *
|
||||||
cmd_print(struct cmd *cmd)
|
cmd_print(struct cmd *cmd)
|
||||||
@@ -591,174 +560,6 @@ cmd_print(struct cmd *cmd)
|
|||||||
return (out);
|
return (out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create a new command list. */
|
|
||||||
struct cmd_list *
|
|
||||||
cmd_list_new(void)
|
|
||||||
{
|
|
||||||
struct cmd_list *cmdlist;
|
|
||||||
|
|
||||||
cmdlist = xcalloc(1, sizeof *cmdlist);
|
|
||||||
cmdlist->references = 1;
|
|
||||||
cmdlist->group = cmd_list_next_group++;
|
|
||||||
cmdlist->list = xcalloc(1, sizeof *cmdlist->list);
|
|
||||||
TAILQ_INIT(cmdlist->list);
|
|
||||||
return (cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Append a command to a command list. */
|
|
||||||
void
|
|
||||||
cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd)
|
|
||||||
{
|
|
||||||
cmd->group = cmdlist->group;
|
|
||||||
TAILQ_INSERT_TAIL(cmdlist->list, cmd, qentry);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Append all commands from one list to another. */
|
|
||||||
void
|
|
||||||
cmd_list_append_all(struct cmd_list *cmdlist, struct cmd_list *from)
|
|
||||||
{
|
|
||||||
struct cmd *cmd;
|
|
||||||
|
|
||||||
TAILQ_FOREACH(cmd, from->list, qentry)
|
|
||||||
cmd->group = cmdlist->group;
|
|
||||||
TAILQ_CONCAT(cmdlist->list, from->list, qentry);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Move all commands from one command list to another. */
|
|
||||||
void
|
|
||||||
cmd_list_move(struct cmd_list *cmdlist, struct cmd_list *from)
|
|
||||||
{
|
|
||||||
TAILQ_CONCAT(cmdlist->list, from->list, qentry);
|
|
||||||
cmdlist->group = cmd_list_next_group++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free a command list. */
|
|
||||||
void
|
|
||||||
cmd_list_free(struct cmd_list *cmdlist)
|
|
||||||
{
|
|
||||||
struct cmd *cmd, *cmd1;
|
|
||||||
|
|
||||||
if (--cmdlist->references != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
TAILQ_FOREACH_SAFE(cmd, cmdlist->list, qentry, cmd1) {
|
|
||||||
TAILQ_REMOVE(cmdlist->list, cmd, qentry);
|
|
||||||
cmd_free(cmd);
|
|
||||||
}
|
|
||||||
free(cmdlist->list);
|
|
||||||
free(cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy a command list, expanding %s in arguments. */
|
|
||||||
struct cmd_list *
|
|
||||||
cmd_list_copy(const struct cmd_list *cmdlist, int argc, char **argv)
|
|
||||||
{
|
|
||||||
struct cmd *cmd;
|
|
||||||
struct cmd_list *new_cmdlist;
|
|
||||||
struct cmd *new_cmd;
|
|
||||||
u_int group = cmdlist->group;
|
|
||||||
char *s;
|
|
||||||
|
|
||||||
s = cmd_list_print(cmdlist, 0);
|
|
||||||
log_debug("%s: %s", __func__, s);
|
|
||||||
free(s);
|
|
||||||
|
|
||||||
new_cmdlist = cmd_list_new();
|
|
||||||
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
|
|
||||||
if (cmd->group != group) {
|
|
||||||
new_cmdlist->group = cmd_list_next_group++;
|
|
||||||
group = cmd->group;
|
|
||||||
}
|
|
||||||
new_cmd = cmd_copy(cmd, argc, argv);
|
|
||||||
cmd_list_append(new_cmdlist, new_cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
s = cmd_list_print(new_cmdlist, 0);
|
|
||||||
log_debug("%s: %s", __func__, s);
|
|
||||||
free(s);
|
|
||||||
|
|
||||||
return (new_cmdlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get a command list as a string. */
|
|
||||||
char *
|
|
||||||
cmd_list_print(const struct cmd_list *cmdlist, int flags)
|
|
||||||
{
|
|
||||||
struct cmd *cmd, *next;
|
|
||||||
char *buf, *this;
|
|
||||||
size_t len;
|
|
||||||
const char *separator;
|
|
||||||
int escaped = flags & CMD_LIST_PRINT_ESCAPED;
|
|
||||||
int no_groups = flags & CMD_LIST_PRINT_NO_GROUPS;
|
|
||||||
const char *single_separator = escaped ? " \\; " : " ; ";
|
|
||||||
const char *double_separator = escaped ? " \\;\\; " : " ;; ";
|
|
||||||
|
|
||||||
len = 1;
|
|
||||||
buf = xcalloc(1, len);
|
|
||||||
|
|
||||||
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
|
|
||||||
this = cmd_print(cmd);
|
|
||||||
|
|
||||||
len += strlen(this) + 6;
|
|
||||||
buf = xrealloc(buf, len);
|
|
||||||
|
|
||||||
strlcat(buf, this, len);
|
|
||||||
|
|
||||||
next = TAILQ_NEXT(cmd, qentry);
|
|
||||||
if (next != NULL) {
|
|
||||||
if (!no_groups && cmd->group != next->group)
|
|
||||||
separator = double_separator;
|
|
||||||
else
|
|
||||||
separator = single_separator;
|
|
||||||
strlcat(buf, separator, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get first command in list. */
|
|
||||||
struct cmd *
|
|
||||||
cmd_list_first(struct cmd_list *cmdlist)
|
|
||||||
{
|
|
||||||
return (TAILQ_FIRST(cmdlist->list));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get next command in list. */
|
|
||||||
struct cmd *
|
|
||||||
cmd_list_next(struct cmd *cmd)
|
|
||||||
{
|
|
||||||
return (TAILQ_NEXT(cmd, qentry));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Do all of the commands in this command list have this flag? */
|
|
||||||
int
|
|
||||||
cmd_list_all_have(struct cmd_list *cmdlist, int flag)
|
|
||||||
{
|
|
||||||
struct cmd *cmd;
|
|
||||||
|
|
||||||
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
|
|
||||||
if (~cmd->entry->flags & flag)
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Do any of the commands in this command list have this flag? */
|
|
||||||
int
|
|
||||||
cmd_list_any_have(struct cmd_list *cmdlist, int flag)
|
|
||||||
{
|
|
||||||
struct cmd *cmd;
|
|
||||||
|
|
||||||
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
|
|
||||||
if (cmd->entry->flags & flag)
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adjust current mouse position for a pane. */
|
/* Adjust current mouse position for a pane. */
|
||||||
int
|
int
|
||||||
cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
|
cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# configure.ac
|
# configure.ac
|
||||||
|
|
||||||
AC_INIT([tmux], next-3.9)
|
AC_INIT([tmux], next-3.8)
|
||||||
AC_PREREQ([2.60])
|
AC_PREREQ([2.60])
|
||||||
|
|
||||||
AC_CONFIG_AUX_DIR(etc)
|
AC_CONFIG_AUX_DIR(etc)
|
||||||
|
|||||||
+6
-13
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: control-notify.c,v 1.39 2026/09/08 10:20:08 nicm Exp $ */
|
/* $OpenBSD: control-notify.c,v 1.38 2026/08/03 13:38:42 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -70,7 +70,6 @@ control_window_layout_changed_cb(__unused const char *name,
|
|||||||
struct session *s;
|
struct session *s;
|
||||||
struct winlink *wl;
|
struct winlink *wl;
|
||||||
struct window *w = event_payload_get_window(ep, "window");
|
struct window *w = event_payload_get_window(ep, "window");
|
||||||
struct format_tree *ft;
|
|
||||||
const char *template;
|
const char *template;
|
||||||
char *cp;
|
char *cp;
|
||||||
|
|
||||||
@@ -85,25 +84,19 @@ control_window_layout_changed_cb(__unused const char *name,
|
|||||||
* and we don't need to inform the client about the layout change
|
* and we don't need to inform the client about the layout change
|
||||||
* because the whole window will go away soon.
|
* because the whole window will go away soon.
|
||||||
*/
|
*/
|
||||||
if (TAILQ_FIRST(&w->winlinks) == NULL || w->layout_root == NULL)
|
wl = TAILQ_FIRST(&w->winlinks);
|
||||||
|
if (wl == NULL || w->layout_root == NULL)
|
||||||
return;
|
return;
|
||||||
|
cp = format_single(NULL, template, NULL, NULL, wl, NULL);
|
||||||
|
|
||||||
TAILQ_FOREACH(c, &clients, entry) {
|
TAILQ_FOREACH(c, &clients, entry) {
|
||||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
|
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
|
||||||
continue;
|
continue;
|
||||||
s = c->session;
|
s = c->session;
|
||||||
wl = winlink_find_by_window_id(&s->windows, w->id);
|
if (winlink_find_by_window_id(&s->windows, w->id) != NULL)
|
||||||
if (wl == NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ft = format_create(c, NULL, FORMAT_NONE, 0);
|
|
||||||
format_defaults(ft, c, s, wl, NULL);
|
|
||||||
cp = format_expand(ft, template);
|
|
||||||
format_free(ft);
|
|
||||||
|
|
||||||
control_notify_write(c, "%s", cp);
|
control_notify_write(c, "%s", cp);
|
||||||
free(cp);
|
|
||||||
}
|
}
|
||||||
|
free(cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Notify control clients that window pane changed. */
|
/* Notify control clients that window pane changed. */
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: control.c,v 1.68 2026/09/09 08:30:05 nicm Exp $ */
|
/* $OpenBSD: control.c,v 1.66 2026/08/18 07:43:44 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -108,7 +108,6 @@ struct control_state {
|
|||||||
u_int pending_count;
|
u_int pending_count;
|
||||||
|
|
||||||
TAILQ_HEAD(, control_block) all_blocks;
|
TAILQ_HEAD(, control_block) all_blocks;
|
||||||
size_t queued_reply_bytes;
|
|
||||||
|
|
||||||
struct bufferevent *read_event;
|
struct bufferevent *read_event;
|
||||||
struct bufferevent *write_event;
|
struct bufferevent *write_event;
|
||||||
@@ -133,9 +132,6 @@ struct control_state {
|
|||||||
/* Maximum age for clients that are not using pause mode. */
|
/* Maximum age for clients that are not using pause mode. */
|
||||||
#define CONTROL_MAXIMUM_AGE 300000
|
#define CONTROL_MAXIMUM_AGE 300000
|
||||||
|
|
||||||
/* Maximum buffered command replies for a client that is not reading. */
|
|
||||||
#define CONTROL_MAXIMUM_REPLY_BUFFER (64 * 1024 * 1024)
|
|
||||||
|
|
||||||
/* Flags to ignore client. */
|
/* Flags to ignore client. */
|
||||||
#define CONTROL_IGNORE_FLAGS \
|
#define CONTROL_IGNORE_FLAGS \
|
||||||
(CLIENT_CONTROL_NOOUTPUT| \
|
(CLIENT_CONTROL_NOOUTPUT| \
|
||||||
@@ -169,15 +165,6 @@ RB_GENERATE_STATIC(control_windows, control_window, entry, control_window_cmp);
|
|||||||
static void
|
static void
|
||||||
control_free_block(struct control_state *cs, struct control_block *cb)
|
control_free_block(struct control_state *cs, struct control_block *cb)
|
||||||
{
|
{
|
||||||
size_t size;
|
|
||||||
|
|
||||||
if (cb->size == 0 && cb->line != NULL) {
|
|
||||||
size = strlen(cb->line) + 1;
|
|
||||||
if (cs->queued_reply_bytes > size)
|
|
||||||
cs->queued_reply_bytes -= size;
|
|
||||||
else
|
|
||||||
cs->queued_reply_bytes = 0;
|
|
||||||
}
|
|
||||||
free(cb->line);
|
free(cb->line);
|
||||||
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
|
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
|
||||||
free(cb);
|
free(cb);
|
||||||
@@ -420,52 +407,16 @@ control_reset_pane(struct client *c, struct window_pane *wp)
|
|||||||
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if the replies buffered for a client, including one about to be
|
|
||||||
* added, have grown too large and kill it if so. Returns 1 if further output
|
|
||||||
* for the client should be dropped.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
control_check_reply_buffer(struct client *c, size_t added)
|
|
||||||
{
|
|
||||||
struct control_state *cs = c->control_state;
|
|
||||||
size_t size;
|
|
||||||
|
|
||||||
if (c->flags & CLIENT_CONTROL_DISCARD)
|
|
||||||
return (1);
|
|
||||||
size = EVBUFFER_LENGTH(cs->write_event->output);
|
|
||||||
size += cs->queued_reply_bytes;
|
|
||||||
size += added;
|
|
||||||
if (size < CONTROL_MAXIMUM_REPLY_BUFFER)
|
|
||||||
return (0);
|
|
||||||
|
|
||||||
log_debug("%s: %s: %zu bytes of replies buffered", __func__, c->name,
|
|
||||||
size);
|
|
||||||
if (~c->flags & CLIENT_EXIT) {
|
|
||||||
c->exit_message = xstrdup("too far behind");
|
|
||||||
c->flags |= CLIENT_EXIT;
|
|
||||||
control_discard(c);
|
|
||||||
}
|
|
||||||
c->flags |= CLIENT_CONTROL_DISCARD;
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write an already-formatted line, queueing it behind %output if needed. */
|
/* Write an already-formatted line, queueing it behind %output if needed. */
|
||||||
static void
|
static void
|
||||||
control_write_line(struct client *c, char *line)
|
control_write_line(struct client *c, char *line)
|
||||||
{
|
{
|
||||||
struct control_state *cs = c->control_state;
|
struct control_state *cs = c->control_state;
|
||||||
struct control_block *cb;
|
struct control_block *cb;
|
||||||
size_t size = strlen(line) + 1;
|
|
||||||
|
|
||||||
if (control_check_reply_buffer(c, size)) {
|
|
||||||
free(line);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TAILQ_EMPTY(&cs->all_blocks)) {
|
if (TAILQ_EMPTY(&cs->all_blocks)) {
|
||||||
log_debug("%s: %s: writing line: %s", __func__, c->name, line);
|
log_debug("%s: %s: writing line: %s", __func__, c->name, line);
|
||||||
bufferevent_write(cs->write_event, line, size - 1);
|
bufferevent_write(cs->write_event, line, strlen(line));
|
||||||
bufferevent_write(cs->write_event, "\n", 1);
|
bufferevent_write(cs->write_event, "\n", 1);
|
||||||
bufferevent_enable(cs->write_event, EV_WRITE);
|
bufferevent_enable(cs->write_event, EV_WRITE);
|
||||||
free(line);
|
free(line);
|
||||||
@@ -475,7 +426,6 @@ control_write_line(struct client *c, char *line)
|
|||||||
cb = xcalloc(1, sizeof *cb);
|
cb = xcalloc(1, sizeof *cb);
|
||||||
cb->line = line;
|
cb->line = line;
|
||||||
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
|
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
|
||||||
cs->queued_reply_bytes += size;
|
|
||||||
cb->t = get_timer();
|
cb->t = get_timer();
|
||||||
|
|
||||||
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
|
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
|
||||||
@@ -505,12 +455,8 @@ control_flush_deferred(struct client *c)
|
|||||||
void
|
void
|
||||||
control_write(struct client *c, const char *fmt, ...)
|
control_write(struct client *c, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
struct control_state *cs = c->control_state;
|
|
||||||
char *line;
|
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
char *line;
|
||||||
if (cs == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
xvasprintf(&line, fmt, ap);
|
xvasprintf(&line, fmt, ap);
|
||||||
@@ -533,9 +479,6 @@ control_write_guard(struct client *c, const char *guard, long t, u_int number,
|
|||||||
struct control_state *cs = c->control_state;
|
struct control_state *cs = c->control_state;
|
||||||
char *line;
|
char *line;
|
||||||
|
|
||||||
if (cs == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (strcmp(guard, "begin") == 0)
|
if (strcmp(guard, "begin") == 0)
|
||||||
cs->guard_depth++;
|
cs->guard_depth++;
|
||||||
|
|
||||||
@@ -560,9 +503,6 @@ control_notify_write(struct client *c, const char *fmt, ...)
|
|||||||
va_list ap;
|
va_list ap;
|
||||||
char *line;
|
char *line;
|
||||||
|
|
||||||
if (cs == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
xvasprintf(&line, fmt, ap);
|
xvasprintf(&line, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
@@ -701,8 +641,9 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
|
|||||||
struct control_state *cs = c->control_state;
|
struct control_state *cs = c->control_state;
|
||||||
struct evbuffer *buffer = cs->read_event->input;
|
struct evbuffer *buffer = cs->read_event->input;
|
||||||
char *line, *error;
|
char *line, *error;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmdq_item *new_item;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
enum cmd_parse_status status;
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
|
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
|
||||||
@@ -716,9 +657,14 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
|
state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
|
||||||
status = cmd_parse_and_append(line, NULL, c, state, &error);
|
tree = cmd_parse_from_string(line, NULL, &error);
|
||||||
if (status == CMD_PARSE_ERROR)
|
if (tree == NULL)
|
||||||
cmdq_append(c, cmdq_get_callback(control_error, error));
|
cmdq_append(c, cmdq_get_callback(control_error, error));
|
||||||
|
else {
|
||||||
|
new_item = cmd_invoke_get(tree, state, NULL);
|
||||||
|
cmdq_append(c, new_item);
|
||||||
|
cmd_parse_free(tree);
|
||||||
|
}
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
|
|
||||||
free(line);
|
free(line);
|
||||||
@@ -1057,7 +1003,6 @@ control_discard_all(struct client *c)
|
|||||||
control_discard(c);
|
control_discard(c);
|
||||||
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
||||||
control_free_block(cs, cb);
|
control_free_block(cs, cb);
|
||||||
cs->queued_reply_bytes = 0;
|
|
||||||
bufferevent_disable(cs->write_event, EV_WRITE);
|
bufferevent_disable(cs->write_event, EV_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: format.c,v 1.418 2026/09/20 08:19:31 nicm Exp $ */
|
/* $OpenBSD: format.c,v 1.415 2026/08/31 19:34:09 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -855,42 +855,26 @@ format_cb_window_active_clients_list(struct format_tree *ft)
|
|||||||
static void *
|
static void *
|
||||||
format_cb_window_layout(struct format_tree *ft)
|
format_cb_window_layout(struct format_tree *ft)
|
||||||
{
|
{
|
||||||
struct client *c = ft->client;
|
|
||||||
struct window *w = ft->w;
|
struct window *w = ft->w;
|
||||||
struct layout_cell *lcroot;
|
|
||||||
int flags = 0;
|
|
||||||
|
|
||||||
if (w == NULL)
|
if (w == NULL)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
|
||||||
if (w->saved_layout_root != NULL)
|
if (w->saved_layout_root != NULL)
|
||||||
lcroot = w->saved_layout_root;
|
return (layout_dump(w, w->saved_layout_root));
|
||||||
else
|
return (layout_dump(w, w->layout_root));
|
||||||
lcroot = w->layout_root;
|
|
||||||
|
|
||||||
if (c != NULL &&
|
|
||||||
(c->flags & CLIENT_CONTROL) &&
|
|
||||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
|
||||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
|
||||||
return (layout_dump(w, lcroot, flags));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Callback for window_visible_layout. */
|
/* Callback for window_visible_layout. */
|
||||||
static void *
|
static void *
|
||||||
format_cb_window_visible_layout(struct format_tree *ft)
|
format_cb_window_visible_layout(struct format_tree *ft)
|
||||||
{
|
{
|
||||||
struct client *c = ft->client;
|
|
||||||
struct window *w = ft->w;
|
struct window *w = ft->w;
|
||||||
int flags = 0;
|
|
||||||
|
|
||||||
if (w == NULL)
|
if (w == NULL)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
|
||||||
if (c != NULL &&
|
return (layout_dump(w, w->layout_root));
|
||||||
(c->flags & CLIENT_CONTROL) &&
|
|
||||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
|
||||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
|
||||||
return (layout_dump(w, w->layout_root, flags));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Callback for pane_start_command. */
|
/* Callback for pane_start_command. */
|
||||||
@@ -2876,11 +2860,11 @@ format_cb_session_activity_flag(struct format_tree *ft)
|
|||||||
|
|
||||||
if (ft->s != NULL) {
|
if (ft->s != NULL) {
|
||||||
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
||||||
if (wl->flags & WINLINK_ACTIVITY)
|
if (ft->wl->flags & WINLINK_ACTIVITY)
|
||||||
return (xstrdup("1"));
|
return (xstrdup("1"));
|
||||||
}
|
|
||||||
return (xstrdup("0"));
|
return (xstrdup("0"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2894,9 +2878,9 @@ format_cb_session_bell_flag(struct format_tree *ft)
|
|||||||
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
||||||
if (wl->flags & WINLINK_BELL)
|
if (wl->flags & WINLINK_BELL)
|
||||||
return (xstrdup("1"));
|
return (xstrdup("1"));
|
||||||
}
|
|
||||||
return (xstrdup("0"));
|
return (xstrdup("0"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2908,11 +2892,11 @@ format_cb_session_silence_flag(struct format_tree *ft)
|
|||||||
|
|
||||||
if (ft->s != NULL) {
|
if (ft->s != NULL) {
|
||||||
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
||||||
if (wl->flags & WINLINK_SILENCE)
|
if (ft->wl->flags & WINLINK_SILENCE)
|
||||||
return (xstrdup("1"));
|
return (xstrdup("1"));
|
||||||
}
|
|
||||||
return (xstrdup("0"));
|
return (xstrdup("0"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5250,7 +5234,6 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
|
|||||||
format_log(es, "session loop: $%u", s->id);
|
format_log(es, "session loop: $%u", s->id);
|
||||||
if (active != NULL &&
|
if (active != NULL &&
|
||||||
ft->c != NULL &&
|
ft->c != NULL &&
|
||||||
ft->c->session != NULL &&
|
|
||||||
s->id == ft->c->session->id)
|
s->id == ft->c->session->id)
|
||||||
use = active;
|
use = active;
|
||||||
else
|
else
|
||||||
|
|||||||
+6
-12
@@ -36,7 +36,8 @@ int
|
|||||||
LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
||||||
{
|
{
|
||||||
struct cmd_parse_input pi;
|
struct cmd_parse_input pi;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
|
char *cause = NULL;
|
||||||
|
|
||||||
if (size > 2048 || size == 0)
|
if (size > 2048 || size == 0)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -44,17 +45,10 @@ LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
|||||||
memset(&pi, 0, sizeof pi);
|
memset(&pi, 0, sizeof pi);
|
||||||
pi.flags = CMD_PARSE_QUIET;
|
pi.flags = CMD_PARSE_QUIET;
|
||||||
|
|
||||||
pr = cmd_parse_from_buffer(data, size, &pi);
|
tree = cmd_parse_from_buffer(data, size, &pi, &cause);
|
||||||
switch (pr->status) {
|
if (tree != NULL)
|
||||||
case CMD_PARSE_SUCCESS:
|
cmd_parse_free(tree);
|
||||||
cmd_list_free(pr->cmdlist);
|
free(cause);
|
||||||
break;
|
|
||||||
case CMD_PARSE_ERROR:
|
|
||||||
free(pr->error);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
|||||||
w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0);
|
w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0);
|
||||||
wp = window_add_pane(w, NULL, 0, 0);
|
wp = window_add_pane(w, NULL, 0, 0);
|
||||||
bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty);
|
bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty);
|
||||||
wp->ictx = input_init(wp, vpty[0], NULL);
|
wp->ictx = input_init(wp, vpty[0], NULL, NULL);
|
||||||
window_add_ref(w, __func__);
|
window_add_ref(w, __func__);
|
||||||
|
|
||||||
wp->fd = open("/dev/null", O_WRONLY);
|
wp->fd = open("/dev/null", O_WRONLY);
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2026 Arthur Chan <arthur.chan@adalogics.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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fuzz the custom layout parser.
|
|
||||||
*
|
|
||||||
* A layout string such as "bb62,80x24,0,0{40x24,0,0,1,39x24,41,0,2}" is
|
|
||||||
* accepted by select-layout and is what tmux stores and restores for a
|
|
||||||
* window, so it is parsed from configuration and from commands. It drives
|
|
||||||
* layout-custom.c (the string parser and the checksum), then layout.c, which
|
|
||||||
* resizes and assigns the cells.
|
|
||||||
*
|
|
||||||
* layout_parse() refuses a window with no panes, and refuses a layout whose
|
|
||||||
* cell count is smaller than the pane count, so the window is given a fixed
|
|
||||||
* number of panes. The count is fixed rather than derived from the input, so
|
|
||||||
* a mutation always means a different layout string.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "tmux.h"
|
|
||||||
|
|
||||||
#define FUZZER_MAXLEN 1024
|
|
||||||
#define FUZZER_PANES 4
|
|
||||||
|
|
||||||
struct event_base *libevent;
|
|
||||||
|
|
||||||
int
|
|
||||||
LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
|
||||||
{
|
|
||||||
struct window *w;
|
|
||||||
struct window_pane *wp;
|
|
||||||
char *buf, *cause = NULL, *dump;
|
|
||||||
u_int i;
|
|
||||||
|
|
||||||
if (size == 0 || size > FUZZER_MAXLEN)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* layout_parse() takes a C string. */
|
|
||||||
buf = malloc(size + 1);
|
|
||||||
if (buf == NULL)
|
|
||||||
return 0;
|
|
||||||
memcpy(buf, data, size);
|
|
||||||
buf[size] = '\0';
|
|
||||||
|
|
||||||
w = window_create(80, 24, 0, 0);
|
|
||||||
if (w == NULL) {
|
|
||||||
free(buf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
window_add_ref(w, __func__);
|
|
||||||
|
|
||||||
for (i = 0; i < FUZZER_PANES; i++) {
|
|
||||||
wp = window_add_pane(w, NULL, 0, 0);
|
|
||||||
if (w->active == NULL)
|
|
||||||
w->active = wp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout_parse(w, buf, &cause) == 0) {
|
|
||||||
dump = layout_dump(w, w->layout_root, 1);
|
|
||||||
free(dump);
|
|
||||||
}
|
|
||||||
free(cause);
|
|
||||||
|
|
||||||
window_remove_ref(w, __func__);
|
|
||||||
|
|
||||||
free(buf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
LLVMFuzzerInitialize(__unused int *argc, __unused char ***argv)
|
|
||||||
{
|
|
||||||
const struct options_table_entry *oe;
|
|
||||||
|
|
||||||
global_environ = environ_create();
|
|
||||||
global_options = options_create(NULL);
|
|
||||||
global_s_options = options_create(NULL);
|
|
||||||
global_w_options = options_create(NULL);
|
|
||||||
for (oe = options_table; oe->name != NULL; oe++) {
|
|
||||||
if (oe->scope & OPTIONS_TABLE_SERVER)
|
|
||||||
options_default(global_options, oe);
|
|
||||||
if (oe->scope & OPTIONS_TABLE_SESSION)
|
|
||||||
options_default(global_s_options, oe);
|
|
||||||
if (oe->scope & OPTIONS_TABLE_WINDOW)
|
|
||||||
options_default(global_w_options, oe);
|
|
||||||
}
|
|
||||||
|
|
||||||
libevent = osdep_event_init();
|
|
||||||
|
|
||||||
socket_path = xstrdup("dummy");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# tmux custom layout strings: "<checksum>,<sx>x<sy>,<x>,<y>{...}" / "[...]"
|
|
||||||
","
|
|
||||||
"x"
|
|
||||||
"{"
|
|
||||||
"}"
|
|
||||||
"["
|
|
||||||
"]"
|
|
||||||
"0"
|
|
||||||
"1"
|
|
||||||
"80x24,0,0"
|
|
||||||
"40x24,0,0"
|
|
||||||
"bb62,"
|
|
||||||
"cafe,"
|
|
||||||
",0,0,0"
|
|
||||||
",0,0{"
|
|
||||||
",0,0["
|
|
||||||
"@"
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
[libfuzzer]
|
|
||||||
max_len = 1024
|
|
||||||
@@ -59,35 +59,35 @@ static struct hooks_events hooks_events = TAILQ_HEAD_INITIALIZER(hooks_events);
|
|||||||
/* Insert one hook command list. */
|
/* Insert one hook command list. */
|
||||||
static struct cmdq_item *
|
static struct cmdq_item *
|
||||||
hooks_insert_one(struct cmdq_item *item, struct hooks_data *hd,
|
hooks_insert_one(struct cmdq_item *item, struct hooks_data *hd,
|
||||||
struct cmd_list *cmdlist, struct cmdq_state *state)
|
struct cmd_parse_tree *tree, struct cmdq_state *state)
|
||||||
{
|
{
|
||||||
struct cmdq_item *new_item;
|
struct cmdq_item *new_item;
|
||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
if (cmdlist == NULL)
|
if (tree == NULL)
|
||||||
return (item);
|
return (item);
|
||||||
if (log_get_level() != 0) {
|
if (log_get_level() != 0) {
|
||||||
s = cmd_list_print(cmdlist, 0);
|
s = cmd_parse_print(tree, 0);
|
||||||
log_debug("%s: hook %s is: %s", __func__, hd->name, s);
|
log_debug("%s: hook %s is: %s", __func__, hd->name, s);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
new_item = cmdq_get_command(cmdlist, state);
|
new_item = cmd_invoke_get(tree, state, NULL);
|
||||||
if (item != NULL)
|
if (item != NULL)
|
||||||
return (cmdq_insert_after(item, new_item));
|
return (cmdq_insert_after(item, new_item));
|
||||||
return (cmdq_append(NULL, new_item));
|
return (cmdq_append(NULL, new_item));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse a hook command. */
|
/* Parse a hook command. */
|
||||||
static struct cmd_parse_result *
|
static struct cmd_parse_tree *
|
||||||
hooks_parse(struct hooks_data *hd, struct cmd_find_state *fs,
|
hooks_parse(struct hooks_data *hd, struct cmd_find_state *fs,
|
||||||
const char *value)
|
const char *value, char **cause)
|
||||||
{
|
{
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
struct format_tree *ft;
|
struct format_tree *ft;
|
||||||
char *expanded;
|
char *expanded;
|
||||||
|
|
||||||
if (!hd->expand)
|
if (!hd->expand)
|
||||||
return (cmd_parse_from_string(value, NULL));
|
return (cmd_parse_from_string(value, NULL, cause));
|
||||||
|
|
||||||
ft = format_create_defaults(NULL, hd->client, fs->s, fs->wl, fs->wp);
|
ft = format_create_defaults(NULL, hd->client, fs->s, fs->wl, fs->wp);
|
||||||
if (hd->formats != NULL)
|
if (hd->formats != NULL)
|
||||||
@@ -95,9 +95,9 @@ hooks_parse(struct hooks_data *hd, struct cmd_find_state *fs,
|
|||||||
expanded = format_expand(ft, value);
|
expanded = format_expand(ft, value);
|
||||||
format_free(ft);
|
format_free(ft);
|
||||||
|
|
||||||
pr = cmd_parse_from_string(expanded, NULL);
|
tree = cmd_parse_from_string(expanded, NULL, cause);
|
||||||
free(expanded);
|
free(expanded);
|
||||||
return (pr);
|
return (tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Insert commands for a hook. */
|
/* Insert commands for a hook. */
|
||||||
@@ -109,9 +109,9 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd)
|
|||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
struct options_entry *o;
|
struct options_entry *o;
|
||||||
struct options_array_item *a;
|
struct options_array_item *a;
|
||||||
struct cmd_list *cmdlist;
|
struct cmd_parse_tree *tree;
|
||||||
const char *value;
|
const char *value;
|
||||||
struct cmd_parse_result *pr;
|
char *cause = NULL;
|
||||||
|
|
||||||
log_debug("%s: inserting hook %s", __func__, hd->name);
|
log_debug("%s: inserting hook %s", __func__, hd->name);
|
||||||
|
|
||||||
@@ -155,38 +155,35 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd)
|
|||||||
|
|
||||||
if (*hd->name == '@') {
|
if (*hd->name == '@') {
|
||||||
value = options_get_string(oo, hd->name);
|
value = options_get_string(oo, hd->name);
|
||||||
pr = hooks_parse(hd, &fs, value);
|
cause = NULL;
|
||||||
switch (pr->status) {
|
tree = hooks_parse(hd, &fs, value, &cause);
|
||||||
case CMD_PARSE_ERROR:
|
if (tree == NULL) {
|
||||||
log_debug("%s: can't parse hook %s: %s", __func__,
|
log_debug("%s: can't parse hook %s: %s", __func__,
|
||||||
hd->name, pr->error);
|
hd->name, cause);
|
||||||
free(pr->error);
|
free(cause);
|
||||||
break;
|
} else {
|
||||||
case CMD_PARSE_SUCCESS:
|
hooks_insert_one(item, hd, tree, state);
|
||||||
hooks_insert_one(item, hd, pr->cmdlist, state);
|
cmd_parse_free(tree);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
a = options_array_first(o);
|
a = options_array_first(o);
|
||||||
while (a != NULL) {
|
while (a != NULL) {
|
||||||
if (hd->expand) {
|
if (hd->expand) {
|
||||||
value = options_array_item_value(a)->string;
|
value = options_array_item_value(a)->string;
|
||||||
pr = hooks_parse(hd, &fs, value);
|
cause = NULL;
|
||||||
switch (pr->status) {
|
tree = hooks_parse(hd, &fs, value, &cause);
|
||||||
case CMD_PARSE_ERROR:
|
if (tree == NULL) {
|
||||||
if (pr->error != NULL) {
|
if (cause != NULL)
|
||||||
cmdq_error(item, "%s",
|
cmdq_error(item, "%s", cause);
|
||||||
pr->error);
|
free(cause);
|
||||||
}
|
} else {
|
||||||
break;
|
item = hooks_insert_one(item, hd, tree,
|
||||||
case CMD_PARSE_SUCCESS:
|
state);
|
||||||
item = hooks_insert_one(item, hd,
|
cmd_parse_free(tree);
|
||||||
pr->cmdlist, state);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cmdlist = options_array_item_value(a)->cmdlist;
|
tree = options_array_item_value(a)->cmd;
|
||||||
item = hooks_insert_one(item, hd, cmdlist,
|
item = hooks_insert_one(item, hd, tree,
|
||||||
state);
|
state);
|
||||||
}
|
}
|
||||||
a = options_array_next(a);
|
a = options_array_next(a);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: input.c,v 1.272 2026/09/21 10:22:31 nicm Exp $ */
|
/* $OpenBSD: input.c,v 1.271 2026/08/31 19:34:09 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -101,6 +101,7 @@ struct input_ctx {
|
|||||||
struct bufferevent *event;
|
struct bufferevent *event;
|
||||||
struct screen_write_ctx ctx;
|
struct screen_write_ctx ctx;
|
||||||
struct colour_palette *palette;
|
struct colour_palette *palette;
|
||||||
|
struct client *c;
|
||||||
|
|
||||||
struct input_cell cell;
|
struct input_cell cell;
|
||||||
struct input_cell old_cell;
|
struct input_cell old_cell;
|
||||||
@@ -873,7 +874,7 @@ input_restore_state(struct input_ctx *ictx)
|
|||||||
/* Initialise input parser. */
|
/* Initialise input parser. */
|
||||||
struct input_ctx *
|
struct input_ctx *
|
||||||
input_init(struct window_pane *wp, struct bufferevent *bev,
|
input_init(struct window_pane *wp, struct bufferevent *bev,
|
||||||
struct colour_palette *palette)
|
struct colour_palette *palette, struct client *c)
|
||||||
{
|
{
|
||||||
struct input_ctx *ictx;
|
struct input_ctx *ictx;
|
||||||
|
|
||||||
@@ -881,6 +882,7 @@ input_init(struct window_pane *wp, struct bufferevent *bev,
|
|||||||
ictx->wp = wp;
|
ictx->wp = wp;
|
||||||
ictx->event = bev;
|
ictx->event = bev;
|
||||||
ictx->palette = palette;
|
ictx->palette = palette;
|
||||||
|
ictx->c = c;
|
||||||
|
|
||||||
ictx->input_space = INPUT_BUF_START;
|
ictx->input_space = INPUT_BUF_START;
|
||||||
ictx->input_buf = xmalloc(INPUT_BUF_START);
|
ictx->input_buf = xmalloc(INPUT_BUF_START);
|
||||||
@@ -3423,9 +3425,15 @@ input_osc_52(struct input_ctx *ictx, const char *p)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
|
/* Popup window. */
|
||||||
|
if (ictx->c == NULL) {
|
||||||
free(out);
|
free(out);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
tty_set_selection(&ictx->c->tty, clip, out, outlen);
|
||||||
|
paste_add(NULL, out, outlen);
|
||||||
} else {
|
} else {
|
||||||
|
/* Normal window. */
|
||||||
screen_write_start_pane(&ctx, wp, NULL);
|
screen_write_start_pane(&ctx, wp, NULL);
|
||||||
screen_write_setselection(&ctx, clip, out, outlen);
|
screen_write_setselection(&ctx, clip, out, outlen);
|
||||||
screen_write_stop(&ctx);
|
screen_write_stop(&ctx);
|
||||||
|
|||||||
+31
-44
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: key-bindings.c,v 1.193 2026/09/21 20:45:48 nicm Exp $ */
|
/* $OpenBSD: key-bindings.c,v 1.192 2026/08/06 09:05:04 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -130,7 +130,7 @@ key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2)
|
|||||||
static void
|
static void
|
||||||
key_bindings_free(struct key_binding *bd)
|
key_bindings_free(struct key_binding *bd)
|
||||||
{
|
{
|
||||||
cmd_list_free(bd->cmdlist);
|
cmd_parse_free(bd->cmd);
|
||||||
free((void *)bd->note);
|
free((void *)bd->note);
|
||||||
free(bd);
|
free(bd);
|
||||||
}
|
}
|
||||||
@@ -222,16 +222,15 @@ key_bindings_next(__unused struct key_table *table, struct key_binding *bd)
|
|||||||
|
|
||||||
void
|
void
|
||||||
key_bindings_add(const char *name, key_code key, const char *note, int repeat,
|
key_bindings_add(const char *name, key_code key, const char *note, int repeat,
|
||||||
struct cmd_list *cmdlist)
|
struct cmd_parse_tree *cmd)
|
||||||
{
|
{
|
||||||
struct key_table *table;
|
struct key_table *table = key_bindings_get_table(name, 1);
|
||||||
struct key_binding *bd;
|
struct key_binding *bd;
|
||||||
|
const char *k;
|
||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
table = key_bindings_get_table(name, 1);
|
|
||||||
|
|
||||||
bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS);
|
bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS);
|
||||||
if (cmdlist == NULL) {
|
if (cmd == NULL) {
|
||||||
if (bd != NULL) {
|
if (bd != NULL) {
|
||||||
if (note != NULL) {
|
if (note != NULL) {
|
||||||
free((void *)bd->note);
|
free((void *)bd->note);
|
||||||
@@ -256,11 +255,11 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat,
|
|||||||
|
|
||||||
if (repeat)
|
if (repeat)
|
||||||
bd->flags |= KEY_BINDING_REPEAT;
|
bd->flags |= KEY_BINDING_REPEAT;
|
||||||
bd->cmdlist = cmdlist;
|
bd->cmd = cmd;
|
||||||
|
|
||||||
s = cmd_list_print(bd->cmdlist, 0);
|
k = key_string_lookup_key(bd->key, 1);
|
||||||
log_debug("%s: %#llx %s = %s", __func__, bd->key,
|
s = cmd_parse_print(bd->cmd, 0);
|
||||||
key_string_lookup_key(bd->key, 1), s);
|
log_debug("%s: %#llx %s = %s", __func__, bd->key, k, s);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,6 +268,7 @@ key_bindings_remove(const char *name, key_code key)
|
|||||||
{
|
{
|
||||||
struct key_table *table;
|
struct key_table *table;
|
||||||
struct key_binding *bd;
|
struct key_binding *bd;
|
||||||
|
const char *k;
|
||||||
|
|
||||||
table = key_bindings_get_table(name, 0);
|
table = key_bindings_get_table(name, 0);
|
||||||
if (table == NULL)
|
if (table == NULL)
|
||||||
@@ -278,8 +278,8 @@ key_bindings_remove(const char *name, key_code key)
|
|||||||
if (bd == NULL)
|
if (bd == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
log_debug("%s: %#llx %s", __func__, bd->key,
|
k = key_string_lookup_key(bd->key, 1);
|
||||||
key_string_lookup_key(bd->key, 1));
|
log_debug("%s: %#llx %s", __func__, bd->key, k);
|
||||||
|
|
||||||
RB_REMOVE(key_bindings, &table->key_bindings, bd);
|
RB_REMOVE(key_bindings, &table->key_bindings, bd);
|
||||||
key_bindings_free(bd);
|
key_bindings_free(bd);
|
||||||
@@ -311,9 +311,8 @@ key_bindings_reset(const char *name, key_code key)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_list_free(bd->cmdlist);
|
cmd_parse_free(bd->cmd);
|
||||||
bd->cmdlist = dd->cmdlist;
|
bd->cmd = cmd_parse_add_ref(dd->cmd);
|
||||||
bd->cmdlist->references++;
|
|
||||||
|
|
||||||
free((void *)bd->note);
|
free((void *)bd->note);
|
||||||
if (dd->note != NULL)
|
if (dd->note != NULL)
|
||||||
@@ -370,8 +369,7 @@ key_bindings_init_done(__unused struct cmdq_item *item, __unused void *data)
|
|||||||
if (bd->note != NULL)
|
if (bd->note != NULL)
|
||||||
new_bd->note = xstrdup(bd->note);
|
new_bd->note = xstrdup(bd->note);
|
||||||
new_bd->flags = bd->flags;
|
new_bd->flags = bd->flags;
|
||||||
new_bd->cmdlist = bd->cmdlist;
|
new_bd->cmd = cmd_parse_add_ref(bd->cmd);
|
||||||
new_bd->cmdlist->references++;
|
|
||||||
RB_INSERT(key_bindings, &table->default_key_bindings,
|
RB_INSERT(key_bindings, &table->default_key_bindings,
|
||||||
new_bd);
|
new_bd);
|
||||||
}
|
}
|
||||||
@@ -523,7 +521,7 @@ key_bindings_init(void)
|
|||||||
"bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }",
|
"bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }",
|
||||||
|
|
||||||
/* Mouse button 1 on border. */
|
/* Mouse button 1 on border. */
|
||||||
"bind -n MouseDown1Border { select-pane -t= }",
|
"bind -n MouseDown1Border { select-pane -M }",
|
||||||
|
|
||||||
/* Mouse button 1 drag on border. */
|
/* Mouse button 1 drag on border. */
|
||||||
"bind -n MouseDrag1Border { resize-pane -M }",
|
"bind -n MouseDrag1Border { resize-pane -M }",
|
||||||
@@ -733,50 +731,39 @@ key_bindings_init(void)
|
|||||||
"bind -Tcopy-mode-vi C-Up { send -X scroll-up }",
|
"bind -Tcopy-mode-vi C-Up { send -X scroll-up }",
|
||||||
"bind -Tcopy-mode-vi C-Down { send -X scroll-down }",
|
"bind -Tcopy-mode-vi C-Down { send -X scroll-down }",
|
||||||
};
|
};
|
||||||
|
|
||||||
u_int i;
|
u_int i;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmdq_item *new_item;
|
||||||
|
char *cause = NULL;
|
||||||
|
|
||||||
for (i = 0; i < nitems(defaults); i++) {
|
for (i = 0; i < nitems(defaults); i++) {
|
||||||
pr = cmd_parse_from_string(defaults[i], NULL);
|
tree = cmd_parse_from_string(defaults[i], NULL, &cause);
|
||||||
if (pr->status != CMD_PARSE_SUCCESS) {
|
if (tree == NULL)
|
||||||
log_debug("%s", pr->error);
|
|
||||||
fatalx("bad default key: %s", defaults[i]);
|
fatalx("bad default key: %s", defaults[i]);
|
||||||
}
|
|
||||||
cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL));
|
new_item = cmd_invoke_get(tree, NULL, NULL);
|
||||||
cmd_list_free(pr->cmdlist);
|
cmdq_append(NULL, new_item);
|
||||||
|
cmd_parse_free(tree);
|
||||||
}
|
}
|
||||||
cmdq_append(NULL, cmdq_get_callback(key_bindings_init_done, NULL));
|
cmdq_append(NULL, cmdq_get_callback(key_bindings_init_done, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum cmd_retval
|
|
||||||
key_bindings_read_only(struct cmdq_item *item, __unused void *data)
|
|
||||||
{
|
|
||||||
cmdq_error(item, "client is read-only");
|
|
||||||
return (CMD_RETURN_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct cmdq_item *
|
struct cmdq_item *
|
||||||
key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item,
|
key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item,
|
||||||
struct client *c, struct key_event *event, struct cmd_find_state *fs)
|
struct client *c, struct key_event *event, struct cmd_find_state *fs)
|
||||||
{
|
{
|
||||||
struct cmdq_item *new_item;
|
struct cmdq_item *new_item;
|
||||||
struct cmdq_state *new_state;
|
struct cmdq_state *new_state;
|
||||||
int readonly, flags = 0;
|
int flags = 0;
|
||||||
|
|
||||||
if (c == NULL || (~c->flags & CLIENT_READONLY))
|
|
||||||
readonly = 1;
|
|
||||||
else
|
|
||||||
readonly = cmd_list_all_have(bd->cmdlist, CMD_READONLY);
|
|
||||||
|
|
||||||
if (!readonly)
|
|
||||||
new_item = cmdq_get_callback(key_bindings_read_only, NULL);
|
|
||||||
else {
|
|
||||||
if (bd->flags & KEY_BINDING_REPEAT)
|
if (bd->flags & KEY_BINDING_REPEAT)
|
||||||
flags |= CMDQ_STATE_REPEAT;
|
flags |= CMDQ_STATE_REPEAT;
|
||||||
new_state = cmdq_new_state(fs, event, flags);
|
new_state = cmdq_new_state(fs, event, flags);
|
||||||
new_item = cmdq_get_command(bd->cmdlist, new_state);
|
|
||||||
|
new_item = cmd_invoke_get(bd->cmd, new_state, NULL);
|
||||||
cmdq_free_state(new_state);
|
cmdq_free_state(new_state);
|
||||||
}
|
|
||||||
if (item != NULL)
|
if (item != NULL)
|
||||||
new_item = cmdq_insert_after(item, new_item);
|
new_item = cmdq_insert_after(item, new_item);
|
||||||
else
|
else
|
||||||
|
|||||||
+136
-960
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: layout.c,v 1.101 2026/09/20 08:42:46 nicm Exp $ */
|
/* $OpenBSD: layout.c,v 1.98 2026/08/25 18:38:05 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -106,7 +106,7 @@ layout_free_cell(struct layout_cell *lc, int only_nodes)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LAYOUT_WINDOWPANE:
|
case LAYOUT_WINDOWPANE:
|
||||||
if (lc->wp != NULL && lc->wp->layout_cell != NULL) {
|
if (lc->wp != NULL) {
|
||||||
lc->wp->layout_cell->parent = NULL;
|
lc->wp->layout_cell->parent = NULL;
|
||||||
lc->wp->layout_cell = NULL;
|
lc->wp->layout_cell = NULL;
|
||||||
}
|
}
|
||||||
@@ -233,6 +233,29 @@ layout_make_node(struct layout_cell *lc, enum layout_type type)
|
|||||||
lc->wp = NULL;
|
lc->wp = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fix z-indexes. */
|
||||||
|
void
|
||||||
|
layout_fix_zindexes(struct window *w, struct layout_cell *lc)
|
||||||
|
{
|
||||||
|
struct layout_cell *lcchild;
|
||||||
|
|
||||||
|
if (lc == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (lc->type) {
|
||||||
|
case LAYOUT_WINDOWPANE:
|
||||||
|
TAILQ_INSERT_TAIL(&w->z_index, lc->wp, zentry);
|
||||||
|
break;
|
||||||
|
case LAYOUT_LEFTRIGHT:
|
||||||
|
case LAYOUT_TOPBOTTOM:
|
||||||
|
TAILQ_FOREACH(lcchild, &lc->cells, entry)
|
||||||
|
layout_fix_zindexes(w, lcchild);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
fatalx("bad layout type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
layout_cell_is_tiled(struct layout_cell *lc)
|
layout_cell_is_tiled(struct layout_cell *lc)
|
||||||
{
|
{
|
||||||
@@ -242,7 +265,7 @@ layout_cell_is_tiled(struct layout_cell *lc)
|
|||||||
return is_leaf && !is_floating;
|
return is_leaf && !is_floating;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static int
|
||||||
layout_cell_has_tiled_child(struct layout_cell *lc)
|
layout_cell_has_tiled_child(struct layout_cell *lc)
|
||||||
{
|
{
|
||||||
struct layout_cell *lcchild;
|
struct layout_cell *lcchild;
|
||||||
@@ -469,6 +492,7 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
|||||||
sx = PANE_MINIMUM;
|
sx = PANE_MINIMUM;
|
||||||
else
|
else
|
||||||
sx = sx - sb_w - sb_pad;
|
sx = sx - sb_w - sb_pad;
|
||||||
|
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||||
}
|
}
|
||||||
|
|
||||||
window_pane_resize(wp, sx, sy);
|
window_pane_resize(wp, sx, sy);
|
||||||
@@ -476,32 +500,27 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
|||||||
if (wp->xoff != old_xoff ||
|
if (wp->xoff != old_xoff ||
|
||||||
wp->yoff != old_yoff ||
|
wp->yoff != old_yoff ||
|
||||||
wp->sx != old_sx ||
|
wp->sx != old_sx ||
|
||||||
wp->sy != old_sy) {
|
wp->sy != old_sy)
|
||||||
if (window_pane_scrollbar_reserve(wp))
|
|
||||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
|
||||||
changed = 1;
|
changed = 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (changed)
|
if (changed)
|
||||||
redraw_invalidate_scene(w);
|
redraw_invalidate_scene(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Count the number of available cells in a layout. */
|
/* Count the number of available cells in a layout. */
|
||||||
u_int
|
u_int
|
||||||
layout_count_cells(struct layout_cell *lc, int with_floating)
|
layout_count_cells(struct layout_cell *lc)
|
||||||
{
|
{
|
||||||
struct layout_cell *lcchild;
|
struct layout_cell *lcchild;
|
||||||
u_int count = 0;
|
u_int count = 0;
|
||||||
|
|
||||||
switch (lc->type) {
|
switch (lc->type) {
|
||||||
case LAYOUT_WINDOWPANE:
|
case LAYOUT_WINDOWPANE:
|
||||||
if (lc->flags & LAYOUT_CELL_FLOATING && !with_floating)
|
|
||||||
return 0;
|
|
||||||
return (1);
|
return (1);
|
||||||
case LAYOUT_LEFTRIGHT:
|
case LAYOUT_LEFTRIGHT:
|
||||||
case LAYOUT_TOPBOTTOM:
|
case LAYOUT_TOPBOTTOM:
|
||||||
TAILQ_FOREACH(lcchild, &lc->cells, entry)
|
TAILQ_FOREACH(lcchild, &lc->cells, entry)
|
||||||
count += layout_count_cells(lcchild, with_floating);
|
count += layout_count_cells(lcchild);
|
||||||
return (count);
|
return (count);
|
||||||
default:
|
default:
|
||||||
fatalx("bad layout type");
|
fatalx("bad layout type");
|
||||||
@@ -702,7 +721,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
|
|||||||
/* If no parent, this is the last pane in a window. */
|
/* If no parent, this is the last pane in a window. */
|
||||||
lcparent = lc->parent;
|
lcparent = lc->parent;
|
||||||
if (lcparent == NULL) {
|
if (lcparent == NULL) {
|
||||||
if (*lcroot == lc)
|
if (lc->wp != NULL)
|
||||||
*lcroot = NULL;
|
*lcroot = NULL;
|
||||||
layout_free_cell(lc, 0);
|
layout_free_cell(lc, 0);
|
||||||
return;
|
return;
|
||||||
@@ -770,49 +789,6 @@ layout_free(struct window *w, int only_nodes)
|
|||||||
layout_free_cell(w->layout_root, only_nodes);
|
layout_free_cell(w->layout_root, only_nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Move and resize floating panes so they stay inside the window. */
|
|
||||||
static void
|
|
||||||
layout_clamp_floating_panes(struct window *w, u_int sx, u_int sy)
|
|
||||||
{
|
|
||||||
struct window_pane *wp;
|
|
||||||
struct layout_cell *lc;
|
|
||||||
u_int pad, avail, csx, csy;
|
|
||||||
|
|
||||||
TAILQ_FOREACH(wp, &w->z_index, zentry) {
|
|
||||||
lc = wp->layout_cell;
|
|
||||||
if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING))
|
|
||||||
continue;
|
|
||||||
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
|
|
||||||
pad = 0;
|
|
||||||
else
|
|
||||||
pad = 1;
|
|
||||||
|
|
||||||
csx = lc->g.sx;
|
|
||||||
avail = (sx > 2 * pad) ? sx - 2 * pad : 0;
|
|
||||||
if (csx > avail)
|
|
||||||
csx = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
|
||||||
csy = lc->g.sy;
|
|
||||||
avail = (sy > 2 * pad) ? sy - 2 * pad : 0;
|
|
||||||
if (csy > avail)
|
|
||||||
csy = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
|
||||||
if (csx != lc->g.sx || csy != lc->g.sy)
|
|
||||||
layout_set_size(lc, csx, csy, lc->g.xoff, lc->g.yoff);
|
|
||||||
|
|
||||||
if (lc->g.xoff + lc->g.sx + pad > sx) {
|
|
||||||
if (lc->g.sx + 2 * pad >= sx)
|
|
||||||
lc->g.xoff = pad;
|
|
||||||
else
|
|
||||||
lc->g.xoff = sx - lc->g.sx - pad;
|
|
||||||
}
|
|
||||||
if (lc->g.yoff + lc->g.sy + pad > sy) {
|
|
||||||
if (lc->g.sy + 2 * pad >= sy)
|
|
||||||
lc->g.yoff = pad;
|
|
||||||
else
|
|
||||||
lc->g.yoff = sy - lc->g.sy - pad;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Resize the entire layout after window resize. */
|
/* Resize the entire layout after window resize. */
|
||||||
void
|
void
|
||||||
layout_resize(struct window *w, u_int sx, u_int sy)
|
layout_resize(struct window *w, u_int sx, u_int sy)
|
||||||
@@ -833,11 +809,8 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
|||||||
* out proportionately - this should leave the layout fitting the new
|
* out proportionately - this should leave the layout fitting the new
|
||||||
* window size.
|
* window size.
|
||||||
*/
|
*/
|
||||||
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) {
|
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING))
|
||||||
layout_clamp_floating_panes(w, sx, sy);
|
|
||||||
layout_fix_panes(w, NULL);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
xchange = sx - lc->g.sx;
|
xchange = sx - lc->g.sx;
|
||||||
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
|
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
|
||||||
if (xchange < 0 && xchange < -xlimit)
|
if (xchange < 0 && xchange < -xlimit)
|
||||||
@@ -867,7 +840,6 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
|||||||
|
|
||||||
/* Fix cell offsets. */
|
/* Fix cell offsets. */
|
||||||
layout_fix_offsets(w);
|
layout_fix_offsets(w);
|
||||||
layout_clamp_floating_panes(w, sx, sy);
|
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1730,7 +1702,7 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
|||||||
enum pane_lines lines, struct window *w, struct layout_geometry *lg,
|
enum pane_lines lines, struct window *w, struct layout_geometry *lg,
|
||||||
char **cause)
|
char **cause)
|
||||||
{
|
{
|
||||||
int sx, sy, ox, oy, pad;
|
int sx, sy, ox, oy;
|
||||||
char *error = NULL;
|
char *error = NULL;
|
||||||
|
|
||||||
sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx;
|
sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx;
|
||||||
@@ -1779,20 +1751,12 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window_has_floating_panes(w)) {
|
|
||||||
w->last_new_pane_x = 0;
|
|
||||||
w->last_new_pane_y = 0;
|
|
||||||
}
|
|
||||||
if (ox == INT_MAX) {
|
if (ox == INT_MAX) {
|
||||||
if (w->last_new_pane_x == 0)
|
if (w->last_new_pane_x == 0)
|
||||||
ox = 4;
|
ox = 4;
|
||||||
else {
|
else {
|
||||||
if (lines != PANE_LINES_NONE)
|
|
||||||
pad = 1;
|
|
||||||
else
|
|
||||||
pad = 0;
|
|
||||||
ox = w->last_new_pane_x + 4;
|
ox = w->last_new_pane_x + 4;
|
||||||
if (ox + sx + pad > (int)w->sx)
|
if (w->last_new_pane_x > w->sx)
|
||||||
ox = 4;
|
ox = 4;
|
||||||
}
|
}
|
||||||
w->last_new_pane_x = ox;
|
w->last_new_pane_x = ox;
|
||||||
@@ -1803,12 +1767,8 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
|||||||
if (w->last_new_pane_y == 0)
|
if (w->last_new_pane_y == 0)
|
||||||
oy = 2;
|
oy = 2;
|
||||||
else {
|
else {
|
||||||
if (lines != PANE_LINES_NONE)
|
|
||||||
pad = 1;
|
|
||||||
else
|
|
||||||
pad = 0;
|
|
||||||
oy = w->last_new_pane_y + 2;
|
oy = w->last_new_pane_y + 2;
|
||||||
if (oy + sy + pad > (int)w->sy)
|
if (w->last_new_pane_y > w->sy)
|
||||||
oy = 2;
|
oy = 2;
|
||||||
}
|
}
|
||||||
w->last_new_pane_y = oy;
|
w->last_new_pane_y = oy;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: menu.c,v 1.71 2026/09/21 12:14:32 nicm Exp $ */
|
/* $OpenBSD: menu.c,v 1.70 2026/08/17 07:33:55 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -51,18 +51,6 @@ struct menu_data {
|
|||||||
void *data;
|
void *data;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
|
||||||
menu_get_size(struct menu *menu, enum box_lines lines, u_int *sx, u_int *sy)
|
|
||||||
{
|
|
||||||
if (lines == BOX_LINES_NONE) {
|
|
||||||
*sx = menu->item_width + 2;
|
|
||||||
*sy = menu->count;
|
|
||||||
} else {
|
|
||||||
*sx = menu->width + 4;
|
|
||||||
*sy = menu->count + 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
menu_add_items(struct menu *menu, const struct menu_item *items,
|
menu_add_items(struct menu *menu, const struct menu_item *items,
|
||||||
struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
|
struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
|
||||||
@@ -154,8 +142,6 @@ menu_add_item(struct menu *menu, const struct menu_item *item,
|
|||||||
width = format_width(new_item->name);
|
width = format_width(new_item->name);
|
||||||
if (*new_item->name == '-')
|
if (*new_item->name == '-')
|
||||||
width--;
|
width--;
|
||||||
if (width > menu->item_width)
|
|
||||||
menu->item_width = width;
|
|
||||||
if (width > menu->width)
|
if (width > menu->width)
|
||||||
menu->width = width;
|
menu->width = width;
|
||||||
}
|
}
|
||||||
@@ -252,7 +238,7 @@ menu_update(struct menu_data *md)
|
|||||||
screen_write_clearscreen(&ctx, 8);
|
screen_write_clearscreen(&ctx, 8);
|
||||||
|
|
||||||
if (md->border_lines != BOX_LINES_NONE) {
|
if (md->border_lines != BOX_LINES_NONE) {
|
||||||
screen_write_box(&ctx, menu_width(md), menu_height(md),
|
screen_write_box(&ctx, menu->width + 4, menu->count + 2,
|
||||||
md->border_lines, &md->border_style_gc, menu->title);
|
md->border_lines, &md->border_style_gc, menu->title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,13 +286,11 @@ menu_destroy(struct window *w)
|
|||||||
void
|
void
|
||||||
menu_get_cursor(struct menu_data *md, u_int *cx, u_int *cy)
|
menu_get_cursor(struct menu_data *md, u_int *cx, u_int *cy)
|
||||||
{
|
{
|
||||||
u_int border = (md->border_lines != BOX_LINES_NONE);
|
*cx = md->px + 2;
|
||||||
|
|
||||||
*cx = md->px + 1 + border;
|
|
||||||
if (md->choice == -1)
|
if (md->choice == -1)
|
||||||
*cy = md->py;
|
*cy = md->py;
|
||||||
else
|
else
|
||||||
*cy = md->py + border + md->choice;
|
*cy = md->py + 1 + md->choice;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct screen *
|
struct screen *
|
||||||
@@ -318,19 +302,13 @@ menu_screen(struct menu_data *md)
|
|||||||
u_int
|
u_int
|
||||||
menu_width(struct menu_data *md)
|
menu_width(struct menu_data *md)
|
||||||
{
|
{
|
||||||
u_int sx, sy;
|
return (md->menu->width + 4);
|
||||||
|
|
||||||
menu_get_size(md->menu, md->border_lines, &sx, &sy);
|
|
||||||
return (sx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u_int
|
u_int
|
||||||
menu_height(struct menu_data *md)
|
menu_height(struct menu_data *md)
|
||||||
{
|
{
|
||||||
u_int sx, sy;
|
return (md->menu->count + 2);
|
||||||
|
|
||||||
menu_get_size(md->menu, md->border_lines, &sx, &sy);
|
|
||||||
return (sy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u_int
|
u_int
|
||||||
@@ -355,12 +333,12 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
|||||||
int move;
|
int move;
|
||||||
const char *name = NULL;
|
const char *name = NULL;
|
||||||
const struct menu_item *item;
|
const struct menu_item *item;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmdq_item *new_item;
|
||||||
struct key_event saved_event;
|
struct key_event saved_event;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
enum cmd_parse_status status;
|
|
||||||
char *error;
|
char *error;
|
||||||
key_code key;
|
key_code key;
|
||||||
u_int border;
|
|
||||||
|
|
||||||
if (KEYC_IS_MOUSE(event->key)) {
|
if (KEYC_IS_MOUSE(event->key)) {
|
||||||
/*
|
/*
|
||||||
@@ -376,10 +354,10 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
|||||||
return (1);
|
return (1);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
border = (md->border_lines != BOX_LINES_NONE);
|
if (m->x < md->px ||
|
||||||
if (m->x < md->px || m->x >= md->px + menu_width(md) ||
|
m->x > md->px + 4 + menu->width ||
|
||||||
m->y < md->py + border ||
|
m->y < md->py + 1 ||
|
||||||
m->y >= md->py + border + n) {
|
m->y > md->py + 1 + n - 1) {
|
||||||
if (~md->flags & MENU_STAYOPEN) {
|
if (~md->flags & MENU_STAYOPEN) {
|
||||||
if (!move && MOUSE_RELEASE(m->b))
|
if (!move && MOUSE_RELEASE(m->b))
|
||||||
return (1);
|
return (1);
|
||||||
@@ -402,7 +380,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
|||||||
if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
|
if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
|
||||||
goto chosen;
|
goto chosen;
|
||||||
}
|
}
|
||||||
md->choice = m->y - (md->py + border);
|
md->choice = m->y - (md->py + 1);
|
||||||
if (md->choice != old)
|
if (md->choice != old)
|
||||||
server_redraw_window_menu(md->w);
|
server_redraw_window_menu(md->w);
|
||||||
return (0);
|
return (0);
|
||||||
@@ -551,11 +529,14 @@ chosen:
|
|||||||
} else
|
} else
|
||||||
event = NULL;
|
event = NULL;
|
||||||
state = cmdq_new_state(&md->fs, event, 0);
|
state = cmdq_new_state(&md->fs, event, 0);
|
||||||
|
tree = cmd_parse_from_string(item->command, NULL, &error);
|
||||||
status = cmd_parse_and_append(item->command, NULL, c, state, &error);
|
if (tree == NULL) {
|
||||||
if (status == CMD_PARSE_ERROR) {
|
|
||||||
cmdq_append(c, cmdq_get_error(error));
|
cmdq_append(c, cmdq_get_error(error));
|
||||||
free(error);
|
free(error);
|
||||||
|
} else {
|
||||||
|
new_item = cmd_invoke_get(tree, state, NULL);
|
||||||
|
cmdq_append(c, new_item);
|
||||||
|
cmd_parse_free(tree);
|
||||||
}
|
}
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
|
|
||||||
@@ -569,16 +550,20 @@ menu_resize(struct menu_data *md, struct window *w)
|
|||||||
|
|
||||||
if (md == NULL)
|
if (md == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
nx = md->px;
|
nx = md->px;
|
||||||
ny = md->py;
|
ny = md->py;
|
||||||
|
|
||||||
menu_get_size(md->menu, md->border_lines, &sx, &sy);
|
sx = md->menu->width + 4;
|
||||||
|
sy = md->menu->count + 2;
|
||||||
|
|
||||||
if (nx + sx > w->sx) {
|
if (nx + sx > w->sx) {
|
||||||
if (w->sx <= sx)
|
if (w->sx <= sx)
|
||||||
nx = 0;
|
nx = 0;
|
||||||
else
|
else
|
||||||
nx = w->sx - sx;
|
nx = w->sx - sx;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ny + sy > w->sy) {
|
if (ny + sy > w->sy) {
|
||||||
if (w->sy <= sy)
|
if (w->sy <= sy)
|
||||||
ny = 0;
|
ny = 0;
|
||||||
@@ -610,9 +595,8 @@ menu_display(struct menu *menu, int flags, int starting_choice,
|
|||||||
w = fs->w;
|
w = fs->w;
|
||||||
o = w->options;
|
o = w->options;
|
||||||
|
|
||||||
if (lines == BOX_LINES_DEFAULT)
|
sx = menu->width + 4;
|
||||||
lines = options_get_number(o, "menu-border-lines");
|
sy = menu->count + 2;
|
||||||
menu_get_size(menu, lines, &sx, &sy);
|
|
||||||
if (sx >= w->sx)
|
if (sx >= w->sx)
|
||||||
px = 0;
|
px = 0;
|
||||||
else if (px + sx > w->sx)
|
else if (px + sx > w->sx)
|
||||||
@@ -624,6 +608,9 @@ menu_display(struct menu *menu, int flags, int starting_choice,
|
|||||||
w->menu_last_px = px;
|
w->menu_last_px = px;
|
||||||
w->menu_last_py = py;
|
w->menu_last_py = py;
|
||||||
|
|
||||||
|
if (lines == BOX_LINES_DEFAULT)
|
||||||
|
lines = options_get_number(o, "menu-border-lines");
|
||||||
|
|
||||||
md = xcalloc(1, sizeof *md);
|
md = xcalloc(1, sizeof *md);
|
||||||
md->w = w;
|
md->w = w;
|
||||||
md->flags = flags;
|
md->flags = flags;
|
||||||
|
|||||||
+14
-14
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: mode-tree.c,v 1.102 2026/09/21 12:14:32 nicm Exp $ */
|
/* $OpenBSD: mode-tree.c,v 1.101 2026/08/05 07:50:21 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1375,14 +1375,12 @@ static void
|
|||||||
mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
|
mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
|
||||||
u_int y, int outside)
|
u_int y, int outside)
|
||||||
{
|
{
|
||||||
struct window *w = mtd->wp->window;
|
|
||||||
struct mode_tree_item *mti;
|
struct mode_tree_item *mti;
|
||||||
struct menu *menu;
|
struct menu *menu;
|
||||||
const struct menu_item *items;
|
const struct menu_item *items;
|
||||||
struct mode_tree_menu *mtm;
|
struct mode_tree_menu *mtm;
|
||||||
char *title;
|
char *title;
|
||||||
enum box_lines lines;
|
u_int line;
|
||||||
u_int line, sx, sy;
|
|
||||||
|
|
||||||
if (mtd->offset + y > mtd->line_size - 1)
|
if (mtd->offset + y > mtd->line_size - 1)
|
||||||
line = mtd->current;
|
line = mtd->current;
|
||||||
@@ -1407,17 +1405,14 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
|
|||||||
mtm->line = line;
|
mtm->line = line;
|
||||||
mtd->references++;
|
mtd->references++;
|
||||||
|
|
||||||
lines = options_get_number(w->options, "menu-border-lines");
|
if (x >= (menu->width + 4) / 2)
|
||||||
menu_get_size(menu, lines, &sx, &sy);
|
x -= (menu->width + 4) / 2;
|
||||||
if (x >= sx / 2)
|
|
||||||
x -= sx / 2;
|
|
||||||
else
|
else
|
||||||
x = 0;
|
x = 0;
|
||||||
x += mtd->wp->xoff;
|
x += mtd->wp->xoff;
|
||||||
y += mtd->wp->yoff;
|
y += mtd->wp->yoff;
|
||||||
|
if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL,
|
||||||
if (menu_display(menu, 0, 0, NULL, x, y, c, lines, NULL, NULL, NULL,
|
NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0) {
|
||||||
NULL, mode_tree_menu_callback, mtm) != 0) {
|
|
||||||
mode_tree_remove_ref(mtd);
|
mode_tree_remove_ref(mtd);
|
||||||
free(mtm);
|
free(mtm);
|
||||||
menu_free(menu);
|
menu_free(menu);
|
||||||
@@ -1820,20 +1815,25 @@ void
|
|||||||
mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
|
mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
|
||||||
const char *template, const char *name)
|
const char *template, const char *name)
|
||||||
{
|
{
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmdq_item *new_item;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
char *command, *error;
|
char *command, *error;
|
||||||
enum cmd_parse_status status;
|
|
||||||
|
|
||||||
command = cmd_template_replace(template, name, 1);
|
command = cmd_template_replace(template, name, 1);
|
||||||
if (command != NULL && *command != '\0') {
|
if (command != NULL && *command != '\0') {
|
||||||
state = cmdq_new_state(fs, NULL, 0);
|
state = cmdq_new_state(fs, NULL, 0);
|
||||||
status = cmd_parse_and_append(command, NULL, c, state, &error);
|
tree = cmd_parse_from_string(command, NULL, &error);
|
||||||
if (status == CMD_PARSE_ERROR) {
|
if (tree == NULL) {
|
||||||
if (c != NULL) {
|
if (c != NULL) {
|
||||||
*error = toupper((u_char)*error);
|
*error = toupper((u_char)*error);
|
||||||
status_message_set(c, -1, 1, 0, 0, "%s", error);
|
status_message_set(c, -1, 1, 0, 0, "%s", error);
|
||||||
}
|
}
|
||||||
free(error);
|
free(error);
|
||||||
|
} else {
|
||||||
|
new_item = cmd_invoke_get(tree, state, NULL);
|
||||||
|
cmdq_append(c, new_item);
|
||||||
|
cmd_parse_free(tree);
|
||||||
}
|
}
|
||||||
cmdq_free_state(state);
|
cmdq_free_state(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: monitor.c,v 1.8 2026/09/07 10:15:25 nicm Exp $ */
|
/* $OpenBSD: monitor.c,v 1.7 2026/07/27 19:15:58 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -614,10 +614,8 @@ monitor_parse(const char *value, char **name, enum monitor_type *type, int *id,
|
|||||||
*type = MONITOR_ALL_WINDOWS;
|
*type = MONITOR_ALL_WINDOWS;
|
||||||
else if (sscanf(what, "@%d", id) == 1 && *id >= 0)
|
else if (sscanf(what, "@%d", id) == 1 && *id >= 0)
|
||||||
*type = MONITOR_WINDOW;
|
*type = MONITOR_WINDOW;
|
||||||
else if (*what == '\0')
|
|
||||||
*type = MONITOR_SESSION;
|
|
||||||
else
|
else
|
||||||
goto fail;
|
*type = MONITOR_SESSION;
|
||||||
*name = xstrdup(copy);
|
*name = xstrdup(copy);
|
||||||
*format = xstrdup(split);
|
*format = xstrdup(split);
|
||||||
|
|
||||||
|
|||||||
+33
-8
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: options-table.c,v 1.247 2026/09/21 10:22:31 nicm Exp $ */
|
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -76,10 +76,9 @@ 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", "none",
|
"single", "double", "heavy", "simple", "number", "spaces", "none", NULL
|
||||||
"rounded", NULL
|
|
||||||
};
|
};
|
||||||
static const char *options_table_menu_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
|
||||||
};
|
};
|
||||||
static const char *options_table_set_clipboard_list[] = {
|
static const char *options_table_set_clipboard_list[] = {
|
||||||
@@ -92,7 +91,7 @@ static const char *options_table_window_size_list[] = {
|
|||||||
"largest", "smallest", "manual", "latest", NULL
|
"largest", "smallest", "manual", "latest", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_remain_on_exit_list[] = {
|
static const char *options_table_remain_on_exit_list[] = {
|
||||||
"off", "on", "failed", "key", "failed-key", NULL
|
"off", "on", "failed", "key", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_destroy_unattached_list[] = {
|
static const char *options_table_destroy_unattached_list[] = {
|
||||||
"off", "on", "keep-last", "keep-group", NULL
|
"off", "on", "keep-last", "keep-group", NULL
|
||||||
@@ -493,7 +492,7 @@ const struct options_table_entry options_table[] = {
|
|||||||
{ .name = "menu-border-lines",
|
{ .name = "menu-border-lines",
|
||||||
.type = OPTIONS_TABLE_CHOICE,
|
.type = OPTIONS_TABLE_CHOICE,
|
||||||
.scope = OPTIONS_TABLE_WINDOW,
|
.scope = OPTIONS_TABLE_WINDOW,
|
||||||
.choices = options_table_menu_border_lines_list,
|
.choices = options_table_popup_border_lines_list,
|
||||||
.default_num = BOX_LINES_SINGLE,
|
.default_num = BOX_LINES_SINGLE,
|
||||||
.text = "Type of characters used to draw menu border lines. Some of "
|
.text = "Type of characters used to draw menu border lines. Some of "
|
||||||
"these are only supported on terminals with UTF-8 support."
|
"these are only supported on terminals with UTF-8 support."
|
||||||
@@ -1666,14 +1665,40 @@ const struct options_table_entry options_table[] = {
|
|||||||
.text = "Pane scrollbar position."
|
.text = "Pane scrollbar position."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{ .name = "popup-style",
|
||||||
|
.type = OPTIONS_TABLE_STRING,
|
||||||
|
.scope = OPTIONS_TABLE_WINDOW,
|
||||||
|
.default_str = "bg=themedarkgrey,fg=themewhite",
|
||||||
|
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||||
|
.separator = ",",
|
||||||
|
.text = "Default style of popups."
|
||||||
|
},
|
||||||
|
|
||||||
|
{ .name = "popup-border-style",
|
||||||
|
.type = OPTIONS_TABLE_STRING,
|
||||||
|
.scope = OPTIONS_TABLE_WINDOW,
|
||||||
|
.default_str = "bg=themedarkgrey,fg=themelightgrey",
|
||||||
|
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||||
|
.separator = ",",
|
||||||
|
.text = "Default style of popup borders."
|
||||||
|
},
|
||||||
|
|
||||||
|
{ .name = "popup-border-lines",
|
||||||
|
.type = OPTIONS_TABLE_CHOICE,
|
||||||
|
.scope = OPTIONS_TABLE_WINDOW,
|
||||||
|
.choices = options_table_popup_border_lines_list,
|
||||||
|
.default_num = BOX_LINES_SINGLE,
|
||||||
|
.text = "Type of characters used to draw popup border lines. Some of "
|
||||||
|
"these are only supported on terminals with UTF-8 support."
|
||||||
|
},
|
||||||
|
|
||||||
{ .name = "remain-on-exit",
|
{ .name = "remain-on-exit",
|
||||||
.type = OPTIONS_TABLE_CHOICE,
|
.type = OPTIONS_TABLE_CHOICE,
|
||||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||||
.choices = options_table_remain_on_exit_list,
|
.choices = options_table_remain_on_exit_list,
|
||||||
.default_num = 0,
|
.default_num = 0,
|
||||||
.text = "Whether panes should remain ('on'), remain until a key is "
|
.text = "Whether panes should remain ('on'), remain until a key is "
|
||||||
"pressed after any exit ('key') or after a failure "
|
"pressed ('key') or be automatically killed ('off' or "
|
||||||
"('failed-key'), or be automatically killed ('off' or "
|
|
||||||
"'failed') when the program inside exits."
|
"'failed') when the program inside exits."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -180,8 +180,8 @@ options_value_free(struct options_entry *o, union options_value *ov)
|
|||||||
{
|
{
|
||||||
if (OPTIONS_IS_STRING(o))
|
if (OPTIONS_IS_STRING(o))
|
||||||
free(ov->string);
|
free(ov->string);
|
||||||
if (OPTIONS_IS_COMMAND(o) && ov->cmdlist != NULL)
|
if (OPTIONS_IS_COMMAND(o))
|
||||||
cmd_list_free(ov->cmdlist);
|
cmd_parse_free(ov->cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
@@ -191,7 +191,7 @@ options_value_to_string(struct options_entry *o, union options_value *ov,
|
|||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
if (OPTIONS_IS_COMMAND(o))
|
if (OPTIONS_IS_COMMAND(o))
|
||||||
return (cmd_list_print(ov->cmdlist, 0));
|
return (cmd_parse_print(ov->cmd, 0));
|
||||||
if (OPTIONS_IS_NUMBER(o)) {
|
if (OPTIONS_IS_NUMBER(o)) {
|
||||||
switch (o->tableentry->type) {
|
switch (o->tableentry->type) {
|
||||||
case OPTIONS_TABLE_NUMBER:
|
case OPTIONS_TABLE_NUMBER:
|
||||||
@@ -315,8 +315,8 @@ options_default(struct options *oo, const struct options_table_entry *oe)
|
|||||||
struct options_entry *o;
|
struct options_entry *o;
|
||||||
union options_value *ov;
|
union options_value *ov;
|
||||||
char key[32];
|
char key[32];
|
||||||
|
char *error = NULL;
|
||||||
u_int i;
|
u_int i;
|
||||||
struct cmd_parse_result *pr;
|
|
||||||
|
|
||||||
o = options_empty(oo, oe);
|
o = options_empty(oo, oe);
|
||||||
ov = &o->value;
|
ov = &o->value;
|
||||||
@@ -338,15 +338,9 @@ options_default(struct options *oo, const struct options_table_entry *oe)
|
|||||||
ov->string = xstrdup(oe->default_str);
|
ov->string = xstrdup(oe->default_str);
|
||||||
break;
|
break;
|
||||||
case OPTIONS_TABLE_COMMAND:
|
case OPTIONS_TABLE_COMMAND:
|
||||||
pr = cmd_parse_from_string(oe->default_str, NULL);
|
ov->cmd = cmd_parse_from_string(oe->default_str, NULL, &error);
|
||||||
switch (pr->status) {
|
if (ov->cmd == NULL)
|
||||||
case CMD_PARSE_ERROR:
|
fatalx("bad default option %s", oe->name);
|
||||||
free(pr->error);
|
|
||||||
break;
|
|
||||||
case CMD_PARSE_SUCCESS:
|
|
||||||
ov->cmdlist = pr->cmdlist;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ov->number = oe->default_num;
|
ov->number = oe->default_num;
|
||||||
@@ -549,8 +543,9 @@ options_array_set(struct options_entry *o, const char *key, const char *value,
|
|||||||
{
|
{
|
||||||
struct options_array_item *a;
|
struct options_array_item *a;
|
||||||
char *new, *new_key;
|
char *new, *new_key;
|
||||||
struct cmd_parse_result *pr;
|
|
||||||
long long number;
|
long long number;
|
||||||
|
struct cmd_parse_tree *tree;
|
||||||
|
char *error = NULL;
|
||||||
|
|
||||||
if (!OPTIONS_IS_ARRAY(o)) {
|
if (!OPTIONS_IS_ARRAY(o)) {
|
||||||
if (cause != NULL)
|
if (cause != NULL)
|
||||||
@@ -574,17 +569,14 @@ options_array_set(struct options_entry *o, const char *key, const char *value,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (OPTIONS_IS_COMMAND(o)) {
|
if (OPTIONS_IS_COMMAND(o)) {
|
||||||
pr = cmd_parse_from_string(value, NULL);
|
tree = cmd_parse_from_string(value, NULL, &error);
|
||||||
switch (pr->status) {
|
if (tree == NULL) {
|
||||||
case CMD_PARSE_ERROR:
|
|
||||||
if (cause != NULL)
|
if (cause != NULL)
|
||||||
*cause = pr->error;
|
*cause = error;
|
||||||
else
|
else
|
||||||
free(pr->error);
|
free(error);
|
||||||
free(new_key);
|
free(new_key);
|
||||||
return (-1);
|
return (-1);
|
||||||
case CMD_PARSE_SUCCESS:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a = options_array_item(o, new_key);
|
a = options_array_item(o, new_key);
|
||||||
@@ -592,7 +584,7 @@ options_array_set(struct options_entry *o, const char *key, const char *value,
|
|||||||
a = options_array_new(o, new_key);
|
a = options_array_new(o, new_key);
|
||||||
else
|
else
|
||||||
options_value_free(o, &a->value);
|
options_value_free(o, &a->value);
|
||||||
a->value.cmdlist = pr->cmdlist;
|
a->value.cmd = tree;
|
||||||
free(new_key);
|
free(new_key);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
@@ -912,7 +904,7 @@ options_get_number(struct options *oo, const char *name)
|
|||||||
return (o->value.number);
|
return (o->value.number);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct cmd_list *
|
struct cmd_parse_tree *
|
||||||
options_get_command(struct options *oo, const char *name)
|
options_get_command(struct options *oo, const char *name)
|
||||||
{
|
{
|
||||||
struct options_entry *o;
|
struct options_entry *o;
|
||||||
@@ -922,7 +914,7 @@ options_get_command(struct options *oo, const char *name)
|
|||||||
fatalx("missing option %s", name);
|
fatalx("missing option %s", name);
|
||||||
if (!OPTIONS_IS_COMMAND(o))
|
if (!OPTIONS_IS_COMMAND(o))
|
||||||
fatalx("option %s is not a command", name);
|
fatalx("option %s is not a command", name);
|
||||||
return (o->value.cmdlist);
|
return (o->value.cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct options_entry *
|
struct options_entry *
|
||||||
@@ -988,7 +980,7 @@ options_set_number(struct options *oo, const char *name, long long value)
|
|||||||
|
|
||||||
struct options_entry *
|
struct options_entry *
|
||||||
options_set_command(struct options *oo, const char *name,
|
options_set_command(struct options *oo, const char *name,
|
||||||
struct cmd_list *value)
|
struct cmd_parse_tree *value)
|
||||||
{
|
{
|
||||||
struct options_entry *o;
|
struct options_entry *o;
|
||||||
|
|
||||||
@@ -1004,9 +996,8 @@ options_set_command(struct options *oo, const char *name,
|
|||||||
|
|
||||||
if (!OPTIONS_IS_COMMAND(o))
|
if (!OPTIONS_IS_COMMAND(o))
|
||||||
fatalx("option %s is not a command", name);
|
fatalx("option %s is not a command", name);
|
||||||
if (o->value.cmdlist != NULL)
|
cmd_parse_free(o->value.cmd);
|
||||||
cmd_list_free(o->value.cmdlist);
|
o->value.cmd = cmd_parse_add_ref(value);
|
||||||
o->value.cmdlist = value;
|
|
||||||
return (o);
|
return (o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1282,7 +1273,8 @@ options_from_string(struct options *oo, const struct options_table_entry *oe,
|
|||||||
const char *errstr, *new;
|
const char *errstr, *new;
|
||||||
char *old;
|
char *old;
|
||||||
key_code key;
|
key_code key;
|
||||||
struct cmd_parse_result *pr;
|
struct cmd_parse_tree *tree;
|
||||||
|
struct cmd_parse_input pi = { 0 };
|
||||||
|
|
||||||
if (oe != NULL) {
|
if (oe != NULL) {
|
||||||
if (value == NULL &&
|
if (value == NULL &&
|
||||||
@@ -1340,17 +1332,14 @@ options_from_string(struct options *oo, const struct options_table_entry *oe,
|
|||||||
return (options_from_string_flag(oo, name, value, cause));
|
return (options_from_string_flag(oo, name, value, cause));
|
||||||
case OPTIONS_TABLE_CHOICE:
|
case OPTIONS_TABLE_CHOICE:
|
||||||
return (options_from_string_choice(oe, oo, name, value, cause));
|
return (options_from_string_choice(oe, oo, name, value, cause));
|
||||||
case OPTIONS_TABLE_COMMAND:
|
case OPTIONS_TABLE_COMMAND: {
|
||||||
pr = cmd_parse_from_string(value, NULL);
|
tree = cmd_parse_from_string(value, &pi, cause);
|
||||||
switch (pr->status) {
|
if (tree == NULL)
|
||||||
case CMD_PARSE_ERROR:
|
|
||||||
*cause = pr->error;
|
|
||||||
return (-1);
|
return (-1);
|
||||||
case CMD_PARSE_SUCCESS:
|
options_set_command(oo, name, tree);
|
||||||
options_set_command(oo, name, pr->cmdlist);
|
cmd_parse_free(tree);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,661 @@
|
|||||||
|
/* $OpenBSD: popup.c,v 1.77 2026/08/28 08:02:16 nicm Exp $ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 <sys/wait.h>
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "tmux.h"
|
||||||
|
|
||||||
|
struct popup_data {
|
||||||
|
struct client *c;
|
||||||
|
struct cmdq_item *item;
|
||||||
|
int flags;
|
||||||
|
char *title;
|
||||||
|
|
||||||
|
char *style;
|
||||||
|
char *border_style;
|
||||||
|
struct grid_cell border_cell;
|
||||||
|
enum box_lines border_lines;
|
||||||
|
|
||||||
|
struct screen s;
|
||||||
|
struct grid_cell defaults;
|
||||||
|
struct colour_palette palette;
|
||||||
|
|
||||||
|
struct visible_ranges r;
|
||||||
|
|
||||||
|
struct job *job;
|
||||||
|
struct input_ctx *ictx;
|
||||||
|
int status;
|
||||||
|
popup_close_cb cb;
|
||||||
|
void *arg;
|
||||||
|
|
||||||
|
int close;
|
||||||
|
|
||||||
|
/* Current position and size. */
|
||||||
|
u_int px;
|
||||||
|
u_int py;
|
||||||
|
u_int sx;
|
||||||
|
u_int sy;
|
||||||
|
|
||||||
|
/* Preferred position and size. */
|
||||||
|
u_int ppx;
|
||||||
|
u_int ppy;
|
||||||
|
u_int psx;
|
||||||
|
u_int psy;
|
||||||
|
|
||||||
|
enum { OFF, MOVE, SIZE } dragging;
|
||||||
|
u_int dx;
|
||||||
|
u_int dy;
|
||||||
|
|
||||||
|
u_int lx;
|
||||||
|
u_int ly;
|
||||||
|
u_int lb;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_free(struct popup_data *pd)
|
||||||
|
{
|
||||||
|
server_client_unref(pd->c);
|
||||||
|
|
||||||
|
if (pd->job != NULL)
|
||||||
|
job_free(pd->job);
|
||||||
|
if (pd->ictx != NULL)
|
||||||
|
input_free(pd->ictx);
|
||||||
|
|
||||||
|
free(pd->r.ranges);
|
||||||
|
screen_free(&pd->s);
|
||||||
|
colour_palette_free(&pd->palette);
|
||||||
|
|
||||||
|
free(pd->title);
|
||||||
|
free(pd->style);
|
||||||
|
free(pd->border_style);
|
||||||
|
free(pd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_reapply_styles(struct popup_data *pd)
|
||||||
|
{
|
||||||
|
struct client *c = pd->c;
|
||||||
|
struct session *s = c->session;
|
||||||
|
struct options *o;
|
||||||
|
struct format_tree *ft;
|
||||||
|
struct style sytmp;
|
||||||
|
|
||||||
|
if (s == NULL)
|
||||||
|
return;
|
||||||
|
o = s->curw->window->options;
|
||||||
|
|
||||||
|
ft = format_create_defaults(NULL, c, s, s->curw, NULL);
|
||||||
|
|
||||||
|
/* Reapply popup style from options. */
|
||||||
|
memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
|
||||||
|
style_apply(&pd->defaults, o, "popup-style", ft);
|
||||||
|
if (pd->style != NULL) {
|
||||||
|
style_set(&sytmp, &grid_default_cell);
|
||||||
|
if (style_parse(&sytmp, &pd->defaults, pd->style) == 0) {
|
||||||
|
pd->defaults.fg = sytmp.gc.fg;
|
||||||
|
pd->defaults.bg = sytmp.gc.bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pd->defaults.attr = 0;
|
||||||
|
|
||||||
|
/* Reapply border style from options. */
|
||||||
|
memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
|
||||||
|
style_apply(&pd->border_cell, o, "popup-border-style", ft);
|
||||||
|
if (pd->border_style != NULL) {
|
||||||
|
style_set(&sytmp, &grid_default_cell);
|
||||||
|
if (style_parse(&sytmp, &pd->border_cell,
|
||||||
|
pd->border_style) == 0) {
|
||||||
|
pd->border_cell.fg = sytmp.gc.fg;
|
||||||
|
pd->border_cell.bg = sytmp.gc.bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pd->border_cell.attr = 0;
|
||||||
|
|
||||||
|
format_free(ft);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_redraw_cb(const struct tty_ctx *ttyctx)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = ttyctx->arg;
|
||||||
|
|
||||||
|
pd->c->flags |= CLIENT_REDRAWOVERLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = ttyctx->arg;
|
||||||
|
|
||||||
|
if (c != pd->c)
|
||||||
|
return (0);
|
||||||
|
if (pd->c->flags & CLIENT_REDRAWOVERLAY)
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
ttyctx->wox = 0;
|
||||||
|
ttyctx->woy = 0;
|
||||||
|
ttyctx->wsx = c->tty.sx;
|
||||||
|
ttyctx->wsy = c->tty.sy;
|
||||||
|
|
||||||
|
if (pd->border_lines == BOX_LINES_NONE) {
|
||||||
|
ttyctx->xoff = ttyctx->rxoff = pd->px;
|
||||||
|
ttyctx->yoff = ttyctx->ryoff = pd->py;
|
||||||
|
} else {
|
||||||
|
ttyctx->xoff = ttyctx->rxoff = pd->px + 1;
|
||||||
|
ttyctx->yoff = ttyctx->ryoff = pd->py + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = ctx->arg;
|
||||||
|
|
||||||
|
memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults);
|
||||||
|
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
|
||||||
|
ttyctx->style_ctx.defaults = &ttyctx->defaults;
|
||||||
|
ttyctx->style_ctx.palette = &pd->palette;
|
||||||
|
ttyctx->redraw_cb = popup_redraw_cb;
|
||||||
|
ttyctx->set_client_cb = popup_set_client_cb;
|
||||||
|
ttyctx->arg = pd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct screen *
|
||||||
|
popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = data;
|
||||||
|
|
||||||
|
if (pd->border_lines == BOX_LINES_NONE) {
|
||||||
|
*cx = pd->px + pd->s.cx;
|
||||||
|
*cy = pd->py + pd->s.cy;
|
||||||
|
} else {
|
||||||
|
*cx = pd->px + 1 + pd->s.cx;
|
||||||
|
*cy = pd->py + 1 + pd->s.cy;
|
||||||
|
}
|
||||||
|
return (&pd->s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return parts of the input range which are not obstructed by the popup. */
|
||||||
|
static struct visible_ranges *
|
||||||
|
popup_check_cb(__unused struct client* c, void *data, u_int px, u_int py,
|
||||||
|
u_int nx)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = data;
|
||||||
|
struct visible_ranges *r = &pd->r;
|
||||||
|
|
||||||
|
server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx,
|
||||||
|
r);
|
||||||
|
return (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_draw_cb(struct client *c, void *data)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = data;
|
||||||
|
struct tty *tty = &c->tty;
|
||||||
|
struct screen s;
|
||||||
|
struct screen_write_ctx ctx;
|
||||||
|
u_int i, px = pd->px, py = pd->py;
|
||||||
|
struct grid_cell defaults;
|
||||||
|
struct tty_style_ctx style_ctx;
|
||||||
|
|
||||||
|
popup_reapply_styles(pd);
|
||||||
|
|
||||||
|
screen_init(&s, pd->sx, pd->sy, 0);
|
||||||
|
if (pd->s.hyperlinks != NULL) {
|
||||||
|
hyperlinks_free(s.hyperlinks);
|
||||||
|
s.hyperlinks = hyperlinks_copy(pd->s.hyperlinks);
|
||||||
|
}
|
||||||
|
screen_write_start(&ctx, &s);
|
||||||
|
screen_write_clearscreen(&ctx, 8);
|
||||||
|
|
||||||
|
if (pd->border_lines == BOX_LINES_NONE) {
|
||||||
|
screen_write_cursormove(&ctx, 0, 0, 0);
|
||||||
|
screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx, pd->sy);
|
||||||
|
} else if (pd->sx > 2 && pd->sy > 2) {
|
||||||
|
screen_write_box(&ctx, pd->sx, pd->sy, pd->border_lines,
|
||||||
|
&pd->border_cell, pd->title);
|
||||||
|
screen_write_cursormove(&ctx, 1, 1, 0);
|
||||||
|
screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2,
|
||||||
|
pd->sy - 2);
|
||||||
|
}
|
||||||
|
screen_write_stop(&ctx);
|
||||||
|
|
||||||
|
memcpy(&defaults, &pd->defaults, sizeof defaults);
|
||||||
|
if (defaults.fg == 8)
|
||||||
|
defaults.fg = pd->palette.fg;
|
||||||
|
if (defaults.bg == 8)
|
||||||
|
defaults.bg = pd->palette.bg;
|
||||||
|
style_ctx.defaults = &defaults;
|
||||||
|
style_ctx.palette = &pd->palette;
|
||||||
|
style_ctx.dim = 0;
|
||||||
|
style_ctx.hyperlinks = s.hyperlinks;
|
||||||
|
|
||||||
|
c->overlay_check = NULL;
|
||||||
|
c->overlay_data = NULL;
|
||||||
|
for (i = 0; i < pd->sy; i++)
|
||||||
|
tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &style_ctx);
|
||||||
|
screen_free(&s);
|
||||||
|
c->overlay_check = popup_check_cb;
|
||||||
|
c->overlay_data = pd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_free_cb(__unused struct client *c, void *data)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = data;
|
||||||
|
struct cmdq_item *item = pd->item;
|
||||||
|
|
||||||
|
if (pd->cb != NULL)
|
||||||
|
pd->cb(pd->status, pd->arg);
|
||||||
|
|
||||||
|
if (item != NULL) {
|
||||||
|
if (cmdq_get_client(item) != NULL &&
|
||||||
|
cmdq_get_client(item)->session == NULL)
|
||||||
|
cmdq_get_client(item)->retval = pd->status;
|
||||||
|
cmdq_continue(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
popup_free(pd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_resize_cb(__unused struct client *c, void *data)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = data;
|
||||||
|
struct tty *tty = &c->tty;
|
||||||
|
|
||||||
|
if (pd == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Adjust position and size. */
|
||||||
|
if (pd->psy > tty->sy)
|
||||||
|
pd->sy = tty->sy;
|
||||||
|
else
|
||||||
|
pd->sy = pd->psy;
|
||||||
|
if (pd->psx > tty->sx)
|
||||||
|
pd->sx = tty->sx;
|
||||||
|
else
|
||||||
|
pd->sx = pd->psx;
|
||||||
|
if (pd->ppy + pd->sy > tty->sy)
|
||||||
|
pd->py = tty->sy - pd->sy;
|
||||||
|
else
|
||||||
|
pd->py = pd->ppy;
|
||||||
|
if (pd->ppx + pd->sx > tty->sx)
|
||||||
|
pd->px = tty->sx - pd->sx;
|
||||||
|
else
|
||||||
|
pd->px = pd->ppx;
|
||||||
|
|
||||||
|
/* Avoid zero size screens. */
|
||||||
|
if (pd->border_lines == BOX_LINES_NONE) {
|
||||||
|
screen_resize(&pd->s, pd->sx, pd->sy, 0);
|
||||||
|
if (pd->job != NULL)
|
||||||
|
job_resize(pd->job, pd->sx, pd->sy );
|
||||||
|
} else if (pd->sx > 2 && pd->sy > 2) {
|
||||||
|
screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
|
||||||
|
if (pd->job != NULL)
|
||||||
|
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||||
|
struct mouse_event *m)
|
||||||
|
{
|
||||||
|
u_int px, py;
|
||||||
|
|
||||||
|
if (!MOUSE_DRAG(m->b))
|
||||||
|
pd->dragging = OFF;
|
||||||
|
else if (pd->dragging == MOVE) {
|
||||||
|
if (m->x < pd->dx)
|
||||||
|
px = 0;
|
||||||
|
else if (m->x - pd->dx + pd->sx > c->tty.sx)
|
||||||
|
px = c->tty.sx - pd->sx;
|
||||||
|
else
|
||||||
|
px = m->x - pd->dx;
|
||||||
|
if (m->y < pd->dy)
|
||||||
|
py = 0;
|
||||||
|
else if (m->y - pd->dy + pd->sy > c->tty.sy)
|
||||||
|
py = c->tty.sy - pd->sy;
|
||||||
|
else
|
||||||
|
py = m->y - pd->dy;
|
||||||
|
pd->px = px;
|
||||||
|
pd->py = py;
|
||||||
|
pd->dx = m->x - pd->px;
|
||||||
|
pd->dy = m->y - pd->py;
|
||||||
|
pd->ppx = px;
|
||||||
|
pd->ppy = py;
|
||||||
|
server_redraw_client(c);
|
||||||
|
} else if (pd->dragging == SIZE) {
|
||||||
|
if (pd->border_lines == BOX_LINES_NONE) {
|
||||||
|
if (m->x < pd->px + 1)
|
||||||
|
return;
|
||||||
|
if (m->y < pd->py + 1)
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (m->x < pd->px + 3)
|
||||||
|
return;
|
||||||
|
if (m->y < pd->py + 3)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pd->sx = m->x - pd->px;
|
||||||
|
pd->sy = m->y - pd->py;
|
||||||
|
pd->psx = pd->sx;
|
||||||
|
pd->psy = pd->sy;
|
||||||
|
|
||||||
|
if (pd->border_lines == BOX_LINES_NONE) {
|
||||||
|
screen_resize(&pd->s, pd->sx, pd->sy, 0);
|
||||||
|
if (pd->job != NULL)
|
||||||
|
job_resize(pd->job, pd->sx, pd->sy);
|
||||||
|
} else {
|
||||||
|
screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
|
||||||
|
if (pd->job != NULL)
|
||||||
|
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
||||||
|
}
|
||||||
|
server_redraw_client(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
popup_key_cb(struct client *c, void *data, struct key_event *event)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = data;
|
||||||
|
struct mouse_event *m = &event->m;
|
||||||
|
const char *buf;
|
||||||
|
size_t len;
|
||||||
|
u_int px, py;
|
||||||
|
enum { NONE, LEFT, RIGHT, TOP, BOTTOM } border = NONE;
|
||||||
|
|
||||||
|
if (KEYC_IS_MOUSE(event->key)) {
|
||||||
|
if (pd->dragging != OFF) {
|
||||||
|
popup_handle_drag(c, pd, m);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (m->x < pd->px ||
|
||||||
|
m->x > pd->px + pd->sx - 1 ||
|
||||||
|
m->y < pd->py ||
|
||||||
|
m->y > pd->py + pd->sy - 1) {
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
if (pd->border_lines != BOX_LINES_NONE) {
|
||||||
|
if (m->x == pd->px)
|
||||||
|
border = LEFT;
|
||||||
|
else if (m->x == pd->px + pd->sx - 1)
|
||||||
|
border = RIGHT;
|
||||||
|
else if (m->y == pd->py)
|
||||||
|
border = TOP;
|
||||||
|
else if (m->y == pd->py + pd->sy - 1)
|
||||||
|
border = BOTTOM;
|
||||||
|
}
|
||||||
|
if ((m->b & MOUSE_MASK_MODIFIERS) == 0 &&
|
||||||
|
MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3 &&
|
||||||
|
(border == LEFT || border == TOP))
|
||||||
|
goto out;
|
||||||
|
if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) ||
|
||||||
|
(border != NONE && !MOUSE_DRAG(m->lb))) {
|
||||||
|
if (!MOUSE_DRAG(m->b))
|
||||||
|
goto out;
|
||||||
|
if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_1)
|
||||||
|
pd->dragging = MOVE;
|
||||||
|
else if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_3)
|
||||||
|
pd->dragging = SIZE;
|
||||||
|
pd->dx = m->lx - pd->px;
|
||||||
|
pd->dy = m->ly - pd->py;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) ||
|
||||||
|
pd->job == NULL) &&
|
||||||
|
(event->key == '\033' || event->key == ('c'|KEYC_CTRL)))
|
||||||
|
return (1);
|
||||||
|
if (pd->job == NULL && (pd->flags & POPUP_CLOSEANYKEY) &&
|
||||||
|
!KEYC_IS_MOUSE(event->key) && !KEYC_IS_PASTE(event->key))
|
||||||
|
return (1);
|
||||||
|
if (pd->job != NULL) {
|
||||||
|
if (KEYC_IS_MOUSE(event->key)) {
|
||||||
|
/* Must be inside, checked already. */
|
||||||
|
if (pd->border_lines == BOX_LINES_NONE) {
|
||||||
|
px = m->x - pd->px;
|
||||||
|
py = m->y - pd->py;
|
||||||
|
} else {
|
||||||
|
px = m->x - pd->px - 1;
|
||||||
|
py = m->y - pd->py - 1;
|
||||||
|
}
|
||||||
|
if (!input_key_get_mouse(&pd->s, m, px, py, &buf, &len))
|
||||||
|
return (0);
|
||||||
|
bufferevent_write(job_get_event(pd->job), buf, len);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
input_key(&pd->s, job_get_event(pd->job), event->key);
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
out:
|
||||||
|
pd->lx = m->x;
|
||||||
|
pd->ly = m->y;
|
||||||
|
pd->lb = m->b;
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_job_update_cb(struct job *job)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = job_get_data(job);
|
||||||
|
struct evbuffer *evb = job_get_event(job)->input;
|
||||||
|
struct client *c = pd->c;
|
||||||
|
struct screen *s = &pd->s;
|
||||||
|
void *data = EVBUFFER_DATA(evb);
|
||||||
|
size_t size = EVBUFFER_LENGTH(evb);
|
||||||
|
|
||||||
|
if (size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
c->overlay_check = NULL;
|
||||||
|
c->overlay_data = NULL;
|
||||||
|
input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size);
|
||||||
|
c->overlay_check = popup_check_cb;
|
||||||
|
c->overlay_data = pd;
|
||||||
|
|
||||||
|
evbuffer_drain(evb, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup_job_complete_cb(struct job *job)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = job_get_data(job);
|
||||||
|
int status;
|
||||||
|
|
||||||
|
status = job_get_status(pd->job);
|
||||||
|
if (WIFEXITED(status))
|
||||||
|
pd->status = WEXITSTATUS(status);
|
||||||
|
else if (WIFSIGNALED(status))
|
||||||
|
pd->status = WTERMSIG(status);
|
||||||
|
else
|
||||||
|
pd->status = 0;
|
||||||
|
pd->job = NULL;
|
||||||
|
|
||||||
|
if ((pd->flags & POPUP_CLOSEEXIT) ||
|
||||||
|
((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
|
||||||
|
server_client_clear_overlay(pd->c);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
popup_present(struct client *c)
|
||||||
|
{
|
||||||
|
return (c->overlay_draw == popup_draw_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
popup_modify(struct client *c, const char *title, const char *style,
|
||||||
|
const char *border_style, enum box_lines lines, int flags)
|
||||||
|
{
|
||||||
|
struct popup_data *pd = c->overlay_data;
|
||||||
|
struct style sytmp;
|
||||||
|
|
||||||
|
if (title != NULL) {
|
||||||
|
if (pd->title != NULL)
|
||||||
|
free(pd->title);
|
||||||
|
pd->title = xstrdup(title);
|
||||||
|
}
|
||||||
|
if (border_style != NULL) {
|
||||||
|
free(pd->border_style);
|
||||||
|
pd->border_style = xstrdup(border_style);
|
||||||
|
style_set(&sytmp, &pd->border_cell);
|
||||||
|
if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
|
||||||
|
pd->border_cell.fg = sytmp.gc.fg;
|
||||||
|
pd->border_cell.bg = sytmp.gc.bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (style != NULL) {
|
||||||
|
free(pd->style);
|
||||||
|
pd->style = xstrdup(style);
|
||||||
|
style_set(&sytmp, &pd->defaults);
|
||||||
|
if (style_parse(&sytmp, &pd->defaults, style) == 0) {
|
||||||
|
pd->defaults.fg = sytmp.gc.fg;
|
||||||
|
pd->defaults.bg = sytmp.gc.bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lines != BOX_LINES_DEFAULT) {
|
||||||
|
if (lines == BOX_LINES_NONE && pd->border_lines != lines) {
|
||||||
|
screen_resize(&pd->s, pd->sx, pd->sy, 1);
|
||||||
|
job_resize(pd->job, pd->sx, pd->sy);
|
||||||
|
} else if (pd->border_lines == BOX_LINES_NONE &&
|
||||||
|
pd->border_lines != lines) {
|
||||||
|
screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 1);
|
||||||
|
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
||||||
|
}
|
||||||
|
pd->border_lines = lines;
|
||||||
|
tty_resize(&c->tty);
|
||||||
|
}
|
||||||
|
if (flags != -1)
|
||||||
|
pd->flags = flags;
|
||||||
|
|
||||||
|
server_redraw_client(c);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
|
||||||
|
u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd,
|
||||||
|
int argc, char **argv, const char *cwd, const char *title, struct client *c,
|
||||||
|
struct session *s, const char *style, const char *border_style,
|
||||||
|
popup_close_cb cb, void *arg)
|
||||||
|
{
|
||||||
|
struct popup_data *pd;
|
||||||
|
u_int jx, jy;
|
||||||
|
struct options *o;
|
||||||
|
struct style sytmp;
|
||||||
|
|
||||||
|
if (s != NULL)
|
||||||
|
o = s->curw->window->options;
|
||||||
|
else
|
||||||
|
o = c->session->curw->window->options;
|
||||||
|
|
||||||
|
if (lines == BOX_LINES_DEFAULT)
|
||||||
|
lines = options_get_number(o, "popup-border-lines");
|
||||||
|
if (lines == BOX_LINES_NONE) {
|
||||||
|
if (sx < 1 || sy < 1)
|
||||||
|
return (-1);
|
||||||
|
jx = sx;
|
||||||
|
jy = sy;
|
||||||
|
} else {
|
||||||
|
if (sx < 3 || sy < 3)
|
||||||
|
return (-1);
|
||||||
|
jx = sx - 2;
|
||||||
|
jy = sy - 2;
|
||||||
|
}
|
||||||
|
if (c->tty.sx < sx || c->tty.sy < sy)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
pd = xcalloc(1, sizeof *pd);
|
||||||
|
pd->item = item;
|
||||||
|
pd->flags = flags;
|
||||||
|
|
||||||
|
if (title != NULL)
|
||||||
|
pd->title = xstrdup(title);
|
||||||
|
if (style != NULL)
|
||||||
|
pd->style = xstrdup(style);
|
||||||
|
if (border_style != NULL)
|
||||||
|
pd->border_style = xstrdup(border_style);
|
||||||
|
|
||||||
|
pd->c = c;
|
||||||
|
pd->c->references++;
|
||||||
|
|
||||||
|
pd->cb = cb;
|
||||||
|
pd->arg = arg;
|
||||||
|
pd->status = 128 + SIGHUP;
|
||||||
|
|
||||||
|
pd->border_lines = lines;
|
||||||
|
memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
|
||||||
|
style_apply(&pd->border_cell, o, "popup-border-style", NULL);
|
||||||
|
if (border_style != NULL) {
|
||||||
|
style_set(&sytmp, &grid_default_cell);
|
||||||
|
if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
|
||||||
|
pd->border_cell.fg = sytmp.gc.fg;
|
||||||
|
pd->border_cell.bg = sytmp.gc.bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pd->border_cell.attr = 0;
|
||||||
|
|
||||||
|
screen_init(&pd->s, jx, jy, 0);
|
||||||
|
screen_set_default_cursor(&pd->s, global_w_options);
|
||||||
|
colour_palette_init(&pd->palette);
|
||||||
|
colour_palette_from_option(&pd->palette, global_w_options);
|
||||||
|
|
||||||
|
memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
|
||||||
|
style_apply(&pd->defaults, o, "popup-style", NULL);
|
||||||
|
if (style != NULL) {
|
||||||
|
style_set(&sytmp, &grid_default_cell);
|
||||||
|
if (style_parse(&sytmp, &pd->defaults, style) == 0) {
|
||||||
|
pd->defaults.fg = sytmp.gc.fg;
|
||||||
|
pd->defaults.bg = sytmp.gc.bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pd->defaults.attr = 0;
|
||||||
|
|
||||||
|
pd->px = px;
|
||||||
|
pd->py = py;
|
||||||
|
pd->sx = sx;
|
||||||
|
pd->sy = sy;
|
||||||
|
|
||||||
|
pd->ppx = px;
|
||||||
|
pd->ppy = py;
|
||||||
|
pd->psx = sx;
|
||||||
|
pd->psy = sy;
|
||||||
|
|
||||||
|
pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
|
||||||
|
popup_job_update_cb, popup_job_complete_cb, NULL, pd,
|
||||||
|
JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
|
||||||
|
if (pd->job == NULL) {
|
||||||
|
popup_free(pd);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: prompt.c,v 1.7 2026/09/09 07:53:03 nicm Exp $ */
|
/* $OpenBSD: prompt.c,v 1.6 2026/08/17 06:45:16 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1493,11 +1493,9 @@ append_key:
|
|||||||
utf8_set(&tmp, key);
|
utf8_set(&tmp, key);
|
||||||
if (key <= 0x1f || key == 0x7f)
|
if (key <= 0x1f || key == 0x7f)
|
||||||
tmp.width = 2;
|
tmp.width = 2;
|
||||||
} else if (KEYC_IS_UNICODE(key)) {
|
} else if (KEYC_IS_UNICODE(key))
|
||||||
utf8_to_data(key, &tmp);
|
utf8_to_data(key, &tmp);
|
||||||
if (tmp.size == 0)
|
else
|
||||||
return (PROMPT_KEY_HANDLED);
|
|
||||||
} else
|
|
||||||
return (PROMPT_KEY_HANDLED);
|
return (PROMPT_KEY_HANDLED);
|
||||||
|
|
||||||
pr->buffer = xreallocarray(pr->buffer, size + 2,
|
pr->buffer = xreallocarray(pr->buffer, size + 2,
|
||||||
|
|||||||
+20
-23
@@ -1,17 +1,33 @@
|
|||||||
TESTS!= echo *.sh
|
TESTS!= echo *.sh
|
||||||
LOGDIR=logs
|
LOGDIR=logs
|
||||||
|
|
||||||
.PHONY: all prepare $(TESTS)
|
.PHONY: all
|
||||||
|
.NOTPARALLEL: all
|
||||||
|
|
||||||
all: prepare $(TESTS)
|
all:
|
||||||
@failed=0; failures=; \
|
@mkdir -p "$(LOGDIR)"; \
|
||||||
|
rm -f "$(LOGDIR)"/*.log; \
|
||||||
|
failed=0; failures=; \
|
||||||
for test in $(TESTS); do \
|
for test in $(TESTS); do \
|
||||||
base=$${test##*/}; \
|
base=$${test##*/}; \
|
||||||
log="$(LOGDIR)/$${base%.sh}.log"; \
|
log="$(LOGDIR)/$${base%.sh}.log"; \
|
||||||
if [ -f "$$log" ]; then \
|
rm -f "$$log"; \
|
||||||
|
printf '%-40s ' "$$test"; \
|
||||||
|
start=$$(date +%s); \
|
||||||
|
env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
|
||||||
|
sh -x "$$test" >"$$log" 2>&1; \
|
||||||
|
if [ $$? -eq 0 ]; then \
|
||||||
|
end=$$(date +%s); \
|
||||||
|
rm -f "$$log"; \
|
||||||
|
echo "PASS ($$((end - start))s)"; \
|
||||||
|
else \
|
||||||
|
end=$$(date +%s); \
|
||||||
|
echo "FAIL ($$((end - start))s)"; \
|
||||||
|
echo " log: $$log"; \
|
||||||
failed=1; \
|
failed=1; \
|
||||||
failures="$$failures $$test"; \
|
failures="$$failures $$test"; \
|
||||||
fi; \
|
fi; \
|
||||||
|
sleep 1; \
|
||||||
done; \
|
done; \
|
||||||
if [ "$$failed" -ne 0 ]; then \
|
if [ "$$failed" -ne 0 ]; then \
|
||||||
echo; \
|
echo; \
|
||||||
@@ -23,22 +39,3 @@ all: prepare $(TESTS)
|
|||||||
rmdir "$(LOGDIR)" 2>/dev/null || true; \
|
rmdir "$(LOGDIR)" 2>/dev/null || true; \
|
||||||
fi; \
|
fi; \
|
||||||
exit $$failed
|
exit $$failed
|
||||||
|
|
||||||
prepare:
|
|
||||||
@mkdir -p "$(LOGDIR)"
|
|
||||||
@rm -f "$(LOGDIR)"/*.log
|
|
||||||
|
|
||||||
$(TESTS): prepare
|
|
||||||
@base="$@"; base=$${base##*/}; \
|
|
||||||
log="$(LOGDIR)/$${base%.sh}.log"; \
|
|
||||||
start=$$(date +%s); \
|
|
||||||
if env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
|
|
||||||
sh -x "$@" >"$$log" 2>&1; then \
|
|
||||||
end=$$(date +%s); \
|
|
||||||
rm -f "$$log"; \
|
|
||||||
printf '%-40s PASS (%ss)\n' "$@" "$$((end - start))"; \
|
|
||||||
else \
|
|
||||||
end=$$(date +%s); \
|
|
||||||
printf '%-40s FAIL (%ss)\n log: %s\n' \
|
|
||||||
"$@" "$$((end - start))" "$$log"; \
|
|
||||||
fi
|
|
||||||
|
|||||||
+2
-32
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
# monitor-activity, monitor-bell and monitor-silence: both the
|
# monitor-activity, monitor-bell and monitor-silence: both the
|
||||||
# alert-activity, alert-bell and alert-silence hooks and the winlink alert
|
# alert-activity, alert-bell and alert-silence hooks and the winlink alert
|
||||||
# flags (window_activity_flag, window_bell_flag, window_silence_flag,
|
# flags (window_activity_flag, window_bell_flag, window_silence_flag and
|
||||||
# their session equivalents and the #, !, ~ characters in window_flags).
|
# the #, !, ~ characters in window_flags). The sessions are detached so
|
||||||
# The sessions are detached so
|
|
||||||
# alert flags are set even on the current window; the *-action options
|
# alert flags are set even on the current window; the *-action options
|
||||||
# still decide whether the hooks fire. Panes run cat: activity is
|
# still decide whether the hooks fire. Panes run cat: activity is
|
||||||
# generated by the tty echo of send-keys and a bell by sending a BEL and
|
# generated by the tty echo of send-keys and a bell by sending a BEL and
|
||||||
@@ -112,24 +111,6 @@ flags_have()
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_session_flag()
|
|
||||||
{
|
|
||||||
flag="#{session_${1}_flag}"
|
|
||||||
expected=$2
|
|
||||||
|
|
||||||
# Only mon exists here. Check both window contexts and list-sessions.
|
|
||||||
for target in mon:w0 "mon:$3"; do
|
|
||||||
value=$($TMUX display -pt "$target" "$flag") ||
|
|
||||||
fail "display $flag failed"
|
|
||||||
[ "$value" = "$expected" ] ||
|
|
||||||
fail "expected $flag for $target to be '$expected' but got '$value'"
|
|
||||||
done
|
|
||||||
value=$($TMUX list-sessions -F "$flag") ||
|
|
||||||
fail "list-sessions $flag failed"
|
|
||||||
[ "$value" = "$expected" ] ||
|
|
||||||
fail "expected list-sessions $flag to be '$expected' but got '$value'"
|
|
||||||
}
|
|
||||||
|
|
||||||
flags_lack()
|
flags_lack()
|
||||||
{
|
{
|
||||||
target=$1
|
target=$1
|
||||||
@@ -169,12 +150,10 @@ $TMUX set-hook -g alert-silence \
|
|||||||
# alert-bell and sets the bell flag, shown as ! in window_flags.
|
# alert-bell and sets the bell flag, shown as ! in window_flags.
|
||||||
$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed"
|
$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed"
|
||||||
assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0
|
assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0
|
||||||
assert_session_flag bell 0 bellw
|
|
||||||
flags_lack mon:bellw '!'
|
flags_lack mon:bellw '!'
|
||||||
bell mon:bellw
|
bell mon:bellw
|
||||||
wait_for @log '|alert-bell:mon:bellw'
|
wait_for @log '|alert-bell:mon:bellw'
|
||||||
wait_for_fmt mon:bellw '#{window_bell_flag}' 1
|
wait_for_fmt mon:bellw '#{window_bell_flag}' 1
|
||||||
assert_session_flag bell 1 bellw
|
|
||||||
flags_have mon:bellw '!'
|
flags_have mon:bellw '!'
|
||||||
|
|
||||||
# Bells are not deduplicated: a second bell fires the hook again even
|
# Bells are not deduplicated: a second bell fires the hook again even
|
||||||
@@ -185,7 +164,6 @@ wait_for @log '|alert-bell:mon:bellw|alert-bell:mon:bellw'
|
|||||||
# Selecting the window clears the alert flags.
|
# Selecting the window clears the alert flags.
|
||||||
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
||||||
wait_for_fmt mon:bellw '#{window_bell_flag}' 0
|
wait_for_fmt mon:bellw '#{window_bell_flag}' 0
|
||||||
assert_session_flag bell 0 bellw
|
|
||||||
flags_lack mon:bellw '!'
|
flags_lack mon:bellw '!'
|
||||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||||
|
|
||||||
@@ -195,7 +173,6 @@ $TMUX set -g @log '' || fail "reset @log failed"
|
|||||||
bell mon:w0
|
bell mon:w0
|
||||||
wait_for @log '|alert-bell:mon:w0'
|
wait_for @log '|alert-bell:mon:w0'
|
||||||
wait_for_fmt mon:w0 '#{window_bell_flag}' 1
|
wait_for_fmt mon:w0 '#{window_bell_flag}' 1
|
||||||
assert_session_flag bell 1 bellw
|
|
||||||
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
||||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||||
wait_for_fmt mon:w0 '#{window_bell_flag}' 0
|
wait_for_fmt mon:w0 '#{window_bell_flag}' 0
|
||||||
@@ -222,7 +199,6 @@ $TMUX neww -d -t mon: -n actw cat || fail "new-window actw failed"
|
|||||||
$TMUX set -g @log '' || fail "reset @log failed"
|
$TMUX set -g @log '' || fail "reset @log failed"
|
||||||
activity mon:actw
|
activity mon:actw
|
||||||
assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0
|
assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0
|
||||||
assert_session_flag activity 0 actw
|
|
||||||
assert_unchanged @log ''
|
assert_unchanged @log ''
|
||||||
|
|
||||||
# With monitor-activity on, output fires alert-activity and sets the
|
# With monitor-activity on, output fires alert-activity and sets the
|
||||||
@@ -232,7 +208,6 @@ $TMUX set -wt mon:actw monitor-activity on ||
|
|||||||
activity mon:actw
|
activity mon:actw
|
||||||
wait_for @log '|alert-activity:mon:actw'
|
wait_for @log '|alert-activity:mon:actw'
|
||||||
wait_for_fmt mon:actw '#{window_activity_flag}' 1
|
wait_for_fmt mon:actw '#{window_activity_flag}' 1
|
||||||
assert_session_flag activity 1 actw
|
|
||||||
flags_have mon:actw '#'
|
flags_have mon:actw '#'
|
||||||
|
|
||||||
# While the flag is set further activity does not fire the hook again.
|
# While the flag is set further activity does not fire the hook again.
|
||||||
@@ -259,7 +234,6 @@ exec 3>"$OUT/fifo"
|
|||||||
wait_for_fmt mon: '#{session_attached}' 1
|
wait_for_fmt mon: '#{session_attached}' 1
|
||||||
$TMUX selectw -t mon:actw || fail "select-window actw failed"
|
$TMUX selectw -t mon:actw || fail "select-window actw failed"
|
||||||
wait_for_fmt mon:actw '#{window_activity_flag}' 0
|
wait_for_fmt mon:actw '#{window_activity_flag}' 0
|
||||||
assert_session_flag activity 0 actw
|
|
||||||
flags_lack mon:actw '#'
|
flags_lack mon:actw '#'
|
||||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||||
exec 3>&-
|
exec 3>&-
|
||||||
@@ -276,7 +250,6 @@ $TMUX set -wt mon:w0 monitor-activity on ||
|
|||||||
$TMUX set -g @log '' || fail "reset @log failed"
|
$TMUX set -g @log '' || fail "reset @log failed"
|
||||||
activity mon:w0
|
activity mon:w0
|
||||||
wait_for_fmt mon:w0 '#{window_activity_flag}' 1
|
wait_for_fmt mon:w0 '#{window_activity_flag}' 1
|
||||||
assert_session_flag activity 1 actw
|
|
||||||
assert_unchanged @log ''
|
assert_unchanged @log ''
|
||||||
$TMUX set -wut mon:w0 monitor-activity ||
|
$TMUX set -wut mon:w0 monitor-activity ||
|
||||||
fail "unset monitor-activity w0 failed"
|
fail "unset monitor-activity w0 failed"
|
||||||
@@ -300,19 +273,16 @@ $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
|||||||
# running.
|
# running.
|
||||||
$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed"
|
$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed"
|
||||||
assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0
|
assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0
|
||||||
assert_session_flag silence 0 silw
|
|
||||||
$TMUX set -g @log '' || fail "reset @log failed"
|
$TMUX set -g @log '' || fail "reset @log failed"
|
||||||
$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed"
|
$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed"
|
||||||
wait_for @log '|alert-silence:mon:silw'
|
wait_for @log '|alert-silence:mon:silw'
|
||||||
wait_for_fmt mon:silw '#{window_silence_flag}' 1
|
wait_for_fmt mon:silw '#{window_silence_flag}' 1
|
||||||
assert_session_flag silence 1 silw
|
|
||||||
flags_have mon:silw '~'
|
flags_have mon:silw '~'
|
||||||
assert_unchanged @log '|alert-silence:mon:silw'
|
assert_unchanged @log '|alert-silence:mon:silw'
|
||||||
$TMUX set -wt mon:silw monitor-silence 0 ||
|
$TMUX set -wt mon:silw monitor-silence 0 ||
|
||||||
fail "reset monitor-silence failed"
|
fail "reset monitor-silence failed"
|
||||||
$TMUX selectw -t mon:silw || fail "select-window silw failed"
|
$TMUX selectw -t mon:silw || fail "select-window silw failed"
|
||||||
wait_for_fmt mon:silw '#{window_silence_flag}' 0
|
wait_for_fmt mon:silw '#{window_silence_flag}' 0
|
||||||
assert_session_flag silence 0 silw
|
|
||||||
flags_lack mon:silw '~'
|
flags_lack mon:silw '~'
|
||||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||||
|
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Selecting a pane with attach-session from an already attached client must
|
|
||||||
# redraw pane contents when window-style and window-active-style differ.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lattach-redraw-outer-$$ -f/dev/null"
|
|
||||||
BEFORE=$DIR/before
|
|
||||||
AFTER=$DIR/after
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
LEFT=$($INNER new-session -dPF '#{pane_id}' -s inner -x 40 -y 8 \
|
|
||||||
"printf 'LEFT'; exec sleep 100") || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g default-terminal screen || exit 1
|
|
||||||
$INNER split-window -h -t "$LEFT" "printf 'RIGHT'; exec sleep 100" || exit 1
|
|
||||||
$INNER set-option -w -t "$LEFT" window-style bg=red || exit 1
|
|
||||||
$INNER set-option -w -t "$LEFT" window-active-style bg=blue || exit 1
|
|
||||||
$INNER bind-key -n x attach-session -t "$LEFT" || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
sleep 1
|
|
||||||
$OUTER send-keys -t outer:0.0 x || exit 1
|
|
||||||
sleep 1
|
|
||||||
[ "$($INNER display-message -p -t inner '#{pane_id}')" = "$LEFT" ] ||
|
|
||||||
fail "attach-session did not select the target pane"
|
|
||||||
$OUTER capture-pane -pe -t outer:0.0 >"$BEFORE" || exit 1
|
|
||||||
|
|
||||||
# A forced redraw produces the correct scene. It must be identical to the
|
|
||||||
# scene drawn immediately by attach-session.
|
|
||||||
$INNER refresh-client -t "$CLIENT" || exit 1
|
|
||||||
sleep 1
|
|
||||||
$OUTER capture-pane -pe -t outer:0.0 >"$AFTER" || exit 1
|
|
||||||
cmp -s "$BEFORE" "$AFTER" ||
|
|
||||||
fail "attach-session left stale active/inactive pane styles"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# capture-pane -I line timestamps
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
TMP=$(mktemp)
|
|
||||||
trap '$TMUX kill-server 2>/dev/null; rm -f "$TMP"' 0 1 15
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
|
|
||||||
before=$(date +%s)
|
|
||||||
$TMUX new-session -d -x 40 -y 5 'seq 1 12; sleep 10' || exit 1
|
|
||||||
sleep 1
|
|
||||||
after=$(date +%s)
|
|
||||||
|
|
||||||
$TMUX capture-pane -pILF -S - -E - >"$TMP" || exit 1
|
|
||||||
awk -v before="$before" -v after="$after" '
|
|
||||||
$1 !~ /^-?[0-9]+$/ || $2 !~ /^[0-9]+$/ { exit 1 }
|
|
||||||
$4 == "1" {
|
|
||||||
if ($2 < before || $2 > after)
|
|
||||||
exit 1
|
|
||||||
history = 1
|
|
||||||
}
|
|
||||||
$4 == "9" {
|
|
||||||
if ($2 != 0)
|
|
||||||
exit 1
|
|
||||||
visible = 1
|
|
||||||
}
|
|
||||||
END { if (!history || !visible) exit 1 }
|
|
||||||
' "$TMP" || {
|
|
||||||
cat "$TMP"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
Regular → Executable
+1
-10
@@ -5,15 +5,6 @@
|
|||||||
PATH=/bin:/usr/bin
|
PATH=/bin:/usr/bin
|
||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
# The pane's shell must not be the user's own interactive shell: a custom
|
|
||||||
# PS1/PROMPT_COMMAND that sets the terminal title (as many do, for tmux/xterm
|
|
||||||
# TERM types) would redraw over the titles this test sets and checks on
|
|
||||||
# every prompt, regardless of how long it waits first.
|
|
||||||
shell=
|
|
||||||
if command -v bash >/dev/null 2>&1; then
|
|
||||||
shell='bash --noprofile --norc +o history'
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
@@ -39,7 +30,7 @@ must_equal()
|
|||||||
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
|
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
|
||||||
}
|
}
|
||||||
|
|
||||||
$TMUX new-session -d -x 80 -y 24 -- $shell || exit 1
|
$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-set-title on || exit 1
|
||||||
$TMUX set-option -qg allow-rename on || exit 1
|
$TMUX set-option -qg allow-rename on || exit 1
|
||||||
$TMUX set-option -qg automatic-rename off || exit 1
|
$TMUX set-option -qg automatic-rename off || exit 1
|
||||||
|
|||||||
@@ -290,92 +290,4 @@ wait_count ': G4' 7
|
|||||||
fail "window 1 of zzz not killed"
|
fail "window 1 of zzz not killed"
|
||||||
exit_mode q
|
exit_mode q
|
||||||
|
|
||||||
# --- help and information preview --------------------------------------------
|
|
||||||
# The complete tree help is taller than the 24-line test terminal, where the
|
|
||||||
# help renderer deliberately leaves the tree unchanged.
|
|
||||||
$TMUX2 resize-window -t out:0 -x 80 -y 50 || exit 1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
# The inner session has a one-line status, leaving a 49-line window.
|
|
||||||
[ "$($TMUX display-message -p -t aaa:0 '#{window_height}')" -ge 49 ] &&
|
|
||||||
break
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "attached client did not resize for tree help"
|
|
||||||
$TMUX choose-tree -t aaa:0 -F 'G5' -O index || exit 1
|
|
||||||
wait_count ': G5' 7
|
|
||||||
$TMUX2 send-keys -t out:0 F1 || fail "send-keys F1 failed"
|
|
||||||
wait_for 'Swap current and previous window'
|
|
||||||
# The first key closes help; the second exits the tree.
|
|
||||||
$TMUX send-keys -t aaa:0 q q || fail "send-keys help exit failed"
|
|
||||||
wait_mode aaa:0 0
|
|
||||||
|
|
||||||
$TMUX choose-tree -t aaa:0 -F 'G6' -O index || exit 1
|
|
||||||
wait_count ': G6' 7
|
|
||||||
$TMUX send-keys -t aaa:0 i || fail "send-keys info failed"
|
|
||||||
wait_for 'Session'
|
|
||||||
$TMUX send-keys -t aaa:0 i || fail "send-keys preview failed"
|
|
||||||
exit_mode q
|
|
||||||
|
|
||||||
# --- window swapping and command prompt for current/tagged items -------------
|
|
||||||
# Restrict a window-only tree to two disposable windows in aaa. J (the
|
|
||||||
# Shift-Down equivalent) swaps their window objects while leaving their indexes
|
|
||||||
# in place.
|
|
||||||
$TMUX new-window -d -t aaa: -n swap-a 'cat' || exit 1
|
|
||||||
$TMUX new-window -d -t aaa: -n swap-b 'cat' || exit 1
|
|
||||||
aidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \
|
|
||||||
awk -F: '$2 == "swap-a" {print $1}')
|
|
||||||
bidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \
|
|
||||||
awk -F: '$2 == "swap-b" {print $1}')
|
|
||||||
$TMUX choose-tree -w -t aaa:0 -O index -F 'WT #{window_name}' \
|
|
||||||
-f '#{m:swap-*,#{window_name}}' || exit 1
|
|
||||||
wait_count 'WT swap-' 2
|
|
||||||
$TMUX send-keys -t aaa:0 g j J || fail "window tree swap failed"
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
aname=$($TMUX display-message -p -t "aaa:$aidx" '#{window_name}')
|
|
||||||
bname=$($TMUX display-message -p -t "aaa:$bidx" '#{window_name}')
|
|
||||||
[ "$aname:$bname" = 'swap-b:swap-a' ] && break
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "window tree did not swap the two windows"
|
|
||||||
|
|
||||||
# ':' runs an entered command for the current item and rebuilds the tree when
|
|
||||||
# its queued command completes.
|
|
||||||
$TMUX set-option -g @tree-command '' || exit 1
|
|
||||||
$TMUX send-keys -t aaa:0 : || fail "window tree command prompt failed"
|
|
||||||
wait_for '(current)'
|
|
||||||
$TMUX send-keys -t aaa:0 -l "set-option -g @tree-command '%%'" || exit 1
|
|
||||||
$TMUX send-keys -t aaa:0 Enter || exit 1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
value=$($TMUX show-option -gqv @tree-command)
|
|
||||||
[ -n "$value" ] && break
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "window tree entered command did not run"
|
|
||||||
case "$value" in
|
|
||||||
=aaa:*.) ;;
|
|
||||||
*) fail "window tree command target was '$value'" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Tag both disposable windows and kill them through the tagged confirmation
|
|
||||||
# callback. The mode-hosting window is outside the filter and survives.
|
|
||||||
$TMUX send-keys -t aaa:0 g j t j t X || fail "tagged kill keys failed"
|
|
||||||
wait_for 'Kill 2 tagged?'
|
|
||||||
$TMUX send-keys -t aaa:0 y || fail "tagged kill confirmation failed"
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
left=$($TMUX list-windows -t aaa -F '#{window_name}' | \
|
|
||||||
grep -c '^swap-' || true)
|
|
||||||
[ "$left" -eq 0 ] && break
|
|
||||||
sleep 0.2
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "tagged windows were not killed"
|
|
||||||
exit_mode q
|
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# With clear-on-attach disabled tmux must preserve the terminal contents by
|
|
||||||
# scrolling them away, rather than entering and clearing the alternate screen.
|
|
||||||
# Capture the bytes written by a real attached client for both settings.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
INNER_ON="$TEST_TMUX -LtestIon$$ -f/dev/null"
|
|
||||||
OUTER_ON="$TEST_TMUX -LtestOon$$ -f/dev/null"
|
|
||||||
INNER_OFF="$TEST_TMUX -LtestIoff$$ -f/dev/null"
|
|
||||||
OUTER_OFF="$TEST_TMUX -LtestOoff$$ -f/dev/null"
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER_ON kill-server 2>/dev/null
|
|
||||||
$INNER_ON kill-server 2>/dev/null
|
|
||||||
$OUTER_OFF kill-server 2>/dev/null
|
|
||||||
$INNER_OFF kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$current_inner list-clients 2>/dev/null | grep -q . && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
capture_attach()
|
|
||||||
{
|
|
||||||
setting=$1
|
|
||||||
output=$2
|
|
||||||
go=$DIR/go-$setting
|
|
||||||
if [ "$setting" = on ]; then
|
|
||||||
current_inner=$INNER_ON
|
|
||||||
current_outer=$OUTER_ON
|
|
||||||
else
|
|
||||||
current_inner=$INNER_OFF
|
|
||||||
current_outer=$OUTER_OFF
|
|
||||||
fi
|
|
||||||
|
|
||||||
$current_inner new-session -d -s inner -x40 -y10 \
|
|
||||||
'exec sleep 100' || exit 1
|
|
||||||
$current_inner set-option -g status off || exit 1
|
|
||||||
$current_inner set-option -g clear-on-attach "$setting" || exit 1
|
|
||||||
$current_outer new-session -d -s outer -x40 -y10 \
|
|
||||||
"while [ ! -e '$go' ]; do sleep 0.01; done; exec $current_inner attach-session -t inner" || exit 1
|
|
||||||
$current_outer set-option -g status off || exit 1
|
|
||||||
$current_outer pipe-pane -O "cat >'$output'" || exit 1
|
|
||||||
touch "$go"
|
|
||||||
wait_for_client
|
|
||||||
sleep 0.5
|
|
||||||
$current_outer pipe-pane -O || exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
on=$DIR/on
|
|
||||||
off=$DIR/off
|
|
||||||
capture_attach on "$on"
|
|
||||||
capture_attach off "$off"
|
|
||||||
|
|
||||||
smcup=$(printf '\033[?1049h')
|
|
||||||
grep -Fq "$smcup" "$on" || fail "clear-on-attach on did not use smcup"
|
|
||||||
if grep -Fq "$smcup" "$off"; then
|
|
||||||
fail "clear-on-attach off used smcup"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Some screen terminfo entries have indn (CSI Ps S), while older ones only
|
|
||||||
# have ind (newline). The preserving path uses indn once or repeats ind once
|
|
||||||
# for every line in the terminal plus one.
|
|
||||||
escape=$(printf '\033')
|
|
||||||
if ! LC_ALL=C grep -Eq "${escape}\\[[0-9]+S" "$off"; then
|
|
||||||
hex=$(od -An -tx1 -v "$off" | tr -d ' \n')
|
|
||||||
case "$hex" in
|
|
||||||
*0a0a0a0a0a0a0a0a0a0a0a*) ;;
|
|
||||||
*) fail "clear-on-attach off did not scroll the old contents away" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# send-keys -K injects a key into a client. Commands produced by that key must
|
|
||||||
# be inserted immediately after send-keys, before later commands in the same
|
|
||||||
# command list. Focus events arriving between a prefix and its following key
|
|
||||||
# must be forwarded without cancelling the prefix table.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
client=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$client" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_option()
|
|
||||||
{
|
|
||||||
option=$1
|
|
||||||
expected=$2
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
actual=$($INNER show-option -gv "$option" 2>/dev/null)
|
|
||||||
[ "$actual" = "$expected" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "$option is '$actual', expected '$expected'"
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g focus-events on || exit 1
|
|
||||||
$OUTER new-session -d -s outer -x40 -y10 "$INNER attach -t inner" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
wait_for_client
|
|
||||||
|
|
||||||
# The binding generated by send-keys -K must run before the command which
|
|
||||||
# follows send-keys in this command list. Before the queue-ordering fix this
|
|
||||||
# produces AB rather than BA.
|
|
||||||
$INNER set-option -g @order '' || exit 1
|
|
||||||
$INNER bind-key -n a set-option -gF @order '#{@order}B' || exit 1
|
|
||||||
$INNER send-keys -K -c "$client" a \; \
|
|
||||||
set-option -gF @order '#{@order}A' || exit 1
|
|
||||||
wait_for_option @order BA
|
|
||||||
|
|
||||||
# Put the real attached client in the prefix table, deliver FocusIn, then the
|
|
||||||
# bound key. FocusIn is not bindable and must not reset the prefix table.
|
|
||||||
$INNER bind-key x set-option -g @focus-prefix yes || exit 1
|
|
||||||
$OUTER send-keys C-b || exit 1
|
|
||||||
focus_in=$(printf '\033[I')
|
|
||||||
$OUTER send-keys -l "$focus_in" || exit 1
|
|
||||||
$OUTER send-keys x || exit 1
|
|
||||||
wait_for_option @focus-prefix yes
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise clock-mode initialization, all four styles, its one-second timer,
|
|
||||||
# resize into the compact renderer, arbitrary-key exit, and cleanup.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
capture()
|
|
||||||
{
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_mode()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
got=$($INNER display-message -p -t clock:0 '#{pane_mode}' 2>/dev/null)
|
|
||||||
[ "$got" = "$want" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "pane mode is '$got', expected '$want'"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_hashes()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
captured=$(capture)
|
|
||||||
printf '%s\n' "$captured" | grep -q '#' && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "large clock did not render"
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s clock -x80 -y24 'exec sleep 100' || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -w clock-mode-colour red || exit 1
|
|
||||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t clock" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
for style in 12 24 12-with-seconds 24-with-seconds; do
|
|
||||||
$INNER set-option -w -t clock:0 clock-mode-style "$style" || exit 1
|
|
||||||
$INNER clock-mode -t clock:0 || exit 1
|
|
||||||
wait_mode clock-mode
|
|
||||||
wait_hashes
|
|
||||||
# Let at least one timer callback observe a new second.
|
|
||||||
[ "$style" != 12-with-seconds ] || sleep 1.2
|
|
||||||
$INNER send-keys -t clock:0 x || exit 1
|
|
||||||
wait_mode ''
|
|
||||||
done
|
|
||||||
|
|
||||||
# Resizing an active mode below the large-glyph threshold selects the compact
|
|
||||||
# renderer. A visible time contains a colon and no block-clock hash.
|
|
||||||
$INNER clock-mode -t clock:0 || exit 1
|
|
||||||
wait_mode clock-mode
|
|
||||||
$INNER resize-window -t clock:0 -x20 -y5 || exit 1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
captured=$(capture)
|
|
||||||
printf '%s\n' "$captured" | grep -Eq '[0-9][0-9]?:[0-9][0-9]' && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "compact clock did not render after resize"
|
|
||||||
printf '%s\n' "$captured" | grep -q '#' &&
|
|
||||||
fail "compact clock still used large glyphs"
|
|
||||||
$INNER send-keys -t clock:0 Enter || exit 1
|
|
||||||
wait_mode ''
|
|
||||||
|
|
||||||
$INNER has-session -t clock || fail "server died during clock-mode tests"
|
|
||||||
exit 0
|
|
||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Client argv parsing and the start-server boundary.
|
||||||
|
#
|
||||||
|
# The command line given to the client is parsed and invoked as one sequence.
|
||||||
|
# This checks: a multi-command argv runs every command and starts the server
|
||||||
|
# (new-session carries CMD_STARTSERVER); a command without start-server fails
|
||||||
|
# cleanly against no server; and a parse error leaves no stray server behind.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Multi-command argv: starts the server and runs all commands in order.
|
||||||
|
$TMUX new-session -d -swork -nbase \; set -g @a one \; new-window -dn W2 \; \
|
||||||
|
set -g @b two || exit 1
|
||||||
|
[ "$($TMUX show -gv @a)" = one ] || { echo "argv cmd 2 did not run" >&2; exit 1; }
|
||||||
|
[ "$($TMUX show -gv @b)" = two ] || { echo "argv cmd 4 did not run" >&2; exit 1; }
|
||||||
|
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
|
||||||
|
[ "$got" = "base,W2," ] || { echo "argv windows: got [$got]" >&2; exit 1; }
|
||||||
|
# kill-server is asynchronous; wait for the server to actually exit before the
|
||||||
|
# checks below that require no server to be running.
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
i=0
|
||||||
|
while $TMUX has-session 2>/dev/null; do
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
[ $i -gt 100 ] && break
|
||||||
|
done
|
||||||
|
|
||||||
|
# A command without start-server fails cleanly when no server is running and
|
||||||
|
# does not fork one.
|
||||||
|
$TMUX new-window -dn ZZ 2>/dev/null && { echo "new-window unexpectedly succeeded" >&2; exit 1; }
|
||||||
|
if $TMUX has-session 2>/dev/null; then
|
||||||
|
echo "non-start-server command left a stray server" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A parse error with no server running starts no server.
|
||||||
|
$TMUX 'if-shell true {' 2>/dev/null && { echo "parse error did not fail" >&2; exit 1; }
|
||||||
|
if $TMUX has-session 2>/dev/null; then
|
||||||
|
echo "parse error left a stray server" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
Executable
+87
@@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# command-alias expansion, against a running server and at server start.
|
||||||
|
#
|
||||||
|
# An alias replaces a command name with parsed command text; arguments after the
|
||||||
|
# alias name are appended to the last command of the expansion. This covers a
|
||||||
|
# single-command alias, a multi-command alias, argument appending, a built-in
|
||||||
|
# default alias, and aliases that are defined and used as the server starts
|
||||||
|
# (both from the startup config and from the client command line that starts
|
||||||
|
# the server).
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
CONF=$(mktemp)
|
||||||
|
trap "rm -f $CONF" 0 1 15
|
||||||
|
|
||||||
|
wait_gone() {
|
||||||
|
i=0
|
||||||
|
while $TMUX has-session 2>/dev/null; do
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
[ $i -gt 100 ] && break
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Against a running server. ---------------------------------------------
|
||||||
|
$TMUX -f/dev/null start \; new-session -d -swork -nbase 2>/dev/null || exit 1
|
||||||
|
|
||||||
|
# Single-command alias.
|
||||||
|
$TMUX split-window -dt work || exit 1
|
||||||
|
$TMUX set -s command-alias[200] 'zoomit=resize-pane -Z' || exit 1
|
||||||
|
$TMUX zoomit -t work:.0 || exit 1
|
||||||
|
[ "$($TMUX display-message -p -t work '#{window_zoomed_flag}')" = 1 ] || {
|
||||||
|
echo "single-command alias did not zoom" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Multi-command alias: both commands run.
|
||||||
|
$TMUX set -s command-alias[201] 'twowin=new-window -dn AA ; new-window -dn BB' || exit 1
|
||||||
|
$TMUX run-shell -C 'twowin' || exit 1
|
||||||
|
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
|
||||||
|
[ "$got" = "base,AA,BB," ] || { echo "multi-command alias: got [$got]" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Arguments after the alias name are appended to the expansion.
|
||||||
|
$TMUX set -s command-alias[202] 'namewin=new-window -d -n' || exit 1
|
||||||
|
$TMUX run-shell -C 'namewin CC' || exit 1
|
||||||
|
$TMUX list-windows -t work -F '#{window_name}' | grep -qx CC || {
|
||||||
|
echo "alias argument append did not create window CC" >&2; exit 1; }
|
||||||
|
|
||||||
|
# A built-in default alias (splitp -> split-window).
|
||||||
|
before=$($TMUX list-panes -t work | wc -l)
|
||||||
|
$TMUX run-shell -C 'splitp -d -t work' || exit 1
|
||||||
|
after=$($TMUX list-panes -t work | wc -l)
|
||||||
|
[ "$after" -gt "$before" ] || { echo "built-in alias splitp did not split" >&2; exit 1; }
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
wait_gone
|
||||||
|
|
||||||
|
# --- At server start: alias defined and used in the startup config. --------
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
set -s command-alias[100] greet='set -g @g started'
|
||||||
|
set -s command-alias[101] mkwins='new-window -dn SW1 ; new-window -dn SW2'
|
||||||
|
new-session -d -swork -nbase
|
||||||
|
greet
|
||||||
|
mkwins
|
||||||
|
EOF
|
||||||
|
$TMUX -f$CONF start 2>/dev/null || exit 1
|
||||||
|
[ "$($TMUX show -gv @g)" = started ] || { echo "startup-config alias did not run" >&2; exit 1; }
|
||||||
|
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
|
||||||
|
[ "$got" = "base,SW1,SW2," ] || { echo "startup-config multi alias: got [$got]" >&2; exit 1; }
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
wait_gone
|
||||||
|
|
||||||
|
# --- At server start: alias from config, used on the command line that starts
|
||||||
|
# the server. ---------------------------------------------------------
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
set -s command-alias[100] greet='set -g @g argv'
|
||||||
|
EOF
|
||||||
|
$TMUX -f$CONF new-session -d -swork \; greet 2>/dev/null || exit 1
|
||||||
|
[ "$($TMUX show -gv @g)" = argv ] || { echo "argv-start alias did not run" >&2; exit 1; }
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
Executable
+102
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Deferred command callbacks.
|
||||||
|
#
|
||||||
|
# Several commands store a parsed tree and invoke it later through
|
||||||
|
# cmd_invoke_get: if-shell (then/else, sync and background), run-shell -C, and -
|
||||||
|
# once a key is pressed by an attached client - a key binding, confirm-before and
|
||||||
|
# command-prompt. The headless cases run directly on the inner server; the
|
||||||
|
# client cases use the nested-tmux pattern from prompt-mechanics.sh: an outer
|
||||||
|
# server hosts the inner client in a pane and keys are injected with send-keys.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
OUT="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
IN="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||||
|
|
||||||
|
$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" 0 1 15
|
||||||
|
|
||||||
|
fail() { echo "[FAIL] $1" >&2; exit 1; }
|
||||||
|
settle() { sleep 0.5; }
|
||||||
|
|
||||||
|
CONF=$(mktemp)
|
||||||
|
trap "rm -f $CONF; $OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" 0 1 15
|
||||||
|
|
||||||
|
# --- Headless deferred callbacks: no client needed. ------------------------
|
||||||
|
$IN new -d -x80 -y23 -nbase "sh -c 'exec sleep 1000'" || exit 1
|
||||||
|
$IN set -g status on || exit 1
|
||||||
|
$IN set -g status-keys emacs || exit 1
|
||||||
|
$IN set -g window-size manual || exit 1
|
||||||
|
|
||||||
|
$IN if-shell true 'set -g @t then' 'set -g @t else' || exit 1
|
||||||
|
[ "$($IN show -gv @t)" = then ] || fail "if-shell true ran wrong branch"
|
||||||
|
$IN if-shell false 'set -g @f then' 'set -g @f else' || exit 1
|
||||||
|
[ "$($IN show -gv @f)" = else ] || fail "if-shell false ran wrong branch"
|
||||||
|
|
||||||
|
# Background (-b) if-shell evaluates its condition in a shell asynchronously.
|
||||||
|
$IN if-shell -b 'true' 'set -g @b yes' 'set -g @b no' || exit 1
|
||||||
|
settle
|
||||||
|
[ "$($IN show -gv @b)" = yes ] || fail "background if-shell ran wrong branch"
|
||||||
|
|
||||||
|
# A multi-command then-body runs both commands.
|
||||||
|
$IN if-shell true 'new-window -dn IF1 ; new-window -dn IF2' '' || exit 1
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx IF1 || fail "if-shell body cmd1 missing"
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx IF2 || fail "if-shell body cmd2 missing"
|
||||||
|
|
||||||
|
# run-shell -C invokes its argument as tmux commands.
|
||||||
|
$IN run-shell -C 'set -g @rc ran' || exit 1
|
||||||
|
[ "$($IN show -gv @rc)" = ran ] || fail "run-shell -C did not run command"
|
||||||
|
|
||||||
|
# --- Client deferred callbacks: key binding, confirm-before, command-prompt. -
|
||||||
|
$IN bind -n M-c confirm-before -p '(ok) ' 'set -g @cb confirmed' || exit 1
|
||||||
|
$IN bind -n M-d command-prompt -I pre -p '(cmd) ' 'set -g @cp %%' || exit 1
|
||||||
|
# A brace body must come through the lexer, so bind it from a config.
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
bind -n M-k { new-window -dn K1 ; new-window -dn K2 }
|
||||||
|
EOF
|
||||||
|
$IN source-file $CONF || exit 1
|
||||||
|
|
||||||
|
$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
|
||||||
|
|
||||||
|
# Key binding with a stored multi-command body fires on keypress.
|
||||||
|
$OUT send-keys M-k || exit 1
|
||||||
|
settle
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx K1 || fail "key binding body cmd1 missing"
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx K2 || fail "key binding body cmd2 missing"
|
||||||
|
|
||||||
|
# confirm-before runs its command only when confirmed.
|
||||||
|
$IN set -g @cb none || exit 1
|
||||||
|
$OUT send-keys M-c || exit 1
|
||||||
|
settle
|
||||||
|
$OUT capture-pane -p | tail -1 | grep -qF '(ok)' || fail "confirm-before prompt not shown"
|
||||||
|
$OUT send-keys n || exit 1
|
||||||
|
settle
|
||||||
|
[ "$($IN show -gv @cb)" = none ] || fail "confirm-before ran command after 'n'"
|
||||||
|
$OUT send-keys M-c || exit 1
|
||||||
|
settle
|
||||||
|
$OUT send-keys y || exit 1
|
||||||
|
settle
|
||||||
|
[ "$($IN show -gv @cb)" = confirmed ] || fail "confirm-before did not run command after 'y'"
|
||||||
|
|
||||||
|
# command-prompt feeds the typed line (with -I prefill) into its template.
|
||||||
|
$IN set -g @cp none || exit 1
|
||||||
|
$OUT send-keys M-d || exit 1
|
||||||
|
settle
|
||||||
|
$OUT capture-pane -p | tail -1 | grep -qF '(cmd)' || fail "command-prompt not shown"
|
||||||
|
$OUT send-keys -l X || exit 1
|
||||||
|
$OUT send-keys Enter || exit 1
|
||||||
|
settle
|
||||||
|
[ "$($IN show -gv @cp)" = preX ] || fail "command-prompt recovered '$($IN show -gv @cp)'"
|
||||||
|
|
||||||
|
$OUT kill-server 2>/dev/null
|
||||||
|
$IN kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
Executable
+75
@@ -0,0 +1,75 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Invoke-time expansion and conditionals.
|
||||||
|
#
|
||||||
|
# The parser builds syntax only; environment/tilde expansion, assignments and
|
||||||
|
# %if/%elif/%else are all evaluated when the tree is invoked. This drives every
|
||||||
|
# one of those through a single config and checks the resulting option values:
|
||||||
|
# - FOO=bar assignments set the environment, read back with ${FOO} and $FOO
|
||||||
|
# - %hidden assignments behave the same
|
||||||
|
# - ~ expands to the home directory
|
||||||
|
# - %if / %elif / %else selects the correct branch
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
CONF=$(mktemp)
|
||||||
|
trap "rm -f $CONF" 0 1 15
|
||||||
|
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
FOO=hello
|
||||||
|
set -g @assign "${FOO}"
|
||||||
|
set -g @dollar "$FOO"
|
||||||
|
%hidden BAR=secret
|
||||||
|
set -g @hidden "${BAR}"
|
||||||
|
set -g @undef "x${NOSUCHVAR_ZZZ}y"
|
||||||
|
set -g @tilde ~
|
||||||
|
set -g @baduser ~nosuchuser_zzz9
|
||||||
|
%if 1
|
||||||
|
set -g @iftrue then
|
||||||
|
%else
|
||||||
|
set -g @iftrue else
|
||||||
|
%endif
|
||||||
|
%if 0
|
||||||
|
set -g @iffalse then
|
||||||
|
%else
|
||||||
|
set -g @iffalse else
|
||||||
|
%endif
|
||||||
|
%if 0
|
||||||
|
set -g @elif a
|
||||||
|
%elif 1
|
||||||
|
set -g @elif b
|
||||||
|
%else
|
||||||
|
set -g @elif c
|
||||||
|
%endif
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1
|
||||||
|
$TMUX source-file $CONF || exit 1
|
||||||
|
|
||||||
|
check() {
|
||||||
|
got=$($TMUX show -gv "$1")
|
||||||
|
[ "$got" = "$2" ] || { echo "$1: got [$got] expected [$2]" >&2; exit 1; }
|
||||||
|
}
|
||||||
|
check @assign hello
|
||||||
|
check @dollar hello
|
||||||
|
check @hidden secret
|
||||||
|
check @undef xy # undefined environment variable expands to nothing
|
||||||
|
check @baduser "" # unknown user in ~user expands to nothing
|
||||||
|
check @iftrue then
|
||||||
|
check @iffalse else
|
||||||
|
check @elif b
|
||||||
|
|
||||||
|
# ~ expands to an absolute home directory (value is machine dependent).
|
||||||
|
tilde=$($TMUX show -gv @tilde)
|
||||||
|
case "$tilde" in
|
||||||
|
/*) ;;
|
||||||
|
*) echo "@tilde did not expand to an absolute path: [$tilde]" >&2; exit 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
Executable
+72
@@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Command failure scope.
|
||||||
|
#
|
||||||
|
# Failure scope is the active sequence (CMD_PARSE_SEQUENCE): commands joined by
|
||||||
|
# ';' share one scope, so a failure skips the rest of that sequence; commands on
|
||||||
|
# separate lines are independent sequences and are not skipped. After a failed
|
||||||
|
# inner brace the enclosing sequence still resumes.
|
||||||
|
#
|
||||||
|
# Each case below creates windows around a deliberately invalid command and the
|
||||||
|
# resulting window list shows exactly which commands ran.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
TMP=$(mktemp)
|
||||||
|
CONF=$(mktemp)
|
||||||
|
trap "rm -f $TMP $CONF" 0 1 15
|
||||||
|
|
||||||
|
# kill-server is asynchronous, so wait for the old server to actually exit
|
||||||
|
# before starting a fresh one (otherwise the next start races a dying server).
|
||||||
|
fresh_server() {
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
i=0
|
||||||
|
while $TMUX list-sessions >/dev/null 2>&1; do
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
[ $i -gt 100 ] && break
|
||||||
|
done
|
||||||
|
$TMUX -f/dev/null start \; new-session -d -swork -nbase || exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# $1 label, $2 config body, $3 expected comma-separated window list.
|
||||||
|
run() {
|
||||||
|
fresh_server
|
||||||
|
printf '%s\n' "$2" >$CONF
|
||||||
|
$TMUX source-file $CONF >/dev/null 2>&1
|
||||||
|
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
|
||||||
|
if [ "$got" != "$3" ]; then
|
||||||
|
echo "$1: got [$got] expected [$3]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ';' sequence in a brace body: A runs, the bad command skips B (same sequence),
|
||||||
|
# the outer sequence resumes so C runs.
|
||||||
|
run "semicolon body" 'if-shell true { new-window -dn A ; nonexistent_cmd ; new-window -dn B }
|
||||||
|
new-window -dn C' 'base,A,C,'
|
||||||
|
|
||||||
|
# Newlines are independent sequences: the bad command skips only itself, so both
|
||||||
|
# B and C still run.
|
||||||
|
run "newline body" 'if-shell true {
|
||||||
|
new-window -dn A
|
||||||
|
nonexistent_cmd
|
||||||
|
new-window -dn B
|
||||||
|
}
|
||||||
|
new-window -dn C' 'base,A,B,C,'
|
||||||
|
|
||||||
|
# Top-level ';' sequence: failure skips the rest of the line.
|
||||||
|
run "top-level semicolon" 'new-window -dn A ; nonexistent_cmd ; new-window -dn B' 'base,A,'
|
||||||
|
|
||||||
|
# Top-level newlines: independent, both run.
|
||||||
|
run "top-level newline" 'new-window -dn A
|
||||||
|
nonexistent_cmd
|
||||||
|
new-window -dn B' 'base,A,B,'
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Hooks run stored command trees.
|
||||||
|
#
|
||||||
|
# A hook stores a parsed command tree that is invoked through cmd_invoke_get when
|
||||||
|
# the hook fires. Covers a single-command hook, a multi-entry hook array (every
|
||||||
|
# entry fires), and that show-hooks prints the stored commands back.
|
||||||
|
|
||||||
|
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 -f/dev/null start \; new-session -d -sfirst -nbase 2>/dev/null || exit 1
|
||||||
|
|
||||||
|
# Single-command hook fires on session creation.
|
||||||
|
$TMUX set-hook -g session-created 'set -g @hook done' || exit 1
|
||||||
|
$TMUX new-session -d -ssecond || exit 1
|
||||||
|
[ "$($TMUX show -gv @hook)" = done ] || {
|
||||||
|
echo "single-command hook did not fire" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# A hook array fires every entry.
|
||||||
|
$TMUX set-hook -g session-created[10] 'new-window -dn H1' || exit 1
|
||||||
|
$TMUX set-hook -g session-created[11] 'set -g @hook2 two' || exit 1
|
||||||
|
$TMUX new-session -d -sthird || exit 1
|
||||||
|
[ "$($TMUX show -gv @hook2)" = two ] || {
|
||||||
|
echo "hook array entry 11 did not fire" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "$($TMUX list-windows -t third -F '#{window_name}')" | grep -qx H1 || {
|
||||||
|
echo "hook array entry 10 did not create window" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# show-hooks prints the stored hook commands.
|
||||||
|
$TMUX show-hooks -g | grep -q 'session-created.* set -g @hook done' || {
|
||||||
|
echo "show-hooks did not print stored hook command" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
Executable
+56
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Read-only client enforcement.
|
||||||
|
#
|
||||||
|
# When the invoking client is read-only, cmd_invoke builds each command and
|
||||||
|
# rejects it (without running it) unless the command entry is marked read-only.
|
||||||
|
# A read-only client is created by attaching with -r; the nested-tmux pattern
|
||||||
|
# from cmd-invoke-deferred.sh is used so a real read-only client presses a key.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
OUT="$TEST_TMUX -Ltest -f/dev/null"
|
||||||
|
IN="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||||
|
|
||||||
|
$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" 0 1 15
|
||||||
|
|
||||||
|
fail() { echo "[FAIL] $1" >&2; exit 1; }
|
||||||
|
settle() { sleep 0.5; }
|
||||||
|
|
||||||
|
# Inner session: a key bound to a non-read-only command.
|
||||||
|
$IN new -d -x80 -y23 -nbase "sh -c 'exec sleep 1000'" || exit 1
|
||||||
|
$IN set -g status on || exit 1
|
||||||
|
$IN set -g status-keys emacs || exit 1
|
||||||
|
$IN set -g window-size manual || exit 1
|
||||||
|
$IN bind -n M-w new-window -dn ROTRY || exit 1
|
||||||
|
|
||||||
|
# Outer session hosts the inner client, attached read-only with -r.
|
||||||
|
$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 -r" || exit 1
|
||||||
|
$OUT send-keys Enter || exit 1
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
[ "$($IN list-clients -F '#{client_readonly}')" = 1 ] || fail "client is not read-only"
|
||||||
|
|
||||||
|
# Pressing the key runs new-window through the read-only client: it must be
|
||||||
|
# rejected, leave no window, and report the error.
|
||||||
|
$OUT send-keys M-w || exit 1
|
||||||
|
settle
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx ROTRY && \
|
||||||
|
fail "read-only client was allowed to create a window"
|
||||||
|
$OUT capture-pane -p | grep -qi 'read-only' || fail "no read-only error was shown"
|
||||||
|
|
||||||
|
# Positive control: the same command with no client is not read-only and runs,
|
||||||
|
# proving the command itself is valid and only the read-only client blocked it.
|
||||||
|
$IN new-window -dn OKWIN || exit 1
|
||||||
|
$IN list-windows -F '#{window_name}' | grep -qx OKWIN || fail "control new-window did not run"
|
||||||
|
|
||||||
|
$OUT kill-server 2>/dev/null
|
||||||
|
$IN kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Malformed input rejection and crash/leak canary.
|
||||||
|
#
|
||||||
|
# Each input below is a syntax error. For every one we require that:
|
||||||
|
# - parsing reports failure (non-zero exit), and
|
||||||
|
# - the server is still alive afterwards (a follow-up command succeeds).
|
||||||
|
# The second check is the important one: a parser that crashes or corrupts state
|
||||||
|
# on bad input would take the server down here.
|
||||||
|
|
||||||
|
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 -f/dev/null start \; new-session -d 2>/dev/null || exit 1
|
||||||
|
|
||||||
|
n=0
|
||||||
|
check() {
|
||||||
|
n=$((n + 1))
|
||||||
|
# Expect a parse failure.
|
||||||
|
if $TMUX run-shell -C "$1" >/dev/null 2>&1; then
|
||||||
|
echo "case $n: expected failure but succeeded: $1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Expect the server to still be responsive.
|
||||||
|
if ! $TMUX list-sessions >/dev/null 2>&1; then
|
||||||
|
echo "case $n: server died after: $1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check 'if-shell true {' # unterminated brace body
|
||||||
|
check 'display-message a }' # stray close brace
|
||||||
|
check 'if-shell true { display-message a' # unclosed nested brace
|
||||||
|
check 'if-shell true { ; }' # empty sequence in body
|
||||||
|
check '%elif 1' # %elif with no %if
|
||||||
|
check '%endif' # %endif with no %if
|
||||||
|
check '%zzz' # unknown % directive
|
||||||
|
check '; display-message a' # leading separator
|
||||||
|
input='%if 1
|
||||||
|
display-message a'
|
||||||
|
check "$input" # unterminated %if
|
||||||
|
input='%if 1
|
||||||
|
%endif'
|
||||||
|
check "$input" # empty %if body
|
||||||
|
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
exit 0
|
||||||
Executable
+120
@@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Parse/print golden suite for the command parser.
|
||||||
|
#
|
||||||
|
# Binds a spread of command syntax forms into a dedicated key table, then prints
|
||||||
|
# them back with list-keys (which calls cmd_parse_print on the stored tree). A
|
||||||
|
# command-valued option is printed too. Three things are checked:
|
||||||
|
# - the default (single line) form is byte-for-byte as expected: quoting,
|
||||||
|
# escaped \; separators, inline braces, and preservation of unexpanded
|
||||||
|
# ${env}, ~ and #{format} inside stored bodies;
|
||||||
|
# - the -p (multiline) form is as expected;
|
||||||
|
# - the default form round-trips: sourcing it again reproduces the same keys.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
SOCKET=$(mktemp -u testXXXXXX)
|
||||||
|
TMUX="$TEST_TMUX -L$SOCKET -f/dev/null"
|
||||||
|
TMUX2=
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
|
TMP=$(mktemp)
|
||||||
|
CONF=$(mktemp)
|
||||||
|
EXP=$(mktemp)
|
||||||
|
RT1=$(mktemp)
|
||||||
|
RT2=$(mktemp)
|
||||||
|
trap "[ -n \"\$TMUX\" ] && \$TMUX kill-server 2>/dev/null; [ -n \"\$TMUX2\" ] && \$TMUX2 kill-server 2>/dev/null; rm -f $TMP $CONF $EXP $RT1 $RT2" 0 1 15
|
||||||
|
|
||||||
|
cat <<'EOF' >$CONF
|
||||||
|
bind -T parsetest a display-message hello
|
||||||
|
bind -T parsetest b display-message "hello world"
|
||||||
|
bind -T parsetest c display-message 'literal $HOME #{p} ~'
|
||||||
|
bind -T parsetest d display-message ""
|
||||||
|
bind -T parsetest e display-message "#{pane_id}"
|
||||||
|
bind -T parsetest f display-message a \; display-message b
|
||||||
|
bind -T parsetest g {
|
||||||
|
display-message one
|
||||||
|
display-message two
|
||||||
|
}
|
||||||
|
bind -T parsetest h if-shell true { display-message yes } { display-message no }
|
||||||
|
bind -T parsetest m if-shell true { display-message "${HOME}" "~" "~root" "#{pane_id}" }
|
||||||
|
bind -T parsetest n if-shell true { display-message "a\nb" "x;y" '#literal' }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Expected output. The default form is single line (round-trippable); the -p
|
||||||
|
# form is multiline, with lines inside braced bodies indented by a single tab.
|
||||||
|
cat <<'EOF' >$EXP
|
||||||
|
bind-key -T parsetest a display-message hello
|
||||||
|
bind-key -T parsetest b display-message 'hello world'
|
||||||
|
bind-key -T parsetest c display-message 'literal $HOME #{p} ~'
|
||||||
|
bind-key -T parsetest d display-message ''
|
||||||
|
bind-key -T parsetest e display-message '#{pane_id}'
|
||||||
|
bind-key -T parsetest f display-message a \; display-message b
|
||||||
|
bind-key -T parsetest g display-message one \; display-message two
|
||||||
|
bind-key -T parsetest h if-shell true { display-message yes } { display-message no }
|
||||||
|
bind-key -T parsetest m if-shell true { display-message ${HOME} ~ ~root '#{pane_id}' }
|
||||||
|
bind-key -T parsetest n if-shell true { display-message a\nb 'x;y' '#literal' }
|
||||||
|
--- options ---
|
||||||
|
display-message 'hi there'
|
||||||
|
--- multiline ---
|
||||||
|
bind-key -T parsetest a display-message hello
|
||||||
|
bind-key -T parsetest b display-message 'hello world'
|
||||||
|
bind-key -T parsetest c display-message 'literal $HOME #{p} ~'
|
||||||
|
bind-key -T parsetest d display-message ''
|
||||||
|
bind-key -T parsetest e display-message '#{pane_id}'
|
||||||
|
bind-key -T parsetest f display-message a \; display-message b
|
||||||
|
bind-key -T parsetest g display-message one
|
||||||
|
display-message two
|
||||||
|
bind-key -T parsetest h if-shell true {
|
||||||
|
display-message yes
|
||||||
|
} {
|
||||||
|
display-message no
|
||||||
|
}
|
||||||
|
bind-key -T parsetest m if-shell true {
|
||||||
|
display-message ${HOME} ~ ~root '#{pane_id}'
|
||||||
|
}
|
||||||
|
bind-key -T parsetest n if-shell true {
|
||||||
|
display-message a\nb 'x;y' '#literal'
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1
|
||||||
|
$TMUX source-file $CONF || exit 1
|
||||||
|
$TMUX set -g default-client-command 'display-message "hi there"' || exit 1
|
||||||
|
|
||||||
|
set +x
|
||||||
|
{
|
||||||
|
$TMUX list-keys -T parsetest
|
||||||
|
echo "--- options ---"
|
||||||
|
$TMUX show -gv default-client-command
|
||||||
|
echo "--- multiline ---"
|
||||||
|
$TMUX list-keys -p -T parsetest
|
||||||
|
} >$TMP 2>&1 || exit 1
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cmp -s $TMP $EXP || {
|
||||||
|
echo "cmd-parse-print: output differs from expected" >&2
|
||||||
|
diff -u $EXP $TMP >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Round-trip: the default form re-sourced into a fresh server must reproduce the
|
||||||
|
# identical key list.
|
||||||
|
$TMUX list-keys -T parsetest >$RT1 || exit 1
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
SOCKET2=$(mktemp -u testXXXXXX)
|
||||||
|
TMUX2="$TEST_TMUX -L$SOCKET2 -f/dev/null"
|
||||||
|
$TMUX2 -f/dev/null start \; new-session -d 2>/dev/null || exit 1
|
||||||
|
$TMUX2 source-file $RT1 || exit 1
|
||||||
|
$TMUX2 list-keys -T parsetest >$RT2 || exit 1
|
||||||
|
$TMUX2 kill-server 2>/dev/null
|
||||||
|
|
||||||
|
cmp -s $RT1 $RT2 || {
|
||||||
|
echo "cmd-parse-print: default form does not round-trip" >&2
|
||||||
|
diff -u $RT1 $RT2 >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Width cache ranges must include their endpoint without overflowing wchar_t.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
SERVER=
|
|
||||||
WATCHDOG=
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
if [ -n "$WATCHDOG" ]; then
|
|
||||||
kill "$WATCHDOG" 2>/dev/null
|
|
||||||
wait "$WATCHDOG" 2>/dev/null
|
|
||||||
fi
|
|
||||||
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
|
|
||||||
}
|
|
||||||
trap cleanup 0
|
|
||||||
trap 'exit 1' 1 2 15
|
|
||||||
|
|
||||||
$TMUX new-session -d 'exec sleep 60' || exit 1
|
|
||||||
SERVER=$($TMUX display-message -p '#{pid}') || exit 1
|
|
||||||
|
|
||||||
# kill-server cannot stop a server stuck rebuilding the width cache.
|
|
||||||
(
|
|
||||||
sleep 5
|
|
||||||
kill -9 "$SERVER" 2>/dev/null
|
|
||||||
) &
|
|
||||||
WATCHDOG=$!
|
|
||||||
|
|
||||||
$TMUX set -g codepoint-widths 'U+1F600=2' || exit 1
|
|
||||||
|
|
||||||
# Cover both signed and unsigned 32-bit wchar_t. Values above WCHAR_MAX
|
|
||||||
# are ignored by the parser on platforms where they cannot be represented.
|
|
||||||
for value in U+7FFFFFFF=1 U+7FFFFFFE-U+7FFFFFFF=2 \
|
|
||||||
U+FFFFFFFF=1 U+FFFFFFFE-U+FFFFFFFF=2; do
|
|
||||||
$TMUX set -g codepoint-widths "$value" || exit 1
|
|
||||||
[ "$($TMUX display-message -p alive)" = alive ] || exit 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check that an ordinary range still includes both endpoints and stops there.
|
|
||||||
$TMUX set -g codepoint-widths 'U+03B1-U+03B3=2' || exit 1
|
|
||||||
$TMUX set -g @text 'αβγδ' || exit 1
|
|
||||||
[ "$($TMUX display-message -p '#{w:@text}')" = 7 ] || exit 1
|
|
||||||
$TMUX set -gu codepoint-widths || exit 1
|
|
||||||
[ "$($TMUX display-message -p '#{w:@text}')" = 4 ] || exit 1
|
|
||||||
|
|
||||||
$TMUX kill-server || exit 1
|
|
||||||
SERVER=
|
|
||||||
exit 0
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise confirm-before's waiting and background callbacks, custom keys and
|
|
||||||
# prompts, default-yes handling and errors. Also cover all three lock command
|
|
||||||
# entry points with a harmless lock-command which leaves a marker.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
capture()
|
|
||||||
{
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_capture()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
captured=$(capture)
|
|
||||||
printf '%s\n' "$captured" | grep -Fq "$marker" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "timed out waiting for '$marker'"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_option()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
got=$($INNER show-option -gqv @confirmed 2>/dev/null)
|
|
||||||
[ "$got" = "$want" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "@confirmed is '$got', expected '$want'"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_file()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
[ -f "$marker" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "lock command did not create $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s test -x80 -y24 'exec sleep 100' || exit 1
|
|
||||||
$INNER set-option -g status on || exit 1
|
|
||||||
$INNER set-option -g status-position bottom || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t test" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
client=$($INNER list-clients -F '#{client_name}')
|
|
||||||
[ -n "$client" ] || fail "inner client did not attach"
|
|
||||||
|
|
||||||
$INNER bind-key -n M-a confirm-before \
|
|
||||||
'set-option -g @confirmed accepted' || exit 1
|
|
||||||
$INNER bind-key -n M-b confirm-before -b -p 'Background?' \
|
|
||||||
'set-option -g @confirmed background' || exit 1
|
|
||||||
$INNER bind-key -n M-c confirm-before -c x -p 'Custom?' \
|
|
||||||
'set-option -g @confirmed custom' || exit 1
|
|
||||||
$INNER bind-key -n M-y confirm-before -y -p 'Default?' \
|
|
||||||
'set-option -g @confirmed default' || exit 1
|
|
||||||
|
|
||||||
# A non-confirming response closes the waiting prompt without running its
|
|
||||||
# command; reopening and accepting inserts the command after the waiting item.
|
|
||||||
$INNER set-option -g @confirmed sentinel || exit 1
|
|
||||||
$OUTER send-keys M-a || exit 1
|
|
||||||
wait_capture "Confirm 'set-option'? (y/n)"
|
|
||||||
$OUTER send-keys n || exit 1
|
|
||||||
sleep 0.2
|
|
||||||
wait_option sentinel
|
|
||||||
$OUTER send-keys M-a || exit 1
|
|
||||||
wait_capture "Confirm 'set-option'? (y/n)"
|
|
||||||
$OUTER send-keys y || exit 1
|
|
||||||
wait_option accepted
|
|
||||||
|
|
||||||
# -b has no waiting queue item and appends its command to the client queue.
|
|
||||||
$OUTER send-keys M-b || exit 1
|
|
||||||
wait_capture 'Background?'
|
|
||||||
$OUTER send-keys y || exit 1
|
|
||||||
wait_option background
|
|
||||||
|
|
||||||
# A custom printable key is accepted, and -y makes Enter affirmative.
|
|
||||||
$OUTER send-keys M-c || exit 1
|
|
||||||
wait_capture 'Custom?'
|
|
||||||
$OUTER send-keys x || exit 1
|
|
||||||
wait_option custom
|
|
||||||
$OUTER send-keys M-y || exit 1
|
|
||||||
wait_capture 'Default?'
|
|
||||||
$OUTER send-keys Enter || exit 1
|
|
||||||
wait_option default
|
|
||||||
|
|
||||||
# Reject invalid multi-character and control confirmation keys.
|
|
||||||
if $INNER confirm-before -b -t "$client" -c xx 'display-message x' \
|
|
||||||
>/dev/null 2>&1; then
|
|
||||||
fail "multi-character confirm key was accepted"
|
|
||||||
fi
|
|
||||||
if $INNER confirm-before -b -t "$client" -c "$(printf '\001')" \
|
|
||||||
'display-message x' >/dev/null 2>&1; then
|
|
||||||
fail "control confirm key was accepted"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# lock-client, lock-session and lock-server all send lock-command to the real
|
|
||||||
# attached client. The command returns immediately and unlocks the client.
|
|
||||||
for kind in client session server; do
|
|
||||||
marker="$DIR/locked-$kind"
|
|
||||||
$INNER set-option -t test lock-command "printf locked >$marker" || exit 1
|
|
||||||
case "$kind" in
|
|
||||||
client) $INNER lock-client -t "$client" || exit 1 ;;
|
|
||||||
session) $INNER lock-session -t test || exit 1 ;;
|
|
||||||
server) $INNER lock-server || exit 1 ;;
|
|
||||||
esac
|
|
||||||
wait_file "$marker"
|
|
||||||
done
|
|
||||||
|
|
||||||
$INNER has-session -t test || fail "server died during prompt or lock tests"
|
|
||||||
exit 0
|
|
||||||
Regular → Executable
+1
-1
@@ -17,7 +17,7 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -Ltest$$"
|
TMUX="$TEST_TMUX -Ltest"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
DIR=$(mktemp -d)
|
DIR=$(mktemp -d)
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -Ltest$$"
|
SOCKET=$(mktemp -u testXXXXXX)
|
||||||
|
TMUX="$TEST_TMUX -L$SOCKET"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
DIR=$(mktemp -d)
|
DIR=$(mktemp -d)
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Check that control client flags are available in every format expansion path
|
|
||||||
# used to produce layouts. The layout strings themselves are tested separately.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
LANG=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL LANG
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
FIFO=$DIR/input
|
|
||||||
OUT=$DIR/output
|
|
||||||
CFG=$DIR/flags.conf
|
|
||||||
PID=
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
exec 3>&-
|
|
||||||
[ -n "$PID" ] && kill "$PID" 2>/dev/null
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
wait_for()
|
|
||||||
{
|
|
||||||
pattern=$1
|
|
||||||
i=0
|
|
||||||
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0
|
|
||||||
if [ -n "$PID" ] && ! kill -0 "$PID" 2>/dev/null; then
|
|
||||||
fail "control client exited waiting for: $pattern"
|
|
||||||
fi
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "missing: $pattern"
|
|
||||||
}
|
|
||||||
|
|
||||||
send()
|
|
||||||
{
|
|
||||||
printf '%s\n' "$*" >&3
|
|
||||||
}
|
|
||||||
|
|
||||||
check_commands()
|
|
||||||
{
|
|
||||||
name=$1
|
|
||||||
expected=$2
|
|
||||||
match='#{m:*new-layouts*,#{client_flags}}'
|
|
||||||
|
|
||||||
send "display-message -p 'DISPLAY-$name $match'"
|
|
||||||
wait_for "DISPLAY-$name $expected"
|
|
||||||
send "list-panes -F 'PANES-$name $match'"
|
|
||||||
wait_for "PANES-$name $expected"
|
|
||||||
send "list-windows -F 'WINDOWS-$name $match'"
|
|
||||||
wait_for "WINDOWS-$name $expected"
|
|
||||||
send "list-sessions -F 'SESSIONS-$name $match'"
|
|
||||||
wait_for "SESSIONS-$name $expected"
|
|
||||||
send "list-clients -F 'CLIENTS-$name $match'"
|
|
||||||
wait_for "CLIENTS-$name $expected"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_config()
|
|
||||||
{
|
|
||||||
name=$1
|
|
||||||
expected=$2
|
|
||||||
|
|
||||||
send "source-file '$CFG'"
|
|
||||||
send "display-message -p 'CONFIG-$name #{@config-new-layouts}'"
|
|
||||||
wait_for "CONFIG-$name $expected"
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$CFG" <<'EOF'
|
|
||||||
%if #{m:*new-layouts*,#{client_flags}}
|
|
||||||
set-option -g @config-new-layouts 1
|
|
||||||
%else
|
|
||||||
set-option -g @config-new-layouts 0
|
|
||||||
%endif
|
|
||||||
EOF
|
|
||||||
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
$TMUX new-session -d -s layouts -x 80 -y 24 || exit 1
|
|
||||||
$TMUX split-window -h -t layouts: || exit 1
|
|
||||||
|
|
||||||
mkfifo "$FIFO" || exit 1
|
|
||||||
: >"$OUT"
|
|
||||||
$TMUX -C attach-session -t layouts <"$FIFO" >"$OUT" 2>&1 &
|
|
||||||
PID=$!
|
|
||||||
exec 3>"$FIFO"
|
|
||||||
|
|
||||||
send 'display-message -p READY'
|
|
||||||
wait_for READY
|
|
||||||
|
|
||||||
check_commands OFF 0
|
|
||||||
check_config OFF 0
|
|
||||||
|
|
||||||
send 'refresh-client -f new-layouts'
|
|
||||||
check_commands ON 1
|
|
||||||
check_config ON 1
|
|
||||||
|
|
||||||
send 'refresh-client -f !new-layouts'
|
|
||||||
check_commands OFF-AGAIN 0
|
|
||||||
check_config OFF-AGAIN 0
|
|
||||||
|
|
||||||
# The list commands also run for unattached command clients. Supplying that
|
|
||||||
# client to formats must not make the session loop dereference a NULL session.
|
|
||||||
$TMUX list-sessions -F '#{S:all,active}' >/dev/null ||
|
|
||||||
fail "session loop failed for unattached command client"
|
|
||||||
$TMUX has-session -t layouts || fail "server exited"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Popups require an attached terminal and cannot be displayed by a control
|
|
||||||
# client.
|
|
||||||
# A popup command from control mode must be ignored cleanly, leaving the
|
|
||||||
# client command queue and server usable.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
export PATH TERM
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
FIFO=$DIR/input
|
|
||||||
OUT=$DIR/output
|
|
||||||
RAN=$DIR/popup-ran
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
exec 3>&-
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
mkfifo "$FIFO" || exit 1
|
|
||||||
$TMUX new-session -d -s control -x40 -y10 'exec sleep 100' || exit 1
|
|
||||||
$TMUX -C attach-session -t control <"$FIFO" >"$OUT" 2>&1 &
|
|
||||||
control_pid=$!
|
|
||||||
exec 3>"$FIFO"
|
|
||||||
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$TMUX list-clients -F '#{client_flags}' 2>/dev/null |
|
|
||||||
grep -q 'control-mode' && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "control client did not attach"
|
|
||||||
|
|
||||||
printf '%s\n' "display-popup -E 'touch $RAN'" >&3
|
|
||||||
printf '%s\n' "display-message -p CONTROL-POPUP-ALIVE" >&3
|
|
||||||
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
grep -q 'CONTROL-POPUP-ALIVE' "$OUT" 2>/dev/null && break
|
|
||||||
if ! kill -0 "$control_pid" 2>/dev/null; then
|
|
||||||
fail "control client exited after display-popup"
|
|
||||||
fi
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "command after control-client popup did not run"
|
|
||||||
$TMUX has-session -t control || fail "server exited after control-client popup"
|
|
||||||
[ ! -e "$RAN" ] || fail "control client started a popup command"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# A waited run-shell command can complete after a control client has closed its
|
|
||||||
# input and exited. The command queue still references the client until the job
|
|
||||||
# callback runs, but control_stop has already destroyed c->control_state. Late
|
|
||||||
# command output must be discarded instead of dereferencing the stopped control
|
|
||||||
# state and killing the server.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -Ltest$$"
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
|
|
||||||
OUT=$(mktemp)
|
|
||||||
trap "$TMUX kill-server 2>/dev/null; rm -f $OUT" 0 1 15
|
|
||||||
|
|
||||||
$TMUX -f/dev/null new-session -d -s main || exit 1
|
|
||||||
|
|
||||||
printf '%s\n' 'run-shell "sleep 0.2; echo TEST"' |
|
|
||||||
$TMUX -f/dev/null -C >"$OUT" 2>&1
|
|
||||||
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
$TMUX has-session -t main 2>/dev/null || {
|
|
||||||
echo "server exited after late run-shell output"
|
|
||||||
cat "$OUT"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
grep -q '^%exit' "$OUT" || {
|
|
||||||
echo "control client did not exit"
|
|
||||||
cat "$OUT"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -30,11 +30,8 @@ killw
|
|||||||
EOF
|
EOF
|
||||||
sleep 1
|
sleep 1
|
||||||
$TMUX has || exit 1
|
$TMUX has || exit 1
|
||||||
# Use a control client to request legacy layouts, keeping only pane lines
|
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1
|
||||||
# from the control protocol output.
|
cat <<EOF|cmp -s $TMP - || exit 1
|
||||||
$TMUX -C lsp -aF '#{pane_id} #{window_layout}' |
|
|
||||||
grep '^%[0-9]' >$TMP || exit 1
|
|
||||||
cat <<EOF|cmp $TMP - || exit 1
|
|
||||||
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||||
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||||
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -Ltest$$"
|
TMUX="$TEST_TMUX -Ltest"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
FIFO=$(mktemp -u)
|
FIFO=$(mktemp -u)
|
||||||
|
|||||||
Regular → Executable
@@ -1,253 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise the broad non-mouse copy-mode command surface: navigation, search,
|
|
||||||
# jumps, state toggles, line/end-of-line copies, append, and pipe variants.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
x()
|
|
||||||
{
|
|
||||||
$TMUX send-keys -t copy:0 -X "$@" || fail "copy command failed: $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_mode()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
got=$($TMUX display-message -p -t copy:0 '#{pane_in_mode}' 2>/dev/null)
|
|
||||||
[ "$got" = "$want" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "pane_in_mode is '$got', expected '$want'"
|
|
||||||
}
|
|
||||||
|
|
||||||
fresh()
|
|
||||||
{
|
|
||||||
$TMUX copy-mode -t copy:0 || exit 1
|
|
||||||
wait_mode 1
|
|
||||||
x history-top
|
|
||||||
x start-of-line
|
|
||||||
}
|
|
||||||
|
|
||||||
select_text()
|
|
||||||
{
|
|
||||||
fresh
|
|
||||||
x begin-selection
|
|
||||||
x cursor-right
|
|
||||||
x cursor-right
|
|
||||||
}
|
|
||||||
|
|
||||||
$TMUX new-session -d -s copy -x40 -y8 \
|
|
||||||
"sh -c 'i=0; while [ \$i -lt 40 ]; do printf \" line-%02d needle (a[b]c) end\\n\" \$i; i=\$((i + 1)); done; printf \"\\nparagraph two needle\\n\\nparagraph three\\n\"; printf \"\\033]133;A\\007prompt-one\\n\\033]133;C\\007output-one\\n\\033]133;A\\007prompt-two\\n\\033]133;C\\007output-two\\n\"; exec sleep 100'" ||
|
|
||||||
exit 1
|
|
||||||
$TMUX set-option -g status off || exit 1
|
|
||||||
$TMUX set-option -g history-limit 200 || exit 1
|
|
||||||
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
history=$($TMUX display-message -p -t copy:0 '#{history_size}')
|
|
||||||
[ "$history" -ge 30 ] && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "history did not fill"
|
|
||||||
|
|
||||||
fresh
|
|
||||||
|
|
||||||
# Copy-mode-specific format callbacks expose the word, line, hyperlink and
|
|
||||||
# active search match at the cursor.
|
|
||||||
formats=$($TMUX display-message -p -t copy:0 \
|
|
||||||
'#{copy_cursor_word}|#{copy_cursor_line}|#{copy_cursor_hyperlink}|#{search_match}') ||
|
|
||||||
exit 1
|
|
||||||
[ -n "$formats" ] || fail "copy-mode cursor formats were empty"
|
|
||||||
|
|
||||||
# Cursor, viewport and paragraph commands. Each is asserted by successful
|
|
||||||
# dispatch and the final mode-liveness check; detailed word/selection geometry
|
|
||||||
# is covered by the existing copy-mode tests.
|
|
||||||
for command in back-to-indentation bottom-line top-line middle-line \
|
|
||||||
cursor-centre-vertical cursor-centre-horizontal end-of-line history-bottom \
|
|
||||||
history-top halfpage-down halfpage-up page-down page-up next-paragraph \
|
|
||||||
previous-paragraph next-matching-bracket previous-matching-bracket; do
|
|
||||||
x "$command"
|
|
||||||
done
|
|
||||||
x goto-line 15
|
|
||||||
x set-mark
|
|
||||||
x cursor-down
|
|
||||||
x jump-to-mark
|
|
||||||
|
|
||||||
# OSC 133 prompt and output markers support shell-integration navigation.
|
|
||||||
x history-top
|
|
||||||
x next-prompt
|
|
||||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
|
||||||
*prompt-one*) ;;
|
|
||||||
*) fail "next-prompt did not find prompt-one" ;;
|
|
||||||
esac
|
|
||||||
x next-prompt
|
|
||||||
x previous-prompt
|
|
||||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
|
||||||
*prompt-one*) ;;
|
|
||||||
*) fail "previous-prompt did not return to prompt-one" ;;
|
|
||||||
esac
|
|
||||||
x next-prompt -o
|
|
||||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
|
||||||
*output-one*) ;;
|
|
||||||
*) fail "next-prompt -o did not find output-one" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Character jumps remember their direction and character for repeat/reverse.
|
|
||||||
x jump-forward e
|
|
||||||
x jump-again
|
|
||||||
x jump-reverse
|
|
||||||
x jump-backward e
|
|
||||||
x jump-to-forward e
|
|
||||||
x jump-to-backward e
|
|
||||||
|
|
||||||
# Direct, text and incremental searches cover both directions and the saved
|
|
||||||
# search used by again/reverse.
|
|
||||||
x search-forward needle
|
|
||||||
x search-again
|
|
||||||
x search-reverse
|
|
||||||
x search-backward needle
|
|
||||||
x search-forward-text needle
|
|
||||||
x search-backward-text needle
|
|
||||||
x search-forward-incremental '=needle'
|
|
||||||
x search-forward-incremental '+needle'
|
|
||||||
x search-backward-incremental '=needle'
|
|
||||||
x search-backward-incremental '-needle'
|
|
||||||
|
|
||||||
# Stateful display and behaviour toggles.
|
|
||||||
for command in rectangle-on rectangle-off rectangle-toggle \
|
|
||||||
line-numbers-on line-numbers-off line-numbers-toggle \
|
|
||||||
refresh-on refresh-now refresh-off refresh-toggle \
|
|
||||||
scroll-exit-on scroll-exit-off scroll-exit-toggle toggle-position; do
|
|
||||||
x "$command"
|
|
||||||
done
|
|
||||||
wait_mode 1
|
|
||||||
x cancel
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
# Line and end-of-line copy commands work without an explicit selection.
|
|
||||||
fresh
|
|
||||||
x select-line
|
|
||||||
x copy-selection-and-cancel
|
|
||||||
case "$($TMUX show-buffer)" in
|
|
||||||
*line-00*) ;;
|
|
||||||
*) fail "select-line did not select the current line" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
fresh
|
|
||||||
x copy-line
|
|
||||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-line produced an empty buffer"
|
|
||||||
x cancel
|
|
||||||
|
|
||||||
fresh
|
|
||||||
x copy-end-of-line
|
|
||||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-end-of-line produced an empty buffer"
|
|
||||||
x cancel
|
|
||||||
|
|
||||||
# Append variants retain or close the mode as advertised.
|
|
||||||
$TMUX set-buffer -b append-buffer prefix || exit 1
|
|
||||||
select_text
|
|
||||||
x append-selection
|
|
||||||
appended=$($TMUX show-buffer -b append-buffer)
|
|
||||||
case "$appended" in
|
|
||||||
prefix*) ;;
|
|
||||||
*) fail "append-selection produced '$appended'" ;;
|
|
||||||
esac
|
|
||||||
x cancel
|
|
||||||
|
|
||||||
$TMUX set-buffer -b append-cancel-buffer prefix || exit 1
|
|
||||||
select_text
|
|
||||||
x append-selection-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
appended=$($TMUX show-buffer -b append-cancel-buffer)
|
|
||||||
case "$appended" in
|
|
||||||
prefix*) ;;
|
|
||||||
*) fail "append-selection-and-cancel produced '$appended'" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# The explicit cancel variants close copy mode.
|
|
||||||
fresh
|
|
||||||
x copy-line-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x copy-end-of-line-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
select_text
|
|
||||||
x copy-selection-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
# Pipe wrappers have distinct command handlers. /dev/null keeps the test
|
|
||||||
# deterministic while exercising asynchronous pipe job creation and cleanup.
|
|
||||||
fresh
|
|
||||||
x copy-pipe-line 'cat >/dev/null'
|
|
||||||
x cancel
|
|
||||||
fresh
|
|
||||||
x copy-pipe-line-and-cancel 'cat >/dev/null'
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x copy-pipe-end-of-line 'cat >/dev/null'
|
|
||||||
x cancel
|
|
||||||
fresh
|
|
||||||
x copy-pipe-end-of-line-and-cancel 'cat >/dev/null'
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
select_text
|
|
||||||
x copy-pipe-no-clear 'cat >/dev/null'
|
|
||||||
x pipe-no-clear 'cat >/dev/null'
|
|
||||||
x copy-pipe 'cat >/dev/null'
|
|
||||||
x pipe 'cat >/dev/null'
|
|
||||||
x cancel
|
|
||||||
select_text
|
|
||||||
x copy-pipe-and-cancel 'cat >/dev/null'
|
|
||||||
wait_mode 0
|
|
||||||
select_text
|
|
||||||
x pipe-and-cancel 'cat >/dev/null'
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
# Commands which cancel on downward movement have separate handlers.
|
|
||||||
fresh
|
|
||||||
x history-bottom
|
|
||||||
x bottom-line
|
|
||||||
x cursor-down-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x history-bottom
|
|
||||||
x halfpage-down-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x history-bottom
|
|
||||||
x page-down-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
fresh
|
|
||||||
x history-bottom
|
|
||||||
x bottom-line
|
|
||||||
x scroll-down-and-cancel
|
|
||||||
wait_mode 0
|
|
||||||
|
|
||||||
$TMUX has-session -t copy || fail "server died during copy-mode commands"
|
|
||||||
exit 0
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Changing an existing character selection to line mode must reset both ends
|
|
||||||
# to complete lines, including when the cursor subsequently crosses the fixed
|
|
||||||
# end. An invalid regular-expression search must also discard marks left by a
|
|
||||||
# previous successful search.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
$TMUX new-session -d -x30 -y6 \
|
|
||||||
"printf 'alpha\nbeta\ngamma\ndelta\n'; exec sleep 100" || exit 1
|
|
||||||
$TMUX set-option -g window-size manual || exit 1
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# A valid regular-expression search creates marks. Replacing it with an
|
|
||||||
# invalid expression, growing the mode, and scrolling must not leave an old
|
|
||||||
# mark array indexed with the new geometry (the original bug was an ASan heap
|
|
||||||
# overflow in this sequence).
|
|
||||||
$TMUX copy-mode || exit 1
|
|
||||||
$TMUX send-keys -X history-top || exit 1
|
|
||||||
$TMUX send-keys -X search-forward beta || exit 1
|
|
||||||
[ "$($TMUX display-message -p '#{search_present}')" = 1 ] ||
|
|
||||||
fail "successful search did not create marks"
|
|
||||||
$TMUX send-keys -X search-forward '[' || exit 1
|
|
||||||
$TMUX resize-window -x40 -y8 || exit 1
|
|
||||||
$TMUX send-keys -X scroll-down || exit 1
|
|
||||||
[ "$($TMUX display-message -p '#{search_present}')" = 0 ] ||
|
|
||||||
fail "invalid regular expression left stale marks after resize"
|
|
||||||
$TMUX send-keys -X cancel || exit 1
|
|
||||||
|
|
||||||
# Select part of alpha through part of beta, then change the existing
|
|
||||||
# selection to line mode. Both lines, and no partial columns, must be copied.
|
|
||||||
$TMUX copy-mode || exit 1
|
|
||||||
$TMUX send-keys -X history-top || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
|
||||||
$TMUX send-keys -X begin-selection || exit 1
|
|
||||||
$TMUX send-keys -X cursor-down || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
|
||||||
$TMUX send-keys -X selection-mode line || exit 1
|
|
||||||
[ "$($TMUX display-message -p '#{selection_mode}')" = line ] ||
|
|
||||||
fail "selection did not change to line mode"
|
|
||||||
$TMUX send-keys -X copy-selection || exit 1
|
|
||||||
expected=$(printf 'alpha\nbeta')
|
|
||||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
|
||||||
fail "line-mode selection did not expand to complete lines"
|
|
||||||
|
|
||||||
# Start with the cursor above the fixed end so selection-mode itself takes the
|
|
||||||
# reverse-direction path, then extend it through another line.
|
|
||||||
$TMUX copy-mode || exit 1
|
|
||||||
$TMUX send-keys -X history-top || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-down || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
|
||||||
$TMUX send-keys -X begin-selection || exit 1
|
|
||||||
$TMUX send-keys -X cursor-up || exit 1
|
|
||||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
|
||||||
$TMUX send-keys -X selection-mode line || exit 1
|
|
||||||
$TMUX send-keys -X cursor-up || exit 1
|
|
||||||
$TMUX send-keys -X copy-selection || exit 1
|
|
||||||
expected=$(printf 'alpha\nbeta\ngamma')
|
|
||||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
|
||||||
fail "reversed line-mode selection did not keep complete lines"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Trimming a blank source must leave a visible line for cursor reflow.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
export PATH TERM
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
}
|
|
||||||
trap cleanup 0
|
|
||||||
trap 'exit 1' 1 2 3 15
|
|
||||||
|
|
||||||
$TMUX new-session -d -x80 -y24 -s test 'sleep 100' || exit 1
|
|
||||||
$TMUX set-option -g window-size manual || exit 1
|
|
||||||
$TMUX new-window -t test:1 'sleep 100' || exit 1
|
|
||||||
$TMUX resize-window -t test:0 -x20 -y24 || exit 1
|
|
||||||
|
|
||||||
# Exercise widening, narrowing, and equal widths with no source history.
|
|
||||||
for pair in '0 1' '1 0' '1 2'; do
|
|
||||||
set -- $pair
|
|
||||||
if [ "$2" = 2 ]; then
|
|
||||||
$TMUX new-window -t test:2 'sleep 100' || exit 1
|
|
||||||
$TMUX resize-window -t test:2 -x80 -y24 || exit 1
|
|
||||||
fi
|
|
||||||
source=test:$1.0
|
|
||||||
target=test:$2.0
|
|
||||||
[ "$($TMUX display-message -p -t "$source" '#{history_size}')" = 0 ] ||
|
|
||||||
fail "source has unexpected history"
|
|
||||||
$TMUX copy-mode -s "$source" -t "$target" || exit 1
|
|
||||||
$TMUX has-session -t test || fail "server died copying blank source"
|
|
||||||
[ "$($TMUX display-message -p -t "$target" \
|
|
||||||
'#{pane_in_mode} #{copy_cursor_x} #{copy_cursor_y}')" = '1 0 0' ] ||
|
|
||||||
fail "blank source did not enter copy mode at the first cell"
|
|
||||||
$TMUX send-keys -t "$target" -X cancel || exit 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Real content still trims trailing empty lines, clamping the cursor to beta.
|
|
||||||
$TMUX respawn-pane -k -t test:0.0 \
|
|
||||||
"printf 'alpha\r\nbeta\r\n\r\n\r\n'; exec sleep 100" || exit 1
|
|
||||||
i=0
|
|
||||||
while [ "$($TMUX display-message -p -t test:0.0 '#{cursor_y}')" != 4 ]; do
|
|
||||||
i=$((i + 1))
|
|
||||||
[ "$i" -lt 10 ] || fail "source output did not arrive"
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
$TMUX copy-mode -s test:0.0 -t test:1.0 || exit 1
|
|
||||||
[ "$($TMUX display-message -p -t test:1.0 \
|
|
||||||
'#{pane_in_mode} #{copy_cursor_y} #{copy_cursor_line}')" = '1 1 beta' ] ||
|
|
||||||
fail "nonblank source content or trailing-line trimming changed"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
Regular → Executable
@@ -153,13 +153,11 @@ wait_format "$p0" '#{pane_in_mode}' '0'
|
|||||||
wait_option @picked "$p1"
|
wait_option @picked "$p1"
|
||||||
|
|
||||||
# Commands after display-panes run immediately while the mode remains.
|
# Commands after display-panes run immediately while the mode remains.
|
||||||
# Record the mode in the same command queue so client startup cannot race
|
|
||||||
# the 500ms timeout.
|
|
||||||
$TMUX set -g @after none || fail "set @after failed"
|
$TMUX set -g @after none || fail "set @after failed"
|
||||||
$TMUX display-panes -Nd 500 -t "$p0" \; \
|
$TMUX display-panes -Nd 500 -t "$p0" \; set -g @after fast ||
|
||||||
set -gF -t "$p0" @after '#{pane_mode}' ||
|
|
||||||
fail "display-panes immediate command failed"
|
fail "display-panes immediate command failed"
|
||||||
wait_option @after 'panes-mode'
|
wait_option @after fast
|
||||||
|
wait_format "$p0" '#{pane_mode}' 'panes-mode'
|
||||||
wait_format "$p0" '#{pane_in_mode}' '0'
|
wait_format "$p0" '#{pane_in_mode}' '0'
|
||||||
|
|
||||||
# Existing zoom is restored on exit.
|
# Existing zoom is restored on exit.
|
||||||
|
|||||||
@@ -1,118 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise find-window's content, name and title filters in every combination,
|
|
||||||
# glob/regex and case-insensitive suffixes, plus zoom restoration.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
TMUX_TMPDIR=$DIR
|
|
||||||
export TMUX_TMPDIR
|
|
||||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
capture()
|
|
||||||
{
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_mode()
|
|
||||||
{
|
|
||||||
want=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
got=$($INNER display-message -p -t find:base.0 '#{pane_mode}' 2>/dev/null)
|
|
||||||
[ "$got" = "$want" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "pane mode is '$got', expected '$want'"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_find()
|
|
||||||
{
|
|
||||||
label=$1
|
|
||||||
expected=$2
|
|
||||||
shift 2
|
|
||||||
$INNER find-window -t find:base.0 "$@" || fail "$label failed"
|
|
||||||
wait_mode tree-mode
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
captured=$(capture)
|
|
||||||
printf '%s\n' "$captured" | grep -Fq "$expected" && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "$label did not show '$expected'"
|
|
||||||
$INNER send-keys -t find:base.0 q || exit 1
|
|
||||||
wait_mode ''
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s find -n base -x80 -y24 \
|
|
||||||
"sh -c 'printf \"BaseBody\\n\"; exec sleep 100'" || exit 1
|
|
||||||
$INNER split-window -d -h -t find:base \
|
|
||||||
"sh -c 'printf \"OtherBody\\n\"; exec sleep 100'" || exit 1
|
|
||||||
$INNER new-window -d -t find: -n NameNeedle 'exec sleep 100' || exit 1
|
|
||||||
$INNER new-window -d -t find: -n body \
|
|
||||||
"sh -c 'printf \"BodyNeedle\\n\"; exec sleep 100'" || exit 1
|
|
||||||
$INNER new-window -d -t find: -n titled 'exec sleep 100' || exit 1
|
|
||||||
$INNER select-pane -t find:titled.0 -T TitleNeedle || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t find:base" || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# Wait until the body text is in the pane history before searching it.
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$INNER capture-pane -p -t find:body.0 | grep -Fq BodyNeedle && break
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
[ "$i" -lt 50 ] || fail "pane content was not ready"
|
|
||||||
|
|
||||||
# No flags means C+N+T. Then cover all three pairs and all three singletons.
|
|
||||||
run_find default NameNeedle NameNeedle
|
|
||||||
run_find content-name NameNeedle -C -N NameNeedle
|
|
||||||
run_find content-title titled -C -T TitleNeedle
|
|
||||||
run_find name-title titled -N -T TitleNeedle
|
|
||||||
run_find content-only body -C BodyNeedle
|
|
||||||
run_find name-only NameNeedle -N NameNeedle
|
|
||||||
run_find title-only titled -T TitleNeedle
|
|
||||||
|
|
||||||
# Glob case folding and all regular-expression suffix variants.
|
|
||||||
run_find insensitive NameNeedle -N -i nameneedle
|
|
||||||
run_find regex NameNeedle -N -r '^NameNeedle$'
|
|
||||||
run_find regex-insensitive NameNeedle -N -r -i '^nameneedle$'
|
|
||||||
|
|
||||||
# -Z temporarily zooms the target window and restores its prior state on exit.
|
|
||||||
$INNER find-window -Z -t find:base.0 -N NameNeedle || exit 1
|
|
||||||
wait_mode tree-mode
|
|
||||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 1 ] ||
|
|
||||||
fail "find-window -Z did not zoom"
|
|
||||||
$INNER send-keys -t find:base.0 q || exit 1
|
|
||||||
wait_mode ''
|
|
||||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 0 ] ||
|
|
||||||
fail "find-window -Z did not restore zoom"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Damage-only redraws must evaluate both active and inactive border styles
|
|
||||||
# for each client, even though the cached border cells belong to the pane.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lborder-style-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lborder-style-outer-$$ -f/dev/null"
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_clients()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
count=$($INNER list-clients 2>/dev/null | wc -l)
|
|
||||||
[ "$count" -eq 2 ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "two inner clients did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_marker()
|
|
||||||
{
|
|
||||||
target=$1
|
|
||||||
marker=$2
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "client in $target did not receive $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$DIR/emitter.pl" <<'PERL'
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
$| = 1;
|
|
||||||
for my $phase (1 .. 2) {
|
|
||||||
while (!-e "$ENV{TRIGGER}-$phase") {
|
|
||||||
select undef, undef, undef, 0.01;
|
|
||||||
}
|
|
||||||
# Change an unused palette entry and acknowledge it in the pane body.
|
|
||||||
print "\e]4;200;rgb:11/22/0$phase\a\e[1;1HDAMAGE$phase";
|
|
||||||
}
|
|
||||||
sleep 100;
|
|
||||||
PERL
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 60 -y 20 \
|
|
||||||
"TRIGGER='$DIR/trigger' perl '$DIR/emitter.pl'" || exit 1
|
|
||||||
$INNER set -g status off || exit 1
|
|
||||||
$INNER set -g window-size manual || exit 1
|
|
||||||
$INNER set -g automatic-rename off || exit 1
|
|
||||||
$INNER set -g status-interval 0 || exit 1
|
|
||||||
$INNER set -g pane-border-lines simple || exit 1
|
|
||||||
$INNER set -g pane-border-status top || exit 1
|
|
||||||
$INNER set -g pane-border-format 'CLIENT=#{client_name}' || exit 1
|
|
||||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 35 -y 6 -X 5 -Y 5 \
|
|
||||||
'sleep 100') || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
|
||||||
$OUTER set -g status off || exit 1
|
|
||||||
$OUTER set -g window-size manual || exit 1
|
|
||||||
$OUTER set -g default-terminal screen || exit 1
|
|
||||||
LEFT=$($OUTER display-message -p -t outer:0.0 '#{pane_id}') || exit 1
|
|
||||||
RIGHT=$($OUTER split-window -h -PF '#{pane_id}' 'sleep 100') || exit 1
|
|
||||||
for target in "$LEFT" "$RIGHT"; do
|
|
||||||
$OUTER respawn-pane -k -t "$target" "$INNER attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
done
|
|
||||||
wait_for_clients
|
|
||||||
|
|
||||||
NAME1=$($OUTER display-message -p -t "$LEFT" '#{pane_tty}') || exit 1
|
|
||||||
NAME2=$($OUTER display-message -p -t "$RIGHT" '#{pane_tty}') || exit 1
|
|
||||||
STYLE="fg=#{?#{==:#{client_name},$NAME1},red,blue}"
|
|
||||||
$INNER set -g pane-border-style "$STYLE" || exit 1
|
|
||||||
$INNER set -g pane-active-border-style "$STYLE" || exit 1
|
|
||||||
|
|
||||||
# Capture just the floating pane's rows, excluding the acknowledgement in
|
|
||||||
# the tiled pane. Exercise each cache by changing the floating pane's focus.
|
|
||||||
phase=1
|
|
||||||
while [ "$phase" -le 2 ]; do
|
|
||||||
if [ "$phase" -eq 2 ]; then
|
|
||||||
$INNER select-pane -t "$FLOAT" || exit 1
|
|
||||||
fi
|
|
||||||
$INNER refresh-client -t "$NAME1" || exit 1
|
|
||||||
$INNER refresh-client -t "$NAME2" || exit 1
|
|
||||||
sleep 0.5
|
|
||||||
for target in "$LEFT" "$RIGHT"; do
|
|
||||||
$OUTER capture-pane -pe -S 5 -E 10 -t "$target" \
|
|
||||||
>"$DIR/before-$target" || exit 1
|
|
||||||
done
|
|
||||||
RED=$(printf '\033[31m')
|
|
||||||
BLUE=$(printf '\033[34m')
|
|
||||||
grep -Fq "$RED" "$DIR/before-$LEFT" || fail "missing red border"
|
|
||||||
grep -Fq "$BLUE" "$DIR/before-$RIGHT" || fail "missing blue border"
|
|
||||||
|
|
||||||
: >"$DIR/trigger-$phase"
|
|
||||||
wait_for_marker "$LEFT" "DAMAGE$phase"
|
|
||||||
wait_for_marker "$RIGHT" "DAMAGE$phase"
|
|
||||||
sleep 0.2
|
|
||||||
for target in "$LEFT" "$RIGHT"; do
|
|
||||||
$OUTER capture-pane -pe -S 5 -E 10 -t "$target" \
|
|
||||||
>"$CAPTURE" || exit 1
|
|
||||||
diff -u "$DIR/before-$target" "$CAPTURE" ||
|
|
||||||
fail "phase $phase changed $target's border style"
|
|
||||||
done
|
|
||||||
phase=$((phase + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Regression test for a floating-pane drag bug: cmd_resize_pane_redraw_floating()
|
|
||||||
# (cmd-resize-pane.c) reported damage for just a dragged floating pane's
|
|
||||||
# content rectangle, not the one-cell border frame drawn around it (see the
|
|
||||||
# "floating" case in screen-redraw.c, which draws that frame at
|
|
||||||
# xoff-1/yoff-1 through xoff+sx/yoff+sy - one cell outside the pane's own
|
|
||||||
# content area). Damage scoped to only the content area left the frame's
|
|
||||||
# previous position undrawn as the pane moved, so dragging it left a trail
|
|
||||||
# of un-erased border frames behind - visible as several "corners" stacked
|
|
||||||
# up rather than just the pane's current one.
|
|
||||||
#
|
|
||||||
# This bug has nothing to do with images - it reproduces with a plain
|
|
||||||
# floating pane and no image support required.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server >/dev/null 2>&1
|
|
||||||
$TMUX2 kill-server >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
cleanup
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
|
||||||
#
|
|
||||||
# Write a plain (unmodified) SGR button-1 press, drag update and release at
|
|
||||||
# 1-based positions to the outer pane holding the inner client - this
|
|
||||||
# matches the default MouseDown1Border/MouseDrag1Border bindings used to
|
|
||||||
# move or resize a floating pane by its border.
|
|
||||||
drag()
|
|
||||||
{
|
|
||||||
scol="$1"
|
|
||||||
srow="$2"
|
|
||||||
ecol="$3"
|
|
||||||
erow="$4"
|
|
||||||
|
|
||||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.2
|
|
||||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.2
|
|
||||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
|
|
||||||
TMP=$(mktemp)
|
|
||||||
trap "cleanup; rm -f $TMP" 0 1 15
|
|
||||||
|
|
||||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
|
||||||
$TMUX set -g mouse on
|
|
||||||
$TMUX set -g default-command 'sh -c "sleep 100"'
|
|
||||||
|
|
||||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
|
||||||
fail "new-pane -X -Y failed"
|
|
||||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
|
||||||
|
|
||||||
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
|
||||||
sleep 1
|
|
||||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
|
||||||
[ -n "$OUTER" ] || fail "No outer pane."
|
|
||||||
|
|
||||||
# Sanity check: exactly one floating pane, so exactly one top-left corner,
|
|
||||||
# before dragging anything.
|
|
||||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
|
||||||
n=$(grep -o '┌' $TMP | wc -l)
|
|
||||||
[ "$n" -eq 1 ] || fail "sanity: expected 1 corner before drag, found $n"
|
|
||||||
|
|
||||||
# Drag the floating pane by its top border (row FTOP-1, some column within
|
|
||||||
# its width) down several rows in a few separate steps, then release. A
|
|
||||||
# single drag() call already does press/motion/release, so call it several
|
|
||||||
# times in a row to simulate a multi-step real drag.
|
|
||||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
|
||||||
STARTROW=$FTOP
|
|
||||||
i=0
|
|
||||||
while [ $i -lt 6 ]; do
|
|
||||||
newrow=$((STARTROW + i + 1))
|
|
||||||
drag $((GRABCOL + 1)) $((STARTROW + i)) $((GRABCOL + 1)) $newrow
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
|
||||||
|
|
||||||
# Exactly one top-left corner should remain - the pane's current position.
|
|
||||||
# This is expected to fail before the fix: multiple corners (a trail of
|
|
||||||
# un-erased frames) would remain from the intermediate drag positions.
|
|
||||||
n=$(grep -o '┌' $TMP | wc -l)
|
|
||||||
[ "$n" -eq 1 ] || fail "expected exactly 1 corner after drag, found $n (ghost frames left behind)"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Each drag motion must draw the floating pane content once.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
cd "$DIR" || exit 1
|
|
||||||
INNER="$TEST_TMUX -Ldoublecomp-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Ldoublecomp-outer-$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
cd /
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
|
||||||
sleep 0.15
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 15 "printf '\\033[15;1HOUTSIDE'; exec sleep 100" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
$INNER set-option -g status-interval 0 || exit 1
|
|
||||||
$INNER set-option -g automatic-rename off || exit 1
|
|
||||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 15 -y 5 -X 5 -Y 2 \
|
|
||||||
"printf 'DRAGMARK'; exec sleep 100") || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 15 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Ldoublecomp-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
sleep 0.5
|
|
||||||
|
|
||||||
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
GRABCOL=$((XOFF + 3))
|
|
||||||
BORDERROW=$YOFF
|
|
||||||
|
|
||||||
# Begin capture after focus changes from the initial mouse press have settled.
|
|
||||||
mouse 0 "$GRABCOL" "$BORDERROW" M
|
|
||||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$DIR/output'" || exit 1
|
|
||||||
$INNER refresh-client || exit 1
|
|
||||||
sleep 0.5
|
|
||||||
grep -aq DRAGMARK "$DIR/output" || fail "capture missed floating pane content"
|
|
||||||
grep -aq OUTSIDE "$DIR/output" || fail "capture missed untouched row"
|
|
||||||
offset=$(wc -c <"$DIR/output")
|
|
||||||
|
|
||||||
i=0
|
|
||||||
steps=6
|
|
||||||
while [ "$i" -lt "$steps" ]; do
|
|
||||||
GRABCOL=$((GRABCOL + 1))
|
|
||||||
mouse 32 "$GRABCOL" "$BORDERROW" M
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
mouse 0 "$GRABCOL" "$BORDERROW" m
|
|
||||||
sleep 0.3
|
|
||||||
|
|
||||||
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
[ "$NEWXOFF" -eq "$((XOFF + steps))" ] || fail "floating pane did not move six columns"
|
|
||||||
tail -c +"$((offset + 1))" "$DIR/output" >"$DIR/drag-output"
|
|
||||||
n=$(perl -0777 -ne '$n = () = /DRAGMARK/g; print "$n\n"' "$DIR/drag-output")
|
|
||||||
[ "$n" -eq "$steps" ] ||
|
|
||||||
fail "$steps drag motions wrote the floating pane content $n times"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Regression test: dragging a floating pane across another pane's ordinary
|
|
||||||
# content must not redraw that other pane's scrollbar, unless the drag
|
|
||||||
# actually crosses the scrollbar's own strip.
|
|
||||||
#
|
|
||||||
# cmd_resize_pane_redraw_floating() (cmd-resize-pane.c) used to flag
|
|
||||||
# PANE_REDRAWSCROLLBAR on any pane whose whole *body* intersected the
|
|
||||||
# floating pane's old or new rectangle, rather than just its narrow
|
|
||||||
# scrollbar strip - so dragging a floating pane back and forth over an
|
|
||||||
# ordinary tiled pane's content (never touching its scrollbar) still
|
|
||||||
# needlessly redrew that pane's scrollbar on every motion step. See
|
|
||||||
# tmux-image-redraw-known-bugs.md for the full write-up.
|
|
||||||
#
|
|
||||||
# This is checked by giving the non-dragged pane a distinctive scrollbar
|
|
||||||
# colour and counting how many times its SGR code appears in the client's
|
|
||||||
# raw output while the floating pane is dragged vertically over that pane's
|
|
||||||
# body, well clear of its scrollbar column: with the fix, it should never
|
|
||||||
# reappear after the initial draw.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
|
||||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$TMUX kill-server >/dev/null 2>&1
|
|
||||||
$TMUX2 kill-server >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
cleanup
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
|
||||||
drag()
|
|
||||||
{
|
|
||||||
scol="$1"
|
|
||||||
srow="$2"
|
|
||||||
ecol="$3"
|
|
||||||
erow="$4"
|
|
||||||
|
|
||||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.2
|
|
||||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.2
|
|
||||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
|
||||||
sleep 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
|
|
||||||
TMP=$(mktemp)
|
|
||||||
trap "cleanup; rm -f $TMP" 0 1 15
|
|
||||||
|
|
||||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
|
||||||
$TMUX set -g mouse on || fail "set mouse failed"
|
|
||||||
$TMUX set -g default-command 'sh -c "sleep 100"' || fail "set default-command failed"
|
|
||||||
$TMUX set -g pane-scrollbars on || fail "set pane-scrollbars failed"
|
|
||||||
|
|
||||||
$TMUX split-window -h -t inner 'sh -c "sleep 100"' || fail "split-window failed"
|
|
||||||
|
|
||||||
PANES=$($TMUX list-panes -t inner -F '#{pane_id} #{pane_left}')
|
|
||||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
|
||||||
[ -n "$LEFT" ] || fail "could not identify left pane"
|
|
||||||
|
|
||||||
# A distinctive scrollbar colour for the non-dragged (left) pane only.
|
|
||||||
$TMUX set -p -t "$LEFT" pane-scrollbars-style 'fg=colour201,bg=colour17' ||
|
|
||||||
fail "set pane-scrollbars-style failed"
|
|
||||||
|
|
||||||
ALEFT=$($TMUX display-message -p -t "$LEFT" '#{pane_left}')
|
|
||||||
ATOP=$($TMUX display-message -p -t "$LEFT" '#{pane_top}')
|
|
||||||
AWIDTH=$($TMUX display-message -p -t "$LEFT" '#{pane_width}')
|
|
||||||
AHEIGHT=$($TMUX display-message -p -t "$LEFT" '#{pane_height}')
|
|
||||||
[ "$AWIDTH" -gt 15 ] || fail "left pane too narrow for this test ($AWIDTH)"
|
|
||||||
|
|
||||||
# A small floating pane placed well inside the left pane's content area,
|
|
||||||
# clear of its (right-hand) scrollbar column by several columns.
|
|
||||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 8 -y 5 \
|
|
||||||
-X $((ALEFT + 2)) -Y $((ATOP + 2))) || fail "new-pane -X -Y failed"
|
|
||||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
|
||||||
[ $((FLEFT + FWIDTH + 3)) -lt $((ALEFT + AWIDTH)) ] ||
|
|
||||||
fail "sanity: floating pane too close to the scrollbar column"
|
|
||||||
|
|
||||||
# Start the outer session with a plain shell, then start capturing before
|
|
||||||
# triggering the attach - starting the attach as the outer pane's initial
|
|
||||||
# command would mean pipe-pane only starts after the attach-driven initial
|
|
||||||
# redraw (which draws the scrollbars) has already happened, missing it.
|
|
||||||
$TMUX2 new-session -d -x 60 -y 20 || exit 1
|
|
||||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
|
||||||
[ -n "$OUTER" ] || fail "No outer pane."
|
|
||||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
|
||||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
|
||||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# Sanity check: the distinctive scrollbar colour reaches the client at all.
|
|
||||||
grep -qa '48;5;201' $TMP || fail "sanity: scrollbar colour never reached the client"
|
|
||||||
: >$TMP
|
|
||||||
|
|
||||||
# Drag the floating pane straight up and down by its top border, staying at
|
|
||||||
# a fixed column the whole time - this never crosses the left pane's
|
|
||||||
# scrollbar strip, only its ordinary content.
|
|
||||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
|
||||||
row=$FTOP
|
|
||||||
i=0
|
|
||||||
while [ $i -lt 6 ]; do
|
|
||||||
newrow=$((row + 1))
|
|
||||||
drag $GRABCOL $row $GRABCOL $newrow
|
|
||||||
row=$newrow
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
i=0
|
|
||||||
while [ $i -lt 6 ]; do
|
|
||||||
newrow=$((row - 1))
|
|
||||||
drag $GRABCOL $row $GRABCOL $newrow
|
|
||||||
row=$newrow
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
# The scrollbar colour should never reappear - its geometry never changed,
|
|
||||||
# and the drag never crossed its column. This is expected to fail before
|
|
||||||
# the fix - see the header comment.
|
|
||||||
n=$(grep -ac '48;5;201' $TMP)
|
|
||||||
[ "$n" -eq 0 ] ||
|
|
||||||
fail "left pane's scrollbar was redrawn $n times while dragging over its body only"
|
|
||||||
|
|
||||||
# Cross the scrollbar and return, comparing each damaged redraw with a full
|
|
||||||
# refresh to check that covering and uncovering its cells leaves no artifacts.
|
|
||||||
offset=$((ALEFT + AWIDTH - FLEFT - FWIDTH / 2))
|
|
||||||
for delta in "$offset" "$((-offset))"; do
|
|
||||||
oldleft=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
newcol=$((GRABCOL + delta))
|
|
||||||
drag "$GRABCOL" "$row" "$newcol" "$row"
|
|
||||||
GRABCOL=$newcol
|
|
||||||
newleft=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
[ "$newleft" -eq "$((oldleft + delta))" ] ||
|
|
||||||
fail "floating pane did not move across the scrollbar as expected"
|
|
||||||
before=$($TMUX2 capture-pane -pe -t "$OUTER" -S 0 -E "$((AHEIGHT - 1))")
|
|
||||||
$TMUX refresh-client || fail "refresh-client failed"
|
|
||||||
sleep 0.5
|
|
||||||
after=$($TMUX2 capture-pane -pe -t "$OUTER" -S 0 -E "$((AHEIGHT - 1))")
|
|
||||||
[ "$before" = "$after" ] ||
|
|
||||||
fail "scrollbar crossing differs from a full redraw"
|
|
||||||
done
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Synchronized drag output must not cause a full redraw of untouched rows.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
cd "$DIR" || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lsyncdefer-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lsyncdefer-outer-$$ -f/dev/null"
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
cd /
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
|
||||||
sleep 0.15
|
|
||||||
}
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 15 "printf '\\033[15;1HOUTSIDE'; exec sleep 100" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
$INNER set-option -g status-interval 0 || exit 1
|
|
||||||
$INNER set-option -g automatic-rename off || exit 1
|
|
||||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 15 -y 5 -X 5 -Y 2 \
|
|
||||||
"printf 'DRAGMARK'; exec sleep 100") || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 15 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
|
||||||
$INNER set-option -as terminal-features ',screen-256color:sync' || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lsyncdefer-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
sleep 0.5
|
|
||||||
|
|
||||||
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
GRABCOL=$((XOFF + 3))
|
|
||||||
BORDERROW=$YOFF
|
|
||||||
|
|
||||||
# Begin capture after focus changes from the initial mouse press have settled.
|
|
||||||
mouse 0 "$GRABCOL" "$BORDERROW" M
|
|
||||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$DIR/output'" || exit 1
|
|
||||||
$INNER refresh-client || exit 1
|
|
||||||
sleep 0.5
|
|
||||||
grep -aq DRAGMARK "$DIR/output" || fail "capture missed floating pane content"
|
|
||||||
grep -aq OUTSIDE "$DIR/output" || fail "capture missed untouched row"
|
|
||||||
offset=$(wc -c <"$DIR/output")
|
|
||||||
|
|
||||||
i=0
|
|
||||||
steps=6
|
|
||||||
while [ "$i" -lt "$steps" ]; do
|
|
||||||
GRABCOL=$((GRABCOL + 1))
|
|
||||||
mouse 32 "$GRABCOL" "$BORDERROW" M
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
mouse 0 "$GRABCOL" "$BORDERROW" m
|
|
||||||
sleep 0.3
|
|
||||||
|
|
||||||
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
[ "$NEWXOFF" -eq "$((XOFF + steps))" ] || fail "floating pane did not move six columns"
|
|
||||||
tail -c +"$((offset + 1))" "$DIR/output" >"$DIR/drag-output"
|
|
||||||
n=$(perl -0777 -ne '$n = () = /DRAGMARK/g; print "$n\n"' "$DIR/drag-output")
|
|
||||||
[ "$n" -ge "$steps" ] || fail "drag output did not reach the client"
|
|
||||||
perl -0777 -ne 'exit(/\e\[\?2026h/ ? 0 : 1)' "$DIR/drag-output" ||
|
|
||||||
fail "drag did not use synchronized output"
|
|
||||||
if grep -aq OUTSIDE "$DIR/drag-output"; then
|
|
||||||
fail "synchronized drag redrew an untouched row"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Mirror of floating-pane-drag-wide-character.sh for the *right* edge of a
|
|
||||||
# damage rectangle. redraw_damage_grow_span_clip() (screen-redraw.c) widens
|
|
||||||
# a damage rectangle's right edge by one cell whenever it isn't already at
|
|
||||||
# the span's own edge, to pull in a wide character's base half when the
|
|
||||||
# edge lands on its padding half - but it did this unconditionally, with no
|
|
||||||
# check of which half it was actually touching. When the edge instead
|
|
||||||
# already lands cleanly on a fresh character's base cell (a character fully
|
|
||||||
# outside the range), growing right pulls in just that base cell -
|
|
||||||
# tty_draw_line() sees it can't fit that character's full width in the
|
|
||||||
# remaining range (tty_draw_line_get_empty()'s gc->data.width > nx check)
|
|
||||||
# and clears it, exactly as the left-edge bug cleared a neighbouring
|
|
||||||
# character's padding half.
|
|
||||||
#
|
|
||||||
# As with the left-edge test, the exact column parity needs to be
|
|
||||||
# deterministic to actually catch it. This constructs it by creating the
|
|
||||||
# floating pane, checking its real position, and recreating it one column
|
|
||||||
# over if necessary until the vacated rectangle's right edge lands on a
|
|
||||||
# base cell.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lwidecharr-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lwidecharr-outer-$$ -f/dev/null"
|
|
||||||
EMITTER=$DIR/emitter.pl
|
|
||||||
BASE=$DIR/base
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
FLOAT=
|
|
||||||
PANEWIDTH=12
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
slice_columns()
|
|
||||||
{
|
|
||||||
# Extract terminal columns [COL1, COL2) from lines [ROW1, ROW2] of
|
|
||||||
# $1, decoding UTF-8 - the pane's own new position (well clear of
|
|
||||||
# this range) must not affect the comparison, so this only looks at
|
|
||||||
# the narrow strip actually vacated, not the whole line. capture-pane
|
|
||||||
# text has one decoded character per double-width cell pair (every
|
|
||||||
# character here is width 2), so terminal columns are converted to
|
|
||||||
# character indices by halving before slicing.
|
|
||||||
perl -CSD -e '
|
|
||||||
my ($row1, $row2, $col1, $col2, $file) = @ARGV;
|
|
||||||
open my $fh, "<:encoding(UTF-8)", $file or die $!;
|
|
||||||
my @lines = <$fh>;
|
|
||||||
my $c1 = int($col1 / 2);
|
|
||||||
my $c2 = int(($col2 + 1) / 2);
|
|
||||||
for my $n ($row1 .. $row2) {
|
|
||||||
my $line = $lines[$n - 1];
|
|
||||||
$line =~ s/\R\z//;
|
|
||||||
print substr($line, $c1, $c2 - $c1), "\n";
|
|
||||||
}
|
|
||||||
' "$ROW1" "$ROW2" "$COL1" "$COL2" "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_old_rows_restored()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
slice_columns "$BASE" >"$DIR/want"
|
|
||||||
slice_columns "$CAPTURE" >"$DIR/got"
|
|
||||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "wide characters under the floating pane's vacated right edge were not restored"
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$EMITTER" <<'PERL'
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
binmode STDOUT, ':encoding(UTF-8)';
|
|
||||||
$| = 1;
|
|
||||||
for my $row (1 .. 10) {
|
|
||||||
print "\e[$row;1H", chr(0x754c) x 20;
|
|
||||||
}
|
|
||||||
sleep 100;
|
|
||||||
PERL
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
$INNER set-option -g pane-scrollbars off || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lwidecharr-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
wait_outer_has '界界界'
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
|
||||||
|
|
||||||
# Create the floating pane, then check its actual resulting position. Try
|
|
||||||
# adjacent starting columns until the vacated rectangle's right edge
|
|
||||||
# (xoff + PANEWIDTH + 1, the border-grown exclusive end - see
|
|
||||||
# window_pane_damage_floating(), window.c) lands on an even (base-cell)
|
|
||||||
# column.
|
|
||||||
startx=5
|
|
||||||
tries=0
|
|
||||||
while [ "$tries" -lt 2 ]; do
|
|
||||||
[ -n "$FLOAT" ] && $INNER kill-pane -t "$FLOAT" 2>/dev/null
|
|
||||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x "$PANEWIDTH" -y 3 \
|
|
||||||
-X "$startx" -Y 5 \
|
|
||||||
'sh -c "printf FLOATMARK; exec sleep 100"') || exit 1
|
|
||||||
sleep 0.2
|
|
||||||
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
oldright=$((XOFF + PANEWIDTH + 1))
|
|
||||||
if [ $((oldright % 2)) -eq 0 ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
startx=$((startx + 1))
|
|
||||||
tries=$((tries + 1))
|
|
||||||
done
|
|
||||||
[ $(((XOFF + PANEWIDTH + 1) % 2)) -eq 0 ] ||
|
|
||||||
fail "could not find bad-parity starting column"
|
|
||||||
|
|
||||||
wait_outer_has FLOATMARK
|
|
||||||
|
|
||||||
ROW1=$((YOFF + 1))
|
|
||||||
ROW2=$((YOFF + 3))
|
|
||||||
COL1=$((XOFF + PANEWIDTH - 2))
|
|
||||||
COL2=$((XOFF + PANEWIDTH + 2))
|
|
||||||
|
|
||||||
# Grab the pane's top border a couple of columns in (avoiding the corner
|
|
||||||
# cells) and drag it well clear of its old rectangle - far enough right
|
|
||||||
# that its new position starts past the checked columns above (which sit
|
|
||||||
# just past the pane's *original* right edge).
|
|
||||||
GRABCOL=$((XOFF + 3))
|
|
||||||
BORDERROW=$((YOFF))
|
|
||||||
|
|
||||||
seq=$(printf '\033[<0;%s;%sM' "$GRABCOL" "$BORDERROW")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
seq=$(printf '\033[<32;%s;%sM' "$((GRABCOL + 20))" "$BORDERROW")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
seq=$(printf '\033[<0;%s;%sm' "$((GRABCOL + 20))" "$BORDERROW")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
|
|
||||||
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
[ "$NEWXOFF" != "$XOFF" ] || fail "sanity: floating pane did not move (still at $XOFF)"
|
|
||||||
|
|
||||||
wait_old_rows_restored
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Damage at a floating pane's vacated edge must always redraw a complete
|
|
||||||
# grid character. redraw_damage_grow_span_clip() (screen-redraw.c) widens a
|
|
||||||
# damage rectangle's left edge by one cell whenever it isn't already at the
|
|
||||||
# span's own edge, to pull in a wide character's base half when the edge
|
|
||||||
# lands on its padding half - but it did this unconditionally, with no
|
|
||||||
# check of which half it was actually touching. When the edge instead
|
|
||||||
# already lands cleanly on a fresh character's base cell, growing left
|
|
||||||
# walks into the *previous*, unrelated character's padding cell and blanks
|
|
||||||
# it (tty_draw_line() treats any leading padding cell as proof its own
|
|
||||||
# range starts mid-character).
|
|
||||||
#
|
|
||||||
# This is a general damage-composition bug, not specific to any one kind of
|
|
||||||
# pane, but the exact column parity needs to be deterministic to actually
|
|
||||||
# catch it (a lucky parity draws fine). This constructs it by creating the
|
|
||||||
# floating pane, checking its real position (the border-framing offset
|
|
||||||
# added to -X is not something to hand-compute), and recreating it one
|
|
||||||
# column over if necessary until the vacated rectangle's left edge lands on
|
|
||||||
# a base cell.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lwidechar-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lwidechar-outer-$$ -f/dev/null"
|
|
||||||
EMITTER=$DIR/emitter.pl
|
|
||||||
BASE=$DIR/base
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
FLOAT=
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
slice_columns()
|
|
||||||
{
|
|
||||||
# Extract terminal columns [COL1, COL2) from lines [ROW1, ROW2] of
|
|
||||||
# $1, decoding UTF-8 - the pane's own new position (well clear of
|
|
||||||
# this range) must not affect the comparison, so this only looks at
|
|
||||||
# the narrow strip actually vacated, not the whole line. capture-pane
|
|
||||||
# text has one decoded character per double-width cell pair (every
|
|
||||||
# character here is width 2), so terminal columns are converted to
|
|
||||||
# character indices by halving before slicing.
|
|
||||||
perl -CSD -e '
|
|
||||||
my ($row1, $row2, $col1, $col2, $file) = @ARGV;
|
|
||||||
open my $fh, "<:encoding(UTF-8)", $file or die $!;
|
|
||||||
my @lines = <$fh>;
|
|
||||||
my $c1 = int($col1 / 2);
|
|
||||||
my $c2 = int(($col2 + 1) / 2);
|
|
||||||
for my $n ($row1 .. $row2) {
|
|
||||||
my $line = $lines[$n - 1];
|
|
||||||
$line =~ s/\R\z//;
|
|
||||||
print substr($line, $c1, $c2 - $c1), "\n";
|
|
||||||
}
|
|
||||||
' "$ROW1" "$ROW2" "$COL1" "$COL2" "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_old_rows_restored()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
slice_columns "$BASE" >"$DIR/want"
|
|
||||||
slice_columns "$CAPTURE" >"$DIR/got"
|
|
||||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "wide characters under the floating pane's vacated edge were not restored"
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse()
|
|
||||||
{
|
|
||||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$EMITTER" <<'PERL'
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
binmode STDOUT, ':encoding(UTF-8)';
|
|
||||||
$| = 1;
|
|
||||||
for my $row (1 .. 10) {
|
|
||||||
print "\e[$row;1H", chr(0x754c) x 20;
|
|
||||||
}
|
|
||||||
sleep 100;
|
|
||||||
PERL
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
$INNER set-option -g mouse on || exit 1
|
|
||||||
$INNER set-option -g pane-scrollbars off || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lwidechar-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
wait_outer_has '界界界'
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
|
||||||
|
|
||||||
# Create the floating pane, then check its actual resulting position. Try
|
|
||||||
# adjacent starting columns until the vacated rectangle's left edge
|
|
||||||
# (xoff - 1) lands on an even (base-cell) column - the odd case is the one
|
|
||||||
# every earlier manual test happened to land on by chance.
|
|
||||||
startx=5
|
|
||||||
tries=0
|
|
||||||
while [ "$tries" -lt 2 ]; do
|
|
||||||
[ -n "$FLOAT" ] && $INNER kill-pane -t "$FLOAT" 2>/dev/null
|
|
||||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 12 -y 3 -X "$startx" \
|
|
||||||
-Y 5 'sh -c "printf FLOATMARK; exec sleep 100"') || exit 1
|
|
||||||
sleep 0.2
|
|
||||||
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
oldleft=$((XOFF - 1))
|
|
||||||
if [ $((oldleft % 2)) -eq 0 ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
startx=$((startx + 1))
|
|
||||||
tries=$((tries + 1))
|
|
||||||
done
|
|
||||||
[ $(((XOFF - 1) % 2)) -eq 0 ] || fail "could not find bad-parity starting column"
|
|
||||||
|
|
||||||
wait_outer_has FLOATMARK
|
|
||||||
|
|
||||||
ROW1=$((YOFF + 1))
|
|
||||||
ROW2=$((YOFF + 3))
|
|
||||||
COL1=$((XOFF - 4))
|
|
||||||
COL2=$((XOFF + 4))
|
|
||||||
|
|
||||||
# Grab the pane's top border a couple of columns in (avoiding the corner
|
|
||||||
# cells) and drag it well clear of its old rectangle.
|
|
||||||
GRABCOL=$((XOFF + 3))
|
|
||||||
BORDERROW=$((YOFF))
|
|
||||||
|
|
||||||
seq=$(printf '\033[<0;%s;%sM' "$GRABCOL" "$BORDERROW")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
seq=$(printf '\033[<32;%s;%sM' "$((GRABCOL + 15))" "$BORDERROW")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
seq=$(printf '\033[<0;%s;%sm' "$((GRABCOL + 15))" "$BORDERROW")
|
|
||||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
|
||||||
sleep 0.1
|
|
||||||
|
|
||||||
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
[ "$NEWXOFF" != "$XOFF" ] || fail "sanity: floating pane did not move (still at $XOFF)"
|
|
||||||
|
|
||||||
wait_old_rows_restored
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -268,149 +268,5 @@ $TMUX kill-pane -t "$floating" || exit 1
|
|||||||
$TMUX kill-pane -t "$right" || exit 1
|
$TMUX kill-pane -t "$right" || exit 1
|
||||||
$TMUX kill-pane -t "$lower" || exit 1
|
$TMUX kill-pane -t "$lower" || exit 1
|
||||||
|
|
||||||
# --- Floating panes clamped when the window shrinks (issue #5581, PR #5582) ---
|
|
||||||
#
|
|
||||||
# layout_resize clamps floating panes back inside the window when it shrinks:
|
|
||||||
# move them and, only if they cannot fit, shrink them (never below
|
|
||||||
# PANE_MINIMUM). A floating cell is the pane's content, so with the default
|
|
||||||
# single-line border the clamp counts 1 cell of border per side, exactly as
|
|
||||||
# layout_floating_args_parse does on creation; with no border it counts 0.
|
|
||||||
# Each case below gets its own window, since resize-window fixes a window at
|
|
||||||
# a manual size for the rest of its life.
|
|
||||||
|
|
||||||
# Case 1: a lone floating pane -- break-pane -W on a window's only pane --
|
|
||||||
# takes the early-return path in layout_resize, added during PR #5582's
|
|
||||||
# review round, since there is no tiled tree to walk.
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for lone float failed"
|
|
||||||
|
|
||||||
# -x 20 -y 6 -> pane 18x4; -X 60 -Y 18 -> pane_left 61, pane_top 19: with the
|
|
||||||
# border, the footprint is columns 60-79, rows 18-23, flush with the 80x24
|
|
||||||
# window's right and bottom edges, so the float fits before it is shrunk.
|
|
||||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
|
||||||
fail "break-pane -W for lone float failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
|
||||||
|
|
||||||
# Shrink to 40x16. The float still fits at its own size (18 <= 40-2, 4 <=
|
|
||||||
# 16-2) so only its position moves, flush to the new right/bottom edges:
|
|
||||||
# xoff = 40 - 18 - 1 = 21; yoff = 16 - 4 - 1 = 11.
|
|
||||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
|
||||||
fail "resize-window (lone float) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
|
||||||
|
|
||||||
# Case 5: grow the window back. The clamp must not chase the window back
|
|
||||||
# outward -- it only ever pulls a float in, never restores where it was.
|
|
||||||
$TMUX resize-window -t "$win" -x 80 -y 24 ||
|
|
||||||
fail "resize-window grow (lone float) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
# Case 2: the same float, but with a tiled sibling surviving alongside it, so
|
|
||||||
# the window's root cell stays tiled and layout_resize takes the normal path
|
|
||||||
# (the clamp call after layout_fix_offsets) instead of the early return
|
|
||||||
# above. Same geometry as case 1, so the same numbers should come out.
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for tiled sibling failed"
|
|
||||||
$TMUX split-window -t "$win" 'sleep 100' ||
|
|
||||||
fail "split-window for tiled sibling failed"
|
|
||||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
|
||||||
fail "break-pane -W for tiled sibling failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
|
||||||
|
|
||||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
|
||||||
fail "resize-window (tiled sibling) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
# Case 3: new-pane float, the original repro from issue #5581 and the PR
|
|
||||||
# body -- also the normal path, via the tiled base pane new-window creates.
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for new-pane repro failed"
|
|
||||||
$TMUX resize-window -t "$win" -x 120 -y 40 ||
|
|
||||||
fail "resize-window to 120x40 failed"
|
|
||||||
|
|
||||||
# -x 30 -y 10 -> pane 28x8; -X 85 -Y 25 -> pane_left 86, pane_top 26.
|
|
||||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
|
||||||
-x 30 -y 10 -X 85 -Y 25 'sleep 100') ||
|
|
||||||
fail "new-pane for new-pane repro failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 86
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 26
|
|
||||||
|
|
||||||
# Shrink to 60x20: xoff = 60 - 28 - 1 = 31; yoff = 20 - 8 - 1 = 11.
|
|
||||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
|
||||||
fail "resize-window (new-pane repro) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 31
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 11
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
# Case 4: a float that already fits inside the shrunk window is left alone --
|
|
||||||
# assert position and size are both unchanged, not just one of them.
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for untouched float failed"
|
|
||||||
|
|
||||||
# -x 20 -y 6 -> pane 18x4; -X 8 -Y 3 -> pane_left 9, pane_top 4 (as at the top
|
|
||||||
# of this file). Footprint columns 8-27, rows 3-8: well inside 60x20 too.
|
|
||||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
|
||||||
-x 20 -y 6 -X 8 -Y 3 'sleep 100') ||
|
|
||||||
fail "new-pane for untouched float failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
|
||||||
|
|
||||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
|
||||||
fail "resize-window (untouched float) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
|
||||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
# Case 6: pad = 0, via the pane-border-lines window option set to none. The
|
|
||||||
# border arithmetic differs here (no -2/+1 adjustment either on creation or
|
|
||||||
# in the clamp).
|
|
||||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
|
||||||
fail "new-window for pad=0 float failed"
|
|
||||||
$TMUX set-option -w -t "$win" pane-border-lines none ||
|
|
||||||
fail "set pane-border-lines none failed"
|
|
||||||
|
|
||||||
# No border: -x 20 -y 6 -> pane 20x6 directly; -X 60 -Y 18 -> pane_left 60,
|
|
||||||
# pane_top 18 directly. Footprint (== content, no border) is columns 60-79,
|
|
||||||
# rows 18-23: flush right/bottom of the 80x24 window, same as case 1.
|
|
||||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
|
||||||
fail "break-pane -W for pad=0 float failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 60
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 18
|
|
||||||
|
|
||||||
# Shrink to 40x16 with pad=0: xoff = 40 - 20 - 0 = 20; yoff = 16 - 6 - 0 = 10.
|
|
||||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
|
||||||
fail "resize-window (pad=0 float) failed"
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 20
|
|
||||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 10
|
|
||||||
$TMUX kill-window -t "$win" || exit 1
|
|
||||||
|
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Returning from the alternate screen must redraw the visible part of a
|
|
||||||
# floating pane clipped at the left edge, the top edge, or both.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Loffscreen-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Loffscreen-outer-$$ -f/dev/null"
|
|
||||||
EMITTER=$DIR/emitter.pl
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$EMITTER" <<'PERL'
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
$| = 1;
|
|
||||||
for my $row (1 .. 5) {
|
|
||||||
print "\e[$row;1H", 'A' x 15;
|
|
||||||
}
|
|
||||||
while (!-e "$ENV{TRIGGER}-alternate") {
|
|
||||||
select undef, undef, undef, 0.01;
|
|
||||||
}
|
|
||||||
print "\e[?1049h";
|
|
||||||
for my $row (1 .. 5) {
|
|
||||||
print "\e[$row;1H", 'B' x 15;
|
|
||||||
}
|
|
||||||
while (!-e "$ENV{TRIGGER}-restore") {
|
|
||||||
select undef, undef, undef, 0.01;
|
|
||||||
}
|
|
||||||
print "\e[?1049l";
|
|
||||||
sleep 100;
|
|
||||||
PERL
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 40 -y 10 'sleep 100' || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Loffscreen-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
for position in left top both; do
|
|
||||||
case "$position" in
|
|
||||||
left) x=-5; y=2 ;;
|
|
||||||
top) x=5; y=-2 ;;
|
|
||||||
both) x=-5; y=-2 ;;
|
|
||||||
esac
|
|
||||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 15 -y 5 -X "$x" -Y "$y" \
|
|
||||||
"TRIGGER='$DIR/$position' perl '$EMITTER'") || exit 1
|
|
||||||
[ "$($INNER display-message -p -t "$FLOAT" '#{pane_left},#{pane_top}')" = "$((x + 1)),$((y + 1))" ] ||
|
|
||||||
fail "$position: floating pane has unexpected position"
|
|
||||||
wait_outer_has AAAAA
|
|
||||||
cp "$CAPTURE" "$DIR/primary"
|
|
||||||
: >"$DIR/$position-alternate"
|
|
||||||
wait_outer_has BBBBB
|
|
||||||
: >"$DIR/$position-restore"
|
|
||||||
wait_outer_has AAAAA
|
|
||||||
cmp -s "$DIR/primary" "$CAPTURE" ||
|
|
||||||
fail "$position: primary screen was not completely restored"
|
|
||||||
$INNER kill-pane -t "$FLOAT" || exit 1
|
|
||||||
done
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# A damage rectangle's clip range is grown to avoid splitting a wide
|
|
||||||
# character, but redraw_damage_grow_span_clip() (screen-redraw.c) only ever
|
|
||||||
# checks the span's own pane *content* grid (wp->screen) for that. For a
|
|
||||||
# REDRAW_SPAN_PANE span, that same range is then also handed to
|
|
||||||
# redraw_damage_draw_pane_prompt() to recompose the pane's separately
|
|
||||||
# rendered prompt (wp->prompt, e.g. from "command-prompt -P") over the
|
|
||||||
# damaged sub-range - but the prompt is drawn into its own, freshly
|
|
||||||
# allocated one-line screen, unrelated to the pane's content grid, so a
|
|
||||||
# range grown (or left ungrown) against the content is not necessarily
|
|
||||||
# grown correctly for the prompt's own wide characters.
|
|
||||||
#
|
|
||||||
# This is invisible when the pane's own content is plain ASCII (as here):
|
|
||||||
# redraw_damage_grow_span_clip() never finds anything to grow against, so
|
|
||||||
# the raw, ungrown geometric range is passed straight through to the
|
|
||||||
# prompt - and if that range's edge lands mid-character in the *prompt's*
|
|
||||||
# grid, tty_draw_line() clears the character it cuts through
|
|
||||||
# (tty_draw_line_get_empty()'s gc->data.width > nx check, for a trailing
|
|
||||||
# base cell with no room left for its padding half).
|
|
||||||
#
|
|
||||||
# The trigger is a palette change (OSC 4) in a tiled pane that is one half
|
|
||||||
# of a vertical split running the full height of the window - occluded
|
|
||||||
# under the floating pane, but still geometrically triggering a redraw of
|
|
||||||
# its own rectangle. Positioned so the split boundary falls inside the
|
|
||||||
# floating pane's own CJK prompt, this reproduces exactly Codex's report:
|
|
||||||
# "a floating pane containing a CJK prompt across a tiled-pane boundary -
|
|
||||||
# a palette update in the tiled pane blanks a prompt character."
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
cd "$DIR" || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lpromptwide-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lpromptwide-outer-$$ -f/dev/null"
|
|
||||||
EMITTER=$DIR/emitter.pl
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
FLOAT=
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
cd /
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_client()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
|
||||||
[ -n "$CLIENT" ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "inner client did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_outer_has()
|
|
||||||
{
|
|
||||||
marker=$1
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "outer client did not show $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_prompt_row_intact()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
|
||||||
line=$(sed -n "${PROMPTROW}p" "$CAPTURE")
|
|
||||||
case $line in
|
|
||||||
*"$PROMPTTEXT"*) return 0 ;;
|
|
||||||
esac
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "the CJK prompt was not intact after the palette-triggered damage - got: $line"
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$EMITTER" <<'PERL'
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
$| = 1;
|
|
||||||
my $line = <STDIN>;
|
|
||||||
print "\e]4;1;rgb:11/22/33\e\\";
|
|
||||||
sleep 100;
|
|
||||||
PERL
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 60 -y 12 "perl '$EMITTER'" || exit 1
|
|
||||||
$INNER set-option -g status off || exit 1
|
|
||||||
$INNER set-option -g window-size manual || exit 1
|
|
||||||
LEFT=$($INNER list-panes -t inner -F '#{pane_id}') || exit 1
|
|
||||||
|
|
||||||
# Split so the boundary between the two tiled panes falls at column 17 -
|
|
||||||
# used below to pick a floating-pane column that lands the boundary
|
|
||||||
# mid-character inside the prompt.
|
|
||||||
RIGHT=$($INNER split-window -t inner -h -l 43 -PF '#{pane_id}' \
|
|
||||||
'sleep 100') || exit 1
|
|
||||||
RX=$($INNER display-message -p -t "$RIGHT" '#{pane_left}') || exit 1
|
|
||||||
|
|
||||||
# Create the floating pane, then check its actual resulting position (the
|
|
||||||
# border-framing offset added to -X is not something to hand-compute). Try
|
|
||||||
# adjacent starting columns until the split boundary lands on an odd
|
|
||||||
# (padding-half) column of the prompt's own numbering, and within the
|
|
||||||
# prompt's 12-column width.
|
|
||||||
startx=10
|
|
||||||
tries=0
|
|
||||||
while [ "$tries" -lt 4 ]; do
|
|
||||||
[ -n "$FLOAT" ] && $INNER kill-pane -t "$FLOAT" 2>/dev/null
|
|
||||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 24 -y 5 -X "$startx" \
|
|
||||||
-Y 2 "sh -c 'i=0; while [ \$i -lt 10 ]; do \
|
|
||||||
printf AAAAAAAAAAAAAAAAAAAAAA\\\\n; i=\$((i+1)); done; sleep 100'") ||
|
|
||||||
exit 1
|
|
||||||
sleep 0.2
|
|
||||||
X1=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
|
||||||
Y1=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
|
||||||
H1=$($INNER display-message -p -t "$FLOAT" '#{pane_height}')
|
|
||||||
local=$((RX - X1 - 1))
|
|
||||||
if [ "$local" -ge 1 ] && [ "$local" -le 11 ] &&
|
|
||||||
[ $((local % 2)) -eq 1 ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
startx=$((startx + 1))
|
|
||||||
tries=$((tries + 1))
|
|
||||||
done
|
|
||||||
local=$((RX - X1 - 1))
|
|
||||||
[ "$local" -ge 1 ] && [ "$local" -le 11 ] && [ $((local % 2)) -eq 1 ] ||
|
|
||||||
fail "could not find bad-parity starting column"
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 60 -y 12 'sleep 100' || exit 1
|
|
||||||
$OUTER set-option -g status off || exit 1
|
|
||||||
$OUTER set-option -g window-size manual || exit 1
|
|
||||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
|
||||||
$OUTER respawn-pane -k -t outer:0.0 \
|
|
||||||
"$TEST_TMUX -Lpromptwide-inner-$$ -f/dev/null attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
wait_for_client
|
|
||||||
wait_outer_has AAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
CLIENT=$($INNER list-clients -F '#{client_name}') || exit 1
|
|
||||||
|
|
||||||
$INNER select-pane -t "$FLOAT" || exit 1
|
|
||||||
PROMPTTEXT=$(printf '\344\270\255' | perl -CSD -ne 'print $_ x 6')
|
|
||||||
$INNER command-prompt -b -P -t "$CLIENT" -p "$PROMPTTEXT" \
|
|
||||||
'display-message -- %1' || exit 1
|
|
||||||
wait_outer_has "$PROMPTTEXT"
|
|
||||||
|
|
||||||
PROMPTROW=$((Y1 + H1))
|
|
||||||
|
|
||||||
# Trigger the damage: unblock the emitter so it fires the palette change in
|
|
||||||
# the left tiled pane, which is occluded under (but geometrically overlaps)
|
|
||||||
# the floating pane's prompt row.
|
|
||||||
$INNER send-keys -t "$LEFT" Enter || exit 1
|
|
||||||
|
|
||||||
wait_prompt_row_intact
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Damage redraws must show each client's own pane status, even though the
|
|
||||||
# cached status screen is shared between clients.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export PATH TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
|
|
||||||
DIR=$(mktemp -d) || exit 1
|
|
||||||
INNER="$TEST_TMUX -Lstatuscc-inner-$$ -f/dev/null"
|
|
||||||
OUTER="$TEST_TMUX -Lstatuscc-outer-$$ -f/dev/null"
|
|
||||||
CAPTURE=$DIR/capture
|
|
||||||
|
|
||||||
fail()
|
|
||||||
{
|
|
||||||
echo "$*" >&2
|
|
||||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
$OUTER kill-server 2>/dev/null
|
|
||||||
$INNER kill-server 2>/dev/null
|
|
||||||
rm -rf "$DIR"
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
wait_for_clients()
|
|
||||||
{
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
count=$($INNER list-clients 2>/dev/null | wc -l)
|
|
||||||
[ "$count" -eq 2 ] && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "two inner clients did not attach"
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_marker()
|
|
||||||
{
|
|
||||||
target=$1
|
|
||||||
marker=$2
|
|
||||||
i=0
|
|
||||||
while [ "$i" -lt 50 ]; do
|
|
||||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
|
||||||
grep -q "$marker" "$CAPTURE" && return 0
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
fail "client in $target did not receive $marker"
|
|
||||||
}
|
|
||||||
|
|
||||||
cat >"$DIR/emitter.pl" <<'PERL'
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
$| = 1;
|
|
||||||
for my $phase (1 .. 4) {
|
|
||||||
while (!-e "$ENV{TRIGGER}-$phase") {
|
|
||||||
select undef, undef, undef, 0.01;
|
|
||||||
}
|
|
||||||
# Change an unused palette entry and acknowledge it in the pane body.
|
|
||||||
print "\e]4;200;rgb:11/22/0$phase\a\e[1;1HDAMAGE$phase";
|
|
||||||
}
|
|
||||||
sleep 100;
|
|
||||||
PERL
|
|
||||||
|
|
||||||
$INNER new-session -d -s inner -x 60 -y 20 \
|
|
||||||
"TRIGGER='$DIR/trigger' perl '$DIR/emitter.pl'" || exit 1
|
|
||||||
$INNER set -g status off || exit 1
|
|
||||||
$INNER set -g window-size manual || exit 1
|
|
||||||
$INNER set -g automatic-rename off || exit 1
|
|
||||||
$INNER set -g status-interval 0 || exit 1
|
|
||||||
$INNER set -g pane-border-lines simple || exit 1
|
|
||||||
$INNER set -g pane-border-status top || exit 1
|
|
||||||
$INNER set -g pane-border-format 'CLIENT=<#{client_name}>' || exit 1
|
|
||||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 35 -y 6 -X 5 -Y 5 \
|
|
||||||
'sleep 100') || exit 1
|
|
||||||
|
|
||||||
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
|
||||||
$OUTER set -g status off || exit 1
|
|
||||||
$OUTER set -g window-size manual || exit 1
|
|
||||||
$OUTER set -g default-terminal screen || exit 1
|
|
||||||
LEFT=$($OUTER display-message -p -t outer:0.0 '#{pane_id}') || exit 1
|
|
||||||
RIGHT=$($OUTER split-window -h -PF '#{pane_id}' 'sleep 100') || exit 1
|
|
||||||
for target in "$LEFT" "$RIGHT"; do
|
|
||||||
$OUTER respawn-pane -k -t "$target" "$INNER attach-session -t inner" ||
|
|
||||||
exit 1
|
|
||||||
done
|
|
||||||
wait_for_clients
|
|
||||||
|
|
||||||
NAME1=$($OUTER display-message -p -t "$LEFT" '#{pane_tty}') || exit 1
|
|
||||||
NAME2=$($OUTER display-message -p -t "$RIGHT" '#{pane_tty}') || exit 1
|
|
||||||
|
|
||||||
# Disable periodic status updates above and trigger damage without a command
|
|
||||||
# that also requests a status redraw. Each client must keep its own title.
|
|
||||||
$INNER refresh-client -t "$NAME1" || exit 1
|
|
||||||
$INNER refresh-client -t "$NAME2" || exit 1
|
|
||||||
sleep 0.5
|
|
||||||
for phase in 0 1 2; do
|
|
||||||
if [ "$phase" -ne 0 ]; then
|
|
||||||
: >"$DIR/trigger-$phase"
|
|
||||||
wait_for_marker "$LEFT" "DAMAGE$phase"
|
|
||||||
wait_for_marker "$RIGHT" "DAMAGE$phase"
|
|
||||||
sleep 0.2
|
|
||||||
fi
|
|
||||||
for target in "$LEFT" "$RIGHT"; do
|
|
||||||
if [ "$target" = "$LEFT" ]; then
|
|
||||||
name=$NAME1
|
|
||||||
other=$NAME2
|
|
||||||
else
|
|
||||||
name=$NAME2
|
|
||||||
other=$NAME1
|
|
||||||
fi
|
|
||||||
$OUTER capture-pane -p -S 5 -E 10 -t "$target" \
|
|
||||||
>"$CAPTURE" || exit 1
|
|
||||||
grep -Fq "CLIENT=<$name>" "$CAPTURE" ||
|
|
||||||
fail "phase $phase: missing $name's pane status"
|
|
||||||
if grep -Fq "CLIENT=<$other>" "$CAPTURE"; then
|
|
||||||
fail "phase $phase: $name received $other's pane status"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
# Leave just one client so a cache keyed only by client would remain stale.
|
|
||||||
$OUTER respawn-pane -k -t "$RIGHT" 'sleep 100' || exit 1
|
|
||||||
i=0
|
|
||||||
while [ "$($INNER list-clients | wc -l)" -ne 1 ]; do
|
|
||||||
[ "$i" -lt 50 ] || fail "second client did not detach"
|
|
||||||
sleep 0.1
|
|
||||||
i=$((i + 1))
|
|
||||||
done
|
|
||||||
$INNER set-environment -g TEST_STATUS_VALUE initial || exit 1
|
|
||||||
$INNER set -g pane-border-format 'VALUE=#{TEST_STATUS_VALUE}' || exit 1
|
|
||||||
$INNER refresh-client -t "$NAME1" || exit 1
|
|
||||||
wait_for_marker "$LEFT" VALUE=initial
|
|
||||||
for phase in 3 4; do
|
|
||||||
# Changing the environment does not itself request a status redraw.
|
|
||||||
$INNER set-environment -g TEST_STATUS_VALUE "phase$phase" || exit 1
|
|
||||||
: >"$DIR/trigger-$phase"
|
|
||||||
wait_for_marker "$LEFT" "DAMAGE$phase"
|
|
||||||
wait_for_marker "$LEFT" "VALUE=phase$phase"
|
|
||||||
done
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user