mirror of
https://github.com/tmux/tmux.git
synced 2026-09-16 18:21:58 +00:00
Compare commits
133 Commits
image-redr
...
osc133_awa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ec4328380 | ||
|
|
8746405e34 | ||
|
|
8c0ac1fe05 | ||
|
|
dc65677ff9 | ||
|
|
2142818e25 | ||
|
|
cda0fe9f7b | ||
|
|
4cc9fc91fa | ||
|
|
081b79fd6e | ||
|
|
6294be4986 | ||
|
|
578e07fcbc | ||
|
|
93169e2e62 | ||
|
|
db24c029be | ||
|
|
a6f9a206b7 | ||
|
|
ce253864f8 | ||
|
|
79f1f43830 | ||
|
|
7b666e62be | ||
|
|
5d5f66239a | ||
|
|
4b1586fa81 | ||
|
|
9fa390aee6 | ||
|
|
ad632726bd | ||
|
|
1dd1717462 | ||
|
|
73db721dab | ||
|
|
6bb6c286a9 | ||
|
|
ca34d190b5 | ||
|
|
57a13664cc | ||
|
|
59dc0e75c4 | ||
|
|
2d6aed1d92 | ||
|
|
3dd575ce55 | ||
|
|
60e871658f | ||
|
|
c49dfdf496 | ||
|
|
9b0ad73a4b | ||
|
|
bd8218e85d | ||
|
|
8f3101d820 | ||
|
|
77309c07b0 | ||
|
|
c8e889694d | ||
|
|
b402687fcd | ||
|
|
0530a7d89a | ||
|
|
6bdb9ba54c | ||
|
|
f2bfd611e2 | ||
|
|
9c1d5a080b | ||
|
|
b28244e2fe | ||
|
|
8b3834c34f | ||
|
|
af3e4d2e5b | ||
|
|
c552d1bf16 | ||
|
|
6a1ecdcce3 | ||
|
|
39357c829c | ||
|
|
40381bdcd9 | ||
|
|
7bad4f7722 | ||
|
|
65e811570b | ||
|
|
dd31900f80 | ||
|
|
013ada58c8 | ||
|
|
78dad45f3b | ||
|
|
52b452948f | ||
|
|
267746603a | ||
|
|
b26b240613 | ||
|
|
8e48c889dd | ||
|
|
f4445a310a | ||
|
|
4157b64754 | ||
|
|
0d2aafe7b7 | ||
|
|
6f4c9b32af | ||
|
|
eb77a75ab7 | ||
|
|
0851650c6b | ||
|
|
1459c90a7f | ||
|
|
af29c080ba | ||
|
|
50ea32162f | ||
|
|
2d5328a860 | ||
|
|
e5db7598b2 | ||
|
|
dc78d8f9ea | ||
|
|
022b8451e3 | ||
|
|
5b54c7d41a | ||
|
|
f94036b2b0 | ||
|
|
7d358208b9 | ||
|
|
c8ff8ea96f | ||
|
|
402d366bba | ||
|
|
41e701ad84 | ||
|
|
f763f94556 | ||
|
|
c9f5ecb4b5 | ||
|
|
7792a74c76 | ||
|
|
4cabc2ae85 | ||
|
|
995b395795 | ||
|
|
3d44f75d09 | ||
|
|
c1f947a3c5 | ||
|
|
696a16cca2 | ||
|
|
936bb6e7cd | ||
|
|
8dfa903346 | ||
|
|
95a7170709 | ||
|
|
554c4bbde4 | ||
|
|
ac23f21a2c | ||
|
|
6696151008 | ||
|
|
50d0348b64 | ||
|
|
bf7bbba7ea | ||
|
|
b28b1b7b9a | ||
|
|
8499d8007e | ||
|
|
e5a2058c7c | ||
|
|
e94fcd96f9 | ||
|
|
c5017e6da8 | ||
|
|
0169f9e18c | ||
|
|
b170c19274 | ||
|
|
22b3679752 | ||
|
|
4714f478fe | ||
|
|
72c5037f06 | ||
|
|
6238793ad7 | ||
|
|
da641e4e8d | ||
|
|
19a085a672 | ||
|
|
f672a0f677 | ||
|
|
db194ced5b | ||
|
|
11ed661e2c | ||
|
|
b2de803871 | ||
|
|
c5a5b99788 | ||
|
|
e23219ec04 | ||
|
|
0f29c47278 | ||
|
|
7c9fdcb770 | ||
|
|
0330d8ac57 | ||
|
|
0a7788b66f | ||
|
|
458d045bd2 | ||
|
|
a81154d6d9 | ||
|
|
cffc86dae5 | ||
|
|
4cfd155ffc | ||
|
|
670ea5d083 | ||
|
|
eaeb234dd4 | ||
|
|
5948722d5a | ||
|
|
d8ad7cd9c5 | ||
|
|
b49cc2d936 | ||
|
|
abb7108e25 | ||
|
|
d080378464 | ||
|
|
473eca949a | ||
|
|
f0eaa5e9d6 | ||
|
|
f44246c764 | ||
|
|
5d4d41da57 | ||
|
|
bacbb4d9d8 | ||
|
|
07a3a16ae7 | ||
|
|
f41288ee27 | ||
|
|
457b6cbefd |
3
.github/workflows/lock.yml
vendored
3
.github/workflows/lock.yml
vendored
@@ -17,8 +17,9 @@ jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'tmux/tmux'
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v6
|
||||
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '30'
|
||||
|
||||
8
.github/workflows/regress.yml
vendored
8
.github/workflows/regress.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
if: github.repository == 'tmux/tmux'
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 300
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -38,7 +38,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: dependencies
|
||||
if: runner.os == 'Linux'
|
||||
@@ -81,7 +83,7 @@ jobs:
|
||||
|
||||
- name: logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: regress-logs-${{ matrix.name }}
|
||||
path: regress/logs/*.log
|
||||
|
||||
49
CHANGES
49
CHANGES
@@ -9,7 +9,12 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
- 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.
|
||||
A modal pane is now used for the editor in buffer mode;
|
||||
-C closes the pane when the mouse is clicked outside it and -K sends all
|
||||
keys, including the prefix key, to the pane. 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
|
||||
mouse dragging;
|
||||
@@ -24,6 +29,9 @@ CHANGES FROM 3.7c TO 3.8
|
||||
-k for choose commands kills the pane when the mode exits, which is useful
|
||||
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
|
||||
default bindings under C-b g for common move and resize operations;
|
||||
|
||||
@@ -74,7 +82,7 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
- new hooks to cover areas including client create and destroy, pane
|
||||
activity, pane mode and prompt changes, pane movement and resizing, session
|
||||
group changes and window creation, close and zoom;
|
||||
group changes and window creation, close, swap and zoom;
|
||||
|
||||
- OSC 133 escape sequences now trigger events: pane-command-started,
|
||||
pane-command-finished and pane-shell-prompt.
|
||||
@@ -87,10 +95,23 @@ CHANGES FROM 3.7c TO 3.8
|
||||
while scrolling or while hovered and disappears after pane-scrollbars-timeout
|
||||
(Michael Grant).
|
||||
|
||||
* Extend the fill-character option so both inside and outside the window can be
|
||||
changed.
|
||||
* 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).
|
||||
|
||||
* Change set-option and set-hooks to use formats and add a -F flag to each.
|
||||
* Copy mode can now automatically refresh as pane content changes with the
|
||||
refresh-on, refresh-off and refresh-toggle commands. Automatic refresh is off
|
||||
by default; refresh-now refreshes once and is bound to r. These replace
|
||||
refresh-from-pane (issue 5165).
|
||||
|
||||
* Extend the fill-character option so both inside and outside the window can be
|
||||
changed, with a new default for unused areas inside the window.
|
||||
|
||||
* Change show-options and show-hooks to use formats and add a -F flag to each.
|
||||
|
||||
* Allow individual terminal features to be disabled by adding @ to their names
|
||||
in terminal-features, and add a utf8 feature for terminals which support
|
||||
UTF-8 output.
|
||||
|
||||
* Add a #{A/count:frames} modifier to show a series of frames as an animation
|
||||
in the status line (issue 5412 from Fernando Daciuk).
|
||||
@@ -105,6 +126,8 @@ CHANGES FROM 3.7c TO 3.8
|
||||
* 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).
|
||||
|
||||
* Add a default C-b T binding to change the current pane title.
|
||||
|
||||
* Menus now belong to the window, so appear on all clients.
|
||||
|
||||
* Dragging over an existing copy mode selection now adjusts it (Michael Grant).
|
||||
@@ -118,6 +141,13 @@ CHANGES FROM 3.7c TO 3.8
|
||||
* Copy mode no longer exits at the bottom while a selection is in progress
|
||||
(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
|
||||
nolink) (issues 4842 and 4280 from Moritz Angermann).
|
||||
|
||||
@@ -128,7 +158,9 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* Add or improve format variables including client_colours,
|
||||
pane_start_command_list, window_manual_width, window_manual_height,
|
||||
pane_last_output_time, pane_modal_flag and window_modal_pane.
|
||||
pane_last_output_time, pane_modal_flag, window_modal_pane, pane_private_modes,
|
||||
pane_output_generation, history_added, history_collected, history_generation,
|
||||
hook_fire_count and hook_fire_time.
|
||||
|
||||
* Add additional pane sort orders, and a z sort order for choose-tree so
|
||||
floating panes can be sorted by z-index.
|
||||
@@ -145,7 +177,10 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* 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
|
||||
already exiting (Ben Maurer, issue 5357).
|
||||
already exiting (Ben Maurer, issue 5357). Queue notifications so they are not
|
||||
sent inside %begin/%end (issue 5458), do not let a stuck client prevent the
|
||||
server from exiting (issue 5444), and reset control mode offsets when a pane
|
||||
is respawned (issue 5498).
|
||||
|
||||
* Fix grouped sessions sometimes being left as unusable command targets while
|
||||
they are being killed (Bryce Miller, issue 5180).
|
||||
|
||||
@@ -1,715 +0,0 @@
|
||||
# Image Redraw and Damage Plan
|
||||
|
||||
## Purpose
|
||||
|
||||
Make image redraws correct and efficient for tiled panes, floating panes,
|
||||
scrolling, copy-mode selection, pane borders, pane status lines, scrollbars,
|
||||
and clients using different output protocols.
|
||||
|
||||
The intended model is retained: the visible screen is a scene of horizontal
|
||||
cell spans. A change damages cell-aligned regions, and the compositor redraws
|
||||
only scene spans intersecting those regions.
|
||||
|
||||
Synchronized output may hide unavoidable intermediate terminal updates, but
|
||||
it must not be required for correctness.
|
||||
|
||||
## Current state and recovery point
|
||||
|
||||
The last clean, pushed commit on `4902-image-support` is:
|
||||
|
||||
```
|
||||
ec9859b1 sixel: preserve image aspect across client geometries
|
||||
```
|
||||
|
||||
That commit already contains the important persistent model:
|
||||
|
||||
- sparse image placements represented by spans attached to grid lines;
|
||||
- input-protocol semantics and placement ordering;
|
||||
- source coordinates for cropping placement spans;
|
||||
- movement, copying, duplication, and removal of image spans with grid data;
|
||||
- per-client output backends for Kitty, SIXEL, and fallback rendering; and
|
||||
- cached image/rendering data.
|
||||
|
||||
The current uncommitted changes touch eight files and mix several attempted
|
||||
optimizations. They are known to damage SIXEL in Mintty and cause unnecessary
|
||||
redraws with floating panes. Do not commit them as the solution.
|
||||
|
||||
Before discarding them, save one patch outside the branch for reference:
|
||||
|
||||
```
|
||||
git diff -- image.c regress/image-support.sh screen-redraw.c screen-write.c \
|
||||
server-client.c tmux.h tty.c window-copy.c \
|
||||
> /tmp/4902-image-redraw-experiment.patch
|
||||
```
|
||||
|
||||
Then restore those tracked files to `ec9859b1` before beginning the new work.
|
||||
Do this only after the user explicitly approves the rollback.
|
||||
|
||||
### Ideas worth retaining, but reimplementing separately
|
||||
|
||||
- Redraw damage should be restricted to scene-visible spans.
|
||||
- Image-related selection tests are useful.
|
||||
- Floating-pane and pane-status regression scenarios are useful.
|
||||
- Window chrome must be emitted after pane image/text composition whenever
|
||||
the same damaged region contains both.
|
||||
- A placement's complete canvas may need to be represented even where its
|
||||
pixels are transparent; visibility and alpha must then be tested while
|
||||
compositing rather than by losing the placement geometry.
|
||||
- Native scrolling should be selected according to the affected region and
|
||||
backend behaviour.
|
||||
|
||||
### Changes not worth retaining in their present form
|
||||
|
||||
- Promoting an obscured-pane update to `PANE_REDRAW` and then sometimes to
|
||||
`CLIENT_REDRAWWINDOW`.
|
||||
- Repairing images directly from individual `tty_cmd_*` text functions.
|
||||
- Separate ad-hoc redraw paths for images, selection, scrollbars, borders,
|
||||
and floating panes.
|
||||
- The current `TTY_CTX_IMAGE_SCROLL`, `TTY_CTX_PANE_FULL_WIDTH`, and
|
||||
`CLIENT_REDRAWSCROLLBARS` plumbing as a combined patch.
|
||||
- Assuming an unchanged pane-status string does not need physical redraw
|
||||
after some other drawing operation has covered its cells.
|
||||
|
||||
## Required invariants
|
||||
|
||||
These rules should be written down in code comments and enforced throughout
|
||||
the implementation.
|
||||
|
||||
1. **Input determines semantics.** Whether text removes an image layer, lies
|
||||
above it, or lies below it is determined by the protocol received from the
|
||||
application. It must not depend on whether the outer client uses Kitty,
|
||||
SIXEL, or fallback output.
|
||||
|
||||
2. **Output backends encode a scene.** An output backend may choose different
|
||||
escape sequences, caches, or cropping, but it must render the same logical
|
||||
result.
|
||||
|
||||
3. **One compositor owns repair.** No output path may independently erase and
|
||||
reconstruct image cells without going through the same scene ordering.
|
||||
|
||||
4. **Damage is regional.** Ordinary content changes do not become pane-wide
|
||||
or window-wide redraws. Full redraw remains appropriate for attachment,
|
||||
resize, geometry changes, terminal reset, or invalid scene geometry.
|
||||
|
||||
5. **Damage uses cell coordinates.** Pixel offsets are handled when an image
|
||||
backend maps the damaged cells to a source-pixel crop.
|
||||
|
||||
6. **Logical state changes before physical output.** Grid text and image spans
|
||||
are moved, removed, or inserted first. Rendering then reads the completed
|
||||
logical state.
|
||||
|
||||
7. **Chrome is part of composition.** Pane borders, pane status lines,
|
||||
scrollbars, menus, and floating panes must not be repaired independently in
|
||||
an order that allows image output to cover them afterward.
|
||||
|
||||
8. **No-image behaviour remains unchanged.** When a damaged region does not
|
||||
intersect image data or graphical overlays, existing fast text output and
|
||||
native scrolling should remain available.
|
||||
|
||||
## Established image semantics
|
||||
|
||||
The image semantics present at the `ec9859b1` recovery point are a
|
||||
compatibility requirement. They resulted from the review discussion in PR
|
||||
#5445, particularly the temporal-versus-positional model described in
|
||||
[Theory and Practice of Sprixels](https://nick-black.com/dankwiki/index.php?title=Theory_and_Practice_of_Sprixels#Transparency_and_z-ordering),
|
||||
and were accepted after commit `a4abd616`, which added the corresponding
|
||||
manual visual checks.
|
||||
|
||||
- SIXEL input is temporal. Transparent pixels preserve what existed when the
|
||||
SIXEL was emitted. A later text write annihilates the affected SIXEL cells;
|
||||
the SIXEL does not remain logically behind that text.
|
||||
- Kitty input is positional. Negative-z placements remain below the text plane;
|
||||
nonnegative placements remain above it. Text writes do not convert or remove
|
||||
Kitty placements merely because the output client uses SIXEL.
|
||||
- Overlapping placements remain separate ordered layers. Transparent pixels in
|
||||
an upper placement reveal retained lower placements or text.
|
||||
- Input protocol, z-index, and placement order determine the logical scene. The
|
||||
attached client's output protocol only determines how that scene is encoded.
|
||||
- Redraw, damage, scrolling, copy mode, and backend conversion must preserve
|
||||
these rules exactly.
|
||||
|
||||
The manual checks introduced by `a4abd616` and the corresponding regression
|
||||
cases are semantic compatibility tests, not merely visual diagnostics.
|
||||
|
||||
## Coordinate spaces
|
||||
|
||||
Keep conversions explicit. Do not pass ambiguous `x` and `y` values between
|
||||
layers.
|
||||
|
||||
- **Grid coordinates:** include history and belong to a pane's `struct grid`.
|
||||
- **Pane coordinates:** visible cells inside a pane.
|
||||
- **Window coordinates:** include pane offsets and floating-pane positions.
|
||||
- **Client scene coordinates:** window coordinates clipped by the client's
|
||||
viewport (`ox`, `oy`, `sx`, and `sy`).
|
||||
- **Terminal coordinates:** include the client status-line offset and any
|
||||
terminal viewport offset.
|
||||
- **Image source coordinates:** identify cell rows/columns within a placement;
|
||||
the backend maps these to source pixels using terminal cell geometry and
|
||||
placement pixel offsets.
|
||||
|
||||
Store damage in window coordinates. Translate and clip it separately for each
|
||||
client when drawing, because clients may have different viewports, status-line
|
||||
positions, cell geometries, and output backends.
|
||||
|
||||
## Compartmentalization
|
||||
|
||||
Keep image implementation details in the existing image files. The rest of
|
||||
tmux should know only that some scene cells require image-aware composition.
|
||||
|
||||
### `image.c`
|
||||
|
||||
Own all protocol-independent image state and composition policy:
|
||||
|
||||
- placements, spans, input protocol, z-index, serial ordering, and alpha;
|
||||
- applying input semantics when text overwrites image cells;
|
||||
- testing whether a grid rectangle intersects image placements;
|
||||
- mapping a damaged cell rectangle to ordered image fragments;
|
||||
- deciding which logical image layers are below or above the text plane;
|
||||
- generic backend dispatch; and
|
||||
- public image helpers used by the redraw subsystem.
|
||||
|
||||
### `image-sixel.c`
|
||||
|
||||
Own only SIXEL-specific output details:
|
||||
|
||||
- cached quantized/indexed representations;
|
||||
- mapping cell crops to pixel and six-pixel-band crops;
|
||||
- emitting SIXEL fragments;
|
||||
- alpha and palette encoding; and
|
||||
- backend-specific reset, erase, and scrolling capabilities.
|
||||
|
||||
### `image-kitty.c`
|
||||
|
||||
Own only Kitty-specific output details:
|
||||
|
||||
- client-side transmission and placement caches;
|
||||
- placement creation, clipping, movement, and deletion;
|
||||
- Kitty IDs and geometry; and
|
||||
- backend-specific reset and scrolling behaviour.
|
||||
|
||||
### Other tmux files
|
||||
|
||||
- `screen-redraw.c` owns scene construction, damage clipping, and the ordering
|
||||
of pane content versus window chrome. It calls generic `image.c` helpers and
|
||||
contains no Kitty or SIXEL protocol logic.
|
||||
- `screen-write.c` updates logical grid state and reports pane/window damage.
|
||||
It must not render image fragments or select an output backend.
|
||||
- `tty.c` owns terminal text and scrolling primitives. It receives an already
|
||||
selected native-scroll operation; it must not reconstruct image layers or
|
||||
call image repair after individual characters.
|
||||
- `server-client.c` schedules and drains per-client damage. It must not inspect
|
||||
image placements or output protocols.
|
||||
- `window-copy.c` reports copy-mode damage and copies logical image state using
|
||||
generic image helpers; it must not render images itself.
|
||||
- `tmux.h` exposes only the minimum generic structures and function prototypes
|
||||
required across those boundaries.
|
||||
|
||||
Prefer one generic call from `screen-redraw.c` into `image.c` over `ENABLE_IMAGES`
|
||||
branches scattered through tty commands. A build without image support should
|
||||
retain the existing redraw path with minimal conditional code.
|
||||
|
||||
## Damage representation
|
||||
|
||||
Add a small regional damage abstraction owned by the redraw subsystem. Do not
|
||||
add per-cell queues or enlarge `struct grid_cell`.
|
||||
|
||||
A damage entry needs:
|
||||
|
||||
```
|
||||
x, y, sx, sy window-cell rectangle
|
||||
reason content, scroll exposure, pane move, chrome, and so on
|
||||
```
|
||||
|
||||
The exact names should follow tmux style. A short list or dynamic array per
|
||||
client is sufficient. When a new rectangle is added:
|
||||
|
||||
- clip it to the window;
|
||||
- discard empty rectangles;
|
||||
- merge overlapping or directly adjacent rectangles where that does not make
|
||||
the result substantially larger;
|
||||
- cap the list at a modest number; above the cap, replace it with their union;
|
||||
- promote to a full redraw only if geometry is invalid or the union covers
|
||||
most of the client scene.
|
||||
|
||||
A shared window damage list must not be cleared after the first client draws.
|
||||
Either append translated window rectangles to every affected client, or give
|
||||
damage events monotonically increasing sequence numbers so each client can
|
||||
consume them independently. The simpler initial implementation is to append
|
||||
window-coordinate rectangles to each client currently displaying the window.
|
||||
|
||||
Suggested public operations are conceptually:
|
||||
|
||||
```
|
||||
redraw_damage_window(window, x, y, sx, sy, reason)
|
||||
redraw_damage_pane(window_pane, x, y, sx, sy, reason)
|
||||
redraw_damage_client(client, x, y, sx, sy, reason)
|
||||
```
|
||||
|
||||
Do not expose image-backend details through these calls.
|
||||
|
||||
## Regional scene composition
|
||||
|
||||
Extend `screen-redraw.c` so an existing `redraw_scene` can be drawn through a
|
||||
set of clipping rectangles.
|
||||
|
||||
For every damaged line:
|
||||
|
||||
1. Intersect the damage rectangle with that line's scene spans.
|
||||
2. Split only at damage boundaries; do not rebuild the complete scene.
|
||||
3. Draw intersecting pane content in logical layer order.
|
||||
4. Draw window chrome intersecting the same rectangle last.
|
||||
|
||||
For a pane span, the scene has already selected the topmost visible pane at
|
||||
that position. Composition inside that pane should then be explicit:
|
||||
|
||||
1. image placements below the text plane, such as negative Kitty z-indexes;
|
||||
2. the text plane, including the requested copy-mode style;
|
||||
3. surviving temporal SIXEL placements and images above the text plane;
|
||||
4. pane borders, pane status, scrollbars, menus, and other chrome.
|
||||
|
||||
The exact placement order is already represented by `struct image_placement`
|
||||
and its input protocol, z-index, and serial. Do not derive it from the client's
|
||||
output backend.
|
||||
|
||||
Only the topmost pane content is normally needed because panes are opaque.
|
||||
Underlying pane content is retained in its own grid and becomes visible when a
|
||||
floating pane moves, at which time the old floating-pane rectangle is damaged
|
||||
and recomposed.
|
||||
|
||||
### Image fragments
|
||||
|
||||
An `image_span` is placement metadata, not a separately stored bitmap. For an
|
||||
intersection, compute the corresponding source-cell rectangle and ask the
|
||||
backend to emit that crop from its cached rendering.
|
||||
|
||||
- Do not rerun quantization or dithering for every small damage rectangle.
|
||||
- SIXEL output operates in six-pixel vertical bands. Expand or clip the
|
||||
backend operation as required, then ensure any additionally touched cells
|
||||
are included in the same composition pass.
|
||||
- A transparent top image requires lower image layers to be drawn first.
|
||||
- Kitty placements may be deleted, clipped, or replaced only within the
|
||||
damaged placement region.
|
||||
|
||||
## Fast text writes
|
||||
|
||||
Do not route every ordinary character through a deferred full compositor.
|
||||
|
||||
For an application text write:
|
||||
|
||||
1. Apply input-protocol damage rules to the logical image spans. For example,
|
||||
text written after a SIXEL image removes the affected SIXEL coverage.
|
||||
2. Test whether the affected cells still intersect any image layer or scene
|
||||
overlay requiring composition.
|
||||
3. If not, retain the existing direct `tty_cmd_cell`/`tty_cmd_cells` path.
|
||||
4. If they do, suppress conflicting direct terminal repair and enqueue only
|
||||
those cells for regional composition.
|
||||
|
||||
This decision belongs above the backend-specific tty output functions. Avoid
|
||||
callbacks such as `image_draw_after_text()` from several `tty_cmd_*` functions;
|
||||
they reproduce ordering logic in multiple places.
|
||||
|
||||
## Selection and copy mode
|
||||
|
||||
Copy mode must not mutate the application's logical image placements.
|
||||
|
||||
- Treat selection as a temporary tmux UI style on the text plane.
|
||||
- Follow the chosen policy that selection highlighting does not overwrite
|
||||
image-covered cells.
|
||||
- Mouse motion should damage only cells whose selection state actually
|
||||
changed.
|
||||
- If changed selection cells do not intersect visible images, draw them using
|
||||
the normal text fast path.
|
||||
- If they intersect an image canvas, compose those cells once; do not erase a
|
||||
whole image or pane.
|
||||
- Entering or leaving copy mode should damage the visible pane rectangle once,
|
||||
not repeatedly for every unchanged mouse event.
|
||||
|
||||
Copy-mode image spans should be copied before its text view is composed, but
|
||||
this should be implemented through the same logical state and damage API, not
|
||||
through special tty repair calls.
|
||||
|
||||
## Floating panes
|
||||
|
||||
### Focus changes
|
||||
|
||||
Changing the active pane should damage only cells whose appearance changes:
|
||||
|
||||
- affected pane borders;
|
||||
- pane status cells whose style or contents change; and
|
||||
- any active-pane indicators.
|
||||
|
||||
It must not redraw pane interiors or retransmit unrelated SIXEL images.
|
||||
|
||||
### Moving or resizing a floating pane
|
||||
|
||||
For an old rectangle `A` and new rectangle `B`:
|
||||
|
||||
- invalidate the scene geometry;
|
||||
- damage `A union B` (represented as a few rectangles rather than necessarily
|
||||
one large bounding box);
|
||||
- rebuild the scene because pane ownership changed;
|
||||
- recompose only spans intersecting those rectangles.
|
||||
|
||||
The old rectangle restores tiled panes and any image fragments that were
|
||||
previously hidden. The new rectangle draws the floating pane and its chrome.
|
||||
Multiple overlapping floating panes follow their window z-order.
|
||||
|
||||
## Scrolling
|
||||
|
||||
Implement scrolling in three stages. Do not begin with the most complicated
|
||||
native-scroll optimization.
|
||||
|
||||
### Stage 1: correct regional redraw
|
||||
|
||||
1. Move grid text and image spans using the existing grid operations.
|
||||
2. Record the pane scroll rectangle and newly exposed rows.
|
||||
3. Recompose that rectangle from the completed logical scene.
|
||||
|
||||
This may still retransmit more image data than desired, but it establishes a
|
||||
correct regional baseline without pane- or window-wide redraws.
|
||||
|
||||
### Stage 2: native scrolling without obstructions
|
||||
|
||||
Use the terminal's native scroll operation only when:
|
||||
|
||||
- the backend/terminal is known to scroll its image pixels consistently;
|
||||
- the source and destination rectangle belong to the same visible pane;
|
||||
- no floating pane, menu, overlay, pane border/status, or reserved scrollbar
|
||||
intersects it;
|
||||
- required vertical and horizontal margins are available and reliable; and
|
||||
- existing BCE and terminal-size checks permit the operation.
|
||||
|
||||
After native scrolling, compose only newly exposed rows and any protocol-sized
|
||||
boundary expansion.
|
||||
|
||||
### Stage 3: mixed native scrolling around obstructions
|
||||
|
||||
Derive safe rectangles from visible scene spans:
|
||||
|
||||
- a destination cell is native-scrollable only when its source cell and
|
||||
destination cell are both visible parts of the same pane;
|
||||
- coalesce equal horizontal ranges across adjacent rows into rectangles;
|
||||
- native-scroll those maximal rectangles;
|
||||
- recompose cells crossing an obstruction boundary and newly exposed cells.
|
||||
|
||||
With one floating pane, left and right strips can often scroll over the full
|
||||
height. Above and below it, cells whose source crosses behind the floating pane
|
||||
must be recomposed rather than recovered from terminal pixels.
|
||||
|
||||
As a simpler optional strategy, native-scroll the full tiled-pane rectangle
|
||||
and recompose the floating pane's old/current rectangles afterward. This is
|
||||
correct if all disturbed cells are marked, but may visibly flash without
|
||||
synchronized output. Prefer safe-rectangle scrolling once correctness is
|
||||
established.
|
||||
|
||||
If rectangle decomposition becomes too fragmented, fall back to regional
|
||||
composition. It is an optimization, never a separate correctness path.
|
||||
|
||||
## Redraw scheduling
|
||||
|
||||
Do not turn queued regional damage into `CLIENT_REDRAWWINDOW` merely because
|
||||
the tty output buffer is nonempty. Preserve the damage list and process it
|
||||
after output drains.
|
||||
|
||||
Use full redraw only for:
|
||||
|
||||
- initial attachment;
|
||||
- client or terminal geometry changes;
|
||||
- a terminal reset or invalid backend cache;
|
||||
- layout/viewport changes where rebuilding and clipping regions is not yet
|
||||
safe; or
|
||||
- a damage union large enough that full redraw is cheaper.
|
||||
|
||||
Focus changes, selection changes, ordinary scrolling, and moving one floating
|
||||
pane should remain regional.
|
||||
|
||||
## Debugging instrumentation
|
||||
|
||||
Add temporary debug logging before optimizing:
|
||||
|
||||
- damage reason and window rectangle;
|
||||
- client clipping and coordinate translation;
|
||||
- scene generation used;
|
||||
- native-scroll rectangles selected;
|
||||
- spans recomposed in each layer phase; and
|
||||
- image fragments emitted by each backend.
|
||||
|
||||
The log should make it possible to answer, for every SIXEL retransmission,
|
||||
which damage rectangle required it. Remove excessively noisy instrumentation
|
||||
before the final commit or keep it behind normal verbose logging.
|
||||
|
||||
## Tests
|
||||
|
||||
Build the tests before each optimization and keep each test focused on one
|
||||
invariant.
|
||||
|
||||
### Automated regressions
|
||||
|
||||
1. **No-image baseline:** ordinary output, scrolling, copy mode, floating pane
|
||||
focus, borders, status lines, and scrollbars behave unchanged.
|
||||
2. **Focus only:** changing focus with a SIXEL elsewhere emits no SIXEL DCS and
|
||||
updates only the affected border/status cells.
|
||||
3. **Selection outside image:** emits no image output.
|
||||
4. **Selection crossing image:** the image remains intact during and after the
|
||||
drag; only changed non-image cells are highlighted.
|
||||
5. **Unobstructed scroll:** logical image spans move with text; where native
|
||||
scrolling is supported, no complete SIXEL retransmission occurs.
|
||||
6. **Scroll with floating pane:** both pane-status lines, borders, scrollbar,
|
||||
floating-pane contents, and visible image fragments remain correct.
|
||||
7. **Move floating pane:** old and new rectangles are restored; unrelated image
|
||||
regions are not emitted.
|
||||
8. **Multiple floating panes:** overlapping z-order remains correct while the
|
||||
top pane moves.
|
||||
9. **Transparent placement:** lower images/text show through correctly, and
|
||||
moving/selection does not treat transparency as absent placement geometry.
|
||||
10. **Cross-backend clients:** the same input scene produces equivalent results
|
||||
on Kitty, SIXEL, and fallback clients attached simultaneously.
|
||||
11. **Pane status top/bottom:** unchanged status contents are physically
|
||||
restored if their cells were damaged.
|
||||
12. **Scrollbar reserved/overlay:** scroll thumb and track remain present and
|
||||
images do not overwrite them.
|
||||
|
||||
Where possible, capture raw terminal output and assert both presence and
|
||||
absence of DCS/APC sequences. `capture-pane` alone cannot prove physical output
|
||||
ordering because it observes logical grid contents rather than terminal image
|
||||
pixels.
|
||||
|
||||
### Manual matrix
|
||||
|
||||
Test at least:
|
||||
|
||||
- Microsoft Terminal with SIXEL;
|
||||
- Mintty with SIXEL;
|
||||
- WezTerm with SIXEL;
|
||||
- Kitty with Kitty output;
|
||||
- a Kitty client attached to a session created from a SIXEL client and the
|
||||
reverse; and
|
||||
- a terminal without graphical image support.
|
||||
|
||||
For each, test scrolling by application output and mouse wheel, selecting text,
|
||||
entering/leaving copy mode, changing focus, opening/closing/moving a floating
|
||||
pane, pane status at top and bottom, and scrollbars.
|
||||
|
||||
## Implementation steps and commit boundaries
|
||||
|
||||
This must not be implemented as one monolithic change. Each numbered step is a
|
||||
separate commit, review, and testing gate. Do not begin the next step until the
|
||||
current step passes its focused tests. If a step exposes a pre-existing bug,
|
||||
record it before expanding the patch.
|
||||
|
||||
### Step 0: archive the experiment and recover the baseline
|
||||
|
||||
**Goal:** Begin development from known source rather than repairing the mixed
|
||||
experimental diff.
|
||||
|
||||
**Work:**
|
||||
|
||||
- Preserve the current diff and this plan on a clearly named WIP branch.
|
||||
- Return `4902-image-support` to clean commit `ec9859b1`.
|
||||
- Build with and without image support.
|
||||
- Run the existing image regression and ordinary tmux regression suite.
|
||||
|
||||
**Gate:** The branch is clean and existing behaviour is reproduced. No product
|
||||
code is changed in this step.
|
||||
|
||||
### Step 1: add focused regressions and tracing
|
||||
|
||||
**Goal:** Establish observable failures before changing redraw behaviour.
|
||||
|
||||
**Primary files:** `regress/image-support.sh`, temporary debug logging in
|
||||
`screen-redraw.c` if necessary.
|
||||
|
||||
**Work:** Add independent tests for focus-only changes, selection crossing an
|
||||
image, unobstructed scrolling, scrolling beneath a floating pane, moving a
|
||||
floating pane, both pane-status lines, and scrollbars. Raw terminal output must
|
||||
be used where a test needs to prove that SIXEL or Kitty data was or was not
|
||||
emitted.
|
||||
|
||||
**Gate:** Each test either passes on the baseline or has a documented expected
|
||||
failure. Tests must not depend on timing races or visual screenshots. The
|
||||
existing edge-case checks introduced by `a4abd616` must pass unchanged on the
|
||||
recovery baseline and establish the semantic compatibility result expected by
|
||||
later gates.
|
||||
|
||||
**Not included:** No redraw implementation changes.
|
||||
|
||||
### Step 2: introduce regional damage scheduling
|
||||
|
||||
**Goal:** Represent and preserve small window-coordinate damage rectangles
|
||||
without changing the rendered result.
|
||||
|
||||
**Primary files:** `screen-redraw.c`, `server-client.c`, minimal declarations in
|
||||
`tmux.h`.
|
||||
|
||||
**Work:** Add per-client damage storage, clipping, merging, a complexity cap,
|
||||
and verbose logging. Existing callers may initially convert the damage to the
|
||||
same redraw they used before. Pending tty output must retain the region rather
|
||||
than promote it automatically to a full-window redraw.
|
||||
|
||||
**Gate:** No-image and image output remain visually and byte-for-byte equivalent
|
||||
where practical. Unit-style tests verify rectangle clipping and merging.
|
||||
|
||||
**Not included:** No image-layer traversal, selection changes, floating-pane
|
||||
optimization, or native scrolling.
|
||||
|
||||
### Step 3: add a generic image-region composition API
|
||||
|
||||
**Goal:** Give the scene compositor one protocol-independent way to render the
|
||||
image layers intersecting a cell rectangle.
|
||||
|
||||
**Primary files:** `image.c`, `image-sixel.c`, `image-kitty.c`, with only generic
|
||||
prototypes in `tmux.h`.
|
||||
|
||||
**Work:**
|
||||
|
||||
- In `image.c`, intersect damage with placement spans and preserve logical
|
||||
input/z/serial order.
|
||||
- In `image-sixel.c`, emit a crop from cached quantized data and handle SIXEL
|
||||
band alignment.
|
||||
- In `image-kitty.c`, update/delete/place only the intersecting cached
|
||||
placements.
|
||||
- Define clearly separate below-text and above-text passes.
|
||||
|
||||
**Gate:** Rendering a whole image as adjacent damaged rectangles produces the
|
||||
same final scene as rendering it in one operation. Test transparent and
|
||||
overlapping placements on both output backends. The `a4abd616` semantic
|
||||
compatibility checks must continue to pass unchanged.
|
||||
|
||||
**Not included:** No changes to individual tty text commands and no native
|
||||
scroll optimization.
|
||||
|
||||
### Step 4: clip scene composition to damage rectangles
|
||||
|
||||
**Goal:** Make `screen-redraw.c` repaint only scene spans intersecting queued
|
||||
damage.
|
||||
|
||||
**Primary files:** `screen-redraw.c`; generic calls into `image.c`.
|
||||
|
||||
**Work:** For each damaged line, intersect existing redraw spans, compose the
|
||||
selected pane's below-text images, text, and above-text images, then draw
|
||||
intersecting chrome last. Full redraw continues to use the same compositor with
|
||||
a full-client damage rectangle.
|
||||
|
||||
**Gate:** Full and tiled panes redraw correctly; pane borders, pane status,
|
||||
scrollbars, menus, and images have deterministic output order. No floating-pane
|
||||
movement or scroll optimization is added yet.
|
||||
|
||||
### Step 5: route text and selection damage through the compositor
|
||||
|
||||
**Goal:** Repair only cells where text or copy-mode selection interacts with
|
||||
image layers.
|
||||
|
||||
**Primary files:** `screen-write.c`, `window-copy.c`, `screen-redraw.c`, and
|
||||
logical helpers in `image.c`.
|
||||
|
||||
**Work:** Apply input semantics to logical spans first. Preserve direct tty text
|
||||
output when no image placement intersects the cells. Otherwise enqueue those
|
||||
cells for composition. Selection changes damage only cells whose selection
|
||||
state changed and follow the policy of not overwriting image-covered cells.
|
||||
|
||||
**Gate:** Dragging selection across an image never damages it, entering/leaving
|
||||
copy mode restores the same scene, and selection outside images emits no image
|
||||
data. The `a4abd616` semantic compatibility checks must continue to pass
|
||||
unchanged.
|
||||
|
||||
**Not included:** Do not add image callbacks to `tty_cmd_cell`,
|
||||
`tty_cmd_cells`, or `tty_cmd_clearcharacter`.
|
||||
|
||||
### Step 6: make floating-pane changes regional
|
||||
|
||||
**Goal:** Focus, movement, resize, open, and close operations damage only the
|
||||
cells whose scene ownership or chrome changed.
|
||||
|
||||
**Primary files:** floating-pane/window code that reports geometry changes,
|
||||
plus `screen-redraw.c`. Image implementation remains in the image files.
|
||||
|
||||
**Work:** Focus changes enqueue border/status cells only. Movement and resize
|
||||
enqueue the old and new pane rectangles and invalidate scene geometry. Regional
|
||||
composition restores underlying panes/images in the old rectangle and draws
|
||||
the floating pane in the new rectangle.
|
||||
|
||||
**Gate:** Changing focus emits no unrelated SIXEL/APC. Moving a floating pane
|
||||
does not damage images, either pane-status line, borders, or scrollbars. Test
|
||||
one and multiple overlapping floating panes.
|
||||
|
||||
### Step 7: implement correct regional scrolling without native optimization
|
||||
|
||||
**Goal:** Establish correct scrolling using logical state plus regional
|
||||
composition.
|
||||
|
||||
**Primary files:** grid/image state helpers in `image.c`, damage reporting in
|
||||
`screen-write.c`, composition in `screen-redraw.c`.
|
||||
|
||||
**Work:** Move text and image spans first, then damage the scroll rectangle and
|
||||
compose it. Do not use special image repair from `tty.c`.
|
||||
|
||||
**Gate:** Application scrolling, mouse-wheel copy-mode scrolling, partial
|
||||
scroll regions, scrollbars, and scrolling beneath floating panes are correct on
|
||||
Kitty, Microsoft Terminal, Mintty, and WezTerm. This step may redraw more than
|
||||
the final optimized version, but must not redraw outside the scroll rectangle.
|
||||
The `a4abd616` semantic compatibility checks must continue to pass unchanged.
|
||||
|
||||
### Step 8: enable native scrolling for unobstructed rectangles
|
||||
|
||||
**Goal:** Avoid retransmitting image pixels when the terminal can safely move
|
||||
them.
|
||||
|
||||
**Primary files:** scroll planning in `screen-redraw.c`, generic backend
|
||||
capability in `image.c`, backend facts in `image-sixel.c` and `image-kitty.c`,
|
||||
terminal primitive execution in `tty.c`.
|
||||
|
||||
**Work:** Select native scrolling only when source/destination cells belong to
|
||||
the same unobstructed pane and terminal margins/backend behaviour permit it.
|
||||
Compose newly exposed rows afterward. Retain Step 7 as the fallback.
|
||||
|
||||
**Gate:** Raw output proves the native scroll is used and a complete image is
|
||||
not retransmitted. Final output must match Step 7 on every tested backend.
|
||||
|
||||
### Step 9: decompose scrolling around floating panes
|
||||
|
||||
**Goal:** Combine native scrolling of safe rectangles with composition of
|
||||
obstructed and boundary rectangles.
|
||||
|
||||
**Primary files:** scroll planning and scene-span traversal in
|
||||
`screen-redraw.c`; no new protocol logic outside the image files.
|
||||
|
||||
**Work:** Derive maximal rectangles where both source and destination are
|
||||
visible cells of the same pane, coalesce adjacent equal ranges, natively scroll
|
||||
them, and compose everything else. Enforce a fragmentation threshold that
|
||||
falls back to Step 7.
|
||||
|
||||
**Gate:** Scrolling beneath one or several floating panes produces the same
|
||||
final scene as Step 7, reduces emitted image data, and never loses pane status,
|
||||
borders, scrollbars, or images.
|
||||
|
||||
### Step 10: cleanup, performance checks, and full regression
|
||||
|
||||
**Goal:** Remove superseded paths only after all replacements are proven.
|
||||
|
||||
**Work:** Remove obsolete redraw helpers and flags introduced or made unused by
|
||||
the new path. Reduce temporary logging. Check memory growth, rectangle-list
|
||||
bounds, output byte counts, and behaviour without image support. Run the entire
|
||||
regression suite and the manual terminal matrix.
|
||||
|
||||
**Gate:** All completion criteria below are satisfied. Synchronized output may
|
||||
improve appearance, but disabling it does not alter final contents. The
|
||||
`a4abd616` semantic compatibility checks must continue to pass unchanged.
|
||||
|
||||
Do not squash these implementation steps during development. Keeping their
|
||||
boundaries visible makes it possible to bisect correctness and performance
|
||||
regressions. They can be reorganized later only after review.
|
||||
|
||||
## Completion criteria
|
||||
|
||||
The work is complete when:
|
||||
|
||||
- Mintty, Microsoft Terminal, WezTerm, and Kitty show no persistent damage;
|
||||
- focus and selection do not retransmit unrelated images;
|
||||
- floating-pane movement redraws only old/new affected regions;
|
||||
- scrolling preserves images, pane borders, both tiled and floating pane
|
||||
status lines, and scrollbars;
|
||||
- input semantics produce the same composition on every output backend;
|
||||
- no-image performance and behaviour remain unchanged;
|
||||
- full regression tests pass; and
|
||||
- synchronized output improves appearance but disabling it never changes the
|
||||
final screen contents.
|
||||
@@ -5,8 +5,7 @@ CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c
|
||||
# Distribution tarball options.
|
||||
EXTRA_DIST = \
|
||||
CHANGES README COPYING example_tmux.conf \
|
||||
osdep-*.c mdoc2man.awk tmux.1 \
|
||||
tools/generate-image-diacritics.sh
|
||||
osdep-*.c mdoc2man.awk tmux.1
|
||||
dist_EXTRA_tmux_SOURCES = compat/*.[ch]
|
||||
|
||||
# Preprocessor flags.
|
||||
@@ -247,9 +246,9 @@ if HAVE_UTF8PROC
|
||||
nodist_tmux_SOURCES += compat/utf8proc.c
|
||||
endif
|
||||
|
||||
# Enable image support.
|
||||
if ENABLE_IMAGES
|
||||
dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c
|
||||
# Enable sixel support.
|
||||
if ENABLE_SIXEL
|
||||
dist_tmux_SOURCES += image.c image-sixel.c
|
||||
endif
|
||||
|
||||
# Add bits for fuzzing if enabled.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: arguments.c,v 1.66 2026/06/26 09:54:56 nicm Exp $ */
|
||||
/* $OpenBSD: arguments.c,v 1.67 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -762,8 +762,6 @@ args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
|
||||
cmdq_error(item, "%s", error);
|
||||
free(error);
|
||||
}
|
||||
else
|
||||
cmdlist->references++;
|
||||
args_make_commands_free(state);
|
||||
return (cmdlist);
|
||||
}
|
||||
@@ -827,8 +825,10 @@ args_make_commands(struct args_command_state *state, int argc, char **argv,
|
||||
int i;
|
||||
|
||||
if (state->cmdlist != NULL) {
|
||||
if (argc == 0)
|
||||
if (argc == 0) {
|
||||
state->cmdlist->references++;
|
||||
return (state->cmdlist);
|
||||
}
|
||||
return (cmd_list_copy(state->cmdlist, argc, argv));
|
||||
}
|
||||
|
||||
|
||||
8
client.c
8
client.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: client.c,v 1.167 2026/08/17 07:56:56 nicm Exp $ */
|
||||
/* $OpenBSD: client.c,v 1.168 2026/08/17 20:14:31 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -712,6 +712,9 @@ client_dispatch_wait(struct imsg *imsg)
|
||||
fprintf(stderr, "server version is too old for client\n");
|
||||
proc_exit(client_proc);
|
||||
break;
|
||||
default:
|
||||
log_debug("unknown message type %u", imsg->hdr.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,5 +801,8 @@ client_dispatch_attached(struct imsg *imsg)
|
||||
system(data);
|
||||
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
|
||||
break;
|
||||
default:
|
||||
log_debug("unknown message type %u", imsg->hdr.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-command-prompt.c,v 1.75 2026/06/25 11:39:11 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-command-prompt.c,v 1.76 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -242,9 +242,11 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
cmd_free_argv(argc, argv);
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ const struct cmd_entry cmd_copy_mode_entry = {
|
||||
.name = "copy-mode",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "dekHMqSs:t:u", 0, 0, NULL },
|
||||
.usage = "[-dekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
|
||||
.args = { "UcdekHMqSs:t:u", 0, 0, NULL },
|
||||
.usage = "[-UcdekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.source = { 's', CMD_FIND_PANE, 0 },
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-display-menu.c,v 1.52 2026/07/14 19:07:03 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-display-menu.c,v 1.53 2026/08/31 07:46:55 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -591,6 +591,8 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_client_clear_overlay(tc);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (tc->flags & CLIENT_CONTROL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (!modify && tc->overlay_draw != NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-if-shell.c,v 1.86 2025/08/01 09:05:51 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-if-shell.c,v 1.87 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -98,6 +98,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_ERROR);
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
@@ -164,9 +165,11 @@ cmd_if_shell_callback(struct job *job)
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-new-window.c,v 1.103 2026/07/08 08:07:42 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-new-window.c,v 1.104 2026/09/03 21:04:11 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -60,7 +60,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct session *s = target->s;
|
||||
struct winlink *wl = target->wl, *new_wl = NULL;
|
||||
int idx = target->idx, before, count = args_count(args);
|
||||
int idx = target->idx, before;
|
||||
int count = args_count(args);
|
||||
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
||||
const char *template, *name;
|
||||
struct cmd_find_state fs;
|
||||
@@ -74,8 +75,10 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
|
||||
/*
|
||||
* If -S and -n are given and -t is not and a single window with this
|
||||
* name already exists, select it.
|
||||
* If -S is given, select an existing window instead of creating a
|
||||
* new one: preferring -t if it already points at a window, or
|
||||
* otherwise -n if one exists with that name. If neither matches,
|
||||
* fall through and create a window as normal.
|
||||
*/
|
||||
name = args_get(args, 'n');
|
||||
if (name != NULL) {
|
||||
@@ -88,32 +91,39 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
wname = clean_name(expanded, 0);
|
||||
free(expanded);
|
||||
}
|
||||
if (args_has(args, 'S') && wname != NULL && target->idx == -1) {
|
||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
if (strcmp(wl->window->name, expanded) != 0)
|
||||
continue;
|
||||
if (new_wl == NULL) {
|
||||
new_wl = wl;
|
||||
continue;
|
||||
if (args_has(args, 'S')) {
|
||||
if (idx != -1)
|
||||
new_wl = winlink_find_by_index(&s->windows, idx);
|
||||
else if (wname != NULL) {
|
||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
if (strcmp(wl->window->name, expanded) != 0)
|
||||
continue;
|
||||
if (new_wl == NULL) {
|
||||
new_wl = wl;
|
||||
continue;
|
||||
}
|
||||
cmdq_error(item, "multiple windows named %s",
|
||||
wname);
|
||||
free(wname);
|
||||
free(expanded);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
cmdq_error(item, "multiple windows named %s", wname);
|
||||
free(wname);
|
||||
free(expanded);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
free(expanded);
|
||||
if (new_wl != NULL) {
|
||||
free(wname);
|
||||
if (args_has(args, 'd'))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (session_set_current(s, new_wl) == 0)
|
||||
server_redraw_session(s);
|
||||
if (c != NULL && c->session != NULL)
|
||||
s->curw->window->latest = c;
|
||||
recalculate_sizes();
|
||||
}
|
||||
|
||||
/* Found an existing window to select instead of creating a new one. */
|
||||
if (new_wl != NULL) {
|
||||
free(wname);
|
||||
if (args_has(args, 'd'))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (session_set_current(s, new_wl) == 0)
|
||||
server_redraw_session(s);
|
||||
if (c != NULL && c->session != NULL)
|
||||
s->curw->window->latest = c;
|
||||
recalculate_sizes();
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
before = args_has(args, 'b');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-parse.y,v 1.58 2026/04/27 12:31:11 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-parse.y,v 1.59 2026/08/31 07:51:56 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -802,6 +802,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
|
||||
if (last == NULL) {
|
||||
pr->status = CMD_PARSE_SUCCESS;
|
||||
pr->cmdlist = cmd_list_new();
|
||||
cmd_parse_free_commands(cmds);
|
||||
return (1);
|
||||
}
|
||||
|
||||
@@ -814,6 +815,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
|
||||
pi->flags |= CMD_PARSE_NOALIAS;
|
||||
cmd_parse_build_commands(cmds, pi, pr);
|
||||
pi->flags &= ~CMD_PARSE_NOALIAS;
|
||||
cmd_parse_free_commands(cmds);
|
||||
return (1);
|
||||
}
|
||||
|
||||
|
||||
15
cmd-queue.c
15
cmd-queue.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-queue.c,v 1.122 2026/08/03 13:38:42 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>
|
||||
@@ -850,7 +850,18 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...)
|
||||
|
||||
if (c == NULL) {
|
||||
cmd_get_source(cmd, &file, &line);
|
||||
cfg_add_cause("%s:%u: %s", file, line, msg);
|
||||
if (!cfg_finished) {
|
||||
if (file != NULL)
|
||||
cfg_add_cause("%s:%u: %s", file, line, msg);
|
||||
else
|
||||
cfg_add_cause("%s", msg);
|
||||
} else {
|
||||
if (file != NULL) {
|
||||
server_add_message("message: %s:%u: %s", file,
|
||||
line, msg);
|
||||
} else
|
||||
server_add_message("message: %s", msg);
|
||||
}
|
||||
} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
|
||||
server_add_message("%s message: %s", c->name, msg);
|
||||
if (~c->flags & CLIENT_UTF8) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.67 2026/07/10 13:38:45 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.68 2026/08/31 07:44:39 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -92,6 +92,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
server_unzoom_window(w);
|
||||
lc = wp->layout_cell; /* may have been replaced by unzoom */
|
||||
|
||||
if (args_has(args, 'x')) {
|
||||
x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.93 2026/07/17 12:42:51 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.94 2026/08/25 06:04:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -233,9 +233,11 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
|
||||
if (cdata->item != NULL)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-select-pane.c,v 1.77 2026/07/17 12:42:51 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-select-pane.c,v 1.78 2026/08/20 09:19:24 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -160,6 +160,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
char *title;
|
||||
const char *style;
|
||||
struct options_entry *o;
|
||||
int visible, Zflag = args_has(args, 'Z');
|
||||
|
||||
if (entry == &cmd_last_pane_entry || args_has(args, 'l')) {
|
||||
/*
|
||||
@@ -185,14 +186,18 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_redraw_window_borders(lastwp->window);
|
||||
server_status_window(lastwp->window);
|
||||
} else {
|
||||
if (window_push_zoom(w, 0, args_has(args, 'Z')))
|
||||
if (w->modal != NULL && lastwp != w->modal)
|
||||
visible = 1;
|
||||
else
|
||||
visible = window_pane_is_visible(lastwp);
|
||||
if (!visible && window_push_zoom(w, 0, Zflag))
|
||||
server_redraw_window(w);
|
||||
window_redraw_active_switch(w, lastwp);
|
||||
if (window_set_active_pane(w, lastwp, 1)) {
|
||||
cmd_find_from_winlink(current, wl, 0);
|
||||
cmd_select_pane_redraw(w);
|
||||
}
|
||||
if (window_pop_zoom(w))
|
||||
if (!visible && window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
@@ -268,14 +273,18 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
if (wp == w->active)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (window_push_zoom(w, 0, args_has(args, 'Z')))
|
||||
if (w->modal != NULL && wp != w->modal)
|
||||
visible = 1;
|
||||
else
|
||||
visible = window_pane_is_visible(wp);
|
||||
if (!visible && window_push_zoom(w, 0, Zflag))
|
||||
server_redraw_window(w);
|
||||
window_redraw_active_switch(w, wp);
|
||||
if (window_set_active_pane(w, wp, 1))
|
||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||
cmdq_insert_hook(s, item, current, "after-select-pane");
|
||||
cmd_select_pane_redraw(w);
|
||||
if (window_pop_zoom(w))
|
||||
if (!visible && window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-set-buffer.c,v 1.37 2026/02/15 17:43:26 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-set-buffer.c,v 1.38 2026/08/24 07:05:23 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -100,6 +100,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cmdq_error(item, "%s", cause);
|
||||
goto fail;
|
||||
}
|
||||
free(bufname);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
@@ -107,8 +108,10 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cmdq_error(item, "no data specified");
|
||||
goto fail;
|
||||
}
|
||||
if ((newsize = strlen(args_string(args, 0))) == 0)
|
||||
if ((newsize = strlen(args_string(args, 0))) == 0) {
|
||||
free(bufname);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
if (args_has(args, 'a') && pb != NULL) {
|
||||
olddata = paste_buffer_data(pb, &bufsize);
|
||||
@@ -127,6 +130,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'w') && tc != NULL)
|
||||
tty_set_selection(&tc->tty, "", bufdata, bufsize);
|
||||
|
||||
free(bufname);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
fail:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-source-file.c,v 1.62 2025/11/18 08:42:09 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-source-file.c,v 1.63 2026/08/18 08:05:05 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -50,6 +50,7 @@ const struct cmd_entry cmd_source_file_entry = {
|
||||
|
||||
struct cmd_source_file_data {
|
||||
struct cmdq_item *item;
|
||||
struct client *client;
|
||||
int flags;
|
||||
|
||||
struct cmdq_item *after;
|
||||
@@ -60,10 +61,24 @@ struct cmd_source_file_data {
|
||||
u_int nfiles;
|
||||
};
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
|
||||
static void
|
||||
cmd_source_file_free_data(struct cmd_source_file_data *cdata)
|
||||
{
|
||||
struct client *c = cmdq_get_client(item);
|
||||
u_int i;
|
||||
|
||||
for (i = 0; i < cdata->nfiles; i++)
|
||||
free(cdata->files[i]);
|
||||
free(cdata->files);
|
||||
if (cdata->client != NULL)
|
||||
server_client_unref(cdata->client);
|
||||
free(cdata);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_source_file_complete_cb(struct cmdq_item *item, void *data)
|
||||
{
|
||||
struct cmd_source_file_data *cdata = data;
|
||||
struct client *c = cdata->client;
|
||||
|
||||
if (c == NULL) {
|
||||
cmd_source_file_depth--;
|
||||
@@ -74,36 +89,36 @@ cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
|
||||
}
|
||||
|
||||
cfg_print_causes(item);
|
||||
cmd_source_file_free_data(cdata);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata)
|
||||
cmd_source_file_complete(struct cmd_source_file_data *cdata)
|
||||
{
|
||||
struct client *c = cdata->client;
|
||||
struct cmdq_item *new_item;
|
||||
u_int i;
|
||||
|
||||
if (cfg_finished) {
|
||||
if (cdata->retval == CMD_RETURN_ERROR &&
|
||||
c != NULL &&
|
||||
c->session == NULL)
|
||||
c->retval = 1;
|
||||
new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL);
|
||||
cmdq_insert_after(cdata->after, new_item);
|
||||
if (!cfg_finished) {
|
||||
cmd_source_file_free_data(cdata);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < cdata->nfiles; i++)
|
||||
free(cdata->files[i]);
|
||||
free(cdata->files);
|
||||
free(cdata);
|
||||
if (cdata->retval == CMD_RETURN_ERROR &&
|
||||
c != NULL &&
|
||||
c->session == NULL)
|
||||
c->retval = 1;
|
||||
new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
|
||||
cmdq_insert_after(cdata->after, new_item);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_source_file_done(struct client *c, const char *path, int error,
|
||||
int closed, struct evbuffer *buffer, void *data)
|
||||
cmd_source_file_done(__unused struct client *oc, const char *path,
|
||||
int error, int closed, struct evbuffer *buffer, void *data)
|
||||
{
|
||||
struct cmd_source_file_data *cdata = data;
|
||||
struct cmdq_item *item = cdata->item;
|
||||
struct client *c = cdata->client;
|
||||
void *bdata = EVBUFFER_DATA(buffer);
|
||||
size_t bsize = EVBUFFER_LENGTH(buffer);
|
||||
u_int n;
|
||||
@@ -127,7 +142,7 @@ cmd_source_file_done(struct client *c, const char *path, int error,
|
||||
if (n < cdata->nfiles)
|
||||
file_read(c, cdata->files[n], cmd_source_file_done, cdata);
|
||||
else {
|
||||
cmd_source_file_complete(c, cdata);
|
||||
cmd_source_file_complete(cdata);
|
||||
cmdq_continue(item);
|
||||
}
|
||||
}
|
||||
@@ -187,6 +202,9 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
cdata = xcalloc(1, sizeof *cdata);
|
||||
cdata->item = item;
|
||||
cdata->client = c;
|
||||
if (c != NULL)
|
||||
c->references++;
|
||||
|
||||
if (args_has(args, 'q'))
|
||||
cdata->flags |= CMD_PARSE_QUIET;
|
||||
@@ -249,7 +267,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
|
||||
file_read(c, cdata->files[0], cmd_source_file_done, cdata);
|
||||
retval = CMD_RETURN_WAIT;
|
||||
} else
|
||||
cmd_source_file_complete(c, cdata);
|
||||
cmd_source_file_complete(cdata);
|
||||
|
||||
free(cwd);
|
||||
return (retval);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-split-window.c,v 1.148 2026/08/07 08:10:53 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>
|
||||
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
|
||||
.name = "new-pane",
|
||||
.alias = "newp",
|
||||
|
||||
.args = { "bB:Cc:de:EfF:hIkl:LMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-bCdefhIklMOPvWZ] [-B border-lines] "
|
||||
.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] "
|
||||
"[-c start-directory] [-e environment] "
|
||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||
"[-s style] [-S active-border-style] "
|
||||
@@ -89,6 +89,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state fs;
|
||||
struct key_event *event = cmdq_get_event(item);
|
||||
int input, empty, is_floating, flags = 0;
|
||||
int restore_zoom = 0;
|
||||
const char *template, *style, *value;
|
||||
char *cause = NULL, *cp, *title;
|
||||
const struct options_table_entry *oe;
|
||||
@@ -96,10 +97,16 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
enum pane_lines lines;
|
||||
u_int count = args_count(args);
|
||||
|
||||
if (window_active_pane_is_over_zoom(w))
|
||||
restore_zoom = 1;
|
||||
|
||||
if (cmd_get_entry(self) == &cmd_new_pane_entry)
|
||||
is_floating = !args_has(args, 'L');
|
||||
else {
|
||||
window_unzoom(w, 1);
|
||||
if (!window_pane_is_visible(wp))
|
||||
restore_zoom = 0;
|
||||
if (!restore_zoom)
|
||||
window_unzoom(w, 1);
|
||||
is_floating = window_pane_is_floating(wp);
|
||||
flags |= SPAWN_SPLIT;
|
||||
}
|
||||
@@ -133,7 +140,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'Z'))
|
||||
flags |= SPAWN_ZOOM;
|
||||
if (args_has(args, 'O'))
|
||||
flags |= SPAWN_MODAL;
|
||||
flags |= SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
|
||||
if (is_floating && args_has(args, 'A'))
|
||||
flags |= SPAWN_FLOATOVERZOOM;
|
||||
if ((w->flags & WINDOW_ZOOMED) && (flags & SPAWN_FLOATOVERZOOM))
|
||||
restore_zoom = 1;
|
||||
|
||||
input = args_has(args, 'I');
|
||||
if (input || (count == 1 && *args_string(args, 0) == '\0'))
|
||||
@@ -169,6 +180,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
if (restore_zoom)
|
||||
window_pop_zoom(w);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
@@ -202,6 +215,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
*/
|
||||
goto fail;
|
||||
}
|
||||
if (args_has(args, 'K') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||
if (args_has(args, 'C') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CLOSEONCLICK;
|
||||
|
||||
@@ -271,7 +286,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (~flags & SPAWN_DETACHED)
|
||||
cmd_find_from_winlink_pane(current, wl, new_wp, 0);
|
||||
|
||||
if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) {
|
||||
if (restore_zoom) {
|
||||
window_pop_zoom(wp->window);
|
||||
server_redraw_window(wp->window);
|
||||
} else if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) {
|
||||
window_pop_zoom(wp->window);
|
||||
server_redraw_window(wp->window);
|
||||
}
|
||||
@@ -322,8 +340,9 @@ fail:
|
||||
if (!is_floating)
|
||||
layout_close_pane(new_wp);
|
||||
window_remove_pane(wp->window, new_wp);
|
||||
} else if (args_has(args, 'O'))
|
||||
window_pop_modal_zoom(wp->window);
|
||||
}
|
||||
if (restore_zoom || (~flags & SPAWN_FLOATING))
|
||||
window_pop_zoom(wp->window);
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-switch-client.c,v 1.74 2026/05/22 15:22:43 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-switch-client.c,v 1.75 2026/08/20 09:19:24 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -53,7 +53,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state target;
|
||||
const char *tflag = args_get(args, 't');
|
||||
enum cmd_find_type type;
|
||||
int flags;
|
||||
int flags, visible, Zflag = args_has(args, 'Z');
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct session *s;
|
||||
@@ -139,11 +139,15 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (wl != NULL && wp != NULL && wp != wl->window->active) {
|
||||
w = wl->window;
|
||||
if (window_push_zoom(w, 0, args_has(args, 'Z')))
|
||||
if (w->modal != NULL && wp != w->modal)
|
||||
visible = 1;
|
||||
else
|
||||
visible = window_pane_is_visible(wp);
|
||||
if (!visible && window_push_zoom(w, 0, Zflag))
|
||||
server_redraw_window(w);
|
||||
window_redraw_active_switch(w, wp);
|
||||
window_set_active_pane(w, wp, 1);
|
||||
if (window_pop_zoom(w))
|
||||
if (!visible && window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
}
|
||||
if (wl != NULL) {
|
||||
|
||||
33
configure.ac
33
configure.ac
@@ -530,34 +530,15 @@ if test "x$enable_cgroups" = xyes; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Enable image support.
|
||||
AC_ARG_ENABLE(
|
||||
images,
|
||||
AS_HELP_STRING(--enable-images, enable Kitty/SIXEL/ASCII images)
|
||||
)
|
||||
|
||||
# Keep the old option as an alias for one compatibility cycle.
|
||||
# Enable sixel support.
|
||||
AC_ARG_ENABLE(
|
||||
sixel,
|
||||
AS_HELP_STRING(--enable-sixel, deprecated alias for --enable-images)
|
||||
AS_HELP_STRING(--enable-sixel, enable sixel images)
|
||||
)
|
||||
if test "x$enable_sixel" = xyes; then
|
||||
enable_images=yes
|
||||
AC_DEFINE(ENABLE_SIXEL)
|
||||
fi
|
||||
if test "x$enable_images" = xyes; then
|
||||
PKG_CHECK_MODULES(
|
||||
IMAGE,
|
||||
[libpng >= 1.6 zlib],
|
||||
[
|
||||
AM_CPPFLAGS="$IMAGE_CFLAGS $AM_CPPFLAGS"
|
||||
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$IMAGE_LIBS $LIBS"
|
||||
],
|
||||
AC_MSG_ERROR("image support requires libpng 1.6 and zlib")
|
||||
)
|
||||
AC_DEFINE(ENABLE_IMAGES)
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_IMAGES, [test "x$enable_images" = xyes])
|
||||
AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes])
|
||||
|
||||
# Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
|
||||
AC_MSG_CHECKING(for b64_ntop)
|
||||
@@ -1156,10 +1137,10 @@ if test "x$enable_asan" = xyes; then
|
||||
else
|
||||
AC_MSG_NOTICE([ASAN: off])
|
||||
fi
|
||||
if test "x$enable_images" = xyes; then
|
||||
AC_MSG_NOTICE([images: on])
|
||||
if test "x$enable_sixel" = xyes; then
|
||||
AC_MSG_NOTICE([SIXEL: on])
|
||||
else
|
||||
AC_MSG_NOTICE([images: off])
|
||||
AC_MSG_NOTICE([SIXEL: off])
|
||||
fi
|
||||
if test "x$enable_debug" = xyes; then
|
||||
AC_MSG_NOTICE([debug: on])
|
||||
|
||||
35
control.c
35
control.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: control.c,v 1.65 2026/08/04 11:18:22 nicm Exp $ */
|
||||
/* $OpenBSD: control.c,v 1.67 2026/09/03 21:35:38 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -388,6 +388,25 @@ control_pause_pane(struct client *c, struct window_pane *wp)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset a pane after its buffer has been replaced: drop any output still
|
||||
* queued from the old buffer and start again from the pane's own offset.
|
||||
*/
|
||||
void
|
||||
control_reset_pane(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
struct control_pane *cp;
|
||||
|
||||
if (c->control_state == NULL)
|
||||
return;
|
||||
cp = control_get_pane(c, wp);
|
||||
if (cp == NULL)
|
||||
return;
|
||||
control_discard_pane(c, cp);
|
||||
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
||||
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
||||
}
|
||||
|
||||
/* Write an already-formatted line, queueing it behind %output if needed. */
|
||||
static void
|
||||
control_write_line(struct client *c, char *line)
|
||||
@@ -436,8 +455,12 @@ control_flush_deferred(struct client *c)
|
||||
void
|
||||
control_write(struct client *c, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char *line;
|
||||
struct control_state *cs = c->control_state;
|
||||
char *line;
|
||||
va_list ap;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
xvasprintf(&line, fmt, ap);
|
||||
@@ -460,6 +483,9 @@ control_write_guard(struct client *c, const char *guard, long t, u_int number,
|
||||
struct control_state *cs = c->control_state;
|
||||
char *line;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
if (strcmp(guard, "begin") == 0)
|
||||
cs->guard_depth++;
|
||||
|
||||
@@ -484,6 +510,9 @@ control_notify_write(struct client *c, const char *fmt, ...)
|
||||
va_list ap;
|
||||
char *line;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
xvasprintf(&line, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
74
format.c
74
format.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: format.c,v 1.412 2026/08/05 07:31:08 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>
|
||||
@@ -1887,6 +1887,37 @@ format_cb_cursor_blinking(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_added. */
|
||||
static void *
|
||||
format_cb_history_added(struct format_tree *ft)
|
||||
{
|
||||
if (ft->wp != NULL)
|
||||
return (format_printf("%u", ft->wp->base.grid->scroll_added));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_collected. */
|
||||
static void *
|
||||
format_cb_history_collected(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
|
||||
if (wp != NULL)
|
||||
return (format_printf("%u", wp->base.grid->scroll_collected));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_generation. */
|
||||
static void *
|
||||
format_cb_history_generation(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
|
||||
if (wp != NULL)
|
||||
return (format_printf("%u", wp->base.grid->scroll_generation));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_limit. */
|
||||
static void *
|
||||
format_cb_history_limit(struct format_tree *ft)
|
||||
@@ -2276,6 +2307,19 @@ format_cb_pane_last_output_time(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_output_generation. */
|
||||
static void *
|
||||
format_cb_pane_output_generation(struct format_tree *ft)
|
||||
{
|
||||
unsigned long long value;
|
||||
|
||||
if (ft->wp != NULL) {
|
||||
value = ft->wp->output_generation;
|
||||
return (format_printf("%llu", value));
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_last_prompt_time. */
|
||||
static void *
|
||||
format_cb_pane_last_prompt_time(struct format_tree *ft)
|
||||
@@ -3018,17 +3062,6 @@ format_cb_sixel_support(__unused struct format_tree *ft)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for image_support. */
|
||||
static void *
|
||||
format_cb_image_support(__unused struct format_tree *ft)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
return (xstrdup("1"));
|
||||
#else
|
||||
return (xstrdup("0"));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for active_window_index. */
|
||||
static void *
|
||||
format_cb_active_window_index(struct format_tree *ft)
|
||||
@@ -3674,12 +3707,21 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "cursor_y", FORMAT_TABLE_STRING,
|
||||
format_cb_cursor_y
|
||||
},
|
||||
{ "history_added", FORMAT_TABLE_STRING,
|
||||
format_cb_history_added
|
||||
},
|
||||
{ "history_all_bytes", FORMAT_TABLE_STRING,
|
||||
format_cb_history_all_bytes
|
||||
},
|
||||
{ "history_bytes", FORMAT_TABLE_STRING,
|
||||
format_cb_history_bytes
|
||||
},
|
||||
{ "history_collected", FORMAT_TABLE_STRING,
|
||||
format_cb_history_collected
|
||||
},
|
||||
{ "history_generation", FORMAT_TABLE_STRING,
|
||||
format_cb_history_generation
|
||||
},
|
||||
{ "history_limit", FORMAT_TABLE_STRING,
|
||||
format_cb_history_limit
|
||||
},
|
||||
@@ -3692,9 +3734,6 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "host_short", FORMAT_TABLE_STRING,
|
||||
format_cb_host_short
|
||||
},
|
||||
{ "image_support", FORMAT_TABLE_STRING,
|
||||
format_cb_image_support
|
||||
},
|
||||
{ "insert_flag", FORMAT_TABLE_STRING,
|
||||
format_cb_insert_flag
|
||||
},
|
||||
@@ -3863,6 +3902,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "pane_mode", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_mode
|
||||
},
|
||||
{ "pane_output_generation", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_output_generation
|
||||
},
|
||||
{ "pane_path", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_path
|
||||
},
|
||||
@@ -4401,7 +4443,7 @@ format_quote_shell(const char *s)
|
||||
|
||||
at = out = xmalloc(strlen(s) * 2 + 1);
|
||||
for (cp = s; *cp != '\0'; cp++) {
|
||||
if (strchr("|&;<>()$`\\\"'*?[# =%\n\t", *cp) != NULL)
|
||||
if (strchr("|&;<>(){}$`\\\"'*?[# =%\n\t", *cp) != NULL)
|
||||
*at++ = '\\';
|
||||
*at++ = *cp;
|
||||
}
|
||||
|
||||
122
grid.c
122
grid.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: grid.c,v 1.156 2026/08/03 12:58:53 nicm Exp $ */
|
||||
/* $OpenBSD: grid.c,v 1.158 2026/09/01 12:49:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -86,9 +86,6 @@ grid_check_is_clear(struct grid *gd)
|
||||
assert(gl->extdsize == 0);
|
||||
assert(gl->flags == 0);
|
||||
assert(gl->time == 0);
|
||||
#ifdef ENABLE_IMAGES
|
||||
assert(gl->images == NULL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -356,9 +353,6 @@ grid_free_line(struct grid *gd, u_int py)
|
||||
assert(gl->cellsize == 0 || gl->celldata != NULL);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_free_line(gd, gl);
|
||||
#endif
|
||||
free(gl->celldata);
|
||||
free(gl->extddata);
|
||||
memset(gl, 0, sizeof *gl);
|
||||
@@ -403,9 +397,6 @@ void
|
||||
grid_destroy(struct grid *gd)
|
||||
{
|
||||
grid_free_lines(gd, 0, gd->hsize + gd->sy);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_free(gd);
|
||||
#endif
|
||||
free(gd->linedata);
|
||||
free(gd);
|
||||
}
|
||||
@@ -738,9 +729,6 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
|
||||
|
||||
if (nx == 0 || ny == 0)
|
||||
return;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_damage(gd, px, py, nx, ny);
|
||||
#endif
|
||||
|
||||
if (px == 0 && nx == gd->sx) {
|
||||
grid_clear_lines(gd, py, ny, bg);
|
||||
@@ -776,10 +764,6 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
|
||||
void
|
||||
grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
|
||||
{
|
||||
struct grid_line *gl;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image_line *images;
|
||||
#endif
|
||||
u_int yy;
|
||||
|
||||
if (ny == 0)
|
||||
@@ -791,18 +775,8 @@ grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
|
||||
return;
|
||||
|
||||
for (yy = py; yy < py + ny; yy++) {
|
||||
gl = &gd->linedata[yy];
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_damage(gd, 0, yy, gd->sx, 1);
|
||||
images = gl->images;
|
||||
#endif
|
||||
free(gl->celldata);
|
||||
free(gl->extddata);
|
||||
memset(gl, 0, sizeof *gl);
|
||||
grid_free_line(gd, yy);
|
||||
grid_empty_line(gd, yy, bg);
|
||||
#ifdef ENABLE_IMAGES
|
||||
gl->images = images;
|
||||
#endif
|
||||
}
|
||||
if (py != 0)
|
||||
gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
|
||||
@@ -867,9 +841,6 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
|
||||
|
||||
grid_expand_line(gd, py, px + nx, 8);
|
||||
grid_expand_line(gd, py, dx + nx, 8);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_move_cells(gd, dx, px, py, nx);
|
||||
#endif
|
||||
memmove(&gl->celldata[dx], &gl->celldata[px],
|
||||
nx * sizeof *gl->celldata);
|
||||
if (dx + nx > gl->cellused)
|
||||
@@ -1242,6 +1213,7 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx,
|
||||
grid_get_cell(gd, xx, py, &gc);
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
continue;
|
||||
|
||||
if (lastgc != NULL && (flags & GRID_STRING_WITH_SEQUENCES)) {
|
||||
grid_string_cells_code(*lastgc, &gc, code, sizeof code,
|
||||
flags, s, &has_link);
|
||||
@@ -1308,18 +1280,11 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
{
|
||||
struct grid_line *dstl, *srcl;
|
||||
u_int yy;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int original_dy, original_sy;
|
||||
#endif
|
||||
|
||||
if (dy + ny > dst->hsize + dst->sy)
|
||||
ny = dst->hsize + dst->sy - dy;
|
||||
if (sy + ny > src->hsize + src->sy)
|
||||
ny = src->hsize + src->sy - sy;
|
||||
#ifdef ENABLE_IMAGES
|
||||
original_dy = dy;
|
||||
original_sy = sy;
|
||||
#endif
|
||||
grid_free_lines(dst, dy, ny);
|
||||
|
||||
for (yy = 0; yy < ny; yy++) {
|
||||
@@ -1327,9 +1292,6 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
dstl = &dst->linedata[dy];
|
||||
|
||||
memcpy(dstl, srcl, sizeof *dstl);
|
||||
#ifdef ENABLE_IMAGES
|
||||
dstl->images = NULL;
|
||||
#endif
|
||||
if (srcl->cellsize != 0) {
|
||||
dstl->celldata = xreallocarray(NULL,
|
||||
srcl->cellsize, sizeof *dstl->celldata);
|
||||
@@ -1349,9 +1311,6 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
sy++;
|
||||
dy++;
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_duplicate_lines(dst, original_dy, src, original_sy, ny);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Mark line as dead. */
|
||||
@@ -1362,30 +1321,6 @@ grid_reflow_dead(struct grid_line *gl)
|
||||
gl->flags = GRID_LINE_DEAD;
|
||||
}
|
||||
|
||||
/* Image rows are cell-aligned and must not be reflowed like text. */
|
||||
static int
|
||||
grid_reflow_has_image(struct grid_line *gl)
|
||||
{
|
||||
struct grid_cell gc;
|
||||
u_int i;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_grid_line_has_images(gl))
|
||||
return (1);
|
||||
#endif
|
||||
if (~gl->flags & GRID_LINE_EXTENDED)
|
||||
return (0);
|
||||
for (i = 0; i < gl->cellused; i++) {
|
||||
grid_get_cell1(gl, i, &gc);
|
||||
/* Kitty Unicode placeholder base character (U+10EEEE). */
|
||||
if (gc.data.size >= 4 && gc.data.data[0] == 0xf4 &&
|
||||
gc.data.data[1] == 0x8e && gc.data.data[2] == 0xbb &&
|
||||
gc.data.data[3] == 0xae)
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Add lines, return the first new one. */
|
||||
static struct grid_line *
|
||||
grid_reflow_add(struct grid *gd, u_int n)
|
||||
@@ -1448,10 +1383,6 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
break;
|
||||
line = yy + 1 + lines;
|
||||
|
||||
/* Do not join wrapped text to a cell-aligned image row. */
|
||||
if (grid_reflow_has_image(&gd->linedata[line]))
|
||||
break;
|
||||
|
||||
/* If the next line is empty, skip it. */
|
||||
if (~gd->linedata[line].flags & GRID_LINE_WRAPPED)
|
||||
wrapped = 0;
|
||||
@@ -1512,7 +1443,8 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
|
||||
/* Remove the lines that were completely consumed. */
|
||||
for (i = yy + 1; i < yy + 1 + lines; i++) {
|
||||
grid_free_line(gd, i);
|
||||
free(gd->linedata[i].celldata);
|
||||
free(gd->linedata[i].extddata);
|
||||
grid_reflow_dead(&gd->linedata[i]);
|
||||
}
|
||||
|
||||
@@ -1614,12 +1546,6 @@ grid_reflow(struct grid *gd, u_int sx)
|
||||
if (gl->flags & GRID_LINE_DEAD)
|
||||
continue;
|
||||
|
||||
/* Keep image layers at their original cell coordinates. */
|
||||
if (grid_reflow_has_image(gl)) {
|
||||
grid_reflow_move(target, gl);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Work out the width of this line. at is the point at which
|
||||
* the available width is hit, and width is the full line
|
||||
@@ -1709,7 +1635,7 @@ grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy)
|
||||
void
|
||||
grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
|
||||
{
|
||||
u_int yy, ay = 0;
|
||||
u_int yy, ay = 0, ey = gd->hsize + gd->sy - 1;
|
||||
|
||||
for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) {
|
||||
if (ay == wy)
|
||||
@@ -1723,7 +1649,7 @@ grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
|
||||
* until we find the end or the line now containing wx.
|
||||
*/
|
||||
if (wx == UINT_MAX) {
|
||||
while (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
|
||||
while (yy < ey && gd->linedata[yy].flags & GRID_LINE_WRAPPED)
|
||||
yy++;
|
||||
wx = gd->linedata[yy].cellused;
|
||||
} else {
|
||||
@@ -1785,21 +1711,29 @@ grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
|
||||
{
|
||||
struct grid_cell gc, tmp_gc;
|
||||
u_int pxx;
|
||||
int has_tab, has_space;
|
||||
|
||||
has_tab = (strchr(set, '\t') != NULL);
|
||||
has_space = (strchr(set, ' ') != NULL);
|
||||
|
||||
grid_get_cell(gd, px, py, &gc);
|
||||
if (strchr(set, '\t')) {
|
||||
if (gc.flags & GRID_FLAG_PADDING) {
|
||||
pxx = px;
|
||||
do
|
||||
grid_get_cell(gd, --pxx, py, &tmp_gc);
|
||||
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
|
||||
if (tmp_gc.flags & GRID_FLAG_TAB)
|
||||
return (tmp_gc.data.width - (px - pxx));
|
||||
} else if (gc.flags & GRID_FLAG_TAB)
|
||||
return (gc.data.width);
|
||||
}
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
if (gc.flags & GRID_FLAG_PADDING) {
|
||||
if (!has_tab && !has_space)
|
||||
return (0);
|
||||
pxx = px;
|
||||
do
|
||||
grid_get_cell(gd, --pxx, py, &tmp_gc);
|
||||
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
|
||||
if (((has_tab || has_space) &&
|
||||
(tmp_gc.flags & GRID_FLAG_TAB)) ||
|
||||
(has_space && utf8_has_whitespace(&tmp_gc.data)))
|
||||
return (tmp_gc.data.width - (px - pxx));
|
||||
return (0);
|
||||
}
|
||||
if ((has_tab || has_space) && (gc.flags & GRID_FLAG_TAB))
|
||||
return (gc.data.width);
|
||||
if (has_space && utf8_has_whitespace(&gc.data))
|
||||
return (gc.data.width == 0 ? 1 : gc.data.width);
|
||||
return (utf8_cstrhas(set, &gc.data));
|
||||
}
|
||||
|
||||
@@ -1826,6 +1760,8 @@ grid_line_flags_string(int flags)
|
||||
strlcat(s, "START_OUTPUT,", sizeof s);
|
||||
if (flags & GRID_LINE_END_OUTPUT)
|
||||
strlcat(s, "END_OUTPUT,", sizeof s);
|
||||
if (flags & GRID_LINE_END_OUTPUT_STATUS)
|
||||
strlcat(s, "END_OUTPUT_STATUS,", sizeof s);
|
||||
if (flags & GRID_LINE_HYPERLINK)
|
||||
strlcat(s, "HYPERLINK,", sizeof s);
|
||||
if (*s == '\0')
|
||||
|
||||
1308
image-kitty.c
1308
image-kitty.c
File diff suppressed because it is too large
Load Diff
1169
image-sixel.c
1169
image-sixel.c
File diff suppressed because it is too large
Load Diff
184
input.c
184
input.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: input.c,v 1.269 2026/07/20 11:16:33 nicm Exp $ */
|
||||
/* $OpenBSD: input.c,v 1.271 2026/08/31 19:34:09 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -145,9 +145,6 @@ struct input_ctx {
|
||||
*/
|
||||
struct evbuffer *since_ground;
|
||||
struct event ground_timer;
|
||||
#ifdef ENABLE_IMAGES
|
||||
void *kitty_state;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Helper functions. */
|
||||
@@ -186,9 +183,6 @@ static void input_enter_osc(struct input_ctx *);
|
||||
static void input_exit_osc(struct input_ctx *);
|
||||
static void input_enter_apc(struct input_ctx *);
|
||||
static void input_exit_apc(struct input_ctx *);
|
||||
#ifdef ENABLE_IMAGES
|
||||
static int input_handle_kitty(struct input_ctx *, const u_char *, size_t);
|
||||
#endif
|
||||
static void input_enter_rename(struct input_ctx *);
|
||||
static void input_exit_rename(struct input_ctx *);
|
||||
|
||||
@@ -922,9 +916,6 @@ input_free(struct input_ctx *ictx)
|
||||
event_del(&ictx->request_timer);
|
||||
|
||||
free(ictx->input_buf);
|
||||
#ifdef ENABLE_IMAGES
|
||||
kitty_free_state(ictx->kitty_state);
|
||||
#endif
|
||||
evbuffer_free(ictx->since_ground);
|
||||
event_del(&ictx->ground_timer);
|
||||
|
||||
@@ -947,7 +938,6 @@ input_reset(struct input_ctx *ictx, int clear)
|
||||
screen_write_start_pane(sctx, wp, &wp->base);
|
||||
else
|
||||
screen_write_start(sctx, &wp->base);
|
||||
sctx->flags |= SCREEN_WRITE_INPUT;
|
||||
screen_write_reset(sctx);
|
||||
screen_write_stop(sctx);
|
||||
}
|
||||
@@ -1059,6 +1049,7 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
wp->output_generation++;
|
||||
window_update_activity(wp->window);
|
||||
if (~wp->flags & PANE_ACTIVITY) {
|
||||
wp->flags |= PANE_ACTIVITY;
|
||||
@@ -1075,7 +1066,6 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
|
||||
screen_write_start_pane(sctx, wp, &wp->base);
|
||||
else
|
||||
screen_write_start(sctx, &wp->base);
|
||||
sctx->flags |= SCREEN_WRITE_INPUT;
|
||||
|
||||
log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id,
|
||||
ictx->state->name, len, (int)len, buf);
|
||||
@@ -1095,7 +1085,6 @@ input_parse_screen(struct input_ctx *ictx, struct screen *s,
|
||||
return;
|
||||
|
||||
screen_write_start_callback(sctx, s, cb, arg);
|
||||
sctx->flags |= SCREEN_WRITE_INPUT;
|
||||
input_parse(ictx, buf, len);
|
||||
screen_write_stop(sctx);
|
||||
}
|
||||
@@ -1595,7 +1584,7 @@ input_csi_dispatch(struct input_ctx *ictx)
|
||||
case -1:
|
||||
break;
|
||||
case 0:
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
input_reply(ictx, 1, "\033[?1;2;4c");
|
||||
#else
|
||||
input_reply(ictx, 1, "\033[?1;2c");
|
||||
@@ -1990,7 +1979,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx)
|
||||
screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
|
||||
break;
|
||||
case 2026:
|
||||
screen_write_stop_sync(ictx->wp);
|
||||
screen_write_end_sync(sctx);
|
||||
break;
|
||||
case 2031:
|
||||
screen_write_mode_clear(sctx, MODE_THEME_UPDATES);
|
||||
@@ -2113,7 +2102,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
|
||||
static void
|
||||
input_csi_dispatch_sm_graphics(__unused struct input_ctx *ictx)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
int n, m, o;
|
||||
|
||||
if (ictx->param_list_len > 3)
|
||||
@@ -2638,10 +2627,10 @@ input_dcs_dispatch(struct input_ctx *ictx)
|
||||
const char prefix[] = "tmux;";
|
||||
const u_int prefixlen = (sizeof prefix) - 1;
|
||||
long long allow_passthrough = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct window *w;
|
||||
struct sixel_image *si;
|
||||
int p1, p2;
|
||||
int p2;
|
||||
#endif
|
||||
|
||||
if (wp == NULL)
|
||||
@@ -2654,18 +2643,15 @@ input_dcs_dispatch(struct input_ctx *ictx)
|
||||
return (0);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (wp != NULL && buf[0] == 'q' && ictx->interm_len == 0) {
|
||||
w = wp->window;
|
||||
if (input_split(ictx) != 0)
|
||||
return (0);
|
||||
p1 = input_get(ictx, 0, 0, 0);
|
||||
if (p1 == -1)
|
||||
p1 = 0;
|
||||
p2 = input_get(ictx, 1, 0, 0);
|
||||
if (p2 == -1)
|
||||
p2 = 0;
|
||||
si = sixel_parse(buf, len, p1, p2, w->xpixel, w->ypixel);
|
||||
si = sixel_parse(buf, len, p2, w->xpixel, w->ypixel);
|
||||
if (si != NULL)
|
||||
screen_write_sixelimage(sctx, si, ictx->cell.cell.bg);
|
||||
}
|
||||
@@ -2804,57 +2790,6 @@ input_enter_apc(struct input_ctx *ictx)
|
||||
}
|
||||
|
||||
/* APC terminator (ST) received. */
|
||||
#ifdef ENABLE_IMAGES
|
||||
static int
|
||||
input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
|
||||
{
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct window_pane *wp = ictx->wp;
|
||||
struct image *im;
|
||||
u_int image_id = 0, replace_id = 0, quiet = 0;
|
||||
u_int placement_id = 0;
|
||||
int32_t z = 0;
|
||||
char action = '\0', delete = '\0';
|
||||
int status;
|
||||
|
||||
if (wp == NULL)
|
||||
return (0);
|
||||
im = kitty_parse_image(&ictx->kitty_state, buf, len,
|
||||
wp->window->xpixel,
|
||||
wp->window->ypixel, &image_id, &replace_id, &quiet, &action,
|
||||
&delete, &placement_id, &z, &status);
|
||||
if (status == KITTY_PARSE_MORE)
|
||||
return (1);
|
||||
if (status != KITTY_PARSE_OK) {
|
||||
if (quiet < 2 && action != '\0') {
|
||||
if (status == KITTY_PARSE_MISSING)
|
||||
input_reply(ictx, 0, "\033_Gi=%u;ENOENT\033\\",
|
||||
image_id);
|
||||
else
|
||||
input_reply(ictx, 0, "\033_Gi=%u;EINVAL\033\\",
|
||||
image_id);
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
if (replace_id != 0)
|
||||
image_clear(sctx, replace_id);
|
||||
if (im != NULL) {
|
||||
if (action == 'd')
|
||||
image_clear_kitty(sctx, delete, image_id, placement_id, z);
|
||||
else
|
||||
image_write_kitty(sctx, im, ictx->cell.cell.bg, image_id,
|
||||
placement_id, z);
|
||||
image_free(image_get_id(im));
|
||||
if (quiet == 0 && image_id != 0)
|
||||
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
|
||||
} else if (action == 'd')
|
||||
image_clear_kitty(sctx, delete, image_id, placement_id, z);
|
||||
else if ((action == 't' || action == 'q' || action == 'u') && quiet == 0)
|
||||
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
|
||||
return (1);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
input_exit_apc(struct input_ctx *ictx)
|
||||
{
|
||||
@@ -2865,13 +2800,6 @@ input_exit_apc(struct input_ctx *ictx)
|
||||
return;
|
||||
log_debug("%s: \"%s\"", __func__, ictx->input_buf);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (ictx->input_len > 1 && ictx->input_buf[0] == 'G' &&
|
||||
input_handle_kitty(ictx, ictx->input_buf + 1,
|
||||
ictx->input_len - 1))
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (wp != NULL &&
|
||||
options_get_number(wp->options, "allow-set-title") &&
|
||||
screen_set_title(sctx->s, ictx->input_buf, 1)) {
|
||||
@@ -2932,12 +2860,6 @@ input_top_bit_set(struct input_ctx *ictx)
|
||||
{
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct utf8_data *ud = &ictx->utf8data;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell gc;
|
||||
struct image *im;
|
||||
u_int x, source_x, source_y, image_id, placement_id;
|
||||
int32_t z;
|
||||
#endif
|
||||
|
||||
ictx->flags &= ~INPUT_LAST;
|
||||
|
||||
@@ -2965,20 +2887,6 @@ input_top_bit_set(struct input_ctx *ictx)
|
||||
utf8_copy(&ictx->cell.cell.data, ud);
|
||||
screen_write_collect_add(sctx, &ictx->cell.cell);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (sctx->s->cx != 0) {
|
||||
x = sctx->s->cx - 1; /* cx-1 is the cell just written. */
|
||||
grid_view_get_cell(sctx->s->grid, x, sctx->s->cy, &gc);
|
||||
if (kitty_placeholder_to_image(ictx->kitty_state,
|
||||
sctx->s->grid, &gc, x, sctx->s->cy, &im, &source_x,
|
||||
&source_y, &image_id, &placement_id, &z)) {
|
||||
grid_view_set_cell(sctx->s->grid, x, sctx->s->cy, &gc);
|
||||
image_place_cell_kitty(sctx, im, x, sctx->s->cy, source_x,
|
||||
source_y, image_id, placement_id, z);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
utf8_copy(&ictx->last, &ictx->cell.cell.data);
|
||||
ictx->flags |= INPUT_LAST;
|
||||
|
||||
@@ -3286,13 +3194,14 @@ input_osc_112(struct input_ctx *ictx, const char *p)
|
||||
|
||||
/* Parse the OSC 133 D exit status. */
|
||||
static int
|
||||
input_osc_133_exit_status(const char *p)
|
||||
input_osc_133_exit_status(const char *p, int *present)
|
||||
{
|
||||
const char *end;
|
||||
char *copy;
|
||||
const char *errstr;
|
||||
char *endptr;
|
||||
long long status;
|
||||
|
||||
*present = 0;
|
||||
if (p[1] != ';' || p[2] == '\0' || strchr(p + 2, '=') == p + 2)
|
||||
return (0);
|
||||
end = strchr(p + 2, ';');
|
||||
@@ -3306,9 +3215,11 @@ input_osc_133_exit_status(const char *p)
|
||||
free(copy);
|
||||
return (0);
|
||||
}
|
||||
status = strtonum(copy, 0, 255, &errstr);
|
||||
*present = 1;
|
||||
errno = 0;
|
||||
status = (int)strtol(copy, &endptr, 10);
|
||||
free(copy);
|
||||
if (errstr != NULL)
|
||||
if (errno != 0 || endptr == copy || status < 0 || status > 255)
|
||||
return (255);
|
||||
return (status);
|
||||
}
|
||||
@@ -3353,6 +3264,22 @@ input_fire_command_event(struct window_pane *wp, const char *name)
|
||||
events_fire(name, ep);
|
||||
}
|
||||
|
||||
/* Check if an OSC 133 prompt is secondary or a continuation. */
|
||||
static int
|
||||
input_osc_133_secondary_prompt(const char *p)
|
||||
{
|
||||
const char *cp;
|
||||
|
||||
while ((cp = strstr(p, ";k=")) != NULL) {
|
||||
cp += 3;
|
||||
if ((*cp == 's' || *cp == 'c') &&
|
||||
(cp[1] == '\0' || cp[1] == ';'))
|
||||
return (1);
|
||||
p = cp;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Handle the OSC 133 sequence. */
|
||||
static void
|
||||
input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
@@ -3362,8 +3289,7 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
struct grid *gd = s->grid;
|
||||
u_int line = s->cy + gd->hsize;
|
||||
struct grid_line *gl = NULL;
|
||||
const char *cp;
|
||||
int status;
|
||||
int status, status_present;
|
||||
|
||||
if (line < gd->hsize + gd->sy)
|
||||
gl = grid_get_line(gd, line);
|
||||
@@ -3372,9 +3298,17 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
case 'A':
|
||||
case 'N':
|
||||
if (gl != NULL) {
|
||||
memset(&gl->osc133_data, 0, sizeof gl->osc133_data);
|
||||
gl->osc133_data.prompt_col = s->cx;
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
if (!(gl->flags & (GRID_LINE_START_PROMPT|
|
||||
GRID_LINE_SECOND_PROMPT))) {
|
||||
gl->osc133_data.prompt_col = s->cx;
|
||||
if (input_osc_133_secondary_prompt(p))
|
||||
gl->flags |= GRID_LINE_SECOND_PROMPT;
|
||||
else
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
log_debug("%s: osc133 %s at %u,%u", __func__,
|
||||
input_osc_133_secondary_prompt(p) ? "secondary prompt" :
|
||||
"prompt", s->cx, line);
|
||||
}
|
||||
}
|
||||
if (wp != NULL) {
|
||||
wp->last_prompt_time = time(NULL);
|
||||
@@ -3383,25 +3317,33 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
break;
|
||||
case 'P':
|
||||
if (gl != NULL) {
|
||||
cp = strstr(p, ";k=s");
|
||||
if (cp != NULL && (cp[4] == ';' || cp[4] == '\0'))
|
||||
gl->flags |= GRID_LINE_SECOND_PROMPT;
|
||||
else
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
gl->osc133_data.prompt_col = s->cx;
|
||||
if (!(gl->flags & (GRID_LINE_START_PROMPT|
|
||||
GRID_LINE_SECOND_PROMPT))) {
|
||||
gl->osc133_data.prompt_col = s->cx;
|
||||
if (input_osc_133_secondary_prompt(p))
|
||||
gl->flags |= GRID_LINE_SECOND_PROMPT;
|
||||
else
|
||||
gl->flags |= GRID_LINE_START_PROMPT;
|
||||
log_debug("%s: osc133 prompt at %u,%u", __func__, s->cx,
|
||||
line);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
case 'I':
|
||||
if (gl != NULL) {
|
||||
if (gl != NULL && !(gl->flags & GRID_LINE_START_COMMAND)) {
|
||||
gl->flags |= GRID_LINE_START_COMMAND;
|
||||
gl->osc133_data.cmd_col = s->cx;
|
||||
log_debug("%s: osc133 command at %u,%u", __func__, s->cx,
|
||||
line);
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
if (gl != NULL) {
|
||||
if (gl != NULL && !(gl->flags & GRID_LINE_START_OUTPUT)) {
|
||||
gl->flags |= GRID_LINE_START_OUTPUT;
|
||||
gl->osc133_data.out_start_col = s->cx;
|
||||
log_debug("%s: osc133 output start at %u,%u", __func__, s->cx,
|
||||
line);
|
||||
}
|
||||
if (wp != NULL) {
|
||||
wp->cmd_start_time = time(NULL);
|
||||
@@ -3412,7 +3354,7 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
}
|
||||
break;
|
||||
case 'D':
|
||||
status = input_osc_133_exit_status(p);
|
||||
status = input_osc_133_exit_status(p, &status_present);
|
||||
if (wp != NULL) {
|
||||
wp->cmd_end_time = time(NULL);
|
||||
wp->flags &= ~PANE_CMDRUNNING;
|
||||
@@ -3421,8 +3363,12 @@ input_osc_133(struct input_ctx *ictx, const char *p)
|
||||
}
|
||||
if (gl != NULL) {
|
||||
gl->flags |= GRID_LINE_END_OUTPUT;
|
||||
if (status_present)
|
||||
gl->flags |= GRID_LINE_END_OUTPUT_STATUS;
|
||||
gl->osc133_data.out_end_col = s->cx;
|
||||
gl->osc133_data.exit_status = status;
|
||||
log_debug("%s: osc133 output end at %u,%u (status %d)",
|
||||
__func__, s->cx, line, status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@
|
||||
" '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}' '<' {send -X history-top}" \
|
||||
" '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}' '>' {send -X history-bottom}" \
|
||||
" ''" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},#{?copy_line_numbers,Hide Line Numbers,Show Line Numbers},}' 'L' {send -X line-numbers-toggle}" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},#{?copy_line_numbers,Line Numbers Off,Line Numbers On},}' 'L' {send -X line-numbers-toggle}" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},#{?copy_fold_view,Fold View Off,Fold View On},}' 'O' {send -X fold-view-toggle}" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},Edit,}' 'e' {if -F '#{selection_present}' {send -X open-selection} {send -X open-output}}" \
|
||||
" '#{?#{==:#{pane_mode},copy-mode},#{?refresh_active,Refresh Off,Refresh On},}' 'r' {send -X refresh-toggle}" \
|
||||
" ''" \
|
||||
" '#{?#{&&:#{buffer_size},#{!:#{pane_in_mode}}},Paste #[underscore]#{=/9/...:buffer_sample},}' 'p' {paste-buffer}" \
|
||||
@@ -575,6 +577,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode C-l { send -X recentre-top-bottom }",
|
||||
"bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }",
|
||||
"bind -Tcopy-mode C-n { send -X cursor-down }",
|
||||
"bind -Tcopy-mode C-o { send -X select-output }",
|
||||
"bind -Tcopy-mode C-p { send -X cursor-up }",
|
||||
"bind -Tcopy-mode C-r { command-prompt -P -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }",
|
||||
"bind -Tcopy-mode C-s { command-prompt -P -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }",
|
||||
@@ -583,15 +586,19 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode Escape { send -X cancel }",
|
||||
"bind -Tcopy-mode C-[ { send -X cancel }",
|
||||
"bind -Tcopy-mode Space { send -X page-down }",
|
||||
"bind -Tcopy-mode Tab { send -X toggle-output }",
|
||||
"bind -Tcopy-mode BTab { send -X toggle-output -a }",
|
||||
"bind -Tcopy-mode , { send -X jump-reverse }",
|
||||
"bind -Tcopy-mode \\; { send -X jump-again }",
|
||||
"bind -Tcopy-mode F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode L { send -X line-numbers-toggle }",
|
||||
"bind -Tcopy-mode N { send -X search-reverse }",
|
||||
"bind -Tcopy-mode O { send -X fold-view-toggle }",
|
||||
"bind -Tcopy-mode P { send -X toggle-position }",
|
||||
"bind -Tcopy-mode R { send -X rectangle-toggle }",
|
||||
"bind -Tcopy-mode T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode X { send -X set-mark }",
|
||||
"bind -Tcopy-mode e { if -F '#{selection_present}' { send -X open-selection } { send -X open-output } }",
|
||||
"bind -Tcopy-mode f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }",
|
||||
"bind -Tcopy-mode n { send -X search-again }",
|
||||
@@ -600,7 +607,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode Home { send -X start-of-line }",
|
||||
"bind -Tcopy-mode End { send -X end-of-line }",
|
||||
"bind -Tcopy-mode MouseDown1Pane select-pane",
|
||||
"bind -Tcopy-mode MouseDown1Pane { select-pane; send -X toggle-output -m }",
|
||||
"bind -Tcopy-mode MouseDrag1Pane { select-pane; send -X begin-selection }",
|
||||
"bind -Tcopy-mode MouseDragEnd1Pane { send -X copy-pipe-and-cancel }",
|
||||
"bind -Tcopy-mode WheelUpPane { select-pane; send -N5 -X scroll-up }",
|
||||
@@ -660,6 +667,8 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi Escape { send -X clear-selection }",
|
||||
"bind -Tcopy-mode-vi C-[ { send -X clear-selection }",
|
||||
"bind -Tcopy-mode-vi Space { send -X begin-selection }",
|
||||
"bind -Tcopy-mode-vi Tab { send -X toggle-output }",
|
||||
"bind -Tcopy-mode-vi BTab { send -X toggle-output -a }",
|
||||
"bind -Tcopy-mode-vi '$' { send -X end-of-line }",
|
||||
"bind -Tcopy-mode-vi , { send -X jump-reverse }",
|
||||
"bind -Tcopy-mode-vi / { command-prompt -P -T search -p'(search down)' { send -X search-forward -- '%%' } }",
|
||||
@@ -687,7 +696,10 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi K { send -X scroll-up }",
|
||||
"bind -Tcopy-mode-vi L { send -X bottom-line }",
|
||||
"bind -Tcopy-mode-vi M { send -X middle-line }",
|
||||
"bind -Tcopy-mode-vi M-e { if -F '#{selection_present}' { send -X open-selection } { send -X open-output } }",
|
||||
"bind -Tcopy-mode-vi M-o { send -X select-output }",
|
||||
"bind -Tcopy-mode-vi N { send -X search-reverse }",
|
||||
"bind -Tcopy-mode-vi O { send -X fold-view-toggle }",
|
||||
"bind -Tcopy-mode-vi P { send -X toggle-position }",
|
||||
"bind -Tcopy-mode-vi T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi V { send -X select-line }",
|
||||
@@ -715,7 +727,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi % { send -X next-matching-bracket }",
|
||||
"bind -Tcopy-mode-vi Home { send -X start-of-line }",
|
||||
"bind -Tcopy-mode-vi End { send -X end-of-line }",
|
||||
"bind -Tcopy-mode-vi MouseDown1Pane { select-pane }",
|
||||
"bind -Tcopy-mode-vi MouseDown1Pane { select-pane; send -X toggle-output -m }",
|
||||
"bind -Tcopy-mode-vi MouseDrag1Pane { select-pane; send -X begin-selection }",
|
||||
"bind -Tcopy-mode-vi MouseDragEnd1Pane { send -X copy-pipe-and-cancel }",
|
||||
"bind -Tcopy-mode-vi WheelUpPane { select-pane; send -N5 -X scroll-up }",
|
||||
|
||||
50
layout.c
50
layout.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: layout.c,v 1.96 2026/07/15 13:02:33 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>
|
||||
@@ -539,6 +539,10 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
|
||||
|
||||
status = window_get_pane_status(w);
|
||||
|
||||
/* Floating cells do not take space from the tiled layout. */
|
||||
if (!layout_cell_is_tiled(lc) && !layout_cell_has_tiled_child(lc))
|
||||
return (0);
|
||||
|
||||
if (lc->type == LAYOUT_WINDOWPANE) {
|
||||
/* Space available in this cell only. */
|
||||
if (type == LAYOUT_LEFTRIGHT) {
|
||||
@@ -568,6 +572,9 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
|
||||
/* Different type: minimum of available space in child cells. */
|
||||
minimum = UINT_MAX;
|
||||
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
|
||||
if (!layout_cell_is_tiled(lcchild) &&
|
||||
!layout_cell_has_tiled_child(lcchild))
|
||||
continue;
|
||||
available = layout_resize_check(w, lcchild, type);
|
||||
if (available < minimum)
|
||||
minimum = available;
|
||||
@@ -661,7 +668,7 @@ layout_resize_set_size(struct window *w, struct layout_cell *lc,
|
||||
|
||||
/* Find and return the nearest neighbour to a cell in a specific direction. */
|
||||
static struct layout_cell *
|
||||
layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction)
|
||||
layout_cell_get_neighbour_dir(struct layout_cell *lc, int direction)
|
||||
{
|
||||
struct layout_cell *lcn = lc;
|
||||
|
||||
@@ -695,9 +702,9 @@ layout_cell_get_neighbour(struct layout_cell *lc)
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
|
||||
direction = !direction;
|
||||
|
||||
lcother = layout_cell_get_neighbour_direction(lc, direction);
|
||||
lcother = layout_cell_get_neighbour_dir(lc, direction);
|
||||
if (lcother == NULL)
|
||||
lcother = layout_cell_get_neighbour_direction(lc, !direction);
|
||||
lcother = layout_cell_get_neighbour_dir(lc, !direction);
|
||||
|
||||
return (lcother);
|
||||
}
|
||||
@@ -860,7 +867,7 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
|
||||
size = lc->g.sx;
|
||||
else
|
||||
size = lc->g.sy;
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
|
||||
if (layout_cell_is_last_tiled(lc))
|
||||
change = size - new_size;
|
||||
else
|
||||
change = new_size - size;
|
||||
@@ -985,11 +992,11 @@ layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
|
||||
if (lcparent == NULL)
|
||||
return;
|
||||
|
||||
/* If this is the last cell, move back one. */
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) {
|
||||
do
|
||||
lc = TAILQ_PREV(lc, layout_cells, entry);
|
||||
while (lc->flags & LAYOUT_CELL_FLOATING);
|
||||
/* If this is the last tiled cell, move back one. */
|
||||
if (layout_cell_is_last_tiled(lc)) {
|
||||
lc = layout_cell_get_neighbour_dir(lc, 0);
|
||||
if (lc == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
layout_resize_layout(wp->window, lc, type, change, opposite);
|
||||
@@ -1007,22 +1014,22 @@ layout_resize_pane_grow(struct window *w, struct layout_cell *lc,
|
||||
lcadd = lc;
|
||||
|
||||
/* Look towards the tail for a suitable cell for reduction. */
|
||||
lcremove = TAILQ_NEXT(lc, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lc, 1);
|
||||
while (lcremove != NULL) {
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size > 0)
|
||||
break;
|
||||
lcremove = TAILQ_NEXT(lcremove, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 1);
|
||||
}
|
||||
|
||||
/* If none found, look towards the head. */
|
||||
if (opposite && lcremove == NULL) {
|
||||
lcremove = TAILQ_PREV(lc, layout_cells, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lc, 0);
|
||||
while (lcremove != NULL) {
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size > 0)
|
||||
break;
|
||||
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
|
||||
}
|
||||
}
|
||||
if (lcremove == NULL)
|
||||
@@ -1050,13 +1057,13 @@ layout_resize_pane_shrink(struct window *w, struct layout_cell *lc,
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size != 0)
|
||||
break;
|
||||
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
|
||||
} while (lcremove != NULL);
|
||||
if (lcremove == NULL)
|
||||
return (0);
|
||||
|
||||
/* And add onto the next cell (from the original cell). */
|
||||
lcadd = TAILQ_NEXT(lc, entry);
|
||||
lcadd = layout_cell_get_neighbour_dir(lc, 1);
|
||||
if (lcadd == NULL)
|
||||
return (0);
|
||||
|
||||
@@ -1650,7 +1657,10 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args,
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM));
|
||||
if (window_active_pane_is_over_zoom(w))
|
||||
window_push_zoom(w, 0, 1);
|
||||
else
|
||||
window_push_zoom(w, 1, (flags & SPAWN_ZOOM));
|
||||
lc = layout_split_pane(wp, type, size, flags);
|
||||
if (lc == NULL)
|
||||
*cause = xstrdup("no space for a new pane");
|
||||
@@ -1677,8 +1687,10 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args,
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (flags & SPAWN_MODAL)
|
||||
window_push_modal_zoom(w);
|
||||
if (flags & SPAWN_FLOATOVERZOOM)
|
||||
window_push_zoom(wp->window, 0, 1);
|
||||
else if (window_active_pane_is_over_zoom(w))
|
||||
window_push_zoom(wp->window, 0, 1);
|
||||
else
|
||||
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM));
|
||||
lcnew = layout_floating_pane(w, wp, &fg);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: options-table.c,v 1.242 2026/07/27 08:03:01 nicm Exp $ */
|
||||
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -114,7 +114,6 @@ static const char *options_table_theme_list[] = {
|
||||
static const char *options_table_copy_mode_line_numbers_list[] = {
|
||||
"off", "default", "absolute", "relative", "hybrid", NULL
|
||||
};
|
||||
|
||||
/* Status line format. */
|
||||
#define OPTIONS_TABLE_STATUS_FORMAT1 \
|
||||
"#[align=left range=left #{E:status-left-style}]" \
|
||||
@@ -305,6 +304,16 @@ const struct options_table_entry options_table[] = {
|
||||
"When this is reached, the oldest buffer is deleted."
|
||||
},
|
||||
|
||||
{ .name = "clear-on-attach",
|
||||
.type = OPTIONS_TABLE_FLAG,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.default_num = 1,
|
||||
.text = "Whether to use the alternate screen and clear it when "
|
||||
"a client is attached. When disabled, tmux does not "
|
||||
"enter the alternate screen on attach so terminal "
|
||||
"content before tmux remains in scrollback."
|
||||
},
|
||||
|
||||
{ .name = "command-alias",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
@@ -541,7 +550,6 @@ const struct options_table_entry options_table[] = {
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_ARRAY,
|
||||
.default_str = "xterm*:clipboard:ccolour:cstyle:focus:title,"
|
||||
"xterm-kitty*:kitty,"
|
||||
"screen*:title,"
|
||||
"rxvt*:ignorefkeys",
|
||||
.separator = ",",
|
||||
@@ -1241,7 +1249,8 @@ const struct options_table_entry options_table[] = {
|
||||
* underscore.
|
||||
*/
|
||||
.default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~",
|
||||
.text = "Characters considered to separate words."
|
||||
.text = "Characters considered to separate words; a space matches "
|
||||
"any character with the Unicode White_Space property."
|
||||
},
|
||||
|
||||
/* Window options. */
|
||||
@@ -1374,6 +1383,13 @@ const struct options_table_entry options_table[] = {
|
||||
.text = "Style of search matches in copy mode."
|
||||
},
|
||||
|
||||
{ .name = "copy-mode-exit-status-format",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.default_str = "#{?exit_status,#[fg=themered]!, }",
|
||||
.text = "Format of OSC 133 exit status indicator in copy mode."
|
||||
},
|
||||
|
||||
{ .name = "copy-mode-current-match-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
|
||||
5
popup.c
5
popup.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: popup.c,v 1.76 2026/07/14 19:07:03 nicm Exp $ */
|
||||
/* $OpenBSD: popup.c,v 1.77 2026/08/28 08:02:16 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -79,7 +79,8 @@ popup_free(struct popup_data *pd)
|
||||
|
||||
if (pd->job != NULL)
|
||||
job_free(pd->job);
|
||||
input_free(pd->ictx);
|
||||
if (pd->ictx != NULL)
|
||||
input_free(pd->ictx);
|
||||
|
||||
free(pd->r.ranges);
|
||||
screen_free(&pd->s);
|
||||
|
||||
@@ -28,7 +28,7 @@ $TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
TMP2=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$TMP2"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
trap 'rm -f "$TMP" "$TMP2" "$TMP".*; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
@@ -270,6 +270,27 @@ if [ $? -eq 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A write error after a successful open must be reported to the server.
|
||||
dd if=/dev/zero of="$TMP.big" bs=100000 count=1 2>/dev/null
|
||||
check_ok load-buffer -b write-error "$TMP.big"
|
||||
mkfifo "$TMP.fifo" || exit 1
|
||||
dd if="$TMP.fifo" of="$TMP.got" bs=1 count=8 2>/dev/null &
|
||||
reader=$!
|
||||
exec 3>"$TMP.fifo"
|
||||
out=$($TMUX save-buffer -b write-error "$TMP.fifo" 3>&- 2>&1)
|
||||
status=$?
|
||||
exec 3>&-
|
||||
wait "$reader"
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "save-buffer after write error succeeded."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(wc -c <"$TMP.got")" -ne 8 ]; then
|
||||
echo "save-buffer write error test wrote wrong amount."
|
||||
exit 1
|
||||
fi
|
||||
check_ok delete-buffer -b write-error
|
||||
|
||||
# save-buffer - writes to stdout and load-buffer - reads from stdin.
|
||||
out=$($TMUX save-buffer -b sb -)
|
||||
if [ "$out" != "data" ]; then
|
||||
|
||||
0
regress/cfg-client-lost.sh
Executable file → Normal file
0
regress/cfg-client-lost.sh
Executable file → Normal file
@@ -290,4 +290,92 @@ wait_count ': G4' 7
|
||||
fail "window 1 of zzz not killed"
|
||||
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
|
||||
|
||||
96
regress/clear-on-attach.sh
Normal file
96
regress/clear-on-attach.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/bin/sh
|
||||
|
||||
# With clear-on-attach disabled tmux must preserve the terminal contents by
|
||||
# scrolling them away, rather than entering and clearing the alternate screen.
|
||||
# Capture the bytes written by a real attached client for both settings.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
INNER_ON="$TEST_TMUX -LtestIon$$ -f/dev/null"
|
||||
OUTER_ON="$TEST_TMUX -LtestOon$$ -f/dev/null"
|
||||
INNER_OFF="$TEST_TMUX -LtestIoff$$ -f/dev/null"
|
||||
OUTER_OFF="$TEST_TMUX -LtestOoff$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER_ON kill-server 2>/dev/null
|
||||
$INNER_ON kill-server 2>/dev/null
|
||||
$OUTER_OFF kill-server 2>/dev/null
|
||||
$INNER_OFF kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$current_inner list-clients 2>/dev/null | grep -q . && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
capture_attach()
|
||||
{
|
||||
setting=$1
|
||||
output=$2
|
||||
go=$DIR/go-$setting
|
||||
if [ "$setting" = on ]; then
|
||||
current_inner=$INNER_ON
|
||||
current_outer=$OUTER_ON
|
||||
else
|
||||
current_inner=$INNER_OFF
|
||||
current_outer=$OUTER_OFF
|
||||
fi
|
||||
|
||||
$current_inner new-session -d -s inner -x40 -y10 \
|
||||
'exec sleep 100' || exit 1
|
||||
$current_inner set-option -g status off || exit 1
|
||||
$current_inner set-option -g clear-on-attach "$setting" || exit 1
|
||||
$current_outer new-session -d -s outer -x40 -y10 \
|
||||
"while [ ! -e '$go' ]; do sleep 0.01; done; exec $current_inner attach-session -t inner" || exit 1
|
||||
$current_outer set-option -g status off || exit 1
|
||||
$current_outer pipe-pane -O "cat >'$output'" || exit 1
|
||||
touch "$go"
|
||||
wait_for_client
|
||||
sleep 0.5
|
||||
$current_outer pipe-pane -O || exit 1
|
||||
}
|
||||
|
||||
on=$DIR/on
|
||||
off=$DIR/off
|
||||
capture_attach on "$on"
|
||||
capture_attach off "$off"
|
||||
|
||||
smcup=$(printf '\033[?1049h')
|
||||
grep -Fq "$smcup" "$on" || fail "clear-on-attach on did not use smcup"
|
||||
if grep -Fq "$smcup" "$off"; then
|
||||
fail "clear-on-attach off used smcup"
|
||||
fi
|
||||
|
||||
# Some screen terminfo entries have indn (CSI Ps S), while older ones only
|
||||
# have ind (newline). The preserving path uses indn once or repeats ind once
|
||||
# for every line in the terminal plus one.
|
||||
escape=$(printf '\033')
|
||||
if ! LC_ALL=C grep -Eq "${escape}\\[[0-9]+S" "$off"; then
|
||||
hex=$(od -An -tx1 -v "$off" | tr -d ' \n')
|
||||
case "$hex" in
|
||||
*0a0a0a0a0a0a0a0a0a0a0a*) ;;
|
||||
*) fail "clear-on-attach off did not scroll the old contents away" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
exit 0
|
||||
82
regress/client-key-order-focus.sh
Normal file
82
regress/client-key-order-focus.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/bin/sh
|
||||
|
||||
# send-keys -K injects a key into a client. Commands produced by that key must
|
||||
# be inserted immediately after send-keys, before later commands in the same
|
||||
# command list. Focus events arriving between a prefix and its following key
|
||||
# must be forwarded without cancelling the prefix table.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
client=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$client" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_for_option()
|
||||
{
|
||||
option=$1
|
||||
expected=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
actual=$($INNER show-option -gv "$option" 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "$option is '$actual', expected '$expected'"
|
||||
}
|
||||
|
||||
cleanup
|
||||
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g focus-events on || exit 1
|
||||
$OUTER new-session -d -s outer -x40 -y10 "$INNER attach -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
wait_for_client
|
||||
|
||||
# The binding generated by send-keys -K must run before the command which
|
||||
# follows send-keys in this command list. Before the queue-ordering fix this
|
||||
# produces AB rather than BA.
|
||||
$INNER set-option -g @order '' || exit 1
|
||||
$INNER bind-key -n a set-option -gF @order '#{@order}B' || exit 1
|
||||
$INNER send-keys -K -c "$client" a \; \
|
||||
set-option -gF @order '#{@order}A' || exit 1
|
||||
wait_for_option @order BA
|
||||
|
||||
# Put the real attached client in the prefix table, deliver FocusIn, then the
|
||||
# bound key. FocusIn is not bindable and must not reset the prefix table.
|
||||
$INNER bind-key x set-option -g @focus-prefix yes || exit 1
|
||||
$OUTER send-keys C-b || exit 1
|
||||
focus_in=$(printf '\033[I')
|
||||
$OUTER send-keys -l "$focus_in" || exit 1
|
||||
$OUTER send-keys x || exit 1
|
||||
wait_for_option @focus-prefix yes
|
||||
|
||||
exit 0
|
||||
101
regress/clock-mode.sh
Normal file
101
regress/clock-mode.sh
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise clock-mode initialization, all four styles, its one-second timer,
|
||||
# resize into the compact renderer, arbitrary-key exit, and cleanup.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER display-message -p -t clock:0 '#{pane_mode}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane mode is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
wait_hashes()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -q '#' && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "large clock did not render"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s clock -x80 -y24 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -w clock-mode-colour red || exit 1
|
||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t clock" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
for style in 12 24 12-with-seconds 24-with-seconds; do
|
||||
$INNER set-option -w -t clock:0 clock-mode-style "$style" || exit 1
|
||||
$INNER clock-mode -t clock:0 || exit 1
|
||||
wait_mode clock-mode
|
||||
wait_hashes
|
||||
# Let at least one timer callback observe a new second.
|
||||
[ "$style" != 12-with-seconds ] || sleep 1.2
|
||||
$INNER send-keys -t clock:0 x || exit 1
|
||||
wait_mode ''
|
||||
done
|
||||
|
||||
# Resizing an active mode below the large-glyph threshold selects the compact
|
||||
# renderer. A visible time contains a colon and no block-clock hash.
|
||||
$INNER clock-mode -t clock:0 || exit 1
|
||||
wait_mode clock-mode
|
||||
$INNER resize-window -t clock:0 -x20 -y5 || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Eq '[0-9][0-9]?:[0-9][0-9]' && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "compact clock did not render after resize"
|
||||
printf '%s\n' "$captured" | grep -q '#' &&
|
||||
fail "compact clock still used large glyphs"
|
||||
$INNER send-keys -t clock:0 Enter || exit 1
|
||||
wait_mode ''
|
||||
|
||||
$INNER has-session -t clock || fail "server died during clock-mode tests"
|
||||
exit 0
|
||||
81
regress/collapse-output.sh
Normal file
81
regress/collapse-output.sh
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo hi\\n\\033]133;C\\007hello\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
$TMUX send-keys -X search-backward hello || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != hello ] || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != separator ] || exit 1
|
||||
|
||||
$TMUX send-keys BTab || exit 1
|
||||
$TMUX send-keys -X search-backward hello || exit 1
|
||||
shown=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$shown" = hello ] || exit 1
|
||||
$TMUX send-keys -X select-line || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
selected=$($TMUX show-buffer)
|
||||
[ "$selected" = hello ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX kill-server
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"first\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != separator ] || exit 1
|
||||
$TMUX send-keys BTab || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
shown=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$shown" = separator ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX kill-server
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007P> \\033]133;B\\007for i in 1 2 3; do\\n\\033]133;A;k=s\\007> \\033]133;B\\007echo \\$i\\n\\033]133;P;k=s\\007> \\033]133;B\\007done\\033]133;C\\007\\nRESULT\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007P> \\033]133;B\\007\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
$TMUX send-keys -X search-backward RESULT || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != RESULT ] || exit 1
|
||||
$TMUX send-keys -X search-backward 'echo $i' || exit 1
|
||||
shown=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$shown" = 'echo $i' ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward RESULT || exit 1
|
||||
$TMUX send-keys Tab || exit 1
|
||||
cursor=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$cursor" = done ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX kill-server
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007P0> \\033]133;B\\007\\r\\n\\033]133;D;0\\007empty-separator\\n\\033]133;A\\007P1> \\033]133;B\\007\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
collapsed=$($TMUX capture-pane -p)
|
||||
case "$collapsed" in
|
||||
*"+ P0>"*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -X search-backward empty-separator || exit 1
|
||||
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
|
||||
[ "$hidden" != empty-separator ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
150
regress/confirm-before-lock.sh
Normal file
150
regress/confirm-before-lock.sh
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise confirm-before's waiting and background callbacks, custom keys and
|
||||
# prompts, default-yes handling and errors. Also cover all three lock command
|
||||
# entry points with a harmless lock-command which leaves a marker.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_capture()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Fq "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$marker'"
|
||||
}
|
||||
|
||||
wait_option()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER show-option -gqv @confirmed 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "@confirmed is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
wait_file()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
[ -f "$marker" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "lock command did not create $marker"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s test -x80 -y24 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status on || exit 1
|
||||
$INNER set-option -g status-position bottom || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t test" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
client=$($INNER list-clients -F '#{client_name}')
|
||||
[ -n "$client" ] || fail "inner client did not attach"
|
||||
|
||||
$INNER bind-key -n M-a confirm-before \
|
||||
'set-option -g @confirmed accepted' || exit 1
|
||||
$INNER bind-key -n M-b confirm-before -b -p 'Background?' \
|
||||
'set-option -g @confirmed background' || exit 1
|
||||
$INNER bind-key -n M-c confirm-before -c x -p 'Custom?' \
|
||||
'set-option -g @confirmed custom' || exit 1
|
||||
$INNER bind-key -n M-y confirm-before -y -p 'Default?' \
|
||||
'set-option -g @confirmed default' || exit 1
|
||||
|
||||
# A non-confirming response closes the waiting prompt without running its
|
||||
# command; reopening and accepting inserts the command after the waiting item.
|
||||
$INNER set-option -g @confirmed sentinel || exit 1
|
||||
$OUTER send-keys M-a || exit 1
|
||||
wait_capture "Confirm 'set-option'? (y/n)"
|
||||
$OUTER send-keys n || exit 1
|
||||
sleep 0.2
|
||||
wait_option sentinel
|
||||
$OUTER send-keys M-a || exit 1
|
||||
wait_capture "Confirm 'set-option'? (y/n)"
|
||||
$OUTER send-keys y || exit 1
|
||||
wait_option accepted
|
||||
|
||||
# -b has no waiting queue item and appends its command to the client queue.
|
||||
$OUTER send-keys M-b || exit 1
|
||||
wait_capture 'Background?'
|
||||
$OUTER send-keys y || exit 1
|
||||
wait_option background
|
||||
|
||||
# A custom printable key is accepted, and -y makes Enter affirmative.
|
||||
$OUTER send-keys M-c || exit 1
|
||||
wait_capture 'Custom?'
|
||||
$OUTER send-keys x || exit 1
|
||||
wait_option custom
|
||||
$OUTER send-keys M-y || exit 1
|
||||
wait_capture 'Default?'
|
||||
$OUTER send-keys Enter || exit 1
|
||||
wait_option default
|
||||
|
||||
# Reject invalid multi-character and control confirmation keys.
|
||||
if $INNER confirm-before -b -t "$client" -c xx 'display-message x' \
|
||||
>/dev/null 2>&1; then
|
||||
fail "multi-character confirm key was accepted"
|
||||
fi
|
||||
if $INNER confirm-before -b -t "$client" -c "$(printf '\001')" \
|
||||
'display-message x' >/dev/null 2>&1; then
|
||||
fail "control confirm key was accepted"
|
||||
fi
|
||||
|
||||
# lock-client, lock-session and lock-server all send lock-command to the real
|
||||
# attached client. The command returns immediately and unlocks the client.
|
||||
for kind in client session server; do
|
||||
marker="$DIR/locked-$kind"
|
||||
$INNER set-option -t test lock-command "printf locked >$marker" || exit 1
|
||||
case "$kind" in
|
||||
client) $INNER lock-client -t "$client" || exit 1 ;;
|
||||
session) $INNER lock-session -t test || exit 1 ;;
|
||||
server) $INNER lock-server || exit 1 ;;
|
||||
esac
|
||||
wait_file "$marker"
|
||||
done
|
||||
|
||||
$INNER has-session -t test || fail "server died during prompt or lock tests"
|
||||
exit 0
|
||||
0
regress/control-client-exit-stalled.sh
Executable file → Normal file
0
regress/control-client-exit-stalled.sh
Executable file → Normal file
63
regress/control-client-popup.sh
Normal file
63
regress/control-client-popup.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Popups require a tty overlay and cannot be displayed by a control client.
|
||||
# A popup command from control mode must be ignored cleanly, leaving the
|
||||
# client command queue and server usable.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
FIFO=$DIR/input
|
||||
OUT=$DIR/output
|
||||
RAN=$DIR/popup-ran
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&-
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
$TMUX new-session -d -s control -x40 -y10 'exec sleep 100' || exit 1
|
||||
$TMUX -C attach-session -t control <"$FIFO" >"$OUT" 2>&1 &
|
||||
control_pid=$!
|
||||
exec 3>"$FIFO"
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$TMUX list-clients -F '#{client_flags}' 2>/dev/null |
|
||||
grep -q 'control-mode' && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "control client did not attach"
|
||||
|
||||
printf '%s\n' "display-popup -E 'touch $RAN'" >&3
|
||||
printf '%s\n' "display-message -p CONTROL-POPUP-ALIVE" >&3
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
grep -q 'CONTROL-POPUP-ALIVE' "$OUT" 2>/dev/null && break
|
||||
if ! kill -0 "$control_pid" 2>/dev/null; then
|
||||
fail "control client exited after display-popup"
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "command after control-client popup did not run"
|
||||
$TMUX has-session -t control || fail "server exited after control-client popup"
|
||||
[ ! -e "$RAN" ] || fail "control client started a popup command"
|
||||
|
||||
exit 0
|
||||
38
regress/control-client-run-shell-exit.sh
Normal file
38
regress/control-client-run-shell-exit.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A waited run-shell command can complete after a control client has closed its
|
||||
# input and exited. The command queue still references the client until the job
|
||||
# callback runs, but control_stop has already destroyed c->control_state. Late
|
||||
# command output must be discarded instead of dereferencing the stopped control
|
||||
# state and killing the server.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
OUT=$(mktemp)
|
||||
trap "$TMUX kill-server 2>/dev/null; rm -f $OUT" 0 1 15
|
||||
|
||||
$TMUX -f/dev/null new-session -d -s main || exit 1
|
||||
|
||||
printf '%s\n' 'run-shell "sleep 0.2; echo TEST"' |
|
||||
$TMUX -f/dev/null -C >"$OUT" 2>&1
|
||||
|
||||
sleep 1
|
||||
|
||||
$TMUX has-session -t main 2>/dev/null || {
|
||||
echo "server exited after late run-shell output"
|
||||
cat "$OUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
grep -q '^%exit' "$OUT" || {
|
||||
echo "control client did not exit"
|
||||
cat "$OUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
0
regress/control-notify-guard.sh
Executable file → Normal file
0
regress/control-notify-guard.sh
Executable file → Normal file
253
regress/copy-mode-commands.sh
Normal file
253
regress/copy-mode-commands.sh
Normal file
@@ -0,0 +1,253 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise the broad non-mouse copy-mode command surface: navigation, search,
|
||||
# jumps, state toggles, line/end-of-line copies, append, and pipe variants.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
x()
|
||||
{
|
||||
$TMUX send-keys -t copy:0 -X "$@" || fail "copy command failed: $*"
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($TMUX display-message -p -t copy:0 '#{pane_in_mode}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane_in_mode is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
fresh()
|
||||
{
|
||||
$TMUX copy-mode -t copy:0 || exit 1
|
||||
wait_mode 1
|
||||
x history-top
|
||||
x start-of-line
|
||||
}
|
||||
|
||||
select_text()
|
||||
{
|
||||
fresh
|
||||
x begin-selection
|
||||
x cursor-right
|
||||
x cursor-right
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s copy -x40 -y8 \
|
||||
"sh -c 'i=0; while [ \$i -lt 40 ]; do printf \" line-%02d needle (a[b]c) end\\n\" \$i; i=\$((i + 1)); done; printf \"\\nparagraph two needle\\n\\nparagraph three\\n\"; printf \"\\033]133;A\\007prompt-one\\n\\033]133;C\\007output-one\\n\\033]133;A\\007prompt-two\\n\\033]133;C\\007output-two\\n\"; exec sleep 100'" ||
|
||||
exit 1
|
||||
$TMUX set-option -g status off || exit 1
|
||||
$TMUX set-option -g history-limit 200 || exit 1
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
history=$($TMUX display-message -p -t copy:0 '#{history_size}')
|
||||
[ "$history" -ge 30 ] && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "history did not fill"
|
||||
|
||||
fresh
|
||||
|
||||
# Copy-mode-specific format callbacks expose the word, line, hyperlink and
|
||||
# active search match at the cursor.
|
||||
formats=$($TMUX display-message -p -t copy:0 \
|
||||
'#{copy_cursor_word}|#{copy_cursor_line}|#{copy_cursor_hyperlink}|#{search_match}') ||
|
||||
exit 1
|
||||
[ -n "$formats" ] || fail "copy-mode cursor formats were empty"
|
||||
|
||||
# Cursor, viewport and paragraph commands. Each is asserted by successful
|
||||
# dispatch and the final mode-liveness check; detailed word/selection geometry
|
||||
# is covered by the existing copy-mode tests.
|
||||
for command in back-to-indentation bottom-line top-line middle-line \
|
||||
cursor-centre-vertical cursor-centre-horizontal end-of-line history-bottom \
|
||||
history-top halfpage-down halfpage-up page-down page-up next-paragraph \
|
||||
previous-paragraph next-matching-bracket previous-matching-bracket; do
|
||||
x "$command"
|
||||
done
|
||||
x goto-line 15
|
||||
x set-mark
|
||||
x cursor-down
|
||||
x jump-to-mark
|
||||
|
||||
# OSC 133 prompt and output markers support shell-integration navigation.
|
||||
x history-top
|
||||
x next-prompt
|
||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
||||
*prompt-one*) ;;
|
||||
*) fail "next-prompt did not find prompt-one" ;;
|
||||
esac
|
||||
x next-prompt
|
||||
x previous-prompt
|
||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
||||
*prompt-one*) ;;
|
||||
*) fail "previous-prompt did not return to prompt-one" ;;
|
||||
esac
|
||||
x next-prompt -o
|
||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
||||
*output-one*) ;;
|
||||
*) fail "next-prompt -o did not find output-one" ;;
|
||||
esac
|
||||
|
||||
# Character jumps remember their direction and character for repeat/reverse.
|
||||
x jump-forward e
|
||||
x jump-again
|
||||
x jump-reverse
|
||||
x jump-backward e
|
||||
x jump-to-forward e
|
||||
x jump-to-backward e
|
||||
|
||||
# Direct, text and incremental searches cover both directions and the saved
|
||||
# search used by again/reverse.
|
||||
x search-forward needle
|
||||
x search-again
|
||||
x search-reverse
|
||||
x search-backward needle
|
||||
x search-forward-text needle
|
||||
x search-backward-text needle
|
||||
x search-forward-incremental '=needle'
|
||||
x search-forward-incremental '+needle'
|
||||
x search-backward-incremental '=needle'
|
||||
x search-backward-incremental '-needle'
|
||||
|
||||
# Stateful display and behaviour toggles.
|
||||
for command in rectangle-on rectangle-off rectangle-toggle \
|
||||
line-numbers-on line-numbers-off line-numbers-toggle \
|
||||
refresh-on refresh-now refresh-off refresh-toggle \
|
||||
scroll-exit-on scroll-exit-off scroll-exit-toggle toggle-position; do
|
||||
x "$command"
|
||||
done
|
||||
wait_mode 1
|
||||
x cancel
|
||||
wait_mode 0
|
||||
|
||||
# Line and end-of-line copy commands work without an explicit selection.
|
||||
fresh
|
||||
x select-line
|
||||
x copy-selection-and-cancel
|
||||
case "$($TMUX show-buffer)" in
|
||||
*line-00*) ;;
|
||||
*) fail "select-line did not select the current line" ;;
|
||||
esac
|
||||
|
||||
fresh
|
||||
x copy-line
|
||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-line produced an empty buffer"
|
||||
x cancel
|
||||
|
||||
fresh
|
||||
x copy-end-of-line
|
||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-end-of-line produced an empty buffer"
|
||||
x cancel
|
||||
|
||||
# Append variants retain or close the mode as advertised.
|
||||
$TMUX set-buffer -b append-buffer prefix || exit 1
|
||||
select_text
|
||||
x append-selection
|
||||
appended=$($TMUX show-buffer -b append-buffer)
|
||||
case "$appended" in
|
||||
prefix*) ;;
|
||||
*) fail "append-selection produced '$appended'" ;;
|
||||
esac
|
||||
x cancel
|
||||
|
||||
$TMUX set-buffer -b append-cancel-buffer prefix || exit 1
|
||||
select_text
|
||||
x append-selection-and-cancel
|
||||
wait_mode 0
|
||||
appended=$($TMUX show-buffer -b append-cancel-buffer)
|
||||
case "$appended" in
|
||||
prefix*) ;;
|
||||
*) fail "append-selection-and-cancel produced '$appended'" ;;
|
||||
esac
|
||||
|
||||
# The explicit cancel variants close copy mode.
|
||||
fresh
|
||||
x copy-line-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x copy-end-of-line-and-cancel
|
||||
wait_mode 0
|
||||
select_text
|
||||
x copy-selection-and-cancel
|
||||
wait_mode 0
|
||||
|
||||
# Pipe wrappers have distinct command handlers. /dev/null keeps the test
|
||||
# deterministic while exercising asynchronous pipe job creation and cleanup.
|
||||
fresh
|
||||
x copy-pipe-line 'cat >/dev/null'
|
||||
x cancel
|
||||
fresh
|
||||
x copy-pipe-line-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
fresh
|
||||
x copy-pipe-end-of-line 'cat >/dev/null'
|
||||
x cancel
|
||||
fresh
|
||||
x copy-pipe-end-of-line-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
|
||||
select_text
|
||||
x copy-pipe-no-clear 'cat >/dev/null'
|
||||
x pipe-no-clear 'cat >/dev/null'
|
||||
x copy-pipe 'cat >/dev/null'
|
||||
x pipe 'cat >/dev/null'
|
||||
x cancel
|
||||
select_text
|
||||
x copy-pipe-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
select_text
|
||||
x pipe-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
|
||||
# Commands which cancel on downward movement have separate handlers.
|
||||
fresh
|
||||
x history-bottom
|
||||
x bottom-line
|
||||
x cursor-down-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x history-bottom
|
||||
x halfpage-down-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x history-bottom
|
||||
x page-down-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x history-bottom
|
||||
x bottom-line
|
||||
x scroll-down-and-cancel
|
||||
wait_mode 0
|
||||
|
||||
$TMUX has-session -t copy || fail "server died during copy-mode commands"
|
||||
exit 0
|
||||
102
regress/copy-mode-exit-status.sh
Normal file
102
regress/copy-mode-exit-status.sh
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check OSC 133 exit status indicators in copy mode using a real client.
|
||||
|
||||
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"
|
||||
|
||||
fail() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
capture() {
|
||||
$TMUX capture-pane -pS0 -E- >$TMP || exit 1
|
||||
}
|
||||
|
||||
check_grep() {
|
||||
grep -Fq "$1" $TMP || fail "missing pattern: $1"
|
||||
}
|
||||
|
||||
check_no_grep() {
|
||||
grep -Fq "$1" $TMP && fail "unexpected pattern: $1"
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" 0 1 15
|
||||
|
||||
$TMUX2 new-session -d -x80 -y10 \
|
||||
"printf '\033]133;A\007p\$ \033]133;B\007one\n\033]133;C\007out1\n\033]133;D;0\007\033]133;A\007p\$ \033]133;B\007two\n\033]133;C\007out2\n\033]133;D;123\007\033]133;A\007p\$ \033]133;B\007silent\n\033]133;C\007\033]133;D;7\007\033]133;A\007p\$ \033]133;B\007three\n\033]133;C\007out3\n\033]133;D\007\033]133;A\007p\$ \033]133;B\007'; exec sleep 100" || \
|
||||
exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -g copy-mode-position-format '#[align=left]POS' || exit 1
|
||||
$TMUX2 list-keys -Tcopy-mode O | grep -Fq 'fold-view-toggle' || exit 1
|
||||
$TMUX2 list-keys -Tcopy-mode-vi O | grep -Fq 'fold-view-toggle' || exit 1
|
||||
|
||||
$TMUX new-session -d -x80 -y10 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send -l "$TMUX2 attach" || exit 1
|
||||
$TMUX send Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX2 copy-mode -U || exit 1
|
||||
$TMUX2 send -X history-top || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep "!- p\$ two"
|
||||
check_grep "! p\$ silent"
|
||||
check_no_grep "!+ p\$ silent"
|
||||
check_grep "POS"
|
||||
|
||||
# Rebuilding with collapsed output must not expand copy-mode formats before
|
||||
# the viewport has been restored for the shorter backing grid.
|
||||
$TMUX2 send -X collapse-output -a || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep "!+ p\$ two"
|
||||
check_grep "! p\$ silent"
|
||||
check_no_grep "!+ p\$ silent"
|
||||
$TMUX2 send -X line-numbers-on || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep " 3 !+ p\$ two"
|
||||
$TMUX2 send -X fold-view-toggle || exit 1
|
||||
[ "$($TMUX2 display -p '#{copy_fold_view}')" = 0 ] || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep "out2"
|
||||
check_no_grep "!+ p\$ two"
|
||||
check_grep " 3 p\$ two"
|
||||
$TMUX2 send -X fold-view-toggle || exit 1
|
||||
[ "$($TMUX2 display -p '#{copy_fold_view}')" = 1 ] || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep " 3 !- p\$ two"
|
||||
|
||||
# A format wider than the standard three-column gutter is not truncated.
|
||||
$TMUX2 send -X cancel || exit 1
|
||||
$TMUX2 set -g copy-mode-exit-status-format \
|
||||
'#[align=right]#{?exit_status,!#{exit_status}, }' || exit 1
|
||||
$TMUX2 copy-mode -c || exit 1
|
||||
$TMUX2 send -X history-top || exit 1
|
||||
sleep 1
|
||||
capture
|
||||
check_grep "!123+ p\$ two"
|
||||
|
||||
$TMUX2 send -X select-line || exit 1
|
||||
$TMUX2 send -X copy-selection || exit 1
|
||||
selection=$($TMUX2 show-buffer)
|
||||
case $selection in
|
||||
*RC=*) fail "exit status was copied" ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
81
regress/copy-mode-selection-mode.sh
Normal file
81
regress/copy-mode-selection-mode.sh
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Changing an existing character selection to line mode must reset both ends
|
||||
# to complete lines, including when the cursor subsequently crosses the fixed
|
||||
# end. An invalid regular-expression search must also discard marks left by a
|
||||
# previous successful search.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
$TMUX new-session -d -x30 -y6 \
|
||||
"printf 'alpha\nbeta\ngamma\ndelta\n'; exec sleep 100" || exit 1
|
||||
$TMUX set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
# A valid regular-expression search creates marks. Replacing it with an
|
||||
# invalid expression, growing the mode, and scrolling must not leave an old
|
||||
# mark array indexed with the new geometry (the original bug was an ASan heap
|
||||
# overflow in this sequence).
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -X search-forward beta || exit 1
|
||||
[ "$($TMUX display-message -p '#{search_present}')" = 1 ] ||
|
||||
fail "successful search did not create marks"
|
||||
$TMUX send-keys -X search-forward '[' || exit 1
|
||||
$TMUX resize-window -x40 -y8 || exit 1
|
||||
$TMUX send-keys -X scroll-down || exit 1
|
||||
[ "$($TMUX display-message -p '#{search_present}')" = 0 ] ||
|
||||
fail "invalid regular expression left stale marks after resize"
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Select part of alpha through part of beta, then change the existing
|
||||
# selection to line mode. Both lines, and no partial columns, must be copied.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X selection-mode line || exit 1
|
||||
[ "$($TMUX display-message -p '#{selection_mode}')" = line ] ||
|
||||
fail "selection did not change to line mode"
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
expected=$(printf 'alpha\nbeta')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "line-mode selection did not expand to complete lines"
|
||||
|
||||
# Start with the cursor above the fixed end so selection-mode itself takes the
|
||||
# reverse-direction path, then extend it through another line.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X selection-mode line || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
expected=$(printf 'alpha\nbeta\ngamma')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "reversed line-mode selection did not keep complete lines"
|
||||
|
||||
exit 0
|
||||
163
regress/copy-mode-unicode-whitespace.sh
Normal file
163
regress/copy-mode-unicode-whitespace.sh
Normal file
@@ -0,0 +1,163 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
[ -z "$TMUX" ] || $TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
goto_cell()
|
||||
{
|
||||
row=$1
|
||||
column=$2
|
||||
|
||||
$TMUX send-keys -X history-top || fail "$mode: history-top failed"
|
||||
$TMUX send-keys -X start-of-line || fail "$mode: start-of-line failed"
|
||||
[ "$row" -eq 0 ] ||
|
||||
$TMUX send-keys -X -N "$row" cursor-down ||
|
||||
fail "$mode: cursor-down failed"
|
||||
[ "$column" -eq 0 ] ||
|
||||
$TMUX send-keys -X -N "$column" cursor-right ||
|
||||
fail "$mode: cursor-right failed"
|
||||
}
|
||||
|
||||
check_cursor()
|
||||
{
|
||||
expected=$1
|
||||
actual=$($TMUX display-message -p '#{copy_cursor_x},#{copy_cursor_y}')
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected cursor $expected, got $actual"
|
||||
}
|
||||
|
||||
check_word()
|
||||
{
|
||||
row=$1
|
||||
column=$2
|
||||
expected=$3
|
||||
|
||||
goto_cell "$row" "$column"
|
||||
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
|
||||
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
|
||||
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
|
||||
actual=$($TMUX show-buffer 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected word '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
check_next_word()
|
||||
{
|
||||
row=$1
|
||||
expected=$2
|
||||
|
||||
goto_cell "$row" 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: next-word failed"
|
||||
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
|
||||
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
|
||||
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
|
||||
actual=$($TMUX show-buffer 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected next word '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
for mode in emacs vi; do
|
||||
TMUX="$TEST_TMUX -Lunicode-whitespace-$mode-$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x16 -y20 \
|
||||
"printf 'aa bb\naa\tbb\naa\302\240bb\naa\343\200\200bb\naa:bb\naaaaaaaaaaaaaaa\302\240b\nabcdefghijklmnopq\nhardone\nhardtwo\naa\341\232\200bb\naa\342\200\200bb\naa\342\200\250bb\naa\342\200\251bb\naa\342\200\257bb\naa\342\201\237bb\nxa\302\205b c\n'; exec cat" ||
|
||||
fail "$mode: new-session failed"
|
||||
$TMUX set-option -g window-size manual || fail "$mode: set size failed"
|
||||
$TMUX set-window-option -g mode-keys "$mode" ||
|
||||
fail "$mode: set mode-keys failed"
|
||||
$TMUX set-window-option -g word-separators "" ||
|
||||
fail "$mode: clear word-separators failed"
|
||||
$TMUX copy-mode || fail "$mode: copy-mode failed"
|
||||
|
||||
# ASCII space and TAB retain their existing behaviour.
|
||||
goto_cell 0 0
|
||||
$TMUX send-keys -X next-space || fail "$mode: ASCII next-space failed"
|
||||
check_cursor 3,0
|
||||
goto_cell 1 0
|
||||
$TMUX send-keys -X next-space || fail "$mode: TAB next-space failed"
|
||||
check_cursor 8,1
|
||||
$TMUX send-keys -X previous-space ||
|
||||
fail "$mode: TAB previous-space failed"
|
||||
check_cursor 0,1
|
||||
|
||||
# NBSP is whitespace for selection and word movement.
|
||||
goto_cell 2 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: NBSP next-word failed"
|
||||
check_cursor 3,2
|
||||
$TMUX send-keys -X previous-word ||
|
||||
fail "$mode: NBSP previous-word failed"
|
||||
check_cursor 0,2
|
||||
check_word 2 0 aa
|
||||
goto_cell 2 0
|
||||
$TMUX send-keys -X next-word-end ||
|
||||
fail "$mode: NBSP next-word-end failed"
|
||||
if [ "$mode" = emacs ]; then
|
||||
check_cursor 2,2
|
||||
else
|
||||
check_cursor 1,2
|
||||
fi
|
||||
|
||||
# U+3000 is two columns wide; both its leading and padding cells must be
|
||||
# skipped as one whitespace character in either direction.
|
||||
goto_cell 3 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: U+3000 next-word failed"
|
||||
check_cursor 4,3
|
||||
$TMUX send-keys -X previous-word ||
|
||||
fail "$mode: U+3000 previous-word failed"
|
||||
check_cursor 0,3
|
||||
goto_cell 3 0
|
||||
$TMUX send-keys -X next-space-end ||
|
||||
fail "$mode: U+3000 next-space-end failed"
|
||||
if [ "$mode" = emacs ]; then
|
||||
check_cursor 2,3
|
||||
else
|
||||
check_cursor 1,3
|
||||
fi
|
||||
|
||||
# Other characters in word-separators remain literal when space enables
|
||||
# Unicode whitespace matching.
|
||||
$TMUX set-window-option -g word-separators ': ' ||
|
||||
fail "$mode: set custom word-separators failed"
|
||||
goto_cell 4 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: literal next-word failed"
|
||||
check_cursor 2,4
|
||||
$TMUX set-window-option -g word-separators "" ||
|
||||
fail "$mode: restore word-separators failed"
|
||||
|
||||
# Whitespace in the last column remains a boundary across a soft wrap.
|
||||
goto_cell 5 0
|
||||
$TMUX send-keys -X next-space ||
|
||||
fail "$mode: wrapped NBSP next-space failed"
|
||||
check_cursor 0,6
|
||||
|
||||
# A word split only by a soft wrap remains one word, while a hard line
|
||||
# boundary still terminates selection.
|
||||
check_word 7 0 abcdefghijklmnopq
|
||||
check_word 9 0 hardone
|
||||
|
||||
# Check the remaining representative White_Space ranges and U+0085
|
||||
# combined into a cell with another character.
|
||||
check_next_word 11 bb
|
||||
check_next_word 12 bb
|
||||
check_next_word 13 bb
|
||||
check_next_word 14 bb
|
||||
check_next_word 15 bb
|
||||
check_next_word 16 bb
|
||||
goto_cell 17 0
|
||||
$TMUX send-keys -X next-word ||
|
||||
fail "$mode: combined U+0085 next-word failed"
|
||||
check_cursor 2,17
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
done
|
||||
|
||||
exit 0
|
||||
118
regress/find-window.sh
Normal file
118
regress/find-window.sh
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise find-window's content, name and title filters in every combination,
|
||||
# glob/regex and case-insensitive suffixes, plus zoom restoration.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER display-message -p -t find:base.0 '#{pane_mode}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane mode is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
run_find()
|
||||
{
|
||||
label=$1
|
||||
expected=$2
|
||||
shift 2
|
||||
$INNER find-window -t find:base.0 "$@" || fail "$label failed"
|
||||
wait_mode tree-mode
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Fq "$expected" && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "$label did not show '$expected'"
|
||||
$INNER send-keys -t find:base.0 q || exit 1
|
||||
wait_mode ''
|
||||
}
|
||||
|
||||
$INNER new-session -d -s find -n base -x80 -y24 \
|
||||
"sh -c 'printf \"BaseBody\\n\"; exec sleep 100'" || exit 1
|
||||
$INNER split-window -d -h -t find:base \
|
||||
"sh -c 'printf \"OtherBody\\n\"; exec sleep 100'" || exit 1
|
||||
$INNER new-window -d -t find: -n NameNeedle 'exec sleep 100' || exit 1
|
||||
$INNER new-window -d -t find: -n body \
|
||||
"sh -c 'printf \"BodyNeedle\\n\"; exec sleep 100'" || exit 1
|
||||
$INNER new-window -d -t find: -n titled 'exec sleep 100' || exit 1
|
||||
$INNER select-pane -t find:titled.0 -T TitleNeedle || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t find:base" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
# Wait until the body text is in the pane history before searching it.
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t find:body.0 | grep -Fq BodyNeedle && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "pane content was not ready"
|
||||
|
||||
# No flags means C+N+T. Then cover all three pairs and all three singletons.
|
||||
run_find default NameNeedle NameNeedle
|
||||
run_find content-name NameNeedle -C -N NameNeedle
|
||||
run_find content-title titled -C -T TitleNeedle
|
||||
run_find name-title titled -N -T TitleNeedle
|
||||
run_find content-only body -C BodyNeedle
|
||||
run_find name-only NameNeedle -N NameNeedle
|
||||
run_find title-only titled -T TitleNeedle
|
||||
|
||||
# Glob case folding and all regular-expression suffix variants.
|
||||
run_find insensitive NameNeedle -N -i nameneedle
|
||||
run_find regex NameNeedle -N -r '^NameNeedle$'
|
||||
run_find regex-insensitive NameNeedle -N -r -i '^nameneedle$'
|
||||
|
||||
# -Z temporarily zooms the target window and restores its prior state on exit.
|
||||
$INNER find-window -Z -t find:base.0 -N NameNeedle || exit 1
|
||||
wait_mode tree-mode
|
||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 1 ] ||
|
||||
fail "find-window -Z did not zoom"
|
||||
$INNER send-keys -t find:base.0 q || exit 1
|
||||
wait_mode ''
|
||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 0 ] ||
|
||||
fail "find-window -Z did not restore zoom"
|
||||
|
||||
exit 0
|
||||
@@ -199,5 +199,74 @@ must_fail $TMUX resize-pane -t "$id" -x 0
|
||||
must_fail $TMUX resize-pane -t "$id" -y 0
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# --- Tiled pane resize with floating cells in the layout ---
|
||||
|
||||
$TMUX set-option -w -u pane-border-lines || exit 1
|
||||
base=$($TMUX display-message -p '#{pane_id}')
|
||||
|
||||
# A floating cell after the last tiled cell must not become the recipient of a
|
||||
# relative or absolute resize. This is the sequence from GitHub issue 5135.
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 50% -y 50% -X 50% -Y 50% 'sleep 100') ||
|
||||
fail "new-pane for tiled resize test failed"
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for tiled resize test failed"
|
||||
|
||||
$TMUX resize-pane -t "$lower" -U 5 || fail "relative tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 13
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
|
||||
|
||||
$TMUX resize-pane -t "$lower" -y 11 || fail "absolute tiled reset failed"
|
||||
$TMUX resize-pane -t "$lower" -y 16 || fail "absolute tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# A floating cell between tiled siblings must be skipped when finding the cell
|
||||
# which donates space to a resize.
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for middle floating cell test failed"
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 20 -y 8 -X 30 -Y 8 'sleep 100') ||
|
||||
fail "new-pane for middle floating cell test failed"
|
||||
|
||||
$TMUX resize-pane -t "$base" -D 3 || fail "resize past floating cell failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 6
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# A small floating child must not limit the available tiled space in a nested
|
||||
# layout with a different split direction.
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for nested floating cell test failed"
|
||||
right=$($TMUX split-window -dhPF '#{pane_id}' -t "$lower" 'sleep 100') ||
|
||||
fail "horizontal split for nested floating cell test failed"
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$lower" \
|
||||
-x 20 -y 3 -X 30 -Y 10 'sleep 100') ||
|
||||
fail "new-pane for nested floating cell test failed"
|
||||
|
||||
$TMUX resize-pane -t "$base" -D 3 || fail "nested tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$right" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$right" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 1
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$right" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
|
||||
@@ -71,7 +71,10 @@ cursor_very_visible
|
||||
cursor_x
|
||||
cursor_y
|
||||
history_all_bytes
|
||||
history_added
|
||||
history_bytes
|
||||
history_collected
|
||||
history_generation
|
||||
history_limit
|
||||
history_size
|
||||
host
|
||||
@@ -103,6 +106,11 @@ pane_at_right
|
||||
pane_at_top
|
||||
pane_bg
|
||||
pane_bottom
|
||||
pane_command_duration
|
||||
pane_command_end_time
|
||||
pane_command_running
|
||||
pane_command_start_time
|
||||
pane_command_status
|
||||
pane_current_command
|
||||
pane_current_path
|
||||
pane_dead
|
||||
@@ -120,16 +128,21 @@ pane_index
|
||||
pane_input_off
|
||||
pane_key_mode
|
||||
pane_last
|
||||
pane_last_output_time
|
||||
pane_last_prompt_time
|
||||
pane_left
|
||||
pane_marked
|
||||
pane_marked_set
|
||||
pane_modal_flag
|
||||
pane_mode
|
||||
pane_output_generation
|
||||
pane_path
|
||||
pane_pb_progress
|
||||
pane_pb_state
|
||||
pane_pid
|
||||
pane_pipe
|
||||
pane_pipe_pid
|
||||
pane_private_modes
|
||||
pane_right
|
||||
pane_search_string
|
||||
pane_start_command
|
||||
@@ -141,6 +154,8 @@ pane_title
|
||||
pane_top
|
||||
pane_tty
|
||||
pane_unseen_changes
|
||||
pane_unzoomed_height
|
||||
pane_unzoomed_width
|
||||
pane_width
|
||||
pane_x
|
||||
pane_y
|
||||
@@ -206,7 +221,10 @@ window_layout
|
||||
window_linked
|
||||
window_linked_sessions
|
||||
window_linked_sessions_list
|
||||
window_manual_height
|
||||
window_manual_width
|
||||
window_marked_flag
|
||||
window_modal_pane
|
||||
window_name
|
||||
window_offset_x
|
||||
window_offset_y
|
||||
|
||||
@@ -218,11 +218,13 @@ $TMUX set-hook -gu pane-mode-changed || fail "unset pane-mode-changed failed"
|
||||
|
||||
# pane-exited when a pane's command exits with remain-on-exit off.
|
||||
$TMUX set -g @x 0 || fail "set @x failed"
|
||||
$TMUX set-hook -g pane-exited 'set -gF @x "#{hook}:#{hook_pane}"' ||
|
||||
$TMUX set-hook -g pane-exited \
|
||||
'set -gF @x "#{hook}:#{hook_pane}:#{hook_exit_status}:#{hook_exit_success}"' ||
|
||||
fail "set-hook pane-exited failed"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'true') ||
|
||||
fail "split-window true failed"
|
||||
wait_for @x "pane-exited:$pane"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \
|
||||
'trap "" HUP; exec </dev/null >/dev/null 2>&1; sleep 1; exit 42') ||
|
||||
fail "split-window exit 42 failed"
|
||||
wait_for @x "pane-exited:$pane:42:0"
|
||||
$TMUX set-hook -gu pane-exited || fail "unset pane-exited failed"
|
||||
|
||||
# pane-died when a pane's command exits with remain-on-exit on.
|
||||
|
||||
@@ -1,437 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Grid-resident image layers: Kitty input, history, copy and overwrite.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Limage$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -x 20 -y 8 "
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,m=1;/wAA/wD/\033\\'
|
||||
printf '\033_Gm=0;AP8AAP///////w==\033\\'
|
||||
i=0
|
||||
while [ \$i -lt 10 ]; do printf '\nline-%d' \$i; i=\$((i + 1)); done
|
||||
sleep 10"
|
||||
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
|
||||
# Images scroll as grid cells, while capture output contains ordinary spaces.
|
||||
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
|
||||
$TMUX capture-pane -pS- >$TMP || exit 1
|
||||
grep '_G' $TMP >/dev/null && exit 1
|
||||
|
||||
# Copy mode duplicates the backing grid, including image layers.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Ordinary text updates the image underlay through the normal grid write path.
|
||||
# Deleting the image reveals the newly written text.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,i=8;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033[HXY'
|
||||
printf '\033_Ga=d,d=i,q=2,i=8\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# The protocol default is transmit-only; a later placement uses the retained
|
||||
# immutable image and advances by the same canonical one-cell footprint.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Gq=2,f=32,s=1,v=1,c=1,r=1,i=9;/wAA/w==\033\\'
|
||||
printf '\033_Ga=p,q=2,i=9\033\\'
|
||||
printf '\033_Ga=d,d=I,q=2,i=9\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:2 '#{cursor_y}')" = 1 ] || exit 1
|
||||
|
||||
# PNG Kitty input uses the shared image decoder and canonical cell sizing.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Ga=T,q=2,f=100;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURf8AAP///0EdNBEAAAABYktHRAH/Ai3eAAAAB3RJTUUH6ggCDAECH324BwAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 1 ] || exit 1
|
||||
|
||||
# SIXEL input reaches the same grid layer and copy-mode paths.
|
||||
$TMUX new-window -d "cat '$FIXTURE'; sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:4 '#{cursor_y}')" -gt 0 ] || exit 1
|
||||
$TMUX copy-mode -t:4 || exit 1
|
||||
$TMUX send-keys -t:4 -X history-top || exit 1
|
||||
$TMUX capture-pane -pt:4 >$TMP || exit 1
|
||||
|
||||
# A 26-pixel raster occupies two default 16-pixel cells, but the final cell
|
||||
# remains only partially filled instead of stretching the raster to 32 pixels.
|
||||
# A client without an image feature exposes this in the sampled text backend.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# Re-emitting the image preserves the 26-pixel raster rather than expanding
|
||||
# it to the two complete 16-pixel grid cells occupied by the image.
|
||||
$TMUX pipe-pane || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX2 refresh-client -R || exit 1
|
||||
sleep 1
|
||||
grep -a '"1;1;26;26' $TMP >/dev/null || exit 1
|
||||
|
||||
# A selection redraw uses the single-cell path. It must leave ASCII image
|
||||
# cells visible (graphical clients skip these cells to avoid erasing pixels).
|
||||
$TMUX2 copy-mode || exit 1
|
||||
$TMUX2 send-keys -X history-top || exit 1
|
||||
$TMUX2 send-keys -X start-of-line || exit 1
|
||||
$TMUX2 send-keys -N 4 -X cursor-right || exit 1
|
||||
$TMUX2 send-keys -X begin-selection || exit 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX2 send-keys -X cursor-right || exit 1
|
||||
sleep 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
grep -a '"1;1;' $TMP >/dev/null && exit 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# Selection must also leave transparent cells inside the image canvas alone.
|
||||
# This 26-pixel canvas paints only its first pixel column, while the text below
|
||||
# it keeps the copy-mode cursor movable across both occupied terminal cells.
|
||||
$TMUX2 send-keys -X cancel || exit 1
|
||||
$TMUX2 new-window -d "
|
||||
printf 'ab\r'
|
||||
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0~-#0~-#0~-#0~-#0B\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:1 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 copy-mode || exit 1
|
||||
$TMUX2 send-keys -X top-line || exit 1
|
||||
$TMUX2 send-keys -X start-of-line || exit 1
|
||||
$TMUX2 send-keys -X cursor-right || exit 1
|
||||
$TMUX2 send-keys -X begin-selection || exit 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX2 send-keys -X cursor-right || exit 1
|
||||
sleep 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
grep -a 'b' $TMP >/dev/null && exit 1
|
||||
|
||||
# Scrolling moves the terminal's image rows with its text rows. It must not
|
||||
# force tmux to erase and retransmit the complete SIXEL image.
|
||||
$TMUX2 send-keys -X cancel || exit 1
|
||||
SCROLL_WINDOW=$($TMUX2 new-window -P -F '#{window_id}' \
|
||||
"cat '$FIXTURE'; exec sh") || exit 1
|
||||
sleep 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX2 send-keys -t"$SCROLL_WINDOW" -l \
|
||||
"i=0; while [ \$i -lt 12 ]; do printf 'scroll-marker-%d\\n' \$i; i=\$((i + 1)); done" || exit 1
|
||||
$TMUX2 send-keys -t"$SCROLL_WINDOW" Enter || exit 1
|
||||
sleep 1
|
||||
grep -a 'scroll-marker-11' $TMP >/dev/null || exit 1
|
||||
grep -a '"1;1;' $TMP >/dev/null && exit 1
|
||||
|
||||
# A retained Kitty image may be placed repeatedly using source rectangles.
|
||||
# Crop the green and white right column from a red/green/blue/white image.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033_Ga=t,q=2,f=32,s=2,v=2,i=10;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033_Ga=p,q=2,i=10,x=1,y=0,w=1,h=2,c=1,r=2\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "*" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "@" ] || exit 1
|
||||
|
||||
# C=1 places the image without moving the cursor or scrolling. The four-row
|
||||
# image is clipped at the bottom of this four-row pane, leaving its white last
|
||||
# row off screen.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[2;3H'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:1 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{cursor_x},#{cursor_y}')" = "2,1" ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = " -" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " *" ] || exit 1
|
||||
|
||||
# With normal cursor movement, scrolling is calculated from the full image
|
||||
# height. Rows which scrolled above the pane are then cropped from the top, so
|
||||
# the bottom three source rows remain visible and the cursor is on the last row.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[3;1H'
|
||||
printf '\033_Ga=T,q=2,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:2 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{cursor_y}')" = 3 ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "-" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "*" ] || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = "@" ] || exit 1
|
||||
|
||||
# Image rows remain cell-aligned when a narrower terminal causes text
|
||||
# reflow. The ten-column rows are clipped to five columns, not split into four
|
||||
# wrapped rows.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=2,c=10,r=2;/wAA//////8=\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:3 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 5 -y 4 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{window_width}x#{window_height}')" = "5x4" ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "....." ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "@@@@@" ] || exit 1
|
||||
[ -z "$(sed -n 3p $TMP)" ] || exit 1
|
||||
|
||||
# Kitty virtual placements use U+10EEEE placeholder cells. Keep their rows at
|
||||
# fixed coordinates when narrowing the terminal, clipping instead of reflowing
|
||||
# the second half onto the following row.
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 new-window -d "
|
||||
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
|
||||
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:4 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 5 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -n "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP | wc -c)" = 61 ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
# A placement ID supplied with transmit-and-place is reused by a later place.
|
||||
# This is the sequence used by chawan: the newline moves the cursor down before
|
||||
# the second command, so the folder moves down one row without leaving a copy.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=T,f=100,m=0;iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAACmUlEQVR4XmOgF2CEWVRfX8/w//9/BkZGRgy7YeKNjY1kuwtsam1tLQMTE5MG0MBsoEWW6KYBxf8DxScD6UVNTU1kWcZYU1MD0qjLzMx8EmgYJy5TgJb8/ffvn+2vX7+Od3Z2kmwZC9AnIE0FIEuAhl0H4ulA9k9kk4Bi4UB1TkA8H2iRYXl5+Xd2dna8loGCG2Q2LLgZQXED1PEIaLjs379/g4Ds9VDLwQYBxRiAPpFmZWW9DFQjCDRgLxDfJeQloJpdQHPWA9X9A1kGs+gZ0BBJoKQtUOIIeqSDgheoKR6IF5ASZkAH3gKaCzLzFQsxGltaWhjq6uoWguIJ6hi82oCG8wIV+AEdpga0LBfIrmUh1oWg1AYM5iVA9UuI1JMGVDcTiCNIsghkOChIQ0NDwfasXr0ap33QeL8GUgD0HTeIZmIgAUAtYQZqYYZZSKx2oi2CWaKpqbkIhEm1jOg40tLSAjleEBgUUSAGkJ8PpN8Q6yOCFoF8AvQBTvOAqZHh+vXrDPjijGAcIcXJUqBPQOXda5iNQP5rkBiQv5SYYMTrIxUVFZC5IkDCCk8QWQHVgdS8xBeMeBNDe3s7A7CwfQl0uSmwKDIBYhdQGQbCIDZIDCQHUgNSS7ZFII2gUgEU6UADzwJz+kUgDa6zoOyzIDmoGgaKLAJpBheKkArxC7BIeQDCQPEvIEuJrQyJzkdQA38ADbcHYaBFP0ipcVGqCWDYBwPxOkYs1TkDCeA/pEkQBDRnLZD9GKhVjgUkCGTsBgomAekWIJYG4p8MlAFQrZgJMgJo/m4QzQJ1fT9QIBLI1gTiSRRaAm/gAM38DjSvH2QeuHECLW1xNk7IsRhoyXGgJVOBem+AExPMEHzNLTItYiAlVTJQCwAAJxkMkgbHo2UAAAAASUVORK5CYII=\033\\'
|
||||
printf '\n'
|
||||
sleep 1
|
||||
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=p;\033\\'
|
||||
printf '\n'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -n "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
# A nonzero Kitty placement ID identifies one placement of an image. Reusing
|
||||
# the same image and placement IDs moves it rather than leaving the old cells.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=11;/////w==\033\\'
|
||||
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
|
||||
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
|
||||
|
||||
# Deleting one placement ID leaves other placements of the image intact.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=12;/////w==\033\\'
|
||||
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=12,p=7,c=2,r=1\033\\'
|
||||
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=12,p=8,c=2,r=1\033\\'
|
||||
printf '\033_Ga=d,d=i,q=2,i=12,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
|
||||
|
||||
# Image layers retain the cells beneath them. Deleting a transparent image
|
||||
# must reveal the original text rather than replacing it with spaces.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf 'XY'
|
||||
printf '\033[H'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=1,c=2,r=1,i=13,p=7;////AA==\033\\'
|
||||
printf '\033_Ga=d,d=i,q=2,i=13,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 capture-pane -pS0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# Text written over a Kitty placement updates its underlay without deleting
|
||||
# the image. Deleting the placement afterwards reveals the updated text.
|
||||
TEXT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf 'test\r'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=2,v=2,c=2,r=2,i=14,p=7;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\r'
|
||||
printf 'test\n'
|
||||
printf 'test\n'
|
||||
$TEST_TMUX wait-for image-text-delete-$$
|
||||
printf '\033_Ga=d,d=i,q=2,i=14,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$TEXT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = ".*st" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = " @st" ] || exit 1
|
||||
$TMUX2 wait-for -S image-text-delete-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "test" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "test" ] || exit 1
|
||||
|
||||
# Kitty z-indexes and SIXEL's temporal plane are properties of the input, not
|
||||
# of the outer terminal. Exercise the logical scene through the fallback
|
||||
# backend: higher nonnegative z-indexes cover lower ones and text, ordinary
|
||||
# negative z-indexes cover backgrounds but not text, and very negative ones
|
||||
# remain below the background.
|
||||
Z_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=21;/wAA/w==\033\\'
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=22;/////w==\033\\'
|
||||
printf 'X\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=1,z=2,c=1,r=1\033\\'
|
||||
printf '\033_Ga=p,q=2,C=1,i=22,p=2,z=1,c=1,r=1\033\\'
|
||||
printf '\033[2;1H\033[41m \033[0m\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=3,z=-1,c=1,r=1\033\\'
|
||||
printf '\033[3;1HY\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=4,z=-1,c=1,r=1\033\\'
|
||||
printf '\033[4;1H\033[41m \033[0m\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=5,z=-1073741825,c=1,r=1\033\\'
|
||||
$TEST_TMUX wait-for image-z-top-$$
|
||||
printf '\033_Ga=d,d=z,q=2,z=2\033\\'
|
||||
$TEST_TMUX wait-for image-z-lower-$$
|
||||
printf '\033_Ga=d,d=z,q=2,z=1\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$Z_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "." ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "." ] || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = "Y" ] || exit 1
|
||||
[ -z "$(sed -n 4p $TMP)" ] || exit 1
|
||||
$TMUX2 wait-for -S image-z-top-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "@" ] || exit 1
|
||||
$TMUX2 wait-for -S image-z-lower-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "X" ] || exit 1
|
||||
|
||||
# A weighted median at the maximum channel level must still leave colours on
|
||||
# both sides of the split. This skewed black, grey and white image used to stop
|
||||
# palette generation after one colour instead of producing three.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 20 -y 4 "
|
||||
printf '\033_Ga=T,q=2,C=1,f=24,s=100,v=1,c=10,r=1;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 20 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
grep -a '#2;2;' $TMP >/dev/null || exit 1
|
||||
|
||||
# A pane redraw on a SIXEL terminal must not clear the status line, which is
|
||||
# outside the window scene and may not be redrawn after the pane scrolls.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 20 -y 8 || exit 1
|
||||
$TMUX2 set -g 'status-format[0]' 'STATUS' || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 20 -y 8 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -l 'seq 30' || exit 1
|
||||
$TMUX2 send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
[ "$(tail -n 1 $TMP)" = "STATUS" ] || exit 1
|
||||
|
||||
# A partial redraw of a tiled pane must clear only its visible scene spans.
|
||||
# Clearing its entire rectangle would erase a floating pane above it, while
|
||||
# the tiled pane redraw correctly skips the obscured area and cannot restore it.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 40 -y 15 \
|
||||
"printf 'TILED PANE'; sleep 10" || exit 1
|
||||
TILED_PANE=$($TMUX2 display-message -p '#{pane_id}') || exit 1
|
||||
FLOATING_PANE=$($TMUX2 new-pane -dP -F '#{pane_id}' -x 20 -y 6 -X 10 -Y 4 \
|
||||
"printf 'FLOATING PANE'; sleep 10") || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX2 select-pane -t"$FLOATING_PANE" || exit 1
|
||||
$TMUX new-session -d -x 40 -y 15 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX2 copy-mode -t"$TILED_PANE" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
grep 'TILED PANE' $TMP >/dev/null || exit 1
|
||||
grep 'FLOATING PANE' $TMP >/dev/null || exit 1
|
||||
|
||||
# An obscured pane is normally redrawn a line at a time when it scrolls, but
|
||||
# that path cannot restore graphical image layers. It must defer to the scene
|
||||
# redraw so the visible part of the image is emitted around the floating pane.
|
||||
IMAGE_PANE=$($TMUX2 new-window -dP -F '#{pane_id}' \
|
||||
"cat '$FIXTURE'; exec sh") || exit 1
|
||||
$TMUX2 set -g pane-border-status top || exit 1
|
||||
$TMUX2 set -pt"$IMAGE_PANE" pane-border-format 'IMAGE STATUS' || exit 1
|
||||
FLOATING_PANE=$($TMUX2 new-pane -dP -F '#{pane_id}' -x 20 -y 6 -X 10 -Y 4 \
|
||||
"printf 'FLOATING PANE'; sleep 10") || exit 1
|
||||
$TMUX2 set -pt"$FLOATING_PANE" pane-border-format 'FLOAT STATUS' || exit 1
|
||||
$TMUX2 select-pane -t"$FLOATING_PANE" || exit 1
|
||||
sleep 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX2 send-keys -t"$IMAGE_PANE" -l \
|
||||
"i=0; while [ \$i -lt 5 ]; do printf 'image-scroll-%d\\n' \$i; i=\$((i + 1)); done" || exit 1
|
||||
$TMUX2 send-keys -t"$IMAGE_PANE" Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
grep -a 'image-scroll-4' $TMP >/dev/null || exit 1
|
||||
grep -a '"1;1;' $TMP >/dev/null || exit 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
grep 'IMAGE STATUS' $TMP >/dev/null || exit 1
|
||||
grep 'FLOAT STATUS' $TMP >/dev/null || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -5,9 +5,12 @@
|
||||
start_pane alternate 10 3 'MAIN\033[?1049hALT\033[?1049lZ\n'
|
||||
check_capture alternate 'MAINZ'
|
||||
|
||||
start_pane osc133 20 8 'xx\033]133;A\007p>\033]133;B\007cmd\nxy\033]133;P;k=s\007more\nzz\033]133;C\007out\033]133;D;7\007\nq\033]133;C\007bad\033]133;D;-1\007\nqq\033]133;C\007big\033]133;D;300\007\nzzz\033]133;C\007ok\033]133;D\007\n'
|
||||
start_pane osc133 20 12 'xx\033]133;A\007p>\033]133;B\007cmd\nxy\033]133;P;k=s\007more\nxz\033]133;A;k=s\007more\nxw\033]133;P;k=c\007more\nxv\033]133;A;k=c\007more\nzz\033]133;C\007out\033]133;D;7\007\nq\033]133;C\007bad\033]133;D;-1\007\nqq\033]133;C\007big\033]133;D;300\007\nzzz\033]133;C\007ok\033]133;D\007\n'
|
||||
check_capture osc133 'xxp>cmd
|
||||
xymore
|
||||
xzmore
|
||||
xwmore
|
||||
xvmore
|
||||
zzout
|
||||
qbad
|
||||
qqbig
|
||||
@@ -15,10 +18,13 @@ zzzok'
|
||||
check_raw_matches osc133 \
|
||||
'L 0 \(0\) flags=START_PROMPT,START_COMMAND\[[0-9a-f]+\].* osc133=2,4,0,0,0' \
|
||||
'L 1 \(1\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
|
||||
'L 2 \(2\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,2,5,7' \
|
||||
'L 3 \(3\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,1,4,255' \
|
||||
'L 4 \(4\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,2,5,255' \
|
||||
'L 5 \(5\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,3,5,0'
|
||||
'L 2 \(2\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
|
||||
'L 3 \(3\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
|
||||
'L 4 \(4\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
|
||||
'L 5 \(5\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,2,5,7' \
|
||||
'L 6 \(6\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,1,4,255' \
|
||||
'L 7 \(7\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,2,5,255' \
|
||||
'L 8 \(8\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,3,5,0'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
|
||||
126
regress/layout-set.sh
Normal file
126
regress/layout-set.sh
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise every predefined layout, next/previous cycling, lookup and restore
|
||||
# paths, one-pane early returns, minimum-size handling and floating panes.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
||||
SEEN="$DIR/layouts"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
layout()
|
||||
{
|
||||
$TMUX display-message -p -t layouts:many '#{window_layout}'
|
||||
}
|
||||
|
||||
check_layout()
|
||||
{
|
||||
name=$1
|
||||
$TMUX select-layout -t layouts:many "$name" ||
|
||||
fail "select-layout $name failed"
|
||||
value=$(layout)
|
||||
[ -n "$value" ] || fail "$name produced an empty layout"
|
||||
if grep -Fxq "$value" "$SEEN"; then
|
||||
fail "$name produced the same layout as an earlier preset"
|
||||
fi
|
||||
printf '%s\n' "$value" >>"$SEEN"
|
||||
[ "$($TMUX list-panes -t layouts:many -F x | wc -l)" -eq 5 ] ||
|
||||
fail "$name lost a pane"
|
||||
[ "$($TMUX list-panes -t layouts:many -F '#{pane_floating_flag}' | \
|
||||
grep -c '^1$')" -eq 1 ] || fail "$name did not preserve the floating pane"
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s layouts -n many -x100 -y40 'exec sleep 100' || exit 1
|
||||
$TMUX set-option -g window-size manual || exit 1
|
||||
$TMUX split-window -d -h -t layouts:many 'exec sleep 100' || exit 1
|
||||
$TMUX split-window -d -v -t layouts:many 'exec sleep 100' || exit 1
|
||||
$TMUX split-window -d -h -t layouts:many 'exec sleep 100' || exit 1
|
||||
floating=$($TMUX new-pane -dP -F '#{pane_id}' -t layouts:many \
|
||||
-x20 -y8 -X10 -Y5 'exec sleep 100') || exit 1
|
||||
: >"$SEEN"
|
||||
|
||||
# Options exercise percentage and explicit secondary-size calculations.
|
||||
$TMUX set-option -w -t layouts:many main-pane-height 30% || exit 1
|
||||
$TMUX set-option -w -t layouts:many other-pane-height 12 || exit 1
|
||||
$TMUX set-option -w -t layouts:many main-pane-width 35% || exit 1
|
||||
$TMUX set-option -w -t layouts:many other-pane-width 20 || exit 1
|
||||
|
||||
for name in even-horizontal even-vertical main-horizontal \
|
||||
main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do
|
||||
check_layout "$name"
|
||||
done
|
||||
|
||||
# Exact and unique-prefix lookup work; ambiguous and invalid names fail.
|
||||
$TMUX select-layout -t layouts:many even-h || fail "unique layout prefix failed"
|
||||
$TMUX select-layout -t layouts:many main >/dev/null 2>&1 &&
|
||||
fail "ambiguous layout prefix succeeded"
|
||||
$TMUX select-layout -t layouts:many no-such-layout >/dev/null 2>&1 &&
|
||||
fail "invalid layout name succeeded"
|
||||
|
||||
# Cycle through all layouts in both directions, including wraparound. The
|
||||
# aliases and select-layout flags share layout_set_next/previous.
|
||||
first=$(layout)
|
||||
i=0
|
||||
while [ "$i" -lt 7 ]; do
|
||||
$TMUX next-layout -t layouts:many || exit 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$(layout)" = "$first" ] || fail "next-layout did not wrap"
|
||||
i=0
|
||||
while [ "$i" -lt 7 ]; do
|
||||
$TMUX previous-layout -t layouts:many || exit 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$(layout)" = "$first" ] || fail "previous-layout did not wrap"
|
||||
$TMUX select-layout -t layouts:many -n || exit 1
|
||||
$TMUX select-layout -t layouts:many -p || exit 1
|
||||
|
||||
# No argument reapplies the last preset; -o restores the previous layout; -E
|
||||
# spreads the current cell without changing the pane set.
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX select-layout -t layouts:many tiled || exit 1
|
||||
tiled=$(layout)
|
||||
$TMUX select-layout -t layouts:many even-horizontal || exit 1
|
||||
$TMUX select-layout -t layouts:many -o || exit 1
|
||||
[ "$(layout)" = "$tiled" ] || fail "select-layout -o did not restore layout"
|
||||
$TMUX select-layout -t layouts:many || exit 1
|
||||
$TMUX select-layout -t layouts:many -E || exit 1
|
||||
|
||||
# Each arranger has a deliberate one-pane early return.
|
||||
$TMUX new-window -d -t layouts: -n one 'exec sleep 100' || exit 1
|
||||
one=$($TMUX display-message -p -t layouts:one '#{window_layout}')
|
||||
for name in even-horizontal even-vertical main-horizontal \
|
||||
main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do
|
||||
$TMUX select-layout -t layouts:one "$name" || exit 1
|
||||
[ "$($TMUX display-message -p -t layouts:one '#{window_layout}')" = "$one" ] ||
|
||||
fail "$name changed a one-pane layout"
|
||||
done
|
||||
|
||||
# Force the minimum-size paths without requiring a terminal client.
|
||||
$TMUX resize-window -t layouts:many -x10 -y6 || exit 1
|
||||
for name in even-horizontal even-vertical main-horizontal \
|
||||
main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do
|
||||
$TMUX select-layout -t layouts:many "$name" || exit 1
|
||||
done
|
||||
|
||||
$TMUX has-session -t layouts || fail "server died during layout tests"
|
||||
exit 0
|
||||
46
regress/list-commands.sh
Normal file
46
regress/list-commands.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise list-commands for the full table, a single command, aliases, custom
|
||||
# and empty formats, and lookup errors.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
# Keep a server alive across commands. Otherwise a STARTSERVER-only command
|
||||
# may exit between invocations and leave a short socket-cleanup race.
|
||||
$TMUX new-session -d -s list-commands 'exec sleep 100' || exit 1
|
||||
|
||||
all=$($TMUX list-commands) || exit 1
|
||||
printf '%s\n' "$all" | grep -q '^attach-session (attach)' || exit 1
|
||||
printf '%s\n' "$all" | grep -q '^list-commands (lscm)' || exit 1
|
||||
|
||||
one=$($TMUX list-commands -F \
|
||||
'#{command_list_name}|#{command_list_alias}|#{command_list_usage}' \
|
||||
list-commands) || exit 1
|
||||
case "$one" in
|
||||
'list-commands|lscm|'*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
|
||||
# Aliases resolve to their command, while an empty expansion prints no line.
|
||||
[ "$($TMUX list-commands -F '#{command_list_name}' lscm)" = list-commands ] ||
|
||||
exit 1
|
||||
[ -z "$($TMUX list-commands -F '' list-commands)" ] || exit 1
|
||||
|
||||
$TMUX list-commands tmux-no-command-$$ >/dev/null 2>&1 && exit 1
|
||||
$TMUX list-commands list >/dev/null 2>&1 && exit 1
|
||||
exit 0
|
||||
@@ -105,6 +105,24 @@ drag()
|
||||
sleep 1
|
||||
}
|
||||
|
||||
meta_drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<8;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<40;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<8;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
check_ok new-session -d -s modal -x 80 -y 24 'cat'
|
||||
@@ -127,12 +145,16 @@ case "$(fmt "$modal" '#{pane_flags}')" in
|
||||
*O*) ;;
|
||||
*) fail "modal pane flags do not include O" ;;
|
||||
esac
|
||||
case "$(fmt "$modal" '#{pane_flags}')" in
|
||||
*A*) ;;
|
||||
*) fail "modal pane flags do not include A" ;;
|
||||
esac
|
||||
case "$(fmt modal:0 '#{window_flags}')" in
|
||||
*O*) ;;
|
||||
*) fail "modal window flags do not include O" ;;
|
||||
esac
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 0
|
||||
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
|
||||
|
||||
check_fail "window already has a modal pane" \
|
||||
new-pane -O -x 10 -y 4 'cat'
|
||||
@@ -159,12 +181,14 @@ under=$($TMUX split-window -PF '#{pane_id}' -t "$p0" 'cat') ||
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{pane_id}')" "$modal"
|
||||
must_equal "$(fmt "$under" '#{pane_active}')" 0
|
||||
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
|
||||
|
||||
float=$($TMUX new-pane -PF '#{pane_id}' -x 10 -y 4 -X 5 -Y 3 'cat') ||
|
||||
fail "new floating pane under modal failed"
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{pane_id}')" "$modal"
|
||||
must_equal "$(fmt "$float" '#{pane_active}')" 0
|
||||
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
|
||||
|
||||
check_ok new-window -d -t modal: -n other 'cat'
|
||||
check_ok select-window -t modal:other
|
||||
@@ -303,6 +327,164 @@ check_ok kill-pane -t "$modal"
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
must_equal "$(fmt modal:0 '#{pane_id}')" "$p0"
|
||||
must_equal "$(fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 0:0
|
||||
|
||||
ignored=$($TMUX new-pane -KdPF '#{pane_id}' -t "$p0" 'cat') ||
|
||||
fail "new-pane -K without -O failed"
|
||||
check_ok kill-pane -t "$ignored"
|
||||
|
||||
$TMUX set -g @modal-prefix no
|
||||
$TMUX set -g @modal-root no
|
||||
$TMUX bind -n z set -g @modal-root yes
|
||||
|
||||
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||
fail "new-pane -OK failed"
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||
sleep 1
|
||||
must_equal "$($TMUX show -gv @modal-prefix)" no
|
||||
must_equal "$($TMUX show -gv @modal-root)" no
|
||||
case "$($TMUX capture-pane -pt "$modal")" in
|
||||
*xz*) ;;
|
||||
*) fail "keys did not reach key-capturing modal pane" ;;
|
||||
esac
|
||||
left=$(fmt "$modal" '#{pane_left}')
|
||||
top=$(fmt "$modal" '#{pane_top}')
|
||||
meta_drag $((left + 2)) $((top + 2)) $((left + 7)) $((top + 4))
|
||||
new_left=$(fmt "$modal" '#{pane_left}')
|
||||
new_top=$(fmt "$modal" '#{pane_top}')
|
||||
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||
fail "key-capturing modal pane did not move"
|
||||
check_ok kill-pane -t "$modal"
|
||||
sleep 1
|
||||
|
||||
# A nonmodal floating pane may remain above zoom, and switching between it and
|
||||
# the zoomed tiled pane must not unzoom the window.
|
||||
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
|
||||
base=$(fmt modal:float-over-zoom '#{pane_id}')
|
||||
check_ok split-window -dh -t "$base" 'cat'
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
over=$($TMUX new-pane -APF '#{pane_id}' -t "$base" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||
fail "new-pane -A failed"
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_active}')" 1:1
|
||||
case "$(fmt "$over" '#{pane_flags}')" in
|
||||
*A*) ;;
|
||||
*) fail "float-over-zoom pane flags do not include A" ;;
|
||||
esac
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
check_ok select-pane -t "$base"
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
check_ok select-pane -t "$over"
|
||||
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
client=$($TMUX list-clients -F '#{client_name}' | head -1)
|
||||
[ -n "$client" ] || fail "no client for switch-client test"
|
||||
check_ok switch-client -c "$client" -t "$base"
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
check_ok switch-client -c "$client" -t "$over"
|
||||
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
check_ok kill-pane -t "$over"
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
|
||||
ignored=$($TMUX new-pane -ALdPF '#{pane_id}' -t "$base" 'cat') ||
|
||||
fail "new-pane -A -L failed"
|
||||
case "$(fmt "$ignored" '#{pane_flags}')" in
|
||||
*A*) fail "tiled pane flags include A" ;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
# Existing floating panes are filtered when zoom begins: -A panes remain in the
|
||||
# visible layout and ordinary floating panes do not.
|
||||
check_ok new-window -d -t modal: -n existing-over-zoom 'cat'
|
||||
base=$(fmt modal:existing-over-zoom '#{pane_id}')
|
||||
check_ok split-window -dh -t "$base" 'cat'
|
||||
over=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||
fail "pre-existing new-pane -A failed"
|
||||
under=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 15 -y 4 -X 2 -Y 2 'cat') ||
|
||||
fail "pre-existing ordinary new-pane failed"
|
||||
check_ok select-window -t modal:existing-over-zoom
|
||||
check_ok select-pane -t "$base"
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}')" 1
|
||||
must_equal "$(fmt "$under" '#{pane_floating_flag}')" 0
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
|
||||
# Geometry changed in the visible zoom layout is copied back when unzooming.
|
||||
check_ok select-pane -t "$over"
|
||||
left=$(fmt "$over" '#{pane_left}')
|
||||
top=$(fmt "$over" '#{pane_top}')
|
||||
meta_drag $((left + 2)) $((top + 2)) $((left + 7)) $((top + 4))
|
||||
new_left=$(fmt "$over" '#{pane_left}')
|
||||
new_top=$(fmt "$over" '#{pane_top}')
|
||||
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||
fail "float-over-zoom pane did not move"
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
must_equal "$(fmt "$over" '#{pane_left}:#{pane_top}')" \
|
||||
"$new_left:$new_top"
|
||||
must_equal "$(fmt "$under" '#{pane_floating_flag}')" 1
|
||||
|
||||
# Resizing with the over-zoom pane active restores the original zoom target.
|
||||
check_ok resize-pane -Z -t "$base"
|
||||
check_ok select-pane -t "$over"
|
||||
check_ok resize-window -t modal:existing-over-zoom -x 90 -y 30
|
||||
must_equal "$(fmt "$base" \
|
||||
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}')" \
|
||||
90x30:1:1
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_active}')" 1:1
|
||||
|
||||
# Natural pane exit uses a different removal path from kill-pane and must also
|
||||
# preserve zoom.
|
||||
dying=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
|
||||
-x 12 -y 4 -X 4 -Y 3 'true') ||
|
||||
fail "short-lived new-pane -A failed"
|
||||
i=0
|
||||
while $TMUX list-panes -a -F '#{pane_id}' | grep -qx "$dying"; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && fail "short-lived float-over-zoom pane did not exit"
|
||||
sleep 0.1
|
||||
done
|
||||
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
|
||||
|
||||
# A pane with -A is also above a zoom target which was itself floating. The
|
||||
# temporary tiled target must sit behind retained floating panes, then return
|
||||
# to the normal floating z order when unzoomed.
|
||||
check_ok new-window -d -t modal: -n floating-zoom-target 'cat'
|
||||
base=$(fmt modal:floating-zoom-target '#{pane_id}')
|
||||
check_ok split-window -dh -t "$base" 'cat'
|
||||
target=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 30 -y 10 -X 10 -Y 5 'cat') ||
|
||||
fail "floating zoom target creation failed"
|
||||
over=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
|
||||
-x 15 -y 5 -X 15 -Y 8 'cat') ||
|
||||
fail "float-over-zoom pane creation failed"
|
||||
check_ok select-window -t modal:floating-zoom-target
|
||||
check_ok select-pane -t "$target"
|
||||
must_equal "$(fmt "$target" '#{pane_z}')" 0
|
||||
must_equal "$(fmt "$over" '#{pane_z}')" 1
|
||||
|
||||
check_ok resize-pane -Z -t "$target"
|
||||
must_equal "$(fmt "$target" \
|
||||
'#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_floating_flag}:#{pane_z}')" \
|
||||
1:1:0:2
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_z}')" 1:0
|
||||
check_ok select-pane -t "$over"
|
||||
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
|
||||
check_ok resize-pane -Z -t "$target"
|
||||
must_equal "$(fmt "$over" '#{pane_active}:#{pane_z}')" 1:0
|
||||
must_equal "$(fmt "$target" '#{pane_floating_flag}:#{pane_z}')" 1:1
|
||||
|
||||
# If the target remains active, it returns to the front on unzoom.
|
||||
check_ok select-pane -t "$target"
|
||||
check_ok resize-pane -Z -t "$target"
|
||||
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_z}')" 1:0
|
||||
check_ok resize-pane -Z -t "$target"
|
||||
must_equal "$(fmt "$target" '#{pane_active}:#{pane_z}')" 1:0
|
||||
must_equal "$(fmt "$over" '#{pane_z}')" 1
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
|
||||
0
regress/mode-kill.sh
Executable file → Normal file
0
regress/mode-kill.sh
Executable file → Normal file
File diff suppressed because one or more lines are too long
168
regress/output-commands.sh
Normal file
168
regress/output-commands.sh
Normal file
@@ -0,0 +1,168 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -L${TEST_SOCKET:-testO$$} -f/dev/null"
|
||||
OUT=/tmp/tmux-output-commands-$$
|
||||
EXPECTED=$(printf 'one\ntwo')
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -f "$OUT"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\ntwo\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007broken\\n\\033]133;C\\007unfinished\"; exec sleep 100'" || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
|
||||
$TMUX send-keys -X pipe-output "wc -c >$OUT" || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 10 ] || exit 1
|
||||
|
||||
$TMUX send-keys -X copy-pipe-output "wc -c >$OUT" output || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 10 ] || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
|
||||
$TMUX send-keys -X select-output || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward separator || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
copied=$($TMUX show-buffer)
|
||||
[ "$copied" = "$EXPECTED" ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward one || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
copied=$($TMUX show-buffer)
|
||||
[ "$copied" = "$EXPECTED" ] || exit 1
|
||||
|
||||
$TMUX send-keys -X pipe-output "wc -l >$OUT" || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 2 ] || exit 1
|
||||
|
||||
$TMUX send-keys -X copy-pipe-output "wc -l >$OUT" output || exit 1
|
||||
sleep 1
|
||||
[ "$(cat "$OUT")" = 2 ] || exit 1
|
||||
copied=$($TMUX show-buffer)
|
||||
[ "$copied" = "$EXPECTED" ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -c || exit 1
|
||||
$TMUX send-keys -X search-backward echo || exit 1
|
||||
$TMUX send-keys -X select-output || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
selected=$($TMUX show-buffer)
|
||||
[ "$selected" = "$EXPECTED" ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX set-buffer -b keep unchanged || exit 1
|
||||
$TMUX copy-mode -U || exit 1
|
||||
$TMUX send-keys -X search-backward unfinished || exit 1
|
||||
$TMUX send-keys -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n plain "printf 'alpha\\nbeta\\n'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-output -a || exit 1
|
||||
all=$($TMUX show-buffer)
|
||||
case "$all" in
|
||||
*alpha*beta*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
$TMUX set-buffer -b keep unchanged || exit 1
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer -b keep)" = unchanged ] || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X select-output || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X select-output -a || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-selection || exit 1
|
||||
all=$($TMUX show-buffer)
|
||||
case "$all" in
|
||||
*alpha*beta*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n empty "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007true\\033]133;C\\033]133;D;0\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -t :empty || exit 1
|
||||
$TMUX send-keys -t :empty.0 -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :empty.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n prompt "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -U -t :prompt || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X copy-output || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -c -t :prompt || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X expand-output || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X -N 10 cursor-down || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X select-output || exit 1
|
||||
[ "$($TMUX display-message -p -t :prompt.0 '#{selection_present}')" = 1 ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n below "printf '\\033]133;A\\007p\\$ \\033]133;B\\007old\\n\\033]133;C\\007old1\\nold2\\nold3\\nold4\\nold5\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007ps\\n\\033]133;C\\007PID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -c -t :below || exit 1
|
||||
$TMUX send-keys -t :below.0 -X search-backward ps || exit 1
|
||||
$TMUX send-keys -t :below.0 -X expand-output || exit 1
|
||||
$TMUX send-keys -t :below.0 -X -N 100 cursor-down || exit 1
|
||||
$TMUX send-keys -t :below.0 C-o || exit 1
|
||||
[ "$($TMUX display-message -p -t :below.0 '#{selection_present}')" = 1 ] || exit 1
|
||||
$TMUX send-keys -t :below.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\\n1 pts/0')" ] || exit 1
|
||||
$TMUX send-keys -t :below.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n clear "printf 'old1\\nold2\\nold3\\nold4\\nold5\\nold6\\n\\033]133;A\\007p\\$ \\033]133;B\\007echo 1; clear; ps\\n\\033]133;C\\0071\\n\\033[H\\033[2JPID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -t :clear || exit 1
|
||||
$TMUX send-keys -t :clear.0 C-o || exit 1
|
||||
$TMUX send-keys -t :clear.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\\n1 pts/0')" ] || exit 1
|
||||
$TMUX send-keys -t :clear.0 -X cancel || exit 1
|
||||
|
||||
$TMUX new-window -d -n same "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX copy-mode -U -t :same || exit 1
|
||||
$TMUX send-keys -t :same.0 -X search-backward one || exit 1
|
||||
$TMUX send-keys -t :same.0 -X select-output || exit 1
|
||||
$TMUX send-keys -t :same.0 -X copy-selection || exit 1
|
||||
[ "$($TMUX show-buffer)" = one ] || exit 1
|
||||
$TMUX send-keys -t :same.0 -X cancel || exit 1
|
||||
|
||||
$TMUX copy-mode -t :plain || exit 1
|
||||
$TMUX send-keys -t :plain.0 -X copy-output -a || exit 1
|
||||
all=$($TMUX show-buffer)
|
||||
case "$all" in
|
||||
*alpha*beta*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
$TMUX send-keys -t :plain.0 -X cancel || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -19,7 +19,10 @@
|
||||
# - respawn-pane/respawn-window refusing a live pane without -k, working on a
|
||||
# dead pane (remain-on-exit) and killing with -k;
|
||||
# - resize-pane -x/-y in cells and percent, -L/-R/-U/-D adjustments and -Z
|
||||
# zoom/unzoom (including implicit unzoom on split).
|
||||
# zoom/unzoom, layout round trips, zoom target selection, pane removal,
|
||||
# window resizing and implicit unzoom on layout changes;
|
||||
# - swap-pane preserving one or two zoomed windows, including cross-window
|
||||
# swaps of hidden panes and zoom targets.
|
||||
#
|
||||
# window-ops.sh covers window-level commands and buffers.sh paste buffers.
|
||||
|
||||
@@ -144,7 +147,8 @@ check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
|
||||
# break-pane and join-pane.
|
||||
|
||||
# break-pane moves a pane to a new window; -P -F prints where it went and -n
|
||||
# names the new window.
|
||||
# names the new window. Moving a pane out of a zoomed window unzooms it first.
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
out=$($TMUX break-pane -d -P -F '#{window_index}:#{pane_id}' -n broken \
|
||||
-s "$p1" -t P:)
|
||||
if [ "$out" != "1:$p1" ]; then
|
||||
@@ -153,10 +157,14 @@ if [ "$out" != "1:$p1" ]; then
|
||||
fi
|
||||
check_fmt 'P:1' '#{window_name}:#{window_panes}' 'broken:1'
|
||||
check_fmt 'P:0' '#{window_panes}' '3'
|
||||
check_fmt 'P:0' '#{window_zoomed_flag}' '0'
|
||||
|
||||
# join-pane -v moves it back (the source window, left empty, is destroyed).
|
||||
# join-pane -v moves it back (the source window, left empty, is destroyed) and
|
||||
# also unzooms the destination before changing its layout.
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok join-pane -d -v -s P:broken.0 -t "$p2"
|
||||
check_fmt 'P:0' '#{window_panes}' '4'
|
||||
check_fmt 'P:0' '#{window_zoomed_flag}' '0'
|
||||
if $TMUX has-session -t P:broken 2>/dev/null; then
|
||||
echo "Window 'broken' still exists after join-pane."
|
||||
exit 1
|
||||
@@ -299,6 +307,122 @@ check_fmt 'P:0' '#{window_panes}' '5'
|
||||
p6=$($TMUX display-message -p -t P:0.2 '#{pane_id}')
|
||||
check_ok kill-pane -t "$p6"
|
||||
|
||||
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
|
||||
# without -Z unzooms, while -Z transfers zoom to the selected pane.
|
||||
layout=$($TMUX display-message -p -t P:0 '#{window_layout}')
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -Z -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
|
||||
# Directional selection temporarily restores the full layout to find its
|
||||
# neighbour, then follows the same unzoom or -Z transfer rules.
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -t "$p0"
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok select-pane -D -t "$p0"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
||||
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -D -Z -t "$p0"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
|
||||
# The last-pane path has separate zoom handling, both with and without -Z.
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok select-pane -t "$p2"
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_ok select-pane -l -t P:0
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'0:0:1'
|
||||
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok select-pane -t "$p2"
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_ok select-pane -l -Z -t P:0
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
|
||||
# Killing either a hidden ordinary pane or the zoom target unzooms.
|
||||
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
|
||||
zk0=$($TMUX display-message -p -t P:12.0 '#{pane_id}')
|
||||
zk1=$($TMUX split-window -d -P -F '#{pane_id}' -t P:12.0 'cat')
|
||||
zk2=$($TMUX split-window -d -P -F '#{pane_id}' -t P:12.0 'cat')
|
||||
check_ok resize-pane -Z -t "$zk0"
|
||||
check_ok kill-pane -t "$zk1"
|
||||
check_fmt "$zk0" '#{window_panes}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
|
||||
'2:0:0'
|
||||
check_ok resize-pane -Z -t "$zk0"
|
||||
check_ok kill-pane -t "$zk0"
|
||||
check_fmt "$zk2" '#{window_panes}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
|
||||
'1:0:0'
|
||||
check_ok kill-window -t P:12
|
||||
|
||||
# The same cases through natural process exit exercise server_destroy_pane.
|
||||
check_ok new-window -d -t P:13 -n zoom-exit 'cat'
|
||||
ze0=$($TMUX display-message -p -t P:13.0 '#{pane_id}')
|
||||
ze1=$($TMUX split-window -d -P -F '#{pane_id}' -t P:13.0 'cat')
|
||||
ze2=$($TMUX split-window -d -P -F '#{pane_id}' -t P:13.0 'cat')
|
||||
check_ok resize-pane -Z -t "$ze0"
|
||||
check_ok send-keys -t "$ze1" C-d
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t P:13 '#{window_panes}')" != 2 ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && { echo "Hidden pane did not exit."; exit 1; }
|
||||
sleep 0.1
|
||||
done
|
||||
check_fmt "$ze0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
|
||||
check_ok resize-pane -Z -t "$ze2"
|
||||
check_ok send-keys -t "$ze2" C-d
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t P:13 '#{window_panes}')" != 1 ]; do
|
||||
i=$((i + 1))
|
||||
[ $i -gt 50 ] && { echo "Zoomed pane did not exit."; exit 1; }
|
||||
sleep 0.1
|
||||
done
|
||||
check_fmt "$ze0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
|
||||
check_ok kill-window -t P:13
|
||||
|
||||
# resize-window restores both tiled and floating zoom targets after rebuilding
|
||||
# the layout at the new size.
|
||||
check_ok new-window -d -t P:14 -n zoom-resize 'cat'
|
||||
zr0=$($TMUX display-message -p -t P:14.0 '#{pane_id}')
|
||||
check_ok split-window -d -t P:14.0 'cat'
|
||||
check_ok resize-pane -Z -t "$zr0"
|
||||
check_ok resize-window -t P:14 -x 90 -y 30
|
||||
check_fmt "$zr0" \
|
||||
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
|
||||
'90x30:1:1'
|
||||
check_ok resize-pane -Z -t "$zr0"
|
||||
zrf=$($TMUX new-pane -dP -F '#{pane_id}' -t P:14 -x 20 -y 6 'cat')
|
||||
zrf_size=$($TMUX display-message -p -t "$zrf" \
|
||||
'#{pane_width}x#{pane_height}')
|
||||
check_ok resize-pane -Z -t "$zrf"
|
||||
check_ok resize-window -t P:14 -x 100 -y 32
|
||||
check_fmt "$zrf" \
|
||||
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
|
||||
'100x32:1:1'
|
||||
check_ok resize-pane -Z -t "$zrf"
|
||||
check_fmt "$zrf" '#{pane_floating_flag}:#{pane_width}x#{pane_height}' \
|
||||
"1:$zrf_size"
|
||||
check_ok kill-window -t P:14
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# kill-pane.
|
||||
|
||||
@@ -307,8 +431,10 @@ check_ok kill-pane -t "$p3"
|
||||
check_panes P:0 "0:$p0 1:$p2 2:$p1"
|
||||
|
||||
# -a kills every pane except the target.
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok kill-pane -a -t "$p0"
|
||||
check_panes P:0 "0:$p0"
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
|
||||
|
||||
# Killing the last pane in a window kills the window.
|
||||
check_ok new-window -d -t P:7 -n goner
|
||||
@@ -516,6 +642,10 @@ check_panes P:3 "0:$r0 1:$r1 2:$r2"
|
||||
# Swapping a pane with itself quietly does nothing.
|
||||
check_ok swap-pane -d -s "$r1" -t "$r1"
|
||||
check_panes P:3 "0:$r0 1:$r1 2:$r2"
|
||||
check_ok resize-pane -Z -t "$r1"
|
||||
check_ok swap-pane -d -Z -s "$r1" -t "$r1"
|
||||
check_fmt "$r1" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
|
||||
check_ok resize-pane -Z -t "$r1"
|
||||
|
||||
# Panes can be swapped between different windows.
|
||||
check_ok swap-pane -d -s "$o0" -t "$r1"
|
||||
@@ -525,10 +655,38 @@ check_ok swap-pane -d -s "$r1" -t "$o0"
|
||||
check_panes P:3 "0:$r0 1:$r1 2:$r2"
|
||||
check_panes P:5 "0:$o0"
|
||||
|
||||
# With both windows zoomed, -Z preserves each window's zoom when hidden panes
|
||||
# are exchanged and when the zoom targets themselves are exchanged.
|
||||
o1=$($TMUX split-window -dP -F '#{pane_id}' -t P:5.0)
|
||||
check_ok resize-pane -Z -t "$r0"
|
||||
check_ok resize-pane -Z -t "$o0"
|
||||
check_ok swap-pane -d -Z -s "$r1" -t "$o1"
|
||||
check_fmt "$r0" \
|
||||
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'swaps:1:1'
|
||||
check_fmt "$o0" \
|
||||
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'other:1:1'
|
||||
check_fmt "$r1" '#{window_name}' 'other'
|
||||
check_fmt "$o1" '#{window_name}' 'swaps'
|
||||
|
||||
check_ok swap-pane -d -Z -s "$r0" -t "$o0"
|
||||
check_fmt "$o0" \
|
||||
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'swaps:1:1'
|
||||
check_fmt "$r0" \
|
||||
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'other:1:1'
|
||||
check_ok resize-pane -Z -t "$o0"
|
||||
check_ok resize-pane -Z -t "$r0"
|
||||
|
||||
# Restore the original panes before the remaining swap tests.
|
||||
check_ok swap-pane -d -s "$r0" -t "$o0"
|
||||
check_ok swap-pane -d -s "$r1" -t "$o1"
|
||||
check_ok kill-pane -t "$o1"
|
||||
check_panes P:3 "0:$r0 1:$r1 2:$r2"
|
||||
check_panes P:5 "0:$o0"
|
||||
|
||||
# -Z keeps the window zoomed across the swap.
|
||||
check_ok resize-pane -Z -t "$r0"
|
||||
check_ok swap-pane -d -Z -s "$r0" -t "$r1"
|
||||
check_fmt 'P:3' '#{window_zoomed_flag}' '1'
|
||||
check_fmt "$r0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
|
||||
check_ok resize-pane -Z -t P:3
|
||||
check_panes P:3 "0:$r1 1:$r0 2:$r2"
|
||||
|
||||
|
||||
165
regress/prompt-words-history.sh
Normal file
165
regress/prompt-words-history.sh
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Cover prompt word movement in emacs and vi modes, ambiguous inline command
|
||||
# completion, and the show/clear-prompt-history command surface.
|
||||
|
||||
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_result()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER show-option -gqv @result 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "prompt result is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
bind_prompt()
|
||||
{
|
||||
initial=$1
|
||||
$INNER bind-key -n M-r command-prompt -I "$initial" -p '(word)' \
|
||||
"set-option -g @result '%%'" || exit 1
|
||||
}
|
||||
|
||||
run_prompt()
|
||||
{
|
||||
want=$1
|
||||
shift
|
||||
$INNER set-option -g @result sentinel || exit 1
|
||||
$OUTER send-keys M-r || exit 1
|
||||
sleep 0.2
|
||||
$OUTER send-keys "$@" || exit 1
|
||||
$OUTER send-keys Enter || exit 1
|
||||
wait_result "$want"
|
||||
}
|
||||
|
||||
run_vi_prompt()
|
||||
{
|
||||
want=$1
|
||||
shift
|
||||
$INNER set-option -g @result sentinel || exit 1
|
||||
$OUTER send-keys M-r || exit 1
|
||||
sleep 0.2
|
||||
# Send Escape separately so the terminal's escape-time handling does not
|
||||
# combine it with the first vi command as a Meta key.
|
||||
$OUTER send-keys Escape || exit 1
|
||||
# The inner client must see Escape as a complete key, not the prefix of a
|
||||
# Meta sequence (the default escape-time is 500 milliseconds).
|
||||
sleep 0.7
|
||||
$OUTER send-keys "$@" || exit 1
|
||||
$OUTER send-keys Enter || exit 1
|
||||
wait_result "$want"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s prompt -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 prompt" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
# Emacs Meta-f stops after the first word; Meta-b returns to the start of the
|
||||
# previous word. Inserting a marker makes the cursor position observable.
|
||||
$INNER set-option -g status-keys emacs || exit 1
|
||||
bind_prompt 'one two'
|
||||
run_prompt 'oneX two' Home M-f X
|
||||
bind_prompt 'one two'
|
||||
run_prompt 'one Xtwo' M-b X
|
||||
|
||||
# Vi translation and the distinct separator-aware and WORD motions.
|
||||
$INNER set-option -g status-keys vi || exit 1
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'one-two Xthree' b i X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'one-two Xthree' B i X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'oneX-two three' 0 w i X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'one-two Xthree' 0 W i X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'oneX-two three' 0 e a X
|
||||
bind_prompt 'one-two three'
|
||||
run_vi_prompt 'one-twoX three' 0 E a X
|
||||
|
||||
# "show-" has several command matches and no longer common prefix. Tab keeps
|
||||
# the input and draws the sorted candidates inline.
|
||||
$INNER set-option -g status-keys emacs || exit 1
|
||||
bind_prompt ''
|
||||
$OUTER send-keys M-r || exit 1
|
||||
sleep 0.2
|
||||
$OUTER send-keys -l 'show-' || exit 1
|
||||
$OUTER send-keys Tab || exit 1
|
||||
sleep 0.2
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Fq 'show-buffer' ||
|
||||
fail "ambiguous completion list was not drawn"
|
||||
printf '%s\n' "$captured" | grep -Fq 'show-environment' ||
|
||||
fail "ambiguous completion list was incomplete"
|
||||
$OUTER send-keys Escape || exit 1
|
||||
|
||||
# Add entries to both history rings through real prompts.
|
||||
bind_prompt 'history-command'
|
||||
run_prompt 'history-command'
|
||||
$INNER bind-key -n M-s command-prompt -T search -I history-search \
|
||||
-p '(search-history)' "set-option -g @result '%%'" || exit 1
|
||||
$OUTER send-keys M-s || exit 1
|
||||
$OUTER send-keys Enter || exit 1
|
||||
wait_result history-search
|
||||
|
||||
command_history=$($INNER show-prompt-history -T command) || exit 1
|
||||
printf '%s\n' "$command_history" | grep -Fq 'history-command' ||
|
||||
fail "command history entry missing"
|
||||
search_history=$($INNER show-prompt-history -T search) || exit 1
|
||||
printf '%s\n' "$search_history" | grep -Fq 'history-search' ||
|
||||
fail "search history entry missing"
|
||||
all_history=$($INNER show-prompt-history) || exit 1
|
||||
printf '%s\n' "$all_history" | grep -Fq 'History for command:' || exit 1
|
||||
printf '%s\n' "$all_history" | grep -Fq 'History for search:' || exit 1
|
||||
|
||||
$INNER show-prompt-history -T invalid >/dev/null 2>&1 &&
|
||||
fail "invalid show history type succeeded"
|
||||
$INNER clear-prompt-history -T invalid >/dev/null 2>&1 &&
|
||||
fail "invalid clear history type succeeded"
|
||||
$INNER clear-prompt-history -T command || exit 1
|
||||
$INNER show-prompt-history -T command | grep -Fq history-command &&
|
||||
fail "type-specific history clear failed"
|
||||
$INNER clear-prompt-history || exit 1
|
||||
$INNER show-prompt-history | grep -Fq history-search &&
|
||||
fail "all-history clear failed"
|
||||
|
||||
exit 0
|
||||
87
regress/respawn-pane-control-lag.sh
Normal file
87
regress/respawn-pane-control-lag.sh
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/bin/sh
|
||||
|
||||
# respawn-pane frees the pane's buffer and creates an empty one. The pane
|
||||
# offsets and each control client's offsets used to keep pointing into the old
|
||||
# buffer, so if an attached control client had not caught up on the pane's
|
||||
# output, the next read from the new process ran off the end of the new buffer
|
||||
# and the server crashed in input_parse.
|
||||
#
|
||||
# Two ways for a client to be behind are tested: a control client whose output
|
||||
# goes down a fifo that is never read (a second, healthy control client keeps
|
||||
# the pane being read), and a healthy control client viewing a session that
|
||||
# the pane's window has been moved out of.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
DIR=$(mktemp -d)
|
||||
FIFO=$DIR/fifo
|
||||
SERVER=
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
|
||||
cleanup() {
|
||||
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exec 8<&- 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
alive() {
|
||||
if ! kill -0 "$SERVER" 2>/dev/null; then
|
||||
SERVER=
|
||||
echo "server died after $1"
|
||||
exit 1
|
||||
fi
|
||||
$TMUX has -t rt || exit 1
|
||||
}
|
||||
|
||||
wait_clients() {
|
||||
n=0
|
||||
while [ $n -lt 50 ]; do
|
||||
[ "$($TMUX lsc 2>/dev/null | wc -l)" -ge $1 ] && return
|
||||
sleep 0.1
|
||||
n=$((n + 1))
|
||||
done
|
||||
echo "control clients did not attach"; exit 1
|
||||
}
|
||||
|
||||
# A detached session with a pane that writes a line every 10 milliseconds.
|
||||
$TMUX -f/dev/null new -d -x 80 -y 24 -s rt \
|
||||
'while :; do echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; sleep 0.01; done' ||
|
||||
exit 1
|
||||
SERVER=$($TMUX display -pt rt '#{pid}')
|
||||
|
||||
# One control client with output down the unread fifo and one reading
|
||||
# normally; stdin of both held open by sleep so they stay attached.
|
||||
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >"$FIFO" 2>&1 &
|
||||
exec 8<"$FIFO"
|
||||
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >/dev/null 2>&1 &
|
||||
wait_clients 2
|
||||
|
||||
# Let the first client fall behind, then respawn the pane. The new process
|
||||
# writes at once and the server must survive reading it.
|
||||
sleep 3
|
||||
$TMUX respawn-pane -k -t rt:0 'echo respawned; sleep 60' || exit 1
|
||||
sleep 1
|
||||
alive "respawn-pane with a lagging control client"
|
||||
|
||||
# Now a pane whose window is moved out of the session both clients view: their
|
||||
# offsets for it stop advancing, so they fall behind until it is respawned.
|
||||
$TMUX neww -d -t rt \
|
||||
'while :; do echo yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy; sleep 0.01; done' ||
|
||||
exit 1
|
||||
sleep 1
|
||||
$TMUX new -d -s other || exit 1
|
||||
$TMUX movew -d -s rt:1 -t other: || exit 1
|
||||
sleep 2
|
||||
$TMUX respawn-pane -k -t other:1 'echo respawned; sleep 60' || exit 1
|
||||
sleep 1
|
||||
alive "respawn-pane on a window moved out of the clients' session"
|
||||
|
||||
exit 0
|
||||
96
regress/screen-redraw-scrollbars-auto-hide.sh
Normal file
96
regress/screen-redraw-scrollbars-auto-hide.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Auto-hide scrollbars appear on activity or pointer hover, then disappear
|
||||
# after pane-scrollbars-timeout. Compare captures of the same copy-mode view so
|
||||
# only the overlay scrollbar changes.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
OUTER="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
INNER="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
HIDDEN=$DIR/hidden
|
||||
VISIBLE=$DIR/visible
|
||||
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
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -pe -t outer:0.0 >"$1" || exit 1
|
||||
}
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER list-clients 2>/dev/null | grep -q . && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x40 -y12 \
|
||||
"sh -c 'seq 50; 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-window-option pane-scrollbars auto-hide || exit 1
|
||||
$INNER set-window-option pane-scrollbars-timeout 3000 || exit 1
|
||||
$INNER set-window-option pane-scrollbars-style \
|
||||
'bg=colour196,fg=colour231,width=1,pad=0' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x40 -y12 "$INNER attach -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
wait_for_client
|
||||
|
||||
# Enter copy mode and choose a stable view. The initial show timer is allowed
|
||||
# to expire before taking the hidden reference capture.
|
||||
$INNER copy-mode -H || exit 1
|
||||
$INNER send-keys -X history-top || exit 1
|
||||
sleep 3.5
|
||||
capture "$HIDDEN"
|
||||
|
||||
# A copy-mode page movement shows the overlay and starts its timer. Capture
|
||||
# that view immediately, then again after the timeout without moving it.
|
||||
$INNER send-keys -X page-down || exit 1
|
||||
sleep 0.1
|
||||
capture "$VISIBLE"
|
||||
sleep 3.5
|
||||
capture "$AFTER"
|
||||
cmp -s "$VISIBLE" "$AFTER" && fail "scrollbar did not hide after timeout"
|
||||
|
||||
# Use the post-movement hidden scene as the reference for pointer hover. SGR
|
||||
# mouse column 40 is the right-hand scrollbar; moving to column 5 restarts the
|
||||
# timer without changing the copy-mode view.
|
||||
cp "$AFTER" "$HIDDEN"
|
||||
hover=$(printf '\033[<35;40;5M')
|
||||
$OUTER send-keys -l "$hover" || exit 1
|
||||
sleep 0.1
|
||||
capture "$VISIBLE"
|
||||
cmp -s "$HIDDEN" "$VISIBLE" && fail "scrollbar did not appear on hover"
|
||||
away=$(printf '\033[<35;5;5M')
|
||||
$OUTER send-keys -l "$away" || exit 1
|
||||
sleep 3.5
|
||||
capture "$AFTER"
|
||||
cmp -s "$HIDDEN" "$AFTER" || fail "scrollbar did not hide after hover"
|
||||
|
||||
exit 0
|
||||
112
regress/server-access.sh
Normal file
112
regress/server-access.sh
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise the unprivileged server-access surface: user and group ACL entries,
|
||||
# read-only/writable transitions, listing, removal, implied addition, and
|
||||
# command errors. Actual connections from another UID require privileges and
|
||||
# deliberately do not form part of this test.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
OUT=$(mktemp) || exit 1
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -f "$OUT"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
expect_fail()
|
||||
{
|
||||
message=$1
|
||||
shift
|
||||
if $TMUX "$@" >"$OUT" 2>&1; then
|
||||
fail "$message: command unexpectedly succeeded"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_entry()
|
||||
{
|
||||
entry=$1
|
||||
$TMUX server-access -l >"$OUT" || exit 1
|
||||
grep -Fxq "$entry" "$OUT" ||
|
||||
fail "missing ACL entry '$entry'"
|
||||
}
|
||||
|
||||
assert_no_entry()
|
||||
{
|
||||
entry=$1
|
||||
$TMUX server-access -l >"$OUT" || exit 1
|
||||
if grep -Fxq "$entry" "$OUT"; then
|
||||
fail "unexpected ACL entry '$entry'"
|
||||
fi
|
||||
}
|
||||
|
||||
owner_uid=$(id -u)
|
||||
owner_name=$(id -un)
|
||||
|
||||
# nobody is the non-owner test identity. Derive its primary group rather than
|
||||
# assuming whether the system calls it nobody, nogroup, or something else.
|
||||
acl_user=nobody
|
||||
acl_group=$(id -gn "$acl_user" 2>/dev/null) ||
|
||||
fail "the nobody account is unavailable"
|
||||
[ "$owner_name" != "$acl_user" ] || fail "test cannot run as nobody"
|
||||
|
||||
$TMUX new-session -d -s access 'exec sleep 100' || exit 1
|
||||
|
||||
# The owner is installed as writable at server startup (root is intentionally
|
||||
# omitted from display output).
|
||||
if [ "$owner_uid" != 0 ]; then
|
||||
assert_entry "$owner_name (U,W)"
|
||||
fi
|
||||
|
||||
# User ACL lifecycle, including duplicate and missing-entry errors.
|
||||
$TMUX server-access -a "$acl_user" || exit 1
|
||||
assert_entry "$acl_user (U,W)"
|
||||
expect_fail "duplicate user addition" server-access -a "$acl_user"
|
||||
$TMUX server-access -r "$acl_user" || exit 1
|
||||
assert_entry "$acl_user (U,R)"
|
||||
$TMUX server-access -w "$acl_user" || exit 1
|
||||
assert_entry "$acl_user (U,W)"
|
||||
$TMUX server-access -d "$acl_user" || exit 1
|
||||
assert_no_entry "$acl_user (U,W)"
|
||||
expect_fail "removing absent user" server-access -d "$acl_user"
|
||||
|
||||
# -r and -w imply -a when an entry is absent.
|
||||
$TMUX server-access -r "$acl_user" || exit 1
|
||||
assert_entry "$acl_user (U,R)"
|
||||
$TMUX server-access -d "$acl_user" || exit 1
|
||||
|
||||
# The same lifecycle for a group exercises the separate ACL key space.
|
||||
$TMUX server-access -g -a -r "$acl_group" || exit 1
|
||||
assert_entry "$acl_group (G,R)"
|
||||
$TMUX server-access -g -w "$acl_group" || exit 1
|
||||
assert_entry "$acl_group (G,W)"
|
||||
expect_fail "duplicate group addition" server-access -g -a "$acl_group"
|
||||
$TMUX server-access -g -d "$acl_group" || exit 1
|
||||
assert_no_entry "$acl_group (G,W)"
|
||||
expect_fail "removing absent group" server-access -g -d "$acl_group"
|
||||
|
||||
# Parser and lookup errors, plus the immutable owner/root entries.
|
||||
expect_fail "missing ACL subject" server-access -a
|
||||
expect_fail "unknown user" server-access -a "tmux-no-user-$$"
|
||||
expect_fail "unknown group" server-access -g -a "tmux-no-group-$$"
|
||||
expect_fail "conflicting add/delete" server-access -a -d "$acl_user"
|
||||
expect_fail "conflicting read/write" server-access -r -w "$acl_user"
|
||||
expect_fail "changing server owner" server-access -r "$owner_name"
|
||||
if [ "$owner_name" != root ]; then
|
||||
expect_fail "changing root access" server-access -a root
|
||||
fi
|
||||
|
||||
$TMUX has-session -t access || fail "server exited during ACL updates"
|
||||
exit 0
|
||||
0
regress/server-socket-error.sh
Executable file → Normal file
0
regress/server-socket-error.sh
Executable file → Normal file
0
regress/set-hook-E.sh
Executable file → Normal file
0
regress/set-hook-E.sh
Executable file → Normal file
203
regress/switch-mode.sh
Normal file
203
regress/switch-mode.sh
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise switch-mode's session and window lists with a real attached client:
|
||||
# fuzzy filtering, movement and selection, the default and custom commands,
|
||||
# no-match cancellation, resize, zoom restoration, and -k mode teardown.
|
||||
|
||||
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_count()
|
||||
{
|
||||
marker=$1
|
||||
want=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
count=$(printf '%s\n' "$captured" | grep -Fc "$marker")
|
||||
[ "$count" -eq "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "found $count '$marker' rows, expected $want"
|
||||
}
|
||||
|
||||
wait_format()
|
||||
{
|
||||
target=$1
|
||||
format=$2
|
||||
want=$3
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER display-message -p -t "$target" "$format" \
|
||||
2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "$target format $format is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
wait_client_session()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER list-clients -F '#{client_session}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "client session is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
# Three alphabetically ordered sessions. alpha:main has two panes so -Z can be
|
||||
# observed; the two pick-* windows make a deterministic two-row window filter.
|
||||
$INNER new-session -d -s alpha -n main -x60 -y15 'exec sleep 100' || exit 1
|
||||
$INNER split-window -d -h -t alpha:main 'exec sleep 100' || exit 1
|
||||
$INNER new-window -d -t alpha: -n pick-one 'exec sleep 100' || exit 1
|
||||
$INNER new-session -d -s bravo -n pick-two -x60 -y15 \
|
||||
'exec sleep 100' || exit 1
|
||||
$INNER new-session -d -s charlie -n last -x60 -y15 \
|
||||
'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER select-window -t alpha:main || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x60 -y15 "$INNER attach -t alpha" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
wait_client_session alpha
|
||||
|
||||
# Session rows are sorted alpha, bravo, charlie. Up from the first row wraps to
|
||||
# charlie; Enter runs a custom command with the selected session target.
|
||||
$INNER set-option -g @picked '' || exit 1
|
||||
$INNER switch-mode -s -t alpha:main.0 -F 'SESSION #{session_name}' \
|
||||
"set-option -g @picked '%%'" || exit 1
|
||||
wait_count 'SESSION ' 3
|
||||
$INNER send-keys -t alpha:main.0 Up Enter || exit 1
|
||||
wait_format alpha:main.0 '#{@picked}' '=charlie:'
|
||||
wait_format alpha:main.0 '#{pane_in_mode}' 0
|
||||
|
||||
# Fuzzy filtering keeps only bravo for "brv". The default command switches the
|
||||
# real client to that session and closes the mode.
|
||||
$INNER switch-mode -s -t alpha:main.0 -F 'SESSION #{session_name}' || exit 1
|
||||
wait_count 'SESSION ' 3
|
||||
$INNER send-keys -t alpha:main.0 -l brv || exit 1
|
||||
wait_count 'SESSION ' 1
|
||||
printf '%s\n' "$captured" | grep -Fq 'SESSION bravo' ||
|
||||
fail "fuzzy session filter did not select bravo"
|
||||
$INNER send-keys -t alpha:main.0 Enter || exit 1
|
||||
wait_client_session bravo
|
||||
client=$($INNER list-clients -F '#{client_name}')
|
||||
$INNER switch-client -c "$client" -t alpha || exit 1
|
||||
wait_client_session alpha
|
||||
|
||||
# Window mode sees pick-one then pick-two for the "pick" filter. Down selects
|
||||
# the second row, and the custom command receives its window target.
|
||||
$INNER set-option -g @picked '' || exit 1
|
||||
$INNER switch-mode -w -t alpha:main.0 -F \
|
||||
'WINDOW #{session_name}:#{window_name}' \
|
||||
"set-option -g @picked '%%'" || exit 1
|
||||
wait_capture 'WINDOW '
|
||||
$INNER send-keys -t alpha:main.0 -l pick || exit 1
|
||||
wait_count 'WINDOW ' 2
|
||||
$INNER send-keys -t alpha:main.0 Down Enter || exit 1
|
||||
picked=$($INNER show-option -gv @picked)
|
||||
case "$picked" in
|
||||
=bravo:0.) ;;
|
||||
*) fail "window selection produced '$picked', expected '=bravo:0.'" ;;
|
||||
esac
|
||||
|
||||
# Navigation with an empty result set must be harmless, and Escape cancels
|
||||
# without running the custom command.
|
||||
$INNER set-option -g @picked unchanged || exit 1
|
||||
$INNER switch-mode -s -t alpha:main.0 -F 'NONE #{session_name}' \
|
||||
"set-option -g @picked '%%'" || exit 1
|
||||
wait_capture 'NONE '
|
||||
$INNER send-keys -t alpha:main.0 -l no-such-session || exit 1
|
||||
wait_count 'NONE ' 0
|
||||
$INNER send-keys -t alpha:main.0 Up Down PPage NPage Home End || exit 1
|
||||
wait_format alpha:main.0 '#{pane_mode}' switch-mode
|
||||
$INNER send-keys -t alpha:main.0 Escape || exit 1
|
||||
wait_format alpha:main.0 '#{pane_in_mode}' 0
|
||||
[ "$($INNER show-option -gv @picked)" = unchanged ] ||
|
||||
fail "cancelled switch-mode ran its command"
|
||||
|
||||
# -Z temporarily zooms an unzoomed window. Resizing while the mode is open
|
||||
# rebuilds and redraws it; exiting restores the original unzoomed state.
|
||||
$INNER switch-mode -Zs -t alpha:main.0 -F 'RESIZE #{session_name}' || exit 1
|
||||
wait_capture 'RESIZE alpha'
|
||||
wait_format alpha:main.0 '#{window_zoomed_flag}' 1
|
||||
$INNER resize-window -t alpha:main -x50 -y12 || exit 1
|
||||
wait_format alpha:main.0 '#{window_width}x#{window_height}' 50x12
|
||||
wait_format alpha:main.0 '#{pane_mode}' switch-mode
|
||||
$INNER send-keys -t alpha:main.0 Escape || exit 1
|
||||
wait_format alpha:main.0 '#{window_zoomed_flag}' 0
|
||||
|
||||
# If the window was already zoomed, leaving -Z mode must keep it zoomed.
|
||||
$INNER resize-pane -Z -t alpha:main.0 || exit 1
|
||||
$INNER switch-mode -Zs -t alpha:main.0 -F 'ZOOMED #{session_name}' || exit 1
|
||||
wait_capture 'ZOOMED alpha'
|
||||
$INNER send-keys -t alpha:main.0 Escape || exit 1
|
||||
wait_format alpha:main.0 '#{window_zoomed_flag}' 1
|
||||
$INNER resize-pane -Z -t alpha:main.0 || exit 1
|
||||
|
||||
# -k uses the shared mode teardown flag. Put it on a disposable pane and make
|
||||
# sure exiting the mode removes that pane.
|
||||
kill_pane=$($INNER split-window -d -P -F '#{pane_id}' -t alpha:main \
|
||||
'exec sleep 100') || exit 1
|
||||
$INNER switch-mode -ks -t "$kill_pane" -F 'KILL #{session_name}' || exit 1
|
||||
wait_format "$kill_pane" '#{pane_mode}' switch-mode
|
||||
$INNER send-keys -t "$kill_pane" Escape || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER list-panes -s -t alpha -F '#{pane_id}' 2>/dev/null | \
|
||||
grep -q -x "$kill_pane" || break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "switch-mode -k did not kill its pane"
|
||||
|
||||
exit 0
|
||||
@@ -14,8 +14,8 @@ DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
|
||||
INNER="$TEST_TMUX -Lsync-output-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lsync-output-outer-$$ -f/dev/null"
|
||||
INNER="$TEST_TMUX -Li$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lo$$ -f/dev/null"
|
||||
CLIENT_BYTES=$DIR/client-bytes
|
||||
CONTROL=$DIR/control
|
||||
EMITTER=$DIR/emitter.pl
|
||||
@@ -112,7 +112,7 @@ $INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -as terminal-features '*:sync' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 80 -y 24 \
|
||||
"$TEST_TMUX -Lsync-output-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
"$TEST_TMUX -Li$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
|
||||
160
regress/sync-output-cursor.sh
Normal file
160
regress/sync-output-cursor.sh
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A synchronized update elsewhere in the pane must not hide a stationary
|
||||
# cursor while tmux waits for the end of the update.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
TEST_TMUX="$TEST_TMUX" sh "$0" pane || exit 1
|
||||
TEST_TMUX="$TEST_TMUX" sh "$0" prompt || exit 1
|
||||
exit 0
|
||||
fi
|
||||
case $1 in
|
||||
pane|prompt) MODE=$1 ;;
|
||||
*) echo "usage: $0 [pane|prompt]" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$DIR
|
||||
export TMUX_TMPDIR
|
||||
|
||||
INNER="$TEST_TMUX -Li$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lo$$ -f/dev/null"
|
||||
CLIENT_BYTES=$DIR/client-bytes
|
||||
CONTROL=$DIR/control
|
||||
EMITTER=$DIR/emitter.pl
|
||||
ASSERT=$DIR/assert-cursor.pl
|
||||
|
||||
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
|
||||
$INNER list-clients -F '#{client_termfeatures}' 2>/dev/null |
|
||||
grep -q 'sync' && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "sync-capable client did not attach"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $control = $ENV{CONTROL};
|
||||
(my $dir = $control) =~ s{/[^/]+$}{};
|
||||
|
||||
# Draw a prompt and leave its cursor stationary on the last line.
|
||||
syswrite STDOUT, "\e[2J\e[HWorking .\e[24;1H> ";
|
||||
open my $ready, '>', "$dir/ready" or die "$dir/ready: $!\n";
|
||||
close $ready;
|
||||
while (!-e $control) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
# Deliberately split BSU, content, and ESU across pane reads. This mirrors an
|
||||
# animation producer which begins a frame before it has finished rendering it.
|
||||
for my $spinner (qw(| / - \\)) {
|
||||
syswrite STDOUT, "\e[?2026h";
|
||||
select undef, undef, undef, 0.05;
|
||||
syswrite STDOUT, "\e[1;9H$spinner\e[24;3H";
|
||||
select undef, undef, undef, 0.01;
|
||||
syswrite STDOUT, "\e[?2026l";
|
||||
select undef, undef, undef, 0.15;
|
||||
}
|
||||
select undef, undef, undef, 5;
|
||||
PERL
|
||||
|
||||
cat >"$ASSERT" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $path = shift;
|
||||
open my $fh, '<:raw', $path or die "$path: $!\n";
|
||||
local $/;
|
||||
my $bytes = <$fh>;
|
||||
my ($bsu, $esu, $hide, $show) =
|
||||
("\e[?2026h", "\e[?2026l", "\e[?25l", "\e[?25h");
|
||||
my ($sync, $outside, $hides, $shows) = (0, 0, 0, 0);
|
||||
|
||||
for (my $i = 0; $i < length($bytes);) {
|
||||
if (substr($bytes, $i, length($bsu)) eq $bsu) {
|
||||
$sync++;
|
||||
$i += length($bsu);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($esu)) eq $esu) {
|
||||
$sync-- if $sync;
|
||||
$i += length($esu);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($hide)) eq $hide) {
|
||||
$hides++;
|
||||
$outside++ unless $sync;
|
||||
$i += length($hide);
|
||||
next;
|
||||
}
|
||||
if (substr($bytes, $i, length($show)) eq $show) {
|
||||
$shows++;
|
||||
$outside++ unless $sync;
|
||||
$i += length($show);
|
||||
next;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
printf "%s cursor hides: %d; cursor shows: %d; changes outside sync: %d\n",
|
||||
$ENV{MODE}, $hides, $shows, $outside;
|
||||
die "stationary cursor changed outside synchronized output\n" if $outside;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 80 -y 24 \
|
||||
"CONTROL='$CONTROL' perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -as terminal-features '*:sync' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 80 -y 24 \
|
||||
"$TEST_TMUX -Li$$ -f/dev/null attach-session -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
wait_for_client
|
||||
|
||||
if [ "$MODE" = prompt ]; then
|
||||
$OUTER send-keys -t outer:0.0 C-b : || exit 1
|
||||
sleep 0.2
|
||||
fi
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ] && [ ! -e "$DIR/ready" ]; do
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ -e "$DIR/ready" ] || fail "application emitter did not become ready"
|
||||
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$CLIENT_BYTES'" || exit 1
|
||||
: >"$CONTROL"
|
||||
sleep 2
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
|
||||
MODE=$MODE perl "$ASSERT" "$CLIENT_BYTES"
|
||||
0
regress/tab-cell-background.sh
Executable file → Normal file
0
regress/tab-cell-background.sh
Executable file → Normal file
58
regress/terminal-feature-utf8.sh
Normal file
58
regress/terminal-feature-utf8.sh
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
|
||||
# The utf8 terminal feature must mark an attached client as UTF-8 even when
|
||||
# neither its environment nor -u does so. It must also be reported by the
|
||||
# client terminal-feature format lookup.
|
||||
|
||||
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
|
||||
|
||||
cleanup
|
||||
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
|
||||
# A clean C-locale environment avoids the ordinary UTF-8 detection paths.
|
||||
# -T utf8 is therefore solely responsible for setting client_utf8.
|
||||
client_command="env -i PATH=/bin:/usr/bin TERM=screen LC_ALL=C $TEST_TMUX -T utf8 -LtestI$$ -f/dev/null attach-session -t inner"
|
||||
$OUTER new-session -d -s outer -x40 -y10 "$client_command" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
client=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
utf8=$($INNER list-clients -F '#{client_utf8}' 2>/dev/null)
|
||||
[ -n "$client" ] && [ "$utf8" = 1 ] && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "utf8 terminal feature did not set client_utf8"
|
||||
|
||||
features=$($INNER list-clients -F '#{client_termfeatures}')
|
||||
case ",$features," in
|
||||
*,utf8,*) ;;
|
||||
*) fail "utf8 is missing from client_termfeatures: $features" ;;
|
||||
esac
|
||||
|
||||
present=$($INNER display-message -c "$client" -p '#{I/f:utf8}')
|
||||
[ "$present" = 1 ] || fail "utf8 terminal feature lookup returned '$present'"
|
||||
|
||||
exit 0
|
||||
0
regress/theme-report.sh
Executable file → Normal file
0
regress/theme-report.sh
Executable file → Normal file
@@ -105,6 +105,21 @@ sleep 1
|
||||
CLIENT=$($TMUX2 list-clients -F '#{client_name}' | head -1)
|
||||
[ -n "$CLIENT" ] || fail "no inner client"
|
||||
|
||||
# A variation selector which widens the previous character must use window
|
||||
# coordinates when checking visibility in a pane with a nonzero x offset.
|
||||
$TMUX2 set -s variation-selector-always-wide on || exit 1
|
||||
WINDOW=$($TMUX2 neww -dPF '#{window_id}' "exec sleep 100") || exit 1
|
||||
PANE=$($TMUX2 splitw -dhPF '#{pane_id}' -t "$WINDOW" \
|
||||
"exec sleep 100") || exit 1
|
||||
$TMUX2 selectw -t "$WINDOW" || exit 1
|
||||
$TMUX2 respawnp -k -t "$PANE" \
|
||||
"printf '\nA\342\234\217\357\270\217B'; exec sleep 100" || exit 1
|
||||
sleep 1
|
||||
$TMUX2 capturep -p -t "$PANE" >$TMP || exit 1
|
||||
EXPECTED=$(printf 'A\342\234\217\357\270\217B')
|
||||
check_line 2 "$EXPECTED"
|
||||
$TMUX2 killw -t "$WINDOW" || exit 1
|
||||
|
||||
# Long line, then short line: default cells after cellsize must clear stale text.
|
||||
capture
|
||||
check_line 1 "abcdefghijklmnopqrst"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# This exercises:
|
||||
# - new-window placement: next free index, explicit index, index in use with
|
||||
# and without -k, -a (after) and -b (before) insertion with shuffling, and
|
||||
# -S selecting an existing window by name instead of creating;
|
||||
# -S selecting an existing window by name or target instead of creating;
|
||||
# - move-window to a free index, to an occupied index with and without -k,
|
||||
# -a insertion and -r renumbering (including base-index);
|
||||
# - renumber-windows closing gaps;
|
||||
@@ -17,7 +17,8 @@
|
||||
# unlink the last link without -k;
|
||||
# - swap-window within and between sessions, -d keeping the active window,
|
||||
# and the grouped-sessions error;
|
||||
# - rotate-window -U/-D rotating pane positions;
|
||||
# - rotate-window -U/-D rotating pane positions, with and without preserving
|
||||
# zoom using -Z;
|
||||
# - kill-window switching to the last (previously current) window, kill-window
|
||||
# -a killing all other windows and the "-f only valid with -a" guard.
|
||||
#
|
||||
@@ -139,6 +140,36 @@ check_ok new-window -S -t W: -n w3
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '5:w3'
|
||||
|
||||
# -S with a target-window that already exists selects it directly, ignoring
|
||||
# -n (with -d it does not switch).
|
||||
check_ok select-window -t W:0
|
||||
check_ok new-window -S -t W:9 -n ignored
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '9:w9k'
|
||||
check_ok select-window -t W:0
|
||||
check_ok new-window -S -d -t W:9 -n ignored
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '0:wB'
|
||||
|
||||
# -S with a target-window that does not yet exist still creates it normally.
|
||||
check_ok new-window -S -t W:20 -n w20
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k 20:w20'
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '20:w20'
|
||||
check_ok kill-window -t W:20
|
||||
|
||||
# -S also works with a relative target-window (+N): selects the window at
|
||||
# that offset if it exists, or creates it there if not.
|
||||
check_ok select-window -t W:0
|
||||
check_ok new-window -S -t W:+1
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '1:w0'
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
|
||||
|
||||
check_ok select-window -t W:0
|
||||
check_ok new-window -S -t W:+6 -n rel6
|
||||
check_fmt 'W:' '#{window_index}:#{window_name}' '6:rel6'
|
||||
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 6:rel6 9:w9k'
|
||||
check_ok kill-window -t W:6
|
||||
|
||||
# Clean up to a known arrangement.
|
||||
check_ok kill-window -t W:wB
|
||||
check_ok kill-window -t W:wA
|
||||
@@ -306,6 +337,24 @@ check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p1"
|
||||
check_ok rotate-window -D -t R:0
|
||||
check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p0"
|
||||
|
||||
# Rotation without -Z unzooms. With -Z it preserves zoom and transfers it to
|
||||
# the pane which arrives at the active position.
|
||||
layout=$($TMUX display-message -p -t R:0 '#{window_layout}')
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok rotate-window -U -t R:0
|
||||
check_fmt "$p1" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'0:0:1'
|
||||
check_ok rotate-window -D -t R:0
|
||||
check_fmt 'R:0' '#{window_layout}' "$layout"
|
||||
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok rotate-window -U -Z -t R:0
|
||||
check_fmt "$p1" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p1"
|
||||
check_ok rotate-window -D -t R:0
|
||||
check_fmt 'R:0' '#{window_layout}' "$layout"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# kill-window.
|
||||
|
||||
|
||||
20
resize.c
20
resize.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: resize.c,v 1.58 2026/07/17 08:37:29 nicm Exp $ */
|
||||
/* $OpenBSD: resize.c,v 1.59 2026/08/20 09:19:24 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -42,8 +42,8 @@ resize_fire_window_resized(struct window *w, u_int old_sx, u_int old_sy)
|
||||
void
|
||||
resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
|
||||
{
|
||||
u_int old_sx = w->sx, old_sy = w->sy;
|
||||
int zoomed;
|
||||
struct window_pane *zwp;
|
||||
u_int old_sx = w->sx, old_sy = w->sy;
|
||||
|
||||
/* Check size limits. */
|
||||
if (sx < WINDOW_MINIMUM)
|
||||
@@ -56,8 +56,8 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
|
||||
sy = WINDOW_MAXIMUM;
|
||||
|
||||
/* If the window is zoomed, unzoom. */
|
||||
zoomed = w->flags & WINDOW_ZOOMED;
|
||||
if (zoomed)
|
||||
zwp = window_zoomed_pane(w);
|
||||
if (zwp != NULL)
|
||||
window_unzoom(w, 1);
|
||||
|
||||
/* Resize the layout first. */
|
||||
@@ -73,8 +73,8 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
|
||||
sx, sy, w->layout_root->g.sx, w->layout_root->g.sy);
|
||||
|
||||
/* Restore the window zoom state. */
|
||||
if (zoomed)
|
||||
window_zoom(w->active);
|
||||
if (zwp != NULL && window_has_pane(w, zwp))
|
||||
window_zoom(zwp);
|
||||
|
||||
tty_update_window_offset(w);
|
||||
server_redraw_window(w);
|
||||
@@ -386,12 +386,10 @@ recalculate_size(struct window *w, int now)
|
||||
* got a resize scheduled, then use the new size; otherwise the old.
|
||||
*/
|
||||
if (w->flags & WINDOW_RESIZE) {
|
||||
if (!now && changed && w->new_sx == sx && w->new_sy == sy &&
|
||||
w->new_xpixel == xpixel && w->new_ypixel == ypixel)
|
||||
if (!now && changed && w->new_sx == sx && w->new_sy == sy)
|
||||
changed = 0;
|
||||
} else {
|
||||
if (!now && changed && w->sx == sx && w->sy == sy &&
|
||||
w->xpixel == xpixel && w->ypixel == ypixel)
|
||||
if (!now && changed && w->sx == sx && w->sy == sy)
|
||||
changed = 0;
|
||||
}
|
||||
|
||||
|
||||
139
screen-redraw.c
139
screen-redraw.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: screen-redraw.c,v 1.157 2026/07/24 08:49:23 nicm Exp $ */
|
||||
/* $OpenBSD: screen-redraw.c,v 1.158 2026/09/01 19:50:58 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -99,13 +99,6 @@ enum redraw_span_type {
|
||||
#define REDRAW_ALL 0x7fffffff
|
||||
#define REDRAW_IS_ALL(flags) ((flags) == REDRAW_ALL)
|
||||
|
||||
enum redraw_image_phase {
|
||||
REDRAW_IMAGES_BEFORE,
|
||||
REDRAW_TEXT,
|
||||
REDRAW_IMAGES_AFTER,
|
||||
REDRAW_CHROME
|
||||
};
|
||||
|
||||
/* UTF-8 isolate characters. */
|
||||
#define REDRAW_START_ISOLATE "\342\201\246"
|
||||
#define REDRAW_END_ISOLATE "\342\201\251"
|
||||
@@ -1113,8 +1106,7 @@ redraw_get_scene(struct client *c)
|
||||
/* Draw a pane span. */
|
||||
static void
|
||||
redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
|
||||
struct redraw_span *span, u_int x, u_int y, u_int n,
|
||||
enum redraw_image_phase phase)
|
||||
struct redraw_span *span, u_int x, u_int y, u_int n)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct client *c = scene->c;
|
||||
@@ -1132,14 +1124,7 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
|
||||
|
||||
px = span->data.p.px + (x - span->x);
|
||||
py = span->data.p.py;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (phase != REDRAW_TEXT)
|
||||
image_draw_line(tty, s, px, py, n, x, y,
|
||||
phase == REDRAW_IMAGES_BEFORE, &style_ctx);
|
||||
else
|
||||
#endif
|
||||
if (phase == REDRAW_TEXT)
|
||||
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
|
||||
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
|
||||
}
|
||||
|
||||
/* Get default border style for spans without a pane. */
|
||||
@@ -1394,7 +1379,7 @@ redraw_draw_menu_span(struct redraw_draw_ctx *dctx,
|
||||
/* Draw a span. */
|
||||
static void
|
||||
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||
u_int y, enum redraw_image_phase phase)
|
||||
u_int y)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct redraw_span_data *data = &span->data;
|
||||
@@ -1415,15 +1400,10 @@ redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||
continue;
|
||||
x = rr->px;
|
||||
n = rr->nx;
|
||||
if (type == REDRAW_SPAN_PANE) {
|
||||
if (phase == REDRAW_CHROME)
|
||||
continue;
|
||||
} else if (phase != REDRAW_CHROME)
|
||||
continue;
|
||||
|
||||
switch (span->data.type) {
|
||||
case REDRAW_SPAN_PANE:
|
||||
redraw_draw_pane_span(dctx, span, x, y, n, phase);
|
||||
redraw_draw_pane_span(dctx, span, x, y, n);
|
||||
break;
|
||||
case REDRAW_SPAN_BORDER:
|
||||
case REDRAW_SPAN_EMPTY:
|
||||
@@ -1452,11 +1432,6 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
||||
struct redraw_line *line;
|
||||
struct redraw_spans *spans;
|
||||
struct redraw_span *span;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *rr;
|
||||
u_int i;
|
||||
#endif
|
||||
u_int cy;
|
||||
int y, top, bottom;
|
||||
|
||||
@@ -1469,53 +1444,24 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
||||
if (bottom > (int)scene->sy)
|
||||
bottom = scene->sy;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (flags & REDRAW_PANE) {
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy += dctx->status_lines;
|
||||
if (flags & REDRAW_PANE) {
|
||||
spans = &line->spans[REDRAW_SPAN_PANE];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.p.wp != wp)
|
||||
continue;
|
||||
r = tty_check_overlay_range(&scene->c->tty,
|
||||
span->x, cy, span->width);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
rr = &r->ranges[i];
|
||||
if (rr->nx != 0) {
|
||||
image_redraw_start(&scene->c->tty,
|
||||
rr->px, cy, rr->nx, 1);
|
||||
}
|
||||
}
|
||||
if (span->data.p.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_CHROME; phase++) {
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
if (flags & REDRAW_PANE) {
|
||||
spans = &line->spans[REDRAW_SPAN_PANE];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.p.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy, phase);
|
||||
}
|
||||
}
|
||||
if (phase == REDRAW_CHROME &&
|
||||
(flags & REDRAW_PANE_SCROLLBAR)) {
|
||||
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.sb.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy, phase);
|
||||
}
|
||||
if (flags & REDRAW_PANE_SCROLLBAR) {
|
||||
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.sb.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1531,15 +1477,13 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
struct redraw_span *span;
|
||||
u_int y, cy, type;
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_CHROME; phase++) {
|
||||
for (y = 0; y < scene->sy; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
for (y = 0; y < scene->sy; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
if (!REDRAW_IS_ALL(flags)) {
|
||||
switch (type) {
|
||||
case REDRAW_SPAN_PANE:
|
||||
@@ -1576,8 +1520,7 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
}
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry)
|
||||
redraw_draw_span(dctx, span, cy, phase);
|
||||
}
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1598,7 +1541,7 @@ redraw_draw_menu_lines(struct redraw_draw_ctx *dctx)
|
||||
else
|
||||
cy = y;
|
||||
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry)
|
||||
redraw_draw_span(dctx, span, cy, REDRAW_TEXT);
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1788,16 +1731,14 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
}
|
||||
|
||||
if (flags & REDRAW_PANE_STATUS) {
|
||||
redraw = !!(flags & REDRAW_PANE_BORDER);
|
||||
redraw = 0;
|
||||
TAILQ_FOREACH(loop, &scene->w->panes, entry) {
|
||||
if (REDRAW_IS_ALL(flags) ||
|
||||
(flags & REDRAW_PANE_BORDER))
|
||||
if (REDRAW_IS_ALL(flags))
|
||||
loop->flags |= PANE_NEWSTATUS;
|
||||
else
|
||||
loop->flags &= ~PANE_NEWSTATUS;
|
||||
|
||||
width = redraw_pane_status_width(&dctx, loop,
|
||||
&first);
|
||||
width = redraw_pane_status_width(&dctx, loop, &first);
|
||||
if (width == 0)
|
||||
continue;
|
||||
|
||||
@@ -1828,15 +1769,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
tty_sync_start(tty);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (wp == NULL && (flags & REDRAW_PANE)) {
|
||||
y = 0;
|
||||
if (dctx.flags & REDRAW_STATUS_TOP)
|
||||
y = dctx.status_lines;
|
||||
image_redraw_start(tty, 0, y, scene->sx, scene->sy);
|
||||
}
|
||||
#endif
|
||||
tty_sync_start(tty); /* end in server_client_reset_state */
|
||||
tty_update_mode(tty, tty->mode & ~CURSOR_MODES, NULL);
|
||||
|
||||
if (wp != NULL)
|
||||
@@ -1881,7 +1814,15 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
c->overlay_draw(c, c->overlay_data);
|
||||
|
||||
tty_reset(tty);
|
||||
tty_sync_end(tty);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (wp != NULL)
|
||||
tty_draw_images(c, wp);
|
||||
else {
|
||||
TAILQ_FOREACH(loop, &scene->w->panes, entry)
|
||||
tty_draw_images(c, loop);
|
||||
}
|
||||
#endif
|
||||
|
||||
log_debug("%s: finished @%u redraw", c->name, scene->w->id);
|
||||
}
|
||||
|
||||
319
screen-write.c
319
screen-write.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: screen-write.c,v 1.286 2026/08/03 12:58:53 nicm Exp $ */
|
||||
/* $OpenBSD: screen-write.c,v 1.290 2026/08/24 15:05:26 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -34,10 +34,6 @@ static void screen_write_collect_clear(struct screen_write_ctx *, u_int,
|
||||
static void screen_write_collect_scroll(struct screen_write_ctx *, u_int);
|
||||
static void screen_write_collect_flush(struct screen_write_ctx *, int,
|
||||
const char *);
|
||||
#ifdef ENABLE_IMAGES
|
||||
static int screen_write_image_damage(struct screen_write_ctx *, u_int,
|
||||
u_int, u_int, u_int);
|
||||
#endif
|
||||
static int screen_write_overwrite(struct screen_write_ctx *,
|
||||
struct grid_cell *, u_int);
|
||||
static int screen_write_combine(struct screen_write_ctx *,
|
||||
@@ -139,8 +135,6 @@ static int
|
||||
screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
|
||||
{
|
||||
struct window_pane *wp = ttyctx->arg;
|
||||
u_int sx, sb_w = 0;
|
||||
int xoff;
|
||||
|
||||
if (ttyctx->flags & TTY_CTX_INVISIBLE_PANES) {
|
||||
if (session_has(c->session, wp->window))
|
||||
@@ -174,21 +168,6 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
|
||||
|
||||
ttyctx->xoff = ttyctx->rxoff = wp->xoff;
|
||||
ttyctx->yoff = ttyctx->ryoff = wp->yoff;
|
||||
ttyctx->flags &= ~TTY_CTX_PANE_FULL_WIDTH;
|
||||
if (~ttyctx->flags & TTY_CTX_WINDOW_BIGGER &&
|
||||
!window_has_floating_panes(wp->window)) {
|
||||
xoff = wp->xoff;
|
||||
sx = wp->sx;
|
||||
if (window_pane_scrollbar_reserve(wp)) {
|
||||
sb_w = wp->scrollbar_style.width +
|
||||
wp->scrollbar_style.pad;
|
||||
sx += sb_w;
|
||||
if (wp->window->sb_pos == PANE_SCROLLBARS_LEFT)
|
||||
xoff -= sb_w;
|
||||
}
|
||||
if (xoff == 0 && sx >= c->tty.sx)
|
||||
ttyctx->flags |= TTY_CTX_PANE_FULL_WIDTH;
|
||||
}
|
||||
|
||||
if (status_at_line(c) == 0)
|
||||
ttyctx->yoff += status_line_size(c);
|
||||
@@ -326,12 +305,14 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
|
||||
|
||||
if (~ctx->flags & SCREEN_WRITE_SYNC) {
|
||||
/*
|
||||
* For the active pane or for an overlay (no pane), we want to
|
||||
* only use synchronized updates if requested (commands that
|
||||
* move the cursor); for other panes, always use it, since the
|
||||
* cursor will have to move.
|
||||
* For the active pane showing its base screen or for an
|
||||
* overlay (no pane), only use synchronized updates if
|
||||
* requested (commands that move the cursor); for other panes
|
||||
* or a pane in a mode, always use it, since the cursor will
|
||||
* have to move.
|
||||
*/
|
||||
if (ctx->wp != NULL && ctx->wp != ctx->wp->window->active)
|
||||
if (ctx->wp != NULL && (ctx->wp != ctx->wp->window->active ||
|
||||
ctx->wp->screen != &ctx->wp->base))
|
||||
ttyctx->flags |= TTY_CTX_SYNC;
|
||||
else {
|
||||
if (ctx->wp == NULL)
|
||||
@@ -359,10 +340,18 @@ screen_write_make_list(struct screen *s)
|
||||
void
|
||||
screen_write_free_list(struct screen *s)
|
||||
{
|
||||
u_int y;
|
||||
struct screen_write_cline *cl;
|
||||
struct screen_write_citem *ci, *ci1;
|
||||
u_int y;
|
||||
|
||||
for (y = 0; y < screen_size_y(s); y++)
|
||||
free(s->write_list[y].data);
|
||||
for (y = 0; y < screen_size_y(s); y++) {
|
||||
cl = &s->write_list[y];
|
||||
TAILQ_FOREACH_SAFE(ci, &cl->items, entry, ci1) {
|
||||
TAILQ_REMOVE(&cl->items, ci, entry);
|
||||
screen_write_free_citem(ci);
|
||||
}
|
||||
free(cl->data);
|
||||
}
|
||||
free(s->write_list);
|
||||
}
|
||||
|
||||
@@ -382,23 +371,6 @@ screen_write_init(struct screen_write_ctx *ctx, struct screen *s)
|
||||
ctx->bg = 8;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Damage temporal images when application input writes text cells. */
|
||||
static int
|
||||
screen_write_image_damage(struct screen_write_ctx *ctx, u_int x, u_int y,
|
||||
u_int sx, u_int sy)
|
||||
{
|
||||
int found;
|
||||
|
||||
if (~ctx->flags & SCREEN_WRITE_INPUT)
|
||||
return (0);
|
||||
found = image_grid_check_area(ctx->s->grid, x,
|
||||
ctx->s->grid->hsize + y, sx, sy);
|
||||
image_grid_damage(ctx->s->grid, x, ctx->s->grid->hsize + y, sx, sy);
|
||||
return (found);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Initialize writing with a pane. */
|
||||
void
|
||||
screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp,
|
||||
@@ -1097,6 +1069,19 @@ screen_write_stop_sync(struct window_pane *wp)
|
||||
log_debug("%s: %%%u stopped sync mode", __func__, wp->id);
|
||||
}
|
||||
|
||||
/* Flush pending output before clearing sync mode. */
|
||||
void
|
||||
screen_write_end_sync(struct screen_write_ctx *ctx)
|
||||
{
|
||||
struct window_pane *wp = ctx->wp;
|
||||
|
||||
if (wp == NULL)
|
||||
return;
|
||||
if (wp->base.mode & MODE_SYNC)
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
screen_write_stop_sync(wp);
|
||||
}
|
||||
|
||||
/* Cursor up by ny. */
|
||||
void
|
||||
screen_write_cursorup(struct screen_write_ctx *ctx, u_int ny)
|
||||
@@ -1323,14 +1308,15 @@ screen_write_clear_dirty(struct window_pane *wp)
|
||||
}
|
||||
}
|
||||
|
||||
/* Redraw an obscured pane through the scene to preserve floating panes. */
|
||||
/* Redraw all visible cells in a pane. */
|
||||
static void
|
||||
screen_write_redraw_pane(struct screen_write_ctx *ctx)
|
||||
screen_write_redraw_pane(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
|
||||
{
|
||||
if (ctx->wp != NULL) {
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
server_redraw_window_borders(ctx->wp->window);
|
||||
}
|
||||
struct screen *s = ctx->s;
|
||||
u_int yy;
|
||||
|
||||
for (yy = 0; yy < screen_size_y(s); yy++)
|
||||
screen_write_redraw_line(ctx, ttyctx, yy);
|
||||
}
|
||||
|
||||
/* VT100 alignment test. */
|
||||
@@ -1345,11 +1331,9 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
|
||||
memcpy(&gc, &grid_default_cell, sizeof gc);
|
||||
utf8_set(&gc.data, 'E');
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
if (ctx->flags & SCREEN_WRITE_INPUT)
|
||||
image_grid_damage(s->grid, 0, s->grid->hsize,
|
||||
screen_size_x(s), screen_size_y(s));
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
for (yy = 0; yy < screen_size_y(s); yy++) {
|
||||
@@ -1373,7 +1357,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
|
||||
return;
|
||||
}
|
||||
|
||||
screen_write_redraw_pane(ctx);
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
}
|
||||
|
||||
/* Insert nx characters. */
|
||||
@@ -1394,8 +1378,9 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1434,8 +1419,9 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1474,8 +1460,9 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1508,6 +1495,11 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
if (ny == 0)
|
||||
ny = 1;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
if (s->cy < s->rupper || s->cy > s->rlower) {
|
||||
if (ny > sy - s->cy)
|
||||
ny = sy - s->cy;
|
||||
@@ -1529,7 +1521,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
screen_write_redraw_pane(ctx);
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1556,7 +1548,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
screen_write_redraw_pane(ctx);
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
}
|
||||
|
||||
/* Delete ny lines. */
|
||||
@@ -1571,6 +1563,11 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
if (ny == 0)
|
||||
ny = 1;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
if (s->cy < s->rupper || s->cy > s->rlower) {
|
||||
if (ny > sy - s->cy)
|
||||
ny = sy - s->cy;
|
||||
@@ -1593,7 +1590,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
screen_write_redraw_pane(ctx);
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1621,7 +1618,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
screen_write_redraw_pane(ctx);
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
}
|
||||
|
||||
/* Clear line at cursor. */
|
||||
@@ -1639,8 +1636,9 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
|
||||
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
flags = gl->flags & GRID_LINE_OSC133_FLAGS;
|
||||
@@ -1677,8 +1675,9 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
|
||||
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg);
|
||||
@@ -1703,8 +1702,9 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
if (s->cx > sx - 1)
|
||||
@@ -1749,9 +1749,6 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct screen *s = ctx->s;
|
||||
struct tty_ctx ttyctx;
|
||||
u_int ry;
|
||||
#ifdef ENABLE_IMAGES
|
||||
int image_scroll;
|
||||
#endif
|
||||
|
||||
if (s->cy != s->rupper) {
|
||||
if (s->cy > 0)
|
||||
@@ -1759,20 +1756,16 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_scroll = image_grid_check_area(s->grid, 0,
|
||||
s->grid->hsize + s->rupper, screen_size_x(s),
|
||||
s->rlower - s->rupper + 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
ttyctx.bg = bg;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_scroll)
|
||||
ttyctx.flags |= TTY_CTX_IMAGE_SCROLL;
|
||||
#endif
|
||||
|
||||
ry = s->rlower + 1 - s->rupper;
|
||||
if (!screen_write_should_draw_lines(ctx, s->rupper, ry))
|
||||
@@ -1782,7 +1775,7 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
screen_write_redraw_pane(ctx);
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
}
|
||||
|
||||
/* Set scroll region. */
|
||||
@@ -1815,6 +1808,9 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_line *gl;
|
||||
#ifdef ENABLE_SIXEL
|
||||
int redraw = 0;
|
||||
#endif
|
||||
u_int rupper = s->rupper, rlower = s->rlower;
|
||||
|
||||
gl = grid_get_line(gd, gd->hsize + s->cy);
|
||||
@@ -1835,10 +1831,13 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_grid_check_area(gd, 0, gd->hsize + rupper,
|
||||
screen_size_x(s), rlower - rupper + 1))
|
||||
ctx->scrolled_images = 1;
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (rlower == screen_size_y(s) - 1)
|
||||
redraw = image_scroll_up(s, 1);
|
||||
else
|
||||
redraw = image_check_line(s, rupper, rlower - rupper);
|
||||
if (redraw && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
|
||||
@@ -1864,10 +1863,9 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
ctx->bg = bg;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_grid_check_area(gd, 0, gd->hsize + s->rupper,
|
||||
screen_size_x(s), s->rlower - s->rupper + 1))
|
||||
ctx->scrolled_images = 1;
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_scroll_up(s, lines) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
for (i = 0; i < lines; i++) {
|
||||
@@ -1894,10 +1892,9 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
else if (lines > s->rlower - s->rupper + 1)
|
||||
lines = s->rlower - s->rupper + 1;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_grid_check_area(gd, 0, gd->hsize + s->rupper,
|
||||
screen_size_x(s), s->rlower - s->rupper + 1))
|
||||
ttyctx.flags |= TTY_CTX_IMAGE_SCROLL;
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
for (i = 0; i < lines; i++)
|
||||
@@ -1914,7 +1911,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
screen_write_redraw_pane(ctx);
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
}
|
||||
|
||||
/* Carriage return (cursor to start of line). */
|
||||
@@ -1936,8 +1933,9 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2016,8 +2014,9 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, 0, screen_size_x(s), s->cy + 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2088,8 +2087,9 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2297,7 +2297,7 @@ screen_write_collect_flush_scrolled(struct screen_write_ctx *ctx)
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
if (ttyctx.flags & TTY_CTX_PANE_OBSCURED && wp != NULL) {
|
||||
screen_write_redraw_pane(ctx);
|
||||
screen_write_redraw_pane(ctx, &ttyctx);
|
||||
return 0;
|
||||
}
|
||||
if (wp != NULL && window_pane_scrollbar_overlay_visible(wp)) {
|
||||
@@ -2314,10 +2314,6 @@ screen_write_collect_flush_scrolled(struct screen_write_ctx *ctx)
|
||||
ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy);
|
||||
ttyctx.n = ctx->scrolled;
|
||||
ttyctx.bg = ctx->bg;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (ctx->scrolled_images)
|
||||
ttyctx.flags |= TTY_CTX_IMAGE_SCROLL;
|
||||
#endif
|
||||
tty_write(tty_cmd_scrollup, &ttyctx);
|
||||
|
||||
if (wp != NULL)
|
||||
@@ -2447,9 +2443,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only,
|
||||
if (!screen_write_collect_flush_scrolled(ctx))
|
||||
goto discard;
|
||||
ctx->scrolled = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
ctx->scrolled_images = 0;
|
||||
#endif
|
||||
}
|
||||
ctx->bg = 8;
|
||||
|
||||
@@ -2473,9 +2466,6 @@ discard:
|
||||
}
|
||||
}
|
||||
ctx->scrolled = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
ctx->scrolled_images = 0;
|
||||
#endif
|
||||
ctx->bg = 8;
|
||||
}
|
||||
|
||||
@@ -2602,8 +2592,9 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
screen_write_image_damage(ctx, s->cx, s->cy, ci->used, 1);
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#endif
|
||||
|
||||
grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x,
|
||||
@@ -2693,9 +2684,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
u_int width = ud->width, xx, not_wrap, i, n, vis;
|
||||
int selected, skip = 1, redraw = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
int selection_image = 0;
|
||||
#endif
|
||||
int yoff = 0, xoff = 0;
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
@@ -2736,12 +2724,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
return;
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 0);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Input semantics decide which image layers this text damages. */
|
||||
if (screen_write_image_damage(ctx, s->cx, s->cy, width, 1))
|
||||
skip = 0;
|
||||
#endif
|
||||
|
||||
/* Handle overwriting of UTF-8 characters. */
|
||||
gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
|
||||
if (gl->flags & GRID_LINE_EXTENDED) {
|
||||
@@ -2789,14 +2771,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
|
||||
/* Update the selected flag and set the cell. */
|
||||
selected = screen_check_selection(s, s->cx, s->cy);
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Selection highlighting must not overwrite image-covered cells. */
|
||||
if (selected && image_grid_check_area(gd, s->cx,
|
||||
gd->hsize + s->cy, width, 1)) {
|
||||
selected = 0;
|
||||
selection_image = 1;
|
||||
}
|
||||
#endif
|
||||
if (selected && (~gc->flags & GRID_FLAG_SELECTED)) {
|
||||
memcpy(&tmp_gc, gc, sizeof tmp_gc);
|
||||
tmp_gc.flags |= GRID_FLAG_SELECTED;
|
||||
@@ -2807,11 +2781,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
grid_view_set_cell(gd, s->cx, s->cy, &tmp_gc);
|
||||
} else if (!skip)
|
||||
grid_view_set_cell(gd, s->cx, s->cy, gc);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (selection_image)
|
||||
skip = 1;
|
||||
else
|
||||
#endif
|
||||
if (selected)
|
||||
skip = 0;
|
||||
|
||||
@@ -2893,10 +2862,11 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
struct grid *gd = s->grid;
|
||||
const struct utf8_data *ud = &gc->data;
|
||||
struct options *oo = global_options;
|
||||
u_int i, n, cx = s->cx, cy = s->cy, vis, yoff = 0;
|
||||
u_int i, n, cx = s->cx, cy = s->cy, vis;
|
||||
struct grid_cell last;
|
||||
struct tty_ctx ttyctx;
|
||||
int force_wide = 0, zero_width = 0;
|
||||
int xoff = 0, yoff = 0;
|
||||
struct visible_ranges *r;
|
||||
|
||||
/* Ignore U+3164 HANGUL_FILLER entirely. */
|
||||
@@ -2981,9 +2951,6 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
force_wide = 0;
|
||||
|
||||
/* Set the new cell. */
|
||||
#ifdef ENABLE_IMAGES
|
||||
screen_write_image_damage(ctx, cx - n, cy, n, 1);
|
||||
#endif
|
||||
grid_view_set_cell(gd, cx - n, cy, &last);
|
||||
if (force_wide)
|
||||
grid_view_set_padding(gd, cx - 1, cy, last.bg);
|
||||
@@ -2993,9 +2960,11 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
* obscured in the middle, only on left or right, but there could be an
|
||||
* empty range in the visible ranges so we add them all up.
|
||||
*/
|
||||
if (wp != NULL)
|
||||
if (wp != NULL) {
|
||||
xoff = wp->xoff;
|
||||
yoff = wp->yoff;
|
||||
r = window_visible_ranges(wp, cx - n, cy + yoff, n, NULL);
|
||||
}
|
||||
r = window_visible_ranges(wp, xoff + cx - n, cy + yoff, n, NULL);
|
||||
for (i = 0, vis = 0; i < r->used; i++)
|
||||
vis += r->ranges[i].nx;
|
||||
if (vis < n) {
|
||||
@@ -3117,21 +3086,63 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len,
|
||||
tty_write(tty_cmd_rawstring, &ttyctx);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Write a SIXEL image. */
|
||||
void
|
||||
screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
|
||||
u_int bg)
|
||||
{
|
||||
struct image *im;
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct tty_ctx ttyctx;
|
||||
u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines;
|
||||
struct sixel_image *new;
|
||||
|
||||
im = sixel_to_image(si);
|
||||
if (im == NULL) {
|
||||
sixel_free(si);
|
||||
if (screen_size_y(s) == 1)
|
||||
return;
|
||||
|
||||
sixel_size_in_cells(si, &x, &y);
|
||||
if (x > screen_size_x(s) || y > screen_size_y(s) - 1) {
|
||||
if (x > screen_size_x(s) - cx)
|
||||
sx = screen_size_x(s) - cx;
|
||||
else
|
||||
sx = x;
|
||||
if (y > screen_size_y(s) - 1)
|
||||
sy = screen_size_y(s) - 1;
|
||||
else
|
||||
sy = y;
|
||||
new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1);
|
||||
sixel_free(si);
|
||||
if (new == NULL)
|
||||
return;
|
||||
sixel_size_in_cells(new, &x, &y);
|
||||
si = new;
|
||||
}
|
||||
image_write_sixel(ctx, im, bg);
|
||||
image_free(image_get_id(im));
|
||||
|
||||
sy = screen_size_y(s) - cy;
|
||||
if (sy <= y) {
|
||||
lines = y - sy + 1;
|
||||
if (image_scroll_up(s, lines) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
for (i = 0; i < lines; i++) {
|
||||
grid_view_scroll_region_up(gd, 0, screen_size_y(s) - 1,
|
||||
bg);
|
||||
screen_write_collect_scroll(ctx, bg);
|
||||
}
|
||||
ctx->scrolled += lines;
|
||||
if (lines > cy)
|
||||
screen_write_cursormove(ctx, -1, 0, 0);
|
||||
else
|
||||
screen_write_cursormove(ctx, -1, cy - lines, 0);
|
||||
}
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 0);
|
||||
ttyctx.image = image_store(s, si);
|
||||
|
||||
tty_write(tty_cmd_sixelimage, &ttyctx);
|
||||
|
||||
screen_write_cursormove(ctx, 0, cy + y, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
50
screen.c
50
screen.c
@@ -93,6 +93,11 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
|
||||
s->tabs = NULL;
|
||||
s->sel = NULL;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
TAILQ_INIT(&s->images);
|
||||
TAILQ_INIT(&s->saved_images);
|
||||
#endif
|
||||
|
||||
s->write_list = NULL;
|
||||
s->hyperlinks = NULL;
|
||||
|
||||
@@ -126,6 +131,11 @@ screen_reinit(struct screen *s, int check)
|
||||
|
||||
screen_clear_selection(s);
|
||||
screen_free_titles(s);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
#endif
|
||||
|
||||
screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0);
|
||||
screen_reset_hyperlinks(s);
|
||||
}
|
||||
@@ -144,6 +154,10 @@ screen_reset_hyperlinks(struct screen *s)
|
||||
void
|
||||
screen_free(struct screen *s)
|
||||
{
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
free(s->sel);
|
||||
free(s->tabs);
|
||||
free(s->path);
|
||||
@@ -160,6 +174,17 @@ screen_free(struct screen *s)
|
||||
hyperlinks_free(s->hyperlinks);
|
||||
screen_free_titles(s);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/*
|
||||
* Images saved when entering the alternate screen stay linked in the
|
||||
* global list; move them back so they are freed and unlinked here, or
|
||||
* a later eviction would write through this freed screen.
|
||||
*/
|
||||
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
|
||||
TAILQ_FOREACH(im, &s->images, entry)
|
||||
im->list = &s->images;
|
||||
image_free_all(s);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Reset tabs to default, eight spaces apart. */
|
||||
@@ -352,6 +377,10 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow,
|
||||
if (sy != screen_size_y(s))
|
||||
screen_resize_y(s, sy, eat_empty, &cy);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
#endif
|
||||
|
||||
if (reflow)
|
||||
screen_reflow(s, sx, &cx, &cy, cursor);
|
||||
|
||||
@@ -663,6 +692,10 @@ int
|
||||
screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
{
|
||||
u_int sx, sy;
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
if (SCREEN_IS_ALTERNATE(s))
|
||||
return 0;
|
||||
sx = screen_size_x(s);
|
||||
@@ -676,6 +709,12 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
}
|
||||
memcpy(&s->saved_cell, gc, sizeof s->saved_cell);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
TAILQ_CONCAT(&s->saved_images, &s->images, entry);
|
||||
TAILQ_FOREACH(im, &s->saved_images, entry)
|
||||
im->list = &s->saved_images;
|
||||
#endif
|
||||
|
||||
grid_view_clear(s->grid, 0, 0, sx, sy, 8);
|
||||
|
||||
s->saved_flags = s->grid->flags;
|
||||
@@ -689,6 +728,10 @@ int
|
||||
screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
{
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If the current size is different, temporarily resize to the old size
|
||||
* before copying back.
|
||||
@@ -731,6 +774,13 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
grid_destroy(s->saved_grid);
|
||||
s->saved_grid = NULL;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
|
||||
TAILQ_FOREACH(im, &s->images, entry)
|
||||
im->list = &s->images;
|
||||
#endif
|
||||
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
s->cx = screen_size_x(s) - 1;
|
||||
if (s->cy > screen_size_y(s) - 1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server-client.c,v 1.503 2026/08/17 07:56:56 nicm Exp $ */
|
||||
/* $OpenBSD: server-client.c,v 1.510 2026/09/01 19:50:58 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -45,6 +45,7 @@ static void server_client_set_path(struct client *);
|
||||
static void server_client_set_progress_bar(struct client *);
|
||||
static void server_client_reset_state(struct client *);
|
||||
static void server_client_update_latest(struct client *);
|
||||
static int server_client_handle_dead_key(struct window_pane *, key_code);
|
||||
static void server_client_dispatch(struct imsg *, void *);
|
||||
static int server_client_dispatch_command(struct client *, struct imsg *);
|
||||
static int server_client_dispatch_identify(struct client *, struct imsg *);
|
||||
@@ -523,7 +524,10 @@ server_client_lost(struct client *c)
|
||||
input_cancel_requests(c);
|
||||
|
||||
free(c->title);
|
||||
free(c->path);
|
||||
free((void *)c->cwd);
|
||||
free(c->exit_session);
|
||||
free(c->exit_message);
|
||||
|
||||
evtimer_del(&c->repeat_timer);
|
||||
evtimer_del(&c->click_timer);
|
||||
@@ -800,8 +804,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py,
|
||||
} else {
|
||||
/* Try the pane borders. */
|
||||
TAILQ_FOREACH(fwp, &w->panes, entry) {
|
||||
if ((w->flags & WINDOW_ZOOMED) &&
|
||||
(~fwp->flags & PANE_ZOOMED))
|
||||
if (!window_pane_is_visible(fwp))
|
||||
continue;
|
||||
if (window_pane_is_floating(fwp) &&
|
||||
window_pane_get_pane_lines(fwp) == PANE_LINES_NONE)
|
||||
@@ -1388,6 +1391,21 @@ server_client_repeat_time(struct client *c, struct key_binding *bd)
|
||||
return (repeat);
|
||||
}
|
||||
|
||||
/* Handle a key press on a dead pane waiting for a key. */
|
||||
static int
|
||||
server_client_handle_dead_key(struct window_pane *wp, key_code key)
|
||||
{
|
||||
if (wp == NULL ||
|
||||
(~wp->flags & PANE_EXITED) ||
|
||||
KEYC_IS_MOUSE(key) ||
|
||||
KEYC_IS_PASTE(key) ||
|
||||
options_get_number(wp->options, "remain-on-exit") != 3)
|
||||
return (0);
|
||||
options_set_number(wp->options, "remain-on-exit", 0);
|
||||
server_destroy_pane(wp, 0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle data key input from client. This owns and can modify the key event it
|
||||
* is given and is responsible for freeing it.
|
||||
@@ -1473,6 +1491,18 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
||||
server_client_is_assume_paste(c))
|
||||
goto paste_key;
|
||||
|
||||
/* Forward keys directly if this pane is capturing all keys. */
|
||||
if (wp != NULL &&
|
||||
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
||||
(~wp->flags & PANE_EXITED) &&
|
||||
!KEYC_IS_MOUSE(key) &&
|
||||
TAILQ_EMPTY(&wp->modes))
|
||||
goto forward_key;
|
||||
|
||||
/* Focus events are not keys and cannot be bound. */
|
||||
if (key == KEYC_FOCUS_IN || key == KEYC_FOCUS_OUT)
|
||||
goto forward_key;
|
||||
|
||||
/*
|
||||
* Work out the current key table. If the pane is in a mode, use
|
||||
* the mode table instead of the default key table.
|
||||
@@ -1637,15 +1667,8 @@ try_again:
|
||||
}
|
||||
|
||||
forward_key:
|
||||
if (wp != NULL &&
|
||||
(wp->flags & PANE_EXITED) &&
|
||||
!KEYC_IS_MOUSE(key) &&
|
||||
!KEYC_IS_PASTE(key) &&
|
||||
options_get_number(wp->options, "remain-on-exit") == 3) {
|
||||
options_set_number(wp->options, "remain-on-exit", 0);
|
||||
server_destroy_pane(wp, 0);
|
||||
if (server_client_handle_dead_key(wp, key))
|
||||
goto out;
|
||||
}
|
||||
if (c->flags & CLIENT_READONLY)
|
||||
goto out;
|
||||
if (wp != NULL)
|
||||
@@ -1733,9 +1756,9 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
||||
}
|
||||
|
||||
/*
|
||||
* Key presses in overlay mode and the command prompt are a special
|
||||
* case. The queue might be blocked so they need to be processed
|
||||
* immediately rather than queued.
|
||||
* Key presses in overlay mode, for panes capturing all keys and in the
|
||||
* command prompt are a special case. The queue might be blocked so they
|
||||
* need to be processed immediately rather than queued.
|
||||
*/
|
||||
if (~c->flags & CLIENT_READONLY) {
|
||||
if (c->message_string != NULL) {
|
||||
@@ -1755,6 +1778,21 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
||||
}
|
||||
|
||||
server_client_clear_overlay(c);
|
||||
|
||||
wp = s->curw->window->active;
|
||||
if (wp != NULL &&
|
||||
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
||||
TAILQ_EMPTY(&wp->modes) &&
|
||||
!KEYC_IS_MOUSE(event->key)) {
|
||||
if (server_client_handle_dead_key(wp, event->key))
|
||||
return (0);
|
||||
if (~wp->flags & PANE_EXITED) {
|
||||
window_pane_key(wp, c, s, s->curw, event->key,
|
||||
&event->m);
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
if (server_client_handle_menu_key(c, event))
|
||||
return (0);
|
||||
if (c->prompt != NULL) {
|
||||
@@ -2206,7 +2244,7 @@ server_client_reset_state(struct client *c)
|
||||
cy += status_line_size(c);
|
||||
}
|
||||
|
||||
if ((pane_mode & MODE_SYNC) || !cursor)
|
||||
if (!cursor)
|
||||
mode &= ~MODE_CURSOR;
|
||||
}
|
||||
} else if (c->overlay_mode == NULL || s == NULL)
|
||||
@@ -2214,6 +2252,10 @@ server_client_reset_state(struct client *c)
|
||||
if (~pane_mode & MODE_SYNC) {
|
||||
log_debug("%s: cursor to %u,%u", __func__, cx, cy);
|
||||
tty_cursor(tty, cx, cy);
|
||||
} else {
|
||||
mode &= ~CURSOR_MODES;
|
||||
mode |= tty->mode & CURSOR_MODES;
|
||||
s = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2375,8 +2417,6 @@ server_client_check_exit(struct client *c, int force)
|
||||
proc_send(c->peer, c->exit_msgtype, -1, name, strlen(name) + 1);
|
||||
break;
|
||||
}
|
||||
free(c->exit_session);
|
||||
free(c->exit_message);
|
||||
}
|
||||
|
||||
/* Redraw timer callback. */
|
||||
@@ -2441,14 +2481,13 @@ server_client_check_redraw(struct client *c)
|
||||
|
||||
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
|
||||
return;
|
||||
if (c->flags & (CLIENT_ALLREDRAWFLAGS|CLIENT_REDRAWSCROLLBARS)) {
|
||||
log_debug("%s: redraw%s%s%s%s%s%s", c->name,
|
||||
if (c->flags & CLIENT_ALLREDRAWFLAGS) {
|
||||
log_debug("%s: redraw%s%s%s%s%s", c->name,
|
||||
(c->flags & CLIENT_REDRAWWINDOW) ? " window" : "",
|
||||
(c->flags & CLIENT_REDRAWSTATUS) ? " status" : "",
|
||||
(c->flags & CLIENT_REDRAWBORDERS) ? " borders" : "",
|
||||
(c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : "",
|
||||
(c->flags & CLIENT_REDRAWMENU) ? " menu" : "",
|
||||
(c->flags & CLIENT_REDRAWSCROLLBARS) ? " scrollbars" : "");
|
||||
(c->flags & CLIENT_REDRAWMENU) ? " menu" : "");
|
||||
}
|
||||
|
||||
/* Work out if a redraw is actually needed. */
|
||||
|
||||
16
server-fn.c
16
server-fn.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server-fn.c,v 1.149 2026/07/27 14:25:46 nicm Exp $ */
|
||||
/* $OpenBSD: server-fn.c,v 1.152 2026/09/03 19:12:36 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -226,10 +226,11 @@ server_kill_pane(struct window_pane *wp)
|
||||
server_kill_window(w, 1);
|
||||
recalculate_sizes();
|
||||
} else {
|
||||
server_unzoom_window(w);
|
||||
window_push_zoom(w, 0, wp->flags & PANE_FLOATOVERZOOM);
|
||||
server_client_remove_pane(wp);
|
||||
layout_close_pane(wp);
|
||||
window_remove_pane(w, wp);
|
||||
window_pop_zoom(w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
}
|
||||
@@ -378,9 +379,9 @@ server_destroy_pane(struct window_pane *wp, int notify)
|
||||
wp->pipe_fd = -1;
|
||||
}
|
||||
|
||||
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
|
||||
if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY))
|
||||
if (~wp->flags & PANE_STATUSREADY)
|
||||
return;
|
||||
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
|
||||
switch (remain_on_exit) {
|
||||
case 0:
|
||||
break;
|
||||
@@ -421,15 +422,17 @@ server_destroy_pane(struct window_pane *wp, int notify)
|
||||
if (notify)
|
||||
server_fire_pane_exit("pane-exited", wp);
|
||||
|
||||
server_unzoom_window(w);
|
||||
window_push_zoom(w, 0, wp->flags & PANE_FLOATOVERZOOM);
|
||||
server_client_remove_pane(wp);
|
||||
layout_close_pane(wp);
|
||||
window_remove_pane(w, wp);
|
||||
|
||||
if (TAILQ_EMPTY(&w->panes))
|
||||
server_kill_window(w, 1);
|
||||
else
|
||||
else {
|
||||
window_pop_zoom(w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -551,6 +554,7 @@ server_check_unattached(void)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
server_destroy_session(s);
|
||||
session_destroy(s, 1, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
3
server.c
3
server.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server.c,v 1.215 2026/08/03 10:07:57 nicm Exp $ */
|
||||
/* $OpenBSD: server.c,v 1.216 2026/08/18 07:24:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -129,6 +129,7 @@ server_create_socket(uint64_t flags, char **cause)
|
||||
mask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
|
||||
if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
|
||||
saved_errno = errno;
|
||||
umask(mask);
|
||||
close(fd);
|
||||
errno = saved_errno;
|
||||
goto fail;
|
||||
|
||||
4
sort.c
4
sort.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: sort.c,v 1.9 2026/06/29 07:45:09 nicm Exp $ */
|
||||
/* $OpenBSD: sort.c,v 1.10 2026/08/25 07:23:30 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Dane Jensen <dhcjensen@gmail.com>
|
||||
@@ -32,7 +32,7 @@ sort_qsort(void *l, u_int len, u_int size, int (*cmp)(const void *,
|
||||
u_int i;
|
||||
void *tmp, **ll;
|
||||
|
||||
if (sort_crit->order == SORT_END)
|
||||
if (len < 2 || sort_crit->order == SORT_END)
|
||||
return;
|
||||
|
||||
if (sort_crit->order == SORT_ORDER) {
|
||||
|
||||
35
spawn.c
35
spawn.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: spawn.c,v 1.50 2026/07/23 09:38:27 nicm Exp $ */
|
||||
/* $OpenBSD: spawn.c,v 1.52 2026/08/20 09:19:24 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -243,7 +243,7 @@ struct window_pane *
|
||||
spawn_pane(struct spawn_context *sc, char **cause)
|
||||
{
|
||||
struct cmdq_item *item = sc->item;
|
||||
struct client *c;
|
||||
struct client *c, *loop;
|
||||
struct session *s = sc->s;
|
||||
struct session *ts;
|
||||
struct window *w = sc->wl->window;
|
||||
@@ -331,6 +331,20 @@ spawn_pane(struct spawn_context *sc, char **cause)
|
||||
input_free(sc->wp0->ictx);
|
||||
sc->wp0->ictx = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The old buffer is gone and the new one starts empty, so
|
||||
* offsets into the old buffer no longer mean anything. Reset
|
||||
* them, and drop output control clients still had queued.
|
||||
*/
|
||||
sc->wp0->offset.used = 0;
|
||||
sc->wp0->base_offset = 0;
|
||||
sc->wp0->pipe_offset.used = 0;
|
||||
TAILQ_FOREACH(loop, &clients, entry) {
|
||||
if (loop->flags & CLIENT_CONTROL)
|
||||
control_reset_pane(loop, sc->wp0);
|
||||
}
|
||||
|
||||
new_wp = sc->wp0;
|
||||
new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN);
|
||||
} else {
|
||||
@@ -346,6 +360,8 @@ spawn_pane(struct spawn_context *sc, char **cause)
|
||||
}
|
||||
if (sc->flags & SPAWN_FLOATING)
|
||||
new_wp->layout_cell->flags |= LAYOUT_CELL_FLOATING;
|
||||
if (sc->flags & SPAWN_FLOATOVERZOOM)
|
||||
new_wp->flags |= PANE_FLOATOVERZOOM;
|
||||
|
||||
/*
|
||||
* If window currently zoomed, window_set_active_pane calls
|
||||
@@ -685,7 +701,7 @@ spawn_editor_finish(struct window_pane *wp)
|
||||
|
||||
struct spawn_editor_state *
|
||||
spawn_editor(struct client *c, const char *buf, size_t len,
|
||||
spawn_finish_edit_cb cb, void *arg)
|
||||
const char *editor, spawn_finish_edit_cb cb, void *arg)
|
||||
{
|
||||
struct spawn_editor_state *es;
|
||||
struct spawn_context sc = { 0 };
|
||||
@@ -699,13 +715,13 @@ spawn_editor(struct client *c, const char *buf, size_t len,
|
||||
FILE *f;
|
||||
char *cmd, *cause = NULL;
|
||||
char path[] = _PATH_TMP "tmux.XXXXXXXX";
|
||||
const char *editor;
|
||||
int fd;
|
||||
|
||||
if (w->modal != NULL)
|
||||
return (NULL);
|
||||
|
||||
editor = options_get_string(global_options, "editor");
|
||||
if (editor == NULL)
|
||||
editor = options_get_string(global_options, "editor");
|
||||
fd = mkstemp(path);
|
||||
if (fd == -1)
|
||||
return (NULL);
|
||||
@@ -731,10 +747,10 @@ spawn_editor(struct client *c, const char *buf, size_t len,
|
||||
lg.sy = w->sy * 9 / 10;
|
||||
lg.xoff = w->sx / 2 - lg.sx / 2;
|
||||
lg.yoff = w->sy / 2 - lg.sy / 2;
|
||||
window_push_modal_zoom(w);
|
||||
window_push_zoom(w, 0, 1);
|
||||
lc = layout_floating_pane(w, NULL, &lg);
|
||||
if (lc == NULL) {
|
||||
window_pop_modal_zoom(w);
|
||||
window_pop_zoom(w);
|
||||
spawn_editor_free(es);
|
||||
return (NULL);
|
||||
}
|
||||
@@ -751,17 +767,18 @@ spawn_editor(struct client *c, const char *buf, size_t len,
|
||||
sc.environ = env;
|
||||
sc.idx = -1;
|
||||
sc.cwd = _PATH_TMP;
|
||||
sc.flags = SPAWN_FLOATING|SPAWN_MODAL;
|
||||
sc.flags = SPAWN_FLOATING|SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
|
||||
|
||||
wp = spawn_pane(&sc, &cause);
|
||||
free(cmd);
|
||||
environ_free(env);
|
||||
if (wp == NULL) {
|
||||
free(cause);
|
||||
window_pop_modal_zoom(w);
|
||||
window_pop_zoom(w);
|
||||
spawn_editor_free(es);
|
||||
return (NULL);
|
||||
}
|
||||
window_pop_zoom(w);
|
||||
options_set_number(wp->options, "remain-on-exit", 0);
|
||||
es->pid = wp->pid;
|
||||
wp->editor = es;
|
||||
|
||||
3
style.c
3
style.c
@@ -30,8 +30,7 @@
|
||||
|
||||
/* Default style. */
|
||||
static struct style style_default = {
|
||||
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0
|
||||
},
|
||||
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 },
|
||||
0,
|
||||
0,
|
||||
|
||||
|
||||
238
tmux.1
238
tmux.1
@@ -1,4 +1,4 @@
|
||||
.\" $OpenBSD: tmux.1,v 1.1156 2026/08/17 14:47:41 nicm Exp $
|
||||
.\" $OpenBSD: tmux.1,v 1.1164 2026/09/03 21:04:11 nicm Exp $
|
||||
.\"
|
||||
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
.\"
|
||||
@@ -14,7 +14,7 @@
|
||||
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
.\"
|
||||
.Dd $Mdocdate: August 17 2026 $
|
||||
.Dd $Mdocdate: September 3 2026 $
|
||||
.Dt TMUX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -2004,6 +2004,25 @@ Same as
|
||||
.Ic copy\-pipe
|
||||
but also exit copy mode.
|
||||
.It Xo
|
||||
.Ic copy\-pipe\-output
|
||||
.Op Fl aCP
|
||||
.Op Ar command
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the current OSC 133 command output, pipe it to
|
||||
.Ar command ,
|
||||
and clear the selection.
|
||||
.Ar prefix
|
||||
is used to name the new paste buffer.
|
||||
.It Xo
|
||||
.Ic copy\-output
|
||||
.Op Fl aCP
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the current OSC 133 command output and clear the selection.
|
||||
.Ar prefix
|
||||
is used to name the new paste buffer.
|
||||
.It Xo
|
||||
.Ic copy\-selection
|
||||
.Op Fl CP
|
||||
.Op Ar prefix
|
||||
@@ -2169,6 +2188,27 @@ Hide line numbers in copy mode.
|
||||
.Xc
|
||||
Toggle line numbers in copy mode.
|
||||
.It Xo
|
||||
.Ic collapse\-output
|
||||
.Op Fl a
|
||||
.Xc
|
||||
Collapse the output for the command on the cursor line.
|
||||
With
|
||||
.Fl a ,
|
||||
collapse all command output in the copy-mode buffer.
|
||||
Text before the first OSC 133 prompt is treated as an initial command output
|
||||
region.
|
||||
.It Xo
|
||||
.Ic expand\-output
|
||||
.Op Fl a
|
||||
.Xc
|
||||
Expand the output for the command on the cursor line.
|
||||
With
|
||||
.Fl a ,
|
||||
expand all command output in the copy-mode buffer.
|
||||
.Pp
|
||||
The Tab key toggles output for the command on the cursor line and S-Tab
|
||||
toggles all command output.
|
||||
.It Xo
|
||||
.Ic middle\-line
|
||||
(vi: M)
|
||||
(emacs: M\-r)
|
||||
@@ -2208,20 +2248,46 @@ Move to the end of the next word.
|
||||
.Xc
|
||||
Same as
|
||||
.Ic next\-word
|
||||
but use a space alone as the word separator.
|
||||
but treat all non-whitespace characters as part of a word.
|
||||
.It Xo
|
||||
.Ic next\-space\-end
|
||||
(vi: E)
|
||||
.Xc
|
||||
Same as
|
||||
.Ic next\-word\-end
|
||||
but use a space alone as the word separator.
|
||||
but treat all non-whitespace characters as part of a word.
|
||||
.It Xo
|
||||
.Ic other\-end
|
||||
(vi: o)
|
||||
.Xc
|
||||
Switch at which end of the selection the cursor sits.
|
||||
.It Xo
|
||||
.Ic open\-output
|
||||
.Op Fl a
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the current OSC 133 command output in an editor.
|
||||
If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise, the
|
||||
.Ic editor
|
||||
option is used.
|
||||
.It Xo
|
||||
.Ic open\-selection
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the current selection in an editor.
|
||||
If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise, the
|
||||
.Ic editor
|
||||
option is used.
|
||||
In emacs mode,
|
||||
.Ql e ,
|
||||
and in vi mode,
|
||||
.Ql M\-e ,
|
||||
opens the selection if one is present, otherwise the command output.
|
||||
.It Xo
|
||||
.Ic page\-down
|
||||
(vi: C\-f)
|
||||
(emacs: PageDown)
|
||||
@@ -2262,6 +2328,14 @@ Same as
|
||||
.Ic pipe
|
||||
but also exit copy mode.
|
||||
.It Xo
|
||||
.Ic pipe\-output
|
||||
.Op Fl a
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Pipe the current OSC 133 command output to
|
||||
.Ar command
|
||||
and clear the selection.
|
||||
.It Xo
|
||||
.Ic previous\-matching\-bracket
|
||||
(emacs: M\-C\-b)
|
||||
.Xc
|
||||
@@ -2289,7 +2363,7 @@ Move to the previous word.
|
||||
.Xc
|
||||
Same as
|
||||
.Ic previous\-word
|
||||
but use a space alone as the word separator.
|
||||
but treat all non-whitespace characters as part of a word.
|
||||
.It Xo
|
||||
.Ic rectangle\-on
|
||||
.Xc
|
||||
@@ -2456,6 +2530,16 @@ backward becomes forward).
|
||||
.Xc
|
||||
Select the current line.
|
||||
.It Xo
|
||||
.Ic select\-output
|
||||
.Op Fl a
|
||||
.Xc
|
||||
Select the current OSC 133 command output.
|
||||
If it is collapsed, it is expanded first.
|
||||
The default emacs binding is
|
||||
.Ql C\-o
|
||||
and the default vi binding is
|
||||
.Ql M\-o .
|
||||
.It Xo
|
||||
.Ic select\-word
|
||||
.Xc
|
||||
Select the current word.
|
||||
@@ -2494,6 +2578,12 @@ Toggle the visibility of the position indicator in the top right.
|
||||
Move to the top line.
|
||||
.El
|
||||
.Pp
|
||||
The output commands use OSC 133 output text from the C marker to the D marker.
|
||||
If there is no D marker, the output ends at the end of the copy-mode buffer.
|
||||
With
|
||||
.Fl a ,
|
||||
they use the entire copy-mode buffer instead.
|
||||
.Pp
|
||||
The search commands come in several varieties:
|
||||
.Ql search\-forward
|
||||
and
|
||||
@@ -2539,6 +2629,15 @@ Finding the beginning of command output requires the shell to emit an escape
|
||||
sequence (\e033]133;C\e033\e\e) to tell tmux where the output begins.
|
||||
If the shell does not send these escape sequences, these commands do nothing.
|
||||
.Pp
|
||||
The output commands operate on text from an OSC 133
|
||||
.Ql C
|
||||
marker up to its matching
|
||||
.Ql D
|
||||
marker, or the current end of the buffer if there is no
|
||||
.Ql D
|
||||
marker.
|
||||
They do nothing if the cursor is not associated with an output range.
|
||||
.Pp
|
||||
Copy commands may take an optional buffer prefix argument which is used
|
||||
to generate the buffer name (the default is
|
||||
.Ql buffer
|
||||
@@ -2574,8 +2673,8 @@ Word separators can be customized with the
|
||||
session option.
|
||||
Next word moves to the start of the next word, next word end to the end of the
|
||||
next word and previous word to the start of the previous word.
|
||||
The three next and previous space keys work similarly but use a space alone as
|
||||
the word separator.
|
||||
The three next and previous space keys work similarly but treat all
|
||||
non-whitespace characters as part of a word.
|
||||
Setting
|
||||
.Em word\-separators
|
||||
to the empty string makes next/previous word equivalent to next/previous space.
|
||||
@@ -2601,12 +2700,19 @@ The synopsis for the
|
||||
command is:
|
||||
.Bl -tag -width Ds
|
||||
.It Xo Ic copy\-mode
|
||||
.Op Fl dekHMqSu
|
||||
.Op Fl UcdekHMqSu
|
||||
.Op Fl s Ar src\-pane
|
||||
.Op Fl t Ar target\-pane
|
||||
.Xc
|
||||
Enter copy mode.
|
||||
.Pp
|
||||
.Fl c
|
||||
enters copy mode with OSC 133 command output collapsed.
|
||||
.Fl U
|
||||
enters copy mode with OSC 133 output controls shown and all output expanded.
|
||||
When output is collapsed, the prompt and entered command are shown and text
|
||||
from the output start marker to the next primary prompt marker is hidden.
|
||||
.Pp
|
||||
.Fl u
|
||||
enters copy mode and scrolls one page up and
|
||||
.Fl d
|
||||
@@ -2647,6 +2753,60 @@ bind PageDown copy\-mode \-ed
|
||||
kills the pane when the mode is exited.
|
||||
.El
|
||||
.Pp
|
||||
The following commands operate on OSC 133 command output. In copy mode, they
|
||||
use the output on the cursor line; otherwise, they use the most recent output
|
||||
in the target pane. If an output has a C marker but no D marker, it ends at the
|
||||
end of the pane buffer. If there is no OSC 133 output, they do nothing unless
|
||||
.Fl a
|
||||
is given, which uses the entire buffer.
|
||||
.Bl -tag -width Ds
|
||||
.It Xo Ic select\-output
|
||||
.Op Fl a
|
||||
.Op Fl t Ar target\-pane
|
||||
.Xc
|
||||
Enter copy mode if necessary and select the output.
|
||||
.It Xo Ic copy\-output
|
||||
.Op Fl aCP
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the output to a paste buffer.
|
||||
.It Xo Ic copy\-pipe\-output
|
||||
.Op Fl aCP
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Op Ar prefix
|
||||
.Xc
|
||||
Copy the output to a paste buffer and pipe it to
|
||||
.Ar command .
|
||||
.It Xo Ic pipe\-output
|
||||
.Op Fl a
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Pipe the output to
|
||||
.Ar command .
|
||||
.It Xo Ic open\-output
|
||||
.Op Fl a
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the output in an editor. If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise the
|
||||
.Ic editor
|
||||
option is used.
|
||||
.It Xo Ic open\-selection
|
||||
.Op Fl t Ar target\-pane
|
||||
.Op Ar command
|
||||
.Xc
|
||||
Open the current copy-mode selection in an editor. If
|
||||
.Ar command
|
||||
is given, it is used as the editor command; otherwise the
|
||||
.Ic editor
|
||||
option is used.
|
||||
.El
|
||||
.Pp
|
||||
A number of preset arrangements of panes are available, these are called
|
||||
layouts.
|
||||
These may be selected with the
|
||||
@@ -3590,13 +3750,20 @@ represents the window to be created; if the target already exists an error is
|
||||
shown, unless the
|
||||
.Fl k
|
||||
flag is used, in which case it is destroyed.
|
||||
.Ar window\-name
|
||||
is the name given to the new window, if any.
|
||||
.Pp
|
||||
If
|
||||
.Fl S
|
||||
is given and a window named
|
||||
.Ar window\-name
|
||||
already exists, it is selected (unless
|
||||
is given, an existing window is selected instead of creating a new one
|
||||
(unless
|
||||
.Fl d
|
||||
is also given in which case the command does nothing).
|
||||
is also given in which case the command does nothing):
|
||||
.Ar target\-window
|
||||
is preferred if it already identifies an existing window; otherwise a
|
||||
window named
|
||||
.Ar window\-name
|
||||
is selected if one exists.
|
||||
.Pp
|
||||
.Ar shell\-command
|
||||
is the command to execute.
|
||||
@@ -3644,7 +3811,7 @@ but a different format may be specified with
|
||||
.Fl F .
|
||||
.Tg newp
|
||||
.It Xo Ic new\-pane
|
||||
.Op Fl bCdefhIkLMOPvWZ
|
||||
.Op Fl AbCdefhIkKLMOPvWZ
|
||||
.Op Fl B Ar border\-lines
|
||||
.Op Fl c Ar start\-directory
|
||||
.Op Fl e Ar environment
|
||||
@@ -3693,14 +3860,17 @@ sets the pane border lines for floating panes; see
|
||||
.Fl M
|
||||
may be used when bound to a mouse drag key; the new floating pane is resized to
|
||||
follow the mouse drag.
|
||||
It has no effect with
|
||||
.Fl L .
|
||||
.Fl A
|
||||
creates a floating pane which remains visible above a zoomed pane.
|
||||
.Fl O
|
||||
creates a modal pane.
|
||||
A modal pane is always the active pane and prevents interaction with any other
|
||||
panes while it is active.
|
||||
A window can only have one modal pane and it must be a floating pane.
|
||||
With
|
||||
.Fl K ,
|
||||
all keys, including the prefix key, are passed directly to the modal pane.
|
||||
With
|
||||
.Fl C ,
|
||||
the modal pane is closed when the mouse is clicked outside it.
|
||||
.Pp
|
||||
@@ -4751,6 +4921,15 @@ for backspace.
|
||||
Set the number of buffers; as new buffers are added to the top of the stack,
|
||||
old ones are removed from the bottom if necessary to maintain this maximum
|
||||
length.
|
||||
.It Xo Ic clear\-on\-attach
|
||||
.Op Ic on | off
|
||||
.Xc
|
||||
When enabled (the default), the client terminal switches to the alternate
|
||||
screen and clears it when a client is attached.
|
||||
When disabled,
|
||||
.Nm
|
||||
does not enter the alternate screen; instead the existing terminal content is
|
||||
scrolled into the scrollback buffer so it remains accessible.
|
||||
.It Xo Ic command\-alias[]
|
||||
.Ar name=value
|
||||
.Xc
|
||||
@@ -5073,10 +5252,6 @@ Ignore function keys from
|
||||
and use the
|
||||
.Nm
|
||||
internal set only.
|
||||
.It kitty
|
||||
Supports Kitty graphics using Unicode placeholders.
|
||||
The terminal must support the Kitty Unicode placeholder extension, not only
|
||||
the basic Kitty graphics protocol.
|
||||
.It margins
|
||||
Supports DECSLRM margins.
|
||||
.It mouse
|
||||
@@ -5105,6 +5280,8 @@ Supports
|
||||
title setting.
|
||||
.It usstyle
|
||||
Allows underscore style and colour to be set.
|
||||
.It utf8
|
||||
Supports UTF\-8 output.
|
||||
.El
|
||||
.It Ic terminal\-overrides[] Ar string
|
||||
Allow terminal descriptions read using
|
||||
@@ -5639,6 +5816,11 @@ If set to both, a bell and a message are produced.
|
||||
Sets the session's conception of what characters are considered word
|
||||
separators, for the purposes of the next and previous word commands in
|
||||
copy mode.
|
||||
A space in
|
||||
.Ar string
|
||||
matches any character with the Unicode
|
||||
.Em White_Space
|
||||
property.
|
||||
.El
|
||||
.Pp
|
||||
Available window options are:
|
||||
@@ -5776,6 +5958,20 @@ section.
|
||||
.It Ic copy\-mode\-position\-format Ar format
|
||||
Format of the position indicator in copy mode.
|
||||
.Pp
|
||||
.It Ic copy\-mode\-exit\-status\-format Ar format
|
||||
Format of the OSC 133 exit status indicator in the copy-mode gutter.
|
||||
The default displays a theme red
|
||||
.Ql !
|
||||
for a nonzero status and a space otherwise.
|
||||
The gutter grows to fit the expanded format.
|
||||
The format is drawn to the left of the collapse control, and its alignment
|
||||
applies within that area.
|
||||
The format is expanded with
|
||||
.Ql exit_status
|
||||
and
|
||||
.Ql exit_status_present ,
|
||||
which is set only when the OSC 133 end-of-output marker includes a status.
|
||||
.Pp
|
||||
.It Ic copy\-mode\-position\-style Ar style
|
||||
Set the style of the position indicator in copy mode.
|
||||
For how to specify
|
||||
@@ -7308,7 +7504,10 @@ The following variables are available, where appropriate:
|
||||
.It Li "cursor_very_visible" Ta "" Ta "1 if the cursor is in very visible mode"
|
||||
.It Li "cursor_x" Ta "" Ta "Cursor X position in pane"
|
||||
.It Li "cursor_y" Ta "" Ta "Cursor Y position in pane"
|
||||
.It Li "history_added" Ta "" Ta "Number of lines scrolled into pane history"
|
||||
.It Li "history_bytes" Ta "" Ta "Number of bytes in window history"
|
||||
.It Li "history_collected" Ta "" Ta "Number of oldest pane history lines discarded at the history limit"
|
||||
.It Li "history_generation" Ta "" Ta "Generation incremented when pane history is cleared or reflowed"
|
||||
.It Li "history_limit" Ta "" Ta "Maximum window history lines"
|
||||
.It Li "history_size" Ta "" Ta "Size of history in lines"
|
||||
.It Li "hook" Ta "" Ta "Name of running hook, if any"
|
||||
@@ -7407,6 +7606,7 @@ The following variables are available, where appropriate:
|
||||
.It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set"
|
||||
.It Li "pane_modal_flag" Ta "" Ta "1 if pane is modal"
|
||||
.It Li "pane_mode" Ta "" Ta "Name of pane mode, if any"
|
||||
.It Li "pane_output_generation" Ta "" Ta "Generation incremented when pane output is processed"
|
||||
.It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)"
|
||||
.It Li "pane_pid" Ta "" Ta "PID of first process in pane"
|
||||
.It Li "pane_pipe" Ta "" Ta "1 if pane is being piped"
|
||||
|
||||
196
tmux.h
196
tmux.h
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tmux.h,v 1.1424 2026/08/17 14:47:41 nicm Exp $ */
|
||||
/* $OpenBSD: tmux.h,v 1.1433 2026/09/01 12:49:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -75,12 +75,7 @@ struct screen_write_cline;
|
||||
struct screen_write_ctx;
|
||||
struct session;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image;
|
||||
struct image_backend;
|
||||
struct image_rect;
|
||||
struct image_line;
|
||||
struct image_store;
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct sixel_image;
|
||||
#endif
|
||||
|
||||
@@ -488,6 +483,7 @@ enum tty_code_code {
|
||||
TTYC_ICH1,
|
||||
TTYC_IL,
|
||||
TTYC_IL1,
|
||||
TTYC_IND,
|
||||
TTYC_INDN,
|
||||
TTYC_INVIS,
|
||||
TTYC_KCBT,
|
||||
@@ -819,6 +815,7 @@ struct colour_palette {
|
||||
#define GRID_LINE_START_OUTPUT 0x40
|
||||
#define GRID_LINE_END_OUTPUT 0x80
|
||||
#define GRID_LINE_HYPERLINK 0x100
|
||||
#define GRID_LINE_END_OUTPUT_STATUS 0x200
|
||||
|
||||
/* All OSC 133 flags. */
|
||||
#define GRID_LINE_OSC133_FLAGS \
|
||||
@@ -826,7 +823,8 @@ struct colour_palette {
|
||||
GRID_LINE_SECOND_PROMPT| \
|
||||
GRID_LINE_START_COMMAND| \
|
||||
GRID_LINE_START_OUTPUT| \
|
||||
GRID_LINE_END_OUTPUT)
|
||||
GRID_LINE_END_OUTPUT| \
|
||||
GRID_LINE_END_OUTPUT_STATUS)
|
||||
|
||||
/* Grid string flags. */
|
||||
#define GRID_STRING_WITH_SEQUENCES 0x1
|
||||
@@ -863,7 +861,7 @@ struct colour_palette {
|
||||
struct grid_cell {
|
||||
struct utf8_data data;
|
||||
u_short attr;
|
||||
u_short flags;
|
||||
u_char flags;
|
||||
int fg;
|
||||
int bg;
|
||||
int us;
|
||||
@@ -874,7 +872,7 @@ struct grid_cell {
|
||||
struct grid_extd_entry {
|
||||
utf8_char data;
|
||||
u_short attr;
|
||||
u_short flags;
|
||||
u_char flags;
|
||||
int fg;
|
||||
int bg;
|
||||
int us;
|
||||
@@ -892,7 +890,7 @@ struct grid_cell_entry {
|
||||
u_char data;
|
||||
} data;
|
||||
};
|
||||
u_short flags;
|
||||
u_char flags;
|
||||
} __packed;
|
||||
|
||||
/* OSC 133 data for a grid line. */
|
||||
@@ -916,9 +914,6 @@ struct grid_line {
|
||||
u_int time;
|
||||
struct osc133_data osc133_data;
|
||||
u_short flags;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image_line *images;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Entire grid of cells. */
|
||||
@@ -938,9 +933,6 @@ struct grid {
|
||||
u_int scroll_generation;
|
||||
|
||||
struct grid_line *linedata;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image_store *images;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Virtual cursor in a grid. */
|
||||
@@ -1032,8 +1024,24 @@ struct style {
|
||||
u_int link;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Image. */
|
||||
struct image {
|
||||
struct screen *s;
|
||||
struct sixel_image *data;
|
||||
char *fallback;
|
||||
|
||||
u_int px;
|
||||
u_int py;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
|
||||
struct images *list;
|
||||
TAILQ_ENTRY (image) entry;
|
||||
|
||||
TAILQ_ENTRY (image) all_entry;
|
||||
};
|
||||
TAILQ_HEAD(images, image);
|
||||
#endif
|
||||
|
||||
/* Cursor style. */
|
||||
@@ -1092,6 +1100,11 @@ struct screen {
|
||||
bitstr_t *tabs;
|
||||
struct screen_sel *sel;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct images images;
|
||||
struct images saved_images;
|
||||
#endif
|
||||
|
||||
struct screen_write_cline *write_list;
|
||||
|
||||
struct hyperlinks *hyperlinks;
|
||||
@@ -1109,16 +1122,12 @@ struct screen_write_ctx {
|
||||
#define SCREEN_WRITE_SYNC 0x1
|
||||
#define SCREEN_WRITE_OBSCURED 0x2
|
||||
#define SCREEN_WRITE_CHECKED_IF_OBSCURED 0x4
|
||||
#define SCREEN_WRITE_INPUT 0x8
|
||||
|
||||
screen_write_init_ctx_cb init_ctx_cb;
|
||||
void *arg;
|
||||
|
||||
struct screen_write_citem *item;
|
||||
u_int scrolled;
|
||||
#ifdef ENABLE_IMAGES
|
||||
int scrolled_images;
|
||||
#endif
|
||||
u_int bg;
|
||||
};
|
||||
|
||||
@@ -1329,6 +1338,8 @@ struct window_pane {
|
||||
#define PANE_CMDRUNNING 0x20000
|
||||
#define PANE_ACTIVITY 0x40000
|
||||
#define PANE_CLOSEONCLICK 0x80000
|
||||
#define PANE_CAPTUREALLKEYS 0x100000
|
||||
#define PANE_FLOATOVERZOOM 0x200000
|
||||
|
||||
bitstr_t *sync_dirty;
|
||||
u_int sync_dirty_size;
|
||||
@@ -1348,9 +1359,10 @@ struct window_pane {
|
||||
char tty[TTY_NAME_MAX];
|
||||
int status;
|
||||
struct timeval dead_time;
|
||||
struct cmdq_item *wait_item; /* new-pane -W: waiting for pane exit */
|
||||
struct cmdq_item *wait_item;
|
||||
struct spawn_editor_state *editor;
|
||||
|
||||
uint64_t output_generation;
|
||||
time_t last_output_time;
|
||||
time_t last_prompt_time;
|
||||
time_t cmd_start_time;
|
||||
@@ -1435,6 +1447,7 @@ struct window {
|
||||
struct window_pane *active;
|
||||
struct window_pane *modal;
|
||||
struct window_pane *modal_last;
|
||||
struct window_pane *was_zoomed;
|
||||
struct window_panes last_panes;
|
||||
struct window_panes z_index;
|
||||
struct window_panes panes;
|
||||
@@ -1477,7 +1490,6 @@ struct window {
|
||||
#define WINDOW_ZOOMED 0x8
|
||||
#define WINDOW_WASZOOMED 0x10
|
||||
#define WINDOW_RESIZE 0x20
|
||||
#define WINDOW_WASMODALZOOMED 0x40
|
||||
#define WINDOW_ALERTFLAGS (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_SILENCE)
|
||||
|
||||
int alerts_queued;
|
||||
@@ -1725,8 +1737,7 @@ struct tty_term {
|
||||
#define TERM_RGBCOLOURS 0x10
|
||||
#define TERM_VT100LIKE 0x20
|
||||
#define TERM_SIXEL 0x40
|
||||
#define TERM_KITTY 0x80
|
||||
#define TERM_INVALIDMS 0x100
|
||||
#define TERM_INVALIDMS 0x80
|
||||
int flags;
|
||||
|
||||
LIST_ENTRY(tty_term) entry;
|
||||
@@ -1806,8 +1817,9 @@ struct tty {
|
||||
#define TTY_WAITFG 0x2000
|
||||
#define TTY_WAITBG 0x4000
|
||||
#define TTY_BRACKETPASTE 0x8000
|
||||
#define TTY_HAVESYNC 0x10000
|
||||
#define TTY_ALL_REQUEST_FLAGS \
|
||||
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA)
|
||||
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA|TTY_HAVESYNC)
|
||||
int flags;
|
||||
|
||||
struct tty_term *term;
|
||||
@@ -1828,10 +1840,6 @@ struct tty {
|
||||
|
||||
struct event key_timer;
|
||||
struct tty_key *key_tree;
|
||||
#ifdef ENABLE_IMAGES
|
||||
const struct image_backend *image_backend;
|
||||
void *image_data;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Terminal command context. */
|
||||
@@ -1853,8 +1861,6 @@ struct tty_ctx {
|
||||
#define TTY_CTX_OVERLAY_SYNC 0x10
|
||||
#define TTY_CTX_CELL_INVALIDATE 0x20
|
||||
#define TTY_CTX_PANE_OBSCURED 0x40
|
||||
#define TTY_CTX_IMAGE_SCROLL 0x80
|
||||
#define TTY_CTX_PANE_FULL_WIDTH 0x100
|
||||
|
||||
union {
|
||||
u_int n;
|
||||
@@ -1870,7 +1876,7 @@ struct tty_ctx {
|
||||
size_t size;
|
||||
} sel;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *image;
|
||||
#endif
|
||||
};
|
||||
@@ -2534,6 +2540,7 @@ struct spawn_context {
|
||||
#define SPAWN_HORIZONTAL 0x200
|
||||
#define SPAWN_SPLIT 0x400
|
||||
#define SPAWN_MODAL 0x800
|
||||
#define SPAWN_FLOATOVERZOOM 0x1000
|
||||
};
|
||||
|
||||
/* Paste buffer. */
|
||||
@@ -2989,6 +2996,10 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_setselection(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_rawstring(struct tty *, const struct tty_ctx *);
|
||||
#ifdef ENABLE_SIXEL
|
||||
void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *);
|
||||
void tty_draw_images(struct client *, struct window_pane *);
|
||||
#endif
|
||||
void tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
|
||||
void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *);
|
||||
|
||||
@@ -3597,6 +3608,7 @@ void screen_write_mode_set(struct screen_write_ctx *, int);
|
||||
void screen_write_mode_clear(struct screen_write_ctx *, int);
|
||||
void screen_write_start_sync(struct window_pane *);
|
||||
void screen_write_stop_sync(struct window_pane *);
|
||||
void screen_write_end_sync(struct screen_write_ctx *);
|
||||
void screen_write_clear_dirty(struct window_pane *);
|
||||
void screen_write_cursorup(struct screen_write_ctx *, u_int);
|
||||
void screen_write_cursordown(struct screen_write_ctx *, u_int);
|
||||
@@ -3631,7 +3643,7 @@ void screen_write_setselection(struct screen_write_ctx *, const char *,
|
||||
u_char *, u_int);
|
||||
void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
|
||||
int);
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
void screen_write_sixelimage(struct screen_write_ctx *,
|
||||
struct sixel_image *, u_int);
|
||||
#endif
|
||||
@@ -3726,8 +3738,8 @@ void window_resize(struct window *, u_int, u_int, int, int);
|
||||
void window_pane_send_resize(struct window_pane *, u_int, u_int);
|
||||
int window_zoom(struct window_pane *);
|
||||
int window_unzoom(struct window *, int);
|
||||
void window_push_modal_zoom(struct window *);
|
||||
int window_pop_modal_zoom(struct window *);
|
||||
int window_active_pane_is_over_zoom(struct window *);
|
||||
struct window_pane *window_zoomed_pane(struct window *);
|
||||
int window_push_zoom(struct window *, int, int);
|
||||
int window_pop_zoom(struct window *);
|
||||
void window_lost_pane(struct window *, struct window_pane *);
|
||||
@@ -4032,6 +4044,7 @@ void control_set_pane_on(struct client *, struct window_pane *);
|
||||
void control_set_pane_off(struct client *, struct window_pane *);
|
||||
void control_continue_pane(struct client *, struct window_pane *);
|
||||
void control_pause_pane(struct client *, struct window_pane *);
|
||||
void control_reset_pane(struct client *, struct window_pane *);
|
||||
void control_set_window_size(struct client *, u_int, u_int, u_int);
|
||||
int control_get_window_size(struct client *, u_int, u_int *, u_int *);
|
||||
void control_clear_window_size(struct client *, u_int);
|
||||
@@ -4096,6 +4109,7 @@ void session_update_history(struct session *);
|
||||
/* utf8.c */
|
||||
enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *);
|
||||
enum utf8_state utf8_fromwc(wchar_t wc, struct utf8_data *);
|
||||
int utf8_has_whitespace(const struct utf8_data *);
|
||||
void utf8_update_width_cache(void);
|
||||
utf8_char utf8_build_one(u_char);
|
||||
enum utf8_state utf8_from_data(const struct utf8_data *, utf8_char *);
|
||||
@@ -4209,7 +4223,7 @@ struct winlink *spawn_window(struct spawn_context *, char **);
|
||||
struct window_pane *spawn_pane(struct spawn_context *, char **);
|
||||
typedef void (*spawn_finish_edit_cb)(char *, size_t, void *);
|
||||
struct spawn_editor_state *spawn_editor(struct client *, const char *, size_t,
|
||||
spawn_finish_edit_cb, void *);
|
||||
const char *, spawn_finish_edit_cb, void *);
|
||||
void spawn_cancel_editor(struct spawn_editor_state *);
|
||||
pid_t spawn_get_editor_pid(struct spawn_editor_state *);
|
||||
void spawn_editor_finish(struct window_pane *);
|
||||
@@ -4217,106 +4231,17 @@ void spawn_editor_finish(struct window_pane *);
|
||||
/* regsub.c */
|
||||
char *regsub(const char *, const char *, const char *, int);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* image.c */
|
||||
#define IMAGE_BACKEND_GRAPHICAL 0x1
|
||||
#define IMAGE_BACKEND_SCROLLS 0x2
|
||||
int image_free_all(struct screen *);
|
||||
struct image *image_store(struct screen *, struct sixel_image *);
|
||||
int image_check_line(struct screen *, u_int, u_int);
|
||||
int image_check_area(struct screen *, u_int, u_int, u_int, u_int);
|
||||
int image_scroll_up(struct screen *, u_int);
|
||||
|
||||
struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int,
|
||||
u_char *);
|
||||
struct image *image_create_view(struct image *, u_int, u_int, u_int,
|
||||
u_int, u_int, u_int, u_int, u_int, u_int, u_int);
|
||||
struct image *image_find(u_int);
|
||||
u_int image_get_id(const struct image *);
|
||||
void image_get_size(const struct image *, u_int *, u_int *);
|
||||
void image_get_canvas_size(const struct image *, u_int *, u_int *);
|
||||
void image_get_size_in_cells(const struct image *, u_int *, u_int *);
|
||||
const u_char *image_get_pixels(const struct image *, size_t *, size_t *);
|
||||
void image_set_no_cursor(struct image *);
|
||||
struct sixel_image *image_get_sixel(const struct image *);
|
||||
void image_set_sixel(struct image *, struct sixel_image *);
|
||||
void image_ref(u_int);
|
||||
void image_free(u_int);
|
||||
void image_write_sixel(struct screen_write_ctx *, struct image *,
|
||||
u_int);
|
||||
void image_write_kitty(struct screen_write_ctx *, struct image *,
|
||||
u_int, u_int, u_int, int32_t);
|
||||
void image_get_pixel_rect(const struct image *, u_int, u_int,
|
||||
u_int, u_int, u_int *, u_int *, u_int *, u_int *);
|
||||
void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *,
|
||||
u_int *);
|
||||
u_char *image_base64_decode(const char *, size_t, size_t, size_t *);
|
||||
u_char *image_png_decode(const u_char *, size_t, size_t, u_int *,
|
||||
u_int *);
|
||||
void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
|
||||
u_int, u_int);
|
||||
void image_redraw_all(struct screen_write_ctx *);
|
||||
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
int image_backend_flags(struct tty *);
|
||||
void image_tty_update(struct tty *);
|
||||
void image_tty_geometry_changed(struct tty *);
|
||||
void image_tty_free(struct tty *, int);
|
||||
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
|
||||
u_int, u_int, u_int, int, const struct tty_style_ctx *);
|
||||
void image_draw_after_text(struct tty *, struct screen *, u_int,
|
||||
u_int, u_int, u_int, u_int, const struct tty_style_ctx *);
|
||||
void image_get_fallback_cell(struct tty *, struct image *, u_int,
|
||||
u_int, const struct grid_cell *, struct grid_cell *,
|
||||
const struct tty_style_ctx *);
|
||||
int image_get_fallback_at(struct tty *, struct screen *, u_int,
|
||||
u_int, const struct grid_cell *, struct grid_cell *,
|
||||
const struct tty_style_ctx *);
|
||||
struct image *image_rect_get_image(const struct image_rect *);
|
||||
const struct grid_cell *image_rect_get_cell(
|
||||
const struct image_rect *);
|
||||
void image_rect_get_coords(const struct image_rect *,
|
||||
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
|
||||
int32_t image_rect_get_z(const struct image_rect *);
|
||||
void image_clear(struct screen_write_ctx *, u_int);
|
||||
void image_clear_kitty(struct screen_write_ctx *, char, u_int,
|
||||
u_int, int32_t);
|
||||
void image_grid_damage(struct grid *, u_int, u_int, u_int, u_int);
|
||||
void image_grid_free_line(struct grid *, struct grid_line *);
|
||||
void image_grid_free(struct grid *);
|
||||
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
|
||||
u_int);
|
||||
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
|
||||
u_int, u_int);
|
||||
void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
|
||||
u_int, u_int, u_int, u_int);
|
||||
int image_grid_line_has_images(const struct grid_line *);
|
||||
int image_grid_check_area(struct grid *, u_int, u_int, u_int,
|
||||
u_int);
|
||||
int image_grid_get_source(struct grid *, u_int, u_int,
|
||||
struct image *, u_int *, u_int *);
|
||||
void image_place_cell_kitty(struct screen_write_ctx *, struct image *,
|
||||
u_int, u_int, u_int, u_int, u_int, u_int, int32_t);
|
||||
#define KITTY_PARSE_ERROR -1
|
||||
#define KITTY_PARSE_OK 0
|
||||
#define KITTY_PARSE_MORE 1
|
||||
#define KITTY_PARSE_MISSING 2
|
||||
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
|
||||
u_int, u_int *, u_int *, u_int *, char *, char *, u_int *,
|
||||
int32_t *, int *);
|
||||
int kitty_placeholder_to_image(void *, struct grid *,
|
||||
struct grid_cell *, u_int, u_int, struct image **, u_int *,
|
||||
u_int *, u_int *, u_int *, int32_t *);
|
||||
void kitty_free_state(void *);
|
||||
void kitty_draw_rect(struct tty *,
|
||||
const struct image_rect *, const struct tty_style_ctx *);
|
||||
void kitty_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
void kitty_free_output_state(struct tty *, int);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* image-sixel.c */
|
||||
#define SIXEL_COLOUR_REGISTERS 1024
|
||||
void sixel_draw_rect(struct tty *,
|
||||
const struct image_rect *, const struct tty_style_ctx *);
|
||||
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
void sixel_free_output(struct tty *, int);
|
||||
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,
|
||||
u_int);
|
||||
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int);
|
||||
void sixel_free(struct sixel_image *);
|
||||
void sixel_log(struct sixel_image *);
|
||||
void sixel_size_in_cells(struct sixel_image *, u_int *, u_int *);
|
||||
@@ -4325,7 +4250,6 @@ struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int,
|
||||
char *sixel_print(struct sixel_image *, struct sixel_image *,
|
||||
size_t *);
|
||||
struct screen *sixel_to_screen(struct sixel_image *);
|
||||
struct image *sixel_to_image(struct sixel_image *);
|
||||
#endif
|
||||
|
||||
/* server-acl.c */
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Generate the Unicode diacritic table used by Kitty image placeholders.
|
||||
#
|
||||
# Input must be UnicodeData.txt from Unicode 6.0.0:
|
||||
# https://www.unicode.org/Public/6.0.0/ucd/UnicodeData.txt
|
||||
# SHA-256: 90b45b777346bef027556f9c6cb3ea5d7d745bd60d6762855c08d8a22d34f771
|
||||
#
|
||||
# The selection follows the public Kitty graphics protocol: nonspacing marks
|
||||
# with canonical combining class 230, NSM bidi class, no decomposition, minus
|
||||
# characters which may be combined or normalized in ways unsuitable for a
|
||||
# row or column index.
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "usage: $0 UnicodeData.txt" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
awk -F ';' '
|
||||
BEGIN {
|
||||
omit["0300"] = omit["0301"] = omit["0302"] = 1
|
||||
omit["0303"] = omit["0304"] = omit["0306"] = 1
|
||||
omit["0307"] = omit["0308"] = omit["0309"] = 1
|
||||
omit["030A"] = omit["030B"] = omit["030C"] = 1
|
||||
omit["030F"] = omit["0311"] = omit["0313"] = 1
|
||||
omit["0314"] = omit["0342"] = omit["0653"] = 1
|
||||
omit["0654"] = 1
|
||||
}
|
||||
$3 == "Mn" && $4 == "230" && $5 == "NSM" && $6 == "" && !omit[$1] {
|
||||
point[++count] = $1
|
||||
}
|
||||
END {
|
||||
if (count != 297) {
|
||||
printf "expected 297 diacritics, found %u\n", count > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
print "static const uint32_t kitty_diacritics[] = {"
|
||||
for (i = 1; i <= count; i++) {
|
||||
if ((i - 1) % 8 == 0)
|
||||
printf "\t"
|
||||
printf "0x%s", point[i]
|
||||
if (i != count)
|
||||
printf ","
|
||||
if (i == count || i % 8 == 0)
|
||||
printf "\n"
|
||||
else
|
||||
printf " "
|
||||
}
|
||||
print "};"
|
||||
}
|
||||
' "$1"
|
||||
@@ -1,104 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Compact visual check for image subcell geometry and layering. Run inside a
|
||||
# tmux pane on Kitty or a SIXEL terminal; it needs at least 90 columns/24 rows.
|
||||
|
||||
if [ -z "$TMUX" ]; then
|
||||
echo "Run this script inside tmux." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
columns=$(tput cols 2>/dev/null || echo 0)
|
||||
lines=$(tput lines 2>/dev/null || echo 0)
|
||||
if [ "$columns" -lt 90 ] || [ "$lines" -lt 24 ]; then
|
||||
echo "This test needs at least 90 columns and 24 rows." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
at()
|
||||
{
|
||||
printf '\033[%s;%sH' "$1" "$2"
|
||||
}
|
||||
|
||||
kitty()
|
||||
{
|
||||
printf '\033_G%s\033\\' "$1"
|
||||
}
|
||||
|
||||
kitty_solid()
|
||||
{
|
||||
id=$1
|
||||
rgba=$2
|
||||
payload=$(i=0; while [ "$i" -lt 256 ]; do
|
||||
printf "$rgba"
|
||||
i=$((i + 1))
|
||||
done | base64 | tr -d '\n')
|
||||
kitty "a=t,q=2,f=32,s=16,v=16,i=$id;$payload"
|
||||
}
|
||||
|
||||
sixel_folder()
|
||||
{
|
||||
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\\'
|
||||
}
|
||||
|
||||
feature_info=$(tmux display-message -p \
|
||||
'term=#{client_termname} features=#{client_termfeatures} cells=#{client_cell_width}x#{client_cell_height}')
|
||||
|
||||
printf '\033[2J\033[H\033[?25l'
|
||||
at 1 2
|
||||
printf 'tmux image edge-case check'
|
||||
at 2 2
|
||||
printf '%s' "$feature_info"
|
||||
|
||||
kitty_solid 201 '\377\000\000\377'
|
||||
kitty_solid 202 '\000\377\000\377'
|
||||
kitty_solid 203 '\000\000\377\377'
|
||||
|
||||
# The 26-pixel folder occupies whole grid cells, but its visible right and
|
||||
# bottom edges must remain partial rather than stretching to those boundaries.
|
||||
at 4 2
|
||||
printf '1. partial SIXEL: folder should be the same size as direct output'
|
||||
at 4 72
|
||||
sixel_folder
|
||||
|
||||
# Chawan emits X/Y for its image padding. The red square must begin slightly
|
||||
# below and right of the [img] anchor, never over it.
|
||||
at 8 2
|
||||
printf '2. Kitty X/Y: RED begins below/right of the [img] anchor'
|
||||
at 8 72
|
||||
printf '[img]'
|
||||
at 9 72
|
||||
kitty 'a=p,q=2,C=1,i=201,p=1,x=0,y=0,w=16,h=16,c=3,r=2,X=6,Y=6'
|
||||
|
||||
# Positive Kitty z overlays text and other lower z images; negative z is
|
||||
# below text. The shared red/blue edge makes any wrong order obvious.
|
||||
at 12 2
|
||||
printf '3. Kitty z: BLUE covers RED; GREEN stays behind TEXT'
|
||||
at 12 72
|
||||
printf 'TEXT'
|
||||
at 12 72
|
||||
kitty 'a=p,q=2,C=1,i=202,p=2,x=0,y=0,w=16,h=16,c=4,r=2,z=-1'
|
||||
at 14 72
|
||||
kitty 'a=p,q=2,C=1,i=201,p=3,x=0,y=0,w=16,h=16,c=4,r=2,z=1'
|
||||
at 14 74
|
||||
kitty 'a=p,q=2,C=1,i=203,p=4,x=0,y=0,w=16,h=16,c=4,r=2,z=2'
|
||||
|
||||
# SIXEL is temporal: a later image covers text, while later text destroys the
|
||||
# affected image cells. Both results should match on Kitty and SIXEL output.
|
||||
at 18 2
|
||||
printf '4. SIXEL after text: folder covers FOLDER'
|
||||
at 18 72
|
||||
printf 'FOLDER'
|
||||
at 18 72
|
||||
sixel_folder
|
||||
at 21 2
|
||||
printf '5. SIXEL before text: TEXT WINS, no folder remnant'
|
||||
at 21 72
|
||||
sixel_folder
|
||||
at 21 72
|
||||
printf 'TEXT WINS'
|
||||
|
||||
at 23 2
|
||||
printf 'Take one screenshot, then press Enter to return to the shell.'
|
||||
printf '\033[?25h'
|
||||
IFS= read -r _image_edge_reply
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# One-screen visual check for tmux image input and layering. Run this inside a
|
||||
# tmux pane attached to Kitty, WezTerm, Microsoft Terminal, or a basic terminal.
|
||||
|
||||
if [ -z "$TMUX" ]; then
|
||||
echo "Run this script inside tmux." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
columns=$(tput cols 2>/dev/null || echo 0)
|
||||
lines=$(tput lines 2>/dev/null || echo 0)
|
||||
if [ "$columns" -lt 72 ] || [ "$lines" -lt 23 ]; then
|
||||
echo "This test needs at least 72 columns and 23 rows." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
at()
|
||||
{
|
||||
printf '\033[%s;%sH' "$1" "$2"
|
||||
}
|
||||
|
||||
kitty()
|
||||
{
|
||||
printf '\033_G%s\033\\' "$1"
|
||||
}
|
||||
|
||||
# Transmit a 16x16 solid RGBA texture. A one-pixel texture is unsuitable here
|
||||
# because Kitty filters its transparent texture border into scaled edges.
|
||||
kitty_solid()
|
||||
{
|
||||
id=$1
|
||||
rgba=$2
|
||||
payload=$(i=0; while [ "$i" -lt 256 ]; do
|
||||
printf "$rgba"
|
||||
i=$((i + 1))
|
||||
done | base64 | tr -d '\n')
|
||||
kitty "a=t,q=2,f=32,s=16,v=16,i=$id;$payload"
|
||||
}
|
||||
|
||||
# A small 26x26 transparent SIXEL folder, used by the supplied bptato tests.
|
||||
sixel_folder()
|
||||
{
|
||||
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\\'
|
||||
}
|
||||
|
||||
feature_info=$(tmux display-message -p \
|
||||
'term=#{client_termname} features=#{client_termfeatures} cells=#{client_cell_width}x#{client_cell_height}')
|
||||
|
||||
printf '\033[2J\033[H\033[?25l'
|
||||
at 1 2
|
||||
printf 'tmux image visual check'
|
||||
at 2 2
|
||||
printf '%s' "$feature_info"
|
||||
|
||||
# Red is placed after its text at a positive Kitty z-index, so it must cover
|
||||
# the text. Green is placed below the text at a negative Kitty z-index.
|
||||
kitty_solid 101 '\377\000\000\377'
|
||||
kitty_solid 102 '\000\377\000\377'
|
||||
kitty_solid 103 '\000\000\377\377'
|
||||
kitty_solid 104 '\377\000\000\177'
|
||||
|
||||
at 4 2
|
||||
printf '1. positive Kitty z: RED must hide TEXT UNDER at right'
|
||||
at 4 62
|
||||
printf 'TEXT UNDER'
|
||||
at 4 60
|
||||
kitty 'a=p,q=2,C=1,i=101,p=1,x=1,y=1,w=14,h=14,c=18,r=2,z=1'
|
||||
|
||||
at 7 2
|
||||
printf '2. negative Kitty z: GREEN remains behind TEXT ON TOP'
|
||||
at 7 60
|
||||
kitty 'a=p,q=2,C=1,i=102,p=2,x=1,y=1,w=14,h=14,c=18,r=2,z=-1'
|
||||
at 7 62
|
||||
printf 'TEXT ON TOP'
|
||||
|
||||
at 10 2
|
||||
printf '3. Kitty z overlap: RED left, BLUE on top at right'
|
||||
at 11 55
|
||||
kitty 'a=p,q=2,C=1,i=101,p=3,x=1,y=1,w=14,h=14,c=18,r=3,z=1'
|
||||
at 11 64
|
||||
kitty 'a=p,q=2,C=1,i=103,p=4,x=1,y=1,w=14,h=14,c=9,r=3,z=2'
|
||||
|
||||
at 15 2
|
||||
printf '4. alpha: half-transparent RED, dithered on SIXEL'
|
||||
at 15 60
|
||||
kitty 'a=p,q=2,C=1,i=104,p=5,x=1,y=1,w=14,h=14,c=18,r=2,z=1'
|
||||
|
||||
# SIXEL semantics are temporal: later text destroys a SIXEL image, while a
|
||||
# later SIXEL image appears over existing text.
|
||||
at 18 2
|
||||
printf '5. SIXEL after text: folder covers FOLDER at right'
|
||||
at 18 65
|
||||
printf 'FOLDER'
|
||||
at 18 65
|
||||
sixel_folder
|
||||
|
||||
at 21 2
|
||||
printf '6. SIXEL before text: no folder remains behind TEXT WINS'
|
||||
at 21 65
|
||||
sixel_folder
|
||||
at 21 65
|
||||
printf 'TEXT WINS'
|
||||
|
||||
at 23 2
|
||||
printf 'Take one screenshot, then press Enter to return to the shell.'
|
||||
at 23 66
|
||||
printf '\033[?25h'
|
||||
IFS= read -r _image_visual_reply
|
||||
29
tty-draw.c
29
tty-draw.c
@@ -123,10 +123,6 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
||||
struct grid *gd = s->grid;
|
||||
const struct grid_cell *gcp;
|
||||
struct grid_cell gc, ngc, last;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell image_gc;
|
||||
int image_status;
|
||||
#endif
|
||||
struct grid_line *gl;
|
||||
u_int i, j, last_i, cx, ex, width;
|
||||
u_int cellsize, bg;
|
||||
@@ -251,35 +247,20 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
||||
|
||||
if (px >= ex || i >= ex - px) {
|
||||
/* Outside the area being drawn. */
|
||||
empty = nx - i;
|
||||
gcp = &grid_default_cell;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_status = image_get_fallback_at(tty, s, px + i,
|
||||
py, gcp, &image_gc, style_ctx);
|
||||
if (image_status == 1) {
|
||||
gcp = &image_gc;
|
||||
empty = 0;
|
||||
} else if (image_status == -1)
|
||||
empty = 1;
|
||||
else
|
||||
#endif
|
||||
empty = nx - i;
|
||||
} else {
|
||||
/* Get the current cell. */
|
||||
grid_view_get_cell(gd, px + i, py, &gc);
|
||||
gcp = &gc;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_get_fallback_at(tty, s, px + i, py, &gc,
|
||||
&image_gc, style_ctx) == 1)
|
||||
gcp = &image_gc;
|
||||
#endif
|
||||
|
||||
/* Work out empty cells. */
|
||||
empty = tty_draw_line_get_empty(gcp, &last, nx - i);
|
||||
empty = tty_draw_line_get_empty(&gc, &last,
|
||||
nx - i);
|
||||
if (empty != 0)
|
||||
;
|
||||
gcp = &gc;
|
||||
else {
|
||||
/* Update for codeset if needed. */
|
||||
gcp = tty_check_codeset(tty, gcp);
|
||||
gcp = tty_check_codeset(tty, &gc);
|
||||
|
||||
/* And for selection. */
|
||||
if (gcp->flags & GRID_FLAG_SELECTED) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tty-features.c,v 1.42 2026/08/17 14:47:41 nicm Exp $ */
|
||||
/* $OpenBSD: tty-features.c,v 1.43 2026/08/31 12:41:03 kirill Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -224,11 +224,9 @@ static const struct tty_feature tty_feature_strikethrough = {
|
||||
0
|
||||
};
|
||||
|
||||
#define TTY_FEATURE_SYNC "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;"
|
||||
|
||||
/* Terminal supports synchronized updates. */
|
||||
static const char *const tty_feature_sync_capabilities[] = {
|
||||
TTY_FEATURE_SYNC,
|
||||
"Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;",
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_sync = {
|
||||
@@ -359,18 +357,6 @@ static const struct tty_feature tty_feature_sixel = {
|
||||
TERM_SIXEL
|
||||
};
|
||||
|
||||
/* Terminal has Kitty graphics protocol Unicode placeholder capability. */
|
||||
static const char *const tty_feature_kitty_capabilities[] = {
|
||||
"Kty",
|
||||
TTY_FEATURE_SYNC,
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_kitty = {
|
||||
"kitty",
|
||||
tty_feature_kitty_capabilities,
|
||||
TERM_KITTY
|
||||
};
|
||||
|
||||
/* Terminal supports the OSC 9;4 progress bar. */
|
||||
static const char *const tty_feature_progressbar_capabilities[] = {
|
||||
"Spb=\\E]9;4;%p1%d;%p2%d\\E\\\\",
|
||||
@@ -382,6 +368,13 @@ static const struct tty_feature tty_feature_progressbar = {
|
||||
0
|
||||
};
|
||||
|
||||
/* Terminal supports UTF-8. */
|
||||
static const struct tty_feature tty_feature_utf8 = {
|
||||
"utf8",
|
||||
NULL,
|
||||
0
|
||||
};
|
||||
|
||||
/* Available terminal features. */
|
||||
static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_256,
|
||||
@@ -393,7 +386,6 @@ static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_extkeys,
|
||||
&tty_feature_focus,
|
||||
&tty_feature_ignorefkeys,
|
||||
&tty_feature_kitty,
|
||||
&tty_feature_margins,
|
||||
&tty_feature_mouse,
|
||||
&tty_feature_osc7,
|
||||
@@ -405,7 +397,8 @@ static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_strikethrough,
|
||||
&tty_feature_sync,
|
||||
&tty_feature_title,
|
||||
&tty_feature_usstyle
|
||||
&tty_feature_usstyle,
|
||||
&tty_feature_utf8
|
||||
};
|
||||
|
||||
/* Parse features for client. */
|
||||
@@ -488,6 +481,9 @@ tty_feature_present(struct tty_term *term, const char *name)
|
||||
u_int i;
|
||||
char *copy;
|
||||
|
||||
if (strcmp(name, "utf8") == 0)
|
||||
return ((term->tty->client->flags & CLIENT_UTF8) != 0);
|
||||
|
||||
for (i = 0; i < nitems(tty_features); i++) {
|
||||
tf = tty_features[i];
|
||||
if (strcmp(tf->name, name) == 0) {
|
||||
@@ -501,7 +497,8 @@ tty_feature_present(struct tty_term *term, const char *name)
|
||||
* We don't just have the feature flag set. Check if the capabilities
|
||||
* supported by the client are actual set instead.
|
||||
*/
|
||||
if (tf == NULL || strcmp(name, "ignorefkeys") == 0)
|
||||
if (tf == NULL || tf->capabilities == NULL ||
|
||||
strcmp(name, "ignorefkeys") == 0)
|
||||
return (0);
|
||||
if (tf->flags != 0 && (term->flags & tf->flags) != tf->flags)
|
||||
return (0);
|
||||
@@ -549,6 +546,8 @@ tty_apply_features(struct tty_term *term)
|
||||
}
|
||||
}
|
||||
term->flags |= tf->flags;
|
||||
if (tf == &tty_feature_utf8)
|
||||
c->flags |= CLIENT_UTF8;
|
||||
}
|
||||
if ((term->applied_features|feat) == term->applied_features)
|
||||
return (0);
|
||||
@@ -624,7 +623,6 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
||||
"extkeys,"
|
||||
"focus,"
|
||||
"hyperlinks,"
|
||||
"sixel,"
|
||||
"usstyle"
|
||||
},
|
||||
{ .name = "ghostty",
|
||||
@@ -638,20 +636,7 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
||||
"osc7,"
|
||||
"sync,"
|
||||
"usstyle,"
|
||||
"progressbar,"
|
||||
"kitty"
|
||||
},
|
||||
{ .name = "kitty",
|
||||
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
|
||||
"ccolour,"
|
||||
"cstyle,"
|
||||
"extkeys,"
|
||||
"focus,"
|
||||
"hyperlinks,"
|
||||
"osc7,"
|
||||
"sync,"
|
||||
"usstyle,"
|
||||
"kitty"
|
||||
"progressbar"
|
||||
},
|
||||
{ .name = "Rio",
|
||||
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
|
||||
|
||||
63
tty-keys.c
63
tty-keys.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tty-keys.c,v 1.213 2026/08/17 14:47:41 nicm Exp $ */
|
||||
/* $OpenBSD: tty-keys.c,v 1.214 2026/08/18 09:01:20 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -59,6 +59,7 @@ static int tty_keys_device_attributes2(struct tty *, const char *, size_t,
|
||||
size_t *);
|
||||
static int tty_keys_extended_device_attributes(struct tty *, const char *,
|
||||
size_t, size_t *);
|
||||
static int tty_keys_sync(struct tty *, const char *, size_t, size_t *);
|
||||
static int tty_keys_palette(struct tty *, const char *, size_t, size_t *);
|
||||
|
||||
/* A key tree entry. */
|
||||
@@ -728,7 +729,6 @@ tty_keys_winsz(struct tty *tty, const char *buf, size_t len, size_t *size)
|
||||
char_y = (ypixel && tty->sy) ? ypixel / tty->sy : 0;
|
||||
tty_set_size(tty, tty->sx, tty->sy, char_x, char_y);
|
||||
tty_invalidate(tty);
|
||||
recalculate_sizes();
|
||||
|
||||
tty->flags &= ~TTY_WINSIZEQUERY;
|
||||
*size = end + 1;
|
||||
@@ -772,6 +772,17 @@ tty_keys_next(struct tty *tty)
|
||||
goto partial_key;
|
||||
}
|
||||
|
||||
/* Is this a synchronized update mode response? */
|
||||
switch (tty_keys_sync(tty, buf, len, &size)) {
|
||||
case 0: /* yes */
|
||||
key = KEYC_UNKNOWN;
|
||||
goto complete_key;
|
||||
case -1: /* no, or not valid */
|
||||
break;
|
||||
case 1: /* partial */
|
||||
goto partial_key;
|
||||
}
|
||||
|
||||
/* Is this a primary device attributes response? */
|
||||
switch (tty_keys_device_attributes(tty, buf, len, &size)) {
|
||||
case 0: /* yes */
|
||||
@@ -1522,6 +1533,54 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a synchronized update mode response. Returns 0 for success, -1 for
|
||||
* failure, 1 for partial.
|
||||
*/
|
||||
static int
|
||||
tty_keys_sync(struct tty *tty, const char *buf, size_t len, size_t *size)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
static const char prefix[] = "\033[?2026;";
|
||||
size_t i;
|
||||
int status;
|
||||
|
||||
*size = 0;
|
||||
if (tty->flags & TTY_HAVESYNC)
|
||||
return (-1);
|
||||
|
||||
/* The response is always \033[?2026;Ps$y. */
|
||||
for (i = 0; i < (sizeof prefix) - 1; i++) {
|
||||
if (i == len)
|
||||
return (1);
|
||||
if (buf[i] != prefix[i])
|
||||
return (-1);
|
||||
}
|
||||
if (i == len)
|
||||
return (1);
|
||||
if (buf[i] < '0' || buf[i] > '4')
|
||||
return (-1);
|
||||
status = buf[i++] - '0';
|
||||
if (i == len)
|
||||
return (1);
|
||||
if (buf[i++] != '$')
|
||||
return (-1);
|
||||
if (i == len)
|
||||
return (1);
|
||||
if (buf[i++] != 'y')
|
||||
return (-1);
|
||||
*size = i;
|
||||
|
||||
if (status == 1 || status == 2 || status == 3) {
|
||||
tty_parse_client_features(c, "sync", ",");
|
||||
tty_update_features(tty);
|
||||
}
|
||||
log_debug("%s: received DECRPM %.*s", c->name, (int)*size, buf);
|
||||
tty->flags |= TTY_HAVESYNC;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle secondary device attributes input. Returns 0 for success, -1 for
|
||||
* failure, 1 for partial.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tty-term.c,v 1.108 2026/08/17 14:47:41 nicm Exp $ */
|
||||
/* $OpenBSD: tty-term.c,v 1.109 2026/08/25 08:37:08 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -111,6 +111,7 @@ static const struct tty_term_code_entry tty_term_codes[] = {
|
||||
[TTYC_ICH] = { TTYCODE_STRING, "ich" },
|
||||
[TTYC_IL1] = { TTYCODE_STRING, "il1" },
|
||||
[TTYC_IL] = { TTYCODE_STRING, "il" },
|
||||
[TTYC_IND] = { TTYCODE_STRING, "ind" },
|
||||
[TTYC_INDN] = { TTYCODE_STRING, "indn" },
|
||||
[TTYC_INVIS] = { TTYCODE_STRING, "invis" },
|
||||
[TTYC_KCBT] = { TTYCODE_STRING, "kcbt" },
|
||||
@@ -524,7 +525,6 @@ tty_term_apply_overrides(struct tty_term *term)
|
||||
tty_term_validate(term);
|
||||
}
|
||||
|
||||
/* Mark terminals whose Ms capability does not contain a clipboard argument. */
|
||||
static void
|
||||
tty_term_validate(struct tty_term *term)
|
||||
{
|
||||
|
||||
317
tty.c
317
tty.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tty.c,v 1.478 2026/08/17 14:47:41 nicm Exp $ */
|
||||
/* $OpenBSD: tty.c,v 1.480 2026/08/25 08:37:08 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -62,15 +62,17 @@ static void tty_region(struct tty *, u_int, u_int);
|
||||
static void tty_margin_pane(struct tty *, const struct tty_ctx *);
|
||||
static void tty_margin(struct tty *, u_int, u_int);
|
||||
static int tty_large_region(struct tty *, const struct tty_ctx *);
|
||||
static int tty_scroll_images(struct tty *, const struct tty_ctx *, int *);
|
||||
static void tty_redraw_region(struct tty *, const struct tty_ctx *);
|
||||
static void tty_emulate_repeat(struct tty *, enum tty_code_code,
|
||||
enum tty_code_code, u_int);
|
||||
static void tty_draw_pane_line(struct tty *, const struct tty_ctx *, u_int,
|
||||
u_int, u_int, u_int, u_int);
|
||||
static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int);
|
||||
static int tty_check_overlay(struct tty *, u_int, u_int);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *),
|
||||
struct client *, struct tty_ctx *);
|
||||
#endif
|
||||
|
||||
#define tty_use_margin(tty) \
|
||||
(tty->term->flags & TERM_DECSLRM)
|
||||
#define tty_full_width(tty, ctx) \
|
||||
@@ -161,21 +163,10 @@ tty_resize(struct tty *tty)
|
||||
void
|
||||
tty_set_size(struct tty *tty, u_int sx, u_int sy, u_int xpixel, u_int ypixel)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
int geometry_changed;
|
||||
|
||||
geometry_changed = (tty->xpixel != xpixel || tty->ypixel != ypixel);
|
||||
#endif
|
||||
tty->sx = sx;
|
||||
tty->sy = sy;
|
||||
tty->xpixel = xpixel;
|
||||
tty->ypixel = ypixel;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (geometry_changed) {
|
||||
image_tty_geometry_changed(tty);
|
||||
server_redraw_client(tty->client);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -293,10 +284,6 @@ tty_open(struct tty *tty, char **cause)
|
||||
}
|
||||
tty->flags |= TTY_OPENED;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_update(tty);
|
||||
#endif
|
||||
|
||||
tty->flags &= ~(TTY_NOCURSOR|TTY_FREEZE|TTY_BLOCK|TTY_TIMER);
|
||||
|
||||
event_set(&tty->event_in, c->fd, EV_PERSIST|EV_READ,
|
||||
@@ -351,6 +338,7 @@ tty_start_tty(struct tty *tty)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
struct termios tio;
|
||||
u_int i;
|
||||
|
||||
setblocking(c->fd, 0);
|
||||
event_add(&tty->event_in, NULL);
|
||||
@@ -366,10 +354,21 @@ tty_start_tty(struct tty *tty)
|
||||
if (tcsetattr(c->fd, TCSANOW, &tio) == 0)
|
||||
tcflush(c->fd, TCOFLUSH);
|
||||
|
||||
tty_putcode(tty, TTYC_SMCUP);
|
||||
|
||||
if (options_get_number(global_options, "clear-on-attach")) {
|
||||
tty_putcode(tty, TTYC_SMCUP);
|
||||
tty_putcode(tty, TTYC_CLEAR);
|
||||
} else {
|
||||
tty_putcode_ii(tty, TTYC_CSR, 0, tty->sy - 1);
|
||||
tty_putcode_ii(tty, TTYC_CUP, 0, tty->sy - 1);
|
||||
if (tty_term_has(tty->term, TTYC_INDN))
|
||||
tty_putcode_i(tty, TTYC_INDN, tty->sy + 1);
|
||||
else if (tty_term_has(tty->term, TTYC_IND)) {
|
||||
for (i = 0; i < tty->sy + 1; i++)
|
||||
tty_putcode(tty, TTYC_IND);
|
||||
} else
|
||||
tty_putcode(tty, TTYC_CLEAR);
|
||||
}
|
||||
tty_putcode(tty, TTYC_SMKX);
|
||||
tty_putcode(tty, TTYC_CLEAR);
|
||||
|
||||
if (tty_acs_needed(tty)) {
|
||||
log_debug("%s: using capabilities for ACS", c->name);
|
||||
@@ -416,6 +415,8 @@ tty_send_requests(struct tty *tty)
|
||||
tty_puts(tty, "\033[>c");
|
||||
if (~tty->flags & TTY_HAVEXDA)
|
||||
tty_puts(tty, "\033[>q");
|
||||
if (~tty->flags & TTY_HAVESYNC)
|
||||
tty_puts(tty, "\033[?2026$p");
|
||||
tty_puts(tty, "\033]10;?\033\\\033]11;?\033\\");
|
||||
tty->flags |= (TTY_WAITBG|TTY_WAITFG);
|
||||
} else
|
||||
@@ -483,7 +484,8 @@ tty_stop_tty(struct tty *tty)
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_RMACS));
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_SGR0));
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_RMKX));
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
|
||||
if (options_get_number(global_options, "clear-on-attach"))
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
|
||||
if (tty->cstyle != SCREEN_CURSOR_DEFAULT) {
|
||||
if (tty_term_has(tty->term, TTYC_SE))
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_SE));
|
||||
@@ -508,7 +510,10 @@ tty_stop_tty(struct tty *tty)
|
||||
|
||||
if (tty_use_margin(tty))
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG));
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP));
|
||||
if (options_get_number(global_options, "clear-on-attach"))
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP));
|
||||
else
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
|
||||
|
||||
if (tty->term->flags & TERM_VT100LIKE)
|
||||
tty_raw(tty, "\033[?2031l");
|
||||
@@ -524,9 +529,6 @@ tty_close(struct tty *tty)
|
||||
tty_stop_tty(tty);
|
||||
|
||||
if (tty->flags & TTY_OPENED) {
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_free(tty, 1);
|
||||
#endif
|
||||
evbuffer_free(tty->in);
|
||||
event_del(&tty->event_in);
|
||||
evbuffer_free(tty->out);
|
||||
@@ -543,9 +545,6 @@ void
|
||||
tty_free(struct tty *tty)
|
||||
{
|
||||
tty_close(tty);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_free(tty, 0);
|
||||
#endif
|
||||
|
||||
free(tty->r.ranges);
|
||||
}
|
||||
@@ -557,9 +556,6 @@ tty_update_features(struct tty *tty)
|
||||
|
||||
if (tty_apply_features(tty->term))
|
||||
tty_term_apply_overrides(tty->term);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_update(tty);
|
||||
#endif
|
||||
|
||||
if (tty_use_margin(tty))
|
||||
tty_putcode(tty, TTYC_ENMG);
|
||||
@@ -1093,26 +1089,6 @@ tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx)
|
||||
return (ctx->orlower - ctx->orupper >= ctx->sy / 2);
|
||||
}
|
||||
|
||||
/* Work out whether and how the terminal can scroll image pixels. */
|
||||
static int
|
||||
tty_scroll_images(struct tty *tty, const struct tty_ctx *ctx, int *full_width)
|
||||
{
|
||||
*full_width = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (~ctx->flags & TTY_CTX_IMAGE_SCROLL)
|
||||
return (1);
|
||||
if (image_backend_flags(tty) & IMAGE_BACKEND_SCROLLS)
|
||||
return (1);
|
||||
if (ctx->flags & TTY_CTX_PANE_FULL_WIDTH) {
|
||||
*full_width = 1;
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
#else
|
||||
return (1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Return if BCE is needed but the terminal doesn't have it - it'll need to be
|
||||
* emulated.
|
||||
@@ -1446,34 +1422,11 @@ tty_clear_pane_area(struct tty *tty, const struct tty_ctx *ctx, u_int py,
|
||||
tty_clear_area(tty, ctx, y, ry, x, rx, bg);
|
||||
}
|
||||
|
||||
/* Redraw part of a pane line, including its ordered image layers. */
|
||||
static void
|
||||
tty_draw_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int px,
|
||||
u_int py, u_int nx, u_int atx, u_int aty)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
int has_images;
|
||||
|
||||
has_images = image_grid_check_area(ctx->s->grid, px,
|
||||
ctx->s->grid->hsize + py, nx, 1);
|
||||
if (has_images) {
|
||||
image_redraw_start(tty, atx, aty, nx, 1);
|
||||
image_draw_line(tty, ctx->s, px, py, nx, atx, aty, 1,
|
||||
&ctx->style_ctx);
|
||||
}
|
||||
#endif
|
||||
tty_draw_line(tty, ctx->s, px, py, nx, atx, aty, &ctx->style_ctx);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (has_images)
|
||||
image_draw_line(tty, ctx->s, px, py, nx, atx, aty, 0,
|
||||
&ctx->style_ctx);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Redraw a line of a screen at py. */
|
||||
static void
|
||||
tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py)
|
||||
{
|
||||
struct screen *s = ctx->s;
|
||||
u_int nx = ctx->sx, i, x, rx, ry, j;
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *rr;
|
||||
@@ -1486,8 +1439,8 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py)
|
||||
rr = &r->ranges[j];
|
||||
if (rr->nx == 0)
|
||||
continue;
|
||||
tty_draw_pane_line(tty, ctx, rr->px - ctx->xoff, py,
|
||||
rr->nx, rr->px, ctx->yoff + py);
|
||||
tty_draw_line(tty, s, rr->px - ctx->xoff, py, rr->nx,
|
||||
rr->px, ctx->yoff + py, &ctx->style_ctx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1497,8 +1450,8 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py)
|
||||
rr = &r->ranges[j];
|
||||
if (rr->nx == 0)
|
||||
continue;
|
||||
tty_draw_pane_line(tty, ctx, i + rr->px - x, py,
|
||||
rr->nx, rr->px, ry);
|
||||
tty_draw_line(tty, s, i + rr->px - x, py, rr->nx,
|
||||
rr->px, ry, &ctx->style_ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1587,6 +1540,60 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx)
|
||||
return (c->overlay_check(c, c->overlay_data, px, py, nx));
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Update context for client. */
|
||||
static int
|
||||
tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
|
||||
{
|
||||
struct window_pane *wp = ttyctx->arg;
|
||||
|
||||
if (c->session->curw->window != wp->window)
|
||||
return (0);
|
||||
if (wp->layout_cell == NULL)
|
||||
return (0);
|
||||
|
||||
if (tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx,
|
||||
&ttyctx->wsy))
|
||||
ttyctx->flags |= TTY_CTX_WINDOW_BIGGER;
|
||||
else
|
||||
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
|
||||
|
||||
ttyctx->yoff = ttyctx->ryoff = wp->yoff;
|
||||
if (status_at_line(c) == 0)
|
||||
ttyctx->yoff += status_line_size(c);
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
||||
void
|
||||
tty_draw_images(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
struct image *im;
|
||||
struct tty_ctx ttyctx;
|
||||
|
||||
TAILQ_FOREACH(im, &wp->screen->images, entry) {
|
||||
memset(&ttyctx, 0, sizeof ttyctx);
|
||||
|
||||
/* Set the client independent properties. */
|
||||
ttyctx.ocx = im->px;
|
||||
ttyctx.ocy = im->py;
|
||||
|
||||
ttyctx.orlower = wp->screen->rlower;
|
||||
ttyctx.orupper = wp->screen->rupper;
|
||||
|
||||
ttyctx.xoff = ttyctx.rxoff = wp->xoff;
|
||||
ttyctx.sx = wp->sx;
|
||||
ttyctx.sy = wp->sy;
|
||||
|
||||
ttyctx.image = im;
|
||||
ttyctx.arg = wp;
|
||||
ttyctx.set_client_cb = tty_set_client_cb;
|
||||
ttyctx.flags |= TTY_CTX_INVISIBLE_PANES;
|
||||
tty_write_one(tty_cmd_sixelimage, c, &ttyctx);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_sync_start(struct tty *tty)
|
||||
{
|
||||
@@ -1660,6 +1667,19 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *),
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Only write to the incoming tty instead of every client. */
|
||||
static void
|
||||
tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *),
|
||||
struct client *c, struct tty_ctx *ctx)
|
||||
{
|
||||
if (ctx->set_client_cb == NULL)
|
||||
return;
|
||||
if ((ctx->set_client_cb(ctx, c)) == 1)
|
||||
cmdfn(&c->tty, ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
@@ -1707,19 +1727,9 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx)
|
||||
void
|
||||
tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int px, py;
|
||||
#endif
|
||||
|
||||
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
|
||||
|
||||
tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->n, ctx->bg);
|
||||
#ifdef ENABLE_IMAGES
|
||||
px = ctx->xoff + ctx->ocx - ctx->wox;
|
||||
py = ctx->yoff + ctx->ocy - ctx->woy;
|
||||
image_draw_after_text(tty, ctx->s, ctx->ocx, ctx->ocy, ctx->n,
|
||||
px, py, &ctx->style_ctx);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1728,8 +1738,7 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx)
|
||||
struct client *c = tty->client;
|
||||
|
||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!tty_full_width(tty, ctx) &&
|
||||
(~ctx->flags & TTY_CTX_PANE_FULL_WIDTH)) ||
|
||||
!tty_full_width(tty, ctx) ||
|
||||
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||
!tty_term_has(tty->term, TTYC_IL1) ||
|
||||
@@ -1756,8 +1765,7 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx)
|
||||
struct client *c = tty->client;
|
||||
|
||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!tty_full_width(tty, ctx) &&
|
||||
(~ctx->flags & TTY_CTX_PANE_FULL_WIDTH)) ||
|
||||
!tty_full_width(tty, ctx) ||
|
||||
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||
!tty_term_has(tty->term, TTYC_DL1) ||
|
||||
@@ -1808,15 +1816,12 @@ void
|
||||
tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
int full_width;
|
||||
|
||||
if (ctx->ocy != ctx->orupper)
|
||||
return;
|
||||
|
||||
if (!tty_scroll_images(tty, ctx, &full_width) ||
|
||||
(ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!full_width && !tty_full_width(tty, ctx) &&
|
||||
!tty_use_margin(tty)) ||
|
||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
|
||||
tty_fake_bce(tty, &ctx->defaults, 8) ||
|
||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||
(!tty_term_has(tty->term, TTYC_RI) &&
|
||||
@@ -1831,10 +1836,7 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
|
||||
|
||||
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
|
||||
if (full_width)
|
||||
tty_margin_off(tty);
|
||||
else
|
||||
tty_margin_pane(tty, ctx);
|
||||
tty_margin_pane(tty, ctx);
|
||||
tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper);
|
||||
|
||||
if (tty_term_has(tty->term, TTYC_RI))
|
||||
@@ -1847,15 +1849,12 @@ void
|
||||
tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
int full_width;
|
||||
|
||||
if (ctx->ocy != ctx->orlower)
|
||||
return;
|
||||
|
||||
if (!tty_scroll_images(tty, ctx, &full_width) ||
|
||||
(ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!full_width && !tty_full_width(tty, ctx) &&
|
||||
!tty_use_margin(tty)) ||
|
||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
|
||||
tty_fake_bce(tty, &ctx->defaults, 8) ||
|
||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||
ctx->sx == 1 ||
|
||||
@@ -1868,10 +1867,7 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
|
||||
|
||||
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
|
||||
if (full_width)
|
||||
tty_margin_off(tty);
|
||||
else
|
||||
tty_margin_pane(tty, ctx);
|
||||
tty_margin_pane(tty, ctx);
|
||||
|
||||
/*
|
||||
* If we want to wrap a pane while using margins, the cursor needs to
|
||||
@@ -1896,12 +1892,9 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
u_int i;
|
||||
int full_width;
|
||||
|
||||
if (!tty_scroll_images(tty, ctx, &full_width) ||
|
||||
(ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!full_width && !tty_full_width(tty, ctx) &&
|
||||
!tty_use_margin(tty)) ||
|
||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
|
||||
tty_fake_bce(tty, &ctx->defaults, 8) ||
|
||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||
ctx->sx == 1 ||
|
||||
@@ -1914,10 +1907,7 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
|
||||
|
||||
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
|
||||
if (full_width)
|
||||
tty_margin_off(tty);
|
||||
else
|
||||
tty_margin_pane(tty, ctx);
|
||||
tty_margin_pane(tty, ctx);
|
||||
|
||||
if (ctx->n == 1 || !tty_term_has(tty->term, TTYC_INDN)) {
|
||||
if (!tty_use_margin(tty))
|
||||
@@ -1940,12 +1930,9 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
u_int i;
|
||||
struct client *c = tty->client;
|
||||
int full_width;
|
||||
|
||||
if (!tty_scroll_images(tty, ctx, &full_width) ||
|
||||
(ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!full_width && !tty_full_width(tty, ctx) &&
|
||||
!tty_use_margin(tty)) ||
|
||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
|
||||
tty_fake_bce(tty, &ctx->defaults, 8) ||
|
||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||
(!tty_term_has(tty->term, TTYC_RI) &&
|
||||
@@ -1960,10 +1947,7 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
|
||||
|
||||
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
|
||||
if (full_width)
|
||||
tty_margin_off(tty);
|
||||
else
|
||||
tty_margin_pane(tty, ctx);
|
||||
tty_margin_pane(tty, ctx);
|
||||
tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper);
|
||||
|
||||
if (tty_term_has(tty->term, TTYC_RIN))
|
||||
@@ -2086,16 +2070,6 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
|
||||
if (vis < gcp->data.width) {
|
||||
tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width,
|
||||
px, py, &ctx->style_ctx);
|
||||
#ifdef ENABLE_IMAGES
|
||||
for (i = 0; i < r->used; i++) {
|
||||
if (r->ranges[i].nx == 0)
|
||||
continue;
|
||||
image_draw_after_text(tty, s,
|
||||
ctx->ocx + r->ranges[i].px - px, ctx->ocy,
|
||||
r->ranges[i].nx, r->ranges[i].px, py,
|
||||
&ctx->style_ctx);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2112,11 +2086,6 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
|
||||
|
||||
tty_cell(tty, ctx->cell, &ctx->style_ctx);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_draw_after_text(tty, s, ctx->ocx, ctx->ocy, gcp->data.width,
|
||||
px, py, &ctx->style_ctx);
|
||||
#endif
|
||||
|
||||
if (ctx->flags & TTY_CTX_CELL_INVALIDATE)
|
||||
tty_invalidate(tty);
|
||||
}
|
||||
@@ -2164,10 +2133,6 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
|
||||
cx = ri->px - ctx->xoff + ctx->wox;
|
||||
tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy);
|
||||
tty_putn(tty, cp + ri->px - px, ri->nx, ri->nx);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_draw_after_text(tty, ctx->s, cx, ctx->ocy,
|
||||
ri->nx, ri->px, py, &ctx->style_ctx);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2208,6 +2173,58 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty_invalidate(tty);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
void
|
||||
tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
struct image *im = ctx->image;
|
||||
struct sixel_image *si = im->data;
|
||||
struct sixel_image *new;
|
||||
char *data;
|
||||
size_t size;
|
||||
u_int cx = ctx->ocx, cy = ctx->ocy, sx, sy;
|
||||
u_int i, j, x, y, rx, ry;
|
||||
int fallback = 0;
|
||||
|
||||
if ((~tty->term->flags & TERM_SIXEL) &&
|
||||
!tty_term_has(tty->term, TTYC_SXL))
|
||||
fallback = 1;
|
||||
if (tty->xpixel == 0 || tty->ypixel == 0)
|
||||
fallback = 1;
|
||||
|
||||
sixel_size_in_cells(si, &sx, &sy);
|
||||
log_debug("%s: image is %ux%u", __func__, sx, sy);
|
||||
if (!tty_clamp_area(tty, ctx, cx, cy, sx, sy, &i, &j, &x, &y, &rx, &ry))
|
||||
return;
|
||||
log_debug("%s: clamping to %u,%u-%u,%u", __func__, i, j, rx, ry);
|
||||
|
||||
if (fallback == 1) {
|
||||
data = xstrdup(im->fallback);
|
||||
size = strlen(data);
|
||||
} else {
|
||||
new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry, 0);
|
||||
if (new == NULL)
|
||||
return;
|
||||
|
||||
data = sixel_print(new, si, &size);
|
||||
}
|
||||
if (data != NULL) {
|
||||
log_debug("%s: %zu bytes: %s", __func__, size, data);
|
||||
tty_region_off(tty);
|
||||
tty_margin_off(tty);
|
||||
tty_cursor(tty, x, y);
|
||||
|
||||
tty->flags |= TTY_NOBLOCK;
|
||||
tty_add(tty, data, size);
|
||||
tty_invalidate(tty);
|
||||
free(data);
|
||||
}
|
||||
|
||||
if (fallback == 0)
|
||||
sixel_free(new);
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
|
||||
68
utf8.c
68
utf8.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: utf8.c,v 1.71 2026/05/12 09:37:25 nicm Exp $ */
|
||||
/* $OpenBSD: utf8.c,v 1.72 2026/09/01 12:49:49 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -603,6 +603,72 @@ utf8_towc(const struct utf8_data *ud, wchar_t *wc)
|
||||
return (UTF8_DONE);
|
||||
}
|
||||
|
||||
/* Check for a Unicode whitespace character. */
|
||||
int
|
||||
utf8_has_whitespace(const struct utf8_data *ud)
|
||||
{
|
||||
struct utf8_data tmp;
|
||||
wchar_t wc;
|
||||
u_int offset = 0, size;
|
||||
u_char ch;
|
||||
|
||||
while (offset < ud->size) {
|
||||
ch = ud->data[offset];
|
||||
if (ch < 0x80) {
|
||||
wc = ch;
|
||||
size = 1;
|
||||
} else {
|
||||
if (ch >= 0xc2 && ch <= 0xdf)
|
||||
size = 2;
|
||||
else if (ch >= 0xe0 && ch <= 0xef)
|
||||
size = 3;
|
||||
else if (ch >= 0xf0 && ch <= 0xf4)
|
||||
size = 4;
|
||||
else
|
||||
return (0);
|
||||
if (size > ud->size - offset)
|
||||
return (0);
|
||||
|
||||
memset(&tmp, 0, sizeof tmp);
|
||||
memcpy(tmp.data, ud->data + offset, size);
|
||||
tmp.size = tmp.have = size;
|
||||
if (utf8_towc(&tmp, &wc) != UTF8_DONE)
|
||||
return (0);
|
||||
}
|
||||
offset += size;
|
||||
|
||||
switch (wc) {
|
||||
case 0x0009:
|
||||
case 0x000A:
|
||||
case 0x000B:
|
||||
case 0x000C:
|
||||
case 0x000D:
|
||||
case 0x0020:
|
||||
case 0x0085:
|
||||
case 0x00A0:
|
||||
case 0x1680:
|
||||
case 0x2000:
|
||||
case 0x2001:
|
||||
case 0x2002:
|
||||
case 0x2003:
|
||||
case 0x2004:
|
||||
case 0x2005:
|
||||
case 0x2006:
|
||||
case 0x2007:
|
||||
case 0x2008:
|
||||
case 0x2009:
|
||||
case 0x200A:
|
||||
case 0x2028:
|
||||
case 0x2029:
|
||||
case 0x202F:
|
||||
case 0x205F:
|
||||
case 0x3000:
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Convert wide character to UTF-8 character. */
|
||||
enum utf8_state
|
||||
utf8_fromwc(wchar_t wc, struct utf8_data *ud)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window-buffer.c,v 1.51 2026/07/15 12:45:39 nicm Exp $ */
|
||||
/* $OpenBSD: window-buffer.c,v 1.52 2026/08/24 21:19:40 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -567,9 +567,7 @@ window_buffer_edit_close_cb(char *buf, size_t len, void *arg)
|
||||
}
|
||||
|
||||
oldbuf = paste_buffer_data(pb, &oldlen);
|
||||
if (oldlen != '\0' &&
|
||||
oldbuf[oldlen - 1] != '\n' &&
|
||||
buf[len - 1] == '\n')
|
||||
if (oldlen != 0 && oldbuf[oldlen - 1] != '\n' && buf[len - 1] == '\n')
|
||||
len--;
|
||||
if (len != 0)
|
||||
paste_replace(pb, buf, len);
|
||||
@@ -608,7 +606,8 @@ window_buffer_start_edit(struct window_buffer_modedata *data,
|
||||
ed->name = xstrdup(paste_buffer_name(pb));
|
||||
ed->pb = pb;
|
||||
|
||||
ed->editor = spawn_editor(c, buf, len, window_buffer_edit_close_cb, ed);
|
||||
ed->editor = spawn_editor(c, buf, len, NULL,
|
||||
window_buffer_edit_close_cb, ed);
|
||||
if (ed->editor == NULL)
|
||||
window_buffer_finish_edit(ed);
|
||||
else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window-client.c,v 1.48 2026/08/05 08:54:56 nicm Exp $ */
|
||||
/* $OpenBSD: window-client.c,v 1.49 2026/08/31 12:41:03 kirill Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -102,7 +102,8 @@ static const char *window_client_info_lines[] = {
|
||||
WINDOW_CLIENT_FEATURE(sync) " "
|
||||
WINDOW_CLIENT_FEATURE(title),
|
||||
" #[#{E:tree-mode-border-style},acs]x#[default] "
|
||||
WINDOW_CLIENT_FEATURE(usstyle),
|
||||
WINDOW_CLIENT_FEATURE(usstyle) " "
|
||||
WINDOW_CLIENT_FEATURE(utf8),
|
||||
"#[#{E:tree-mode-border-style},acs]qqqqqqqqqqqqqqn#{R:q,#{window_width}}#[default]",
|
||||
|
||||
"#[fg=themelightgrey]prefix #[#{E:tree-mode-border-style},acs]x#[default] "
|
||||
|
||||
1667
window-copy.c
1667
window-copy.c
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user