muxr 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +75 -8
- data/lib/muxr/command_dispatcher.rb +5 -3
- data/lib/muxr/input_handler.rb +6 -0
- data/lib/muxr/layout_manager.rb +157 -4
- data/lib/muxr/renderer.rb +5 -3
- data/lib/muxr/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b49dc8c773efbfdca08e50511ca4c66b7ef9026d5873e7f0d0f6f9adb24f946
|
|
4
|
+
data.tar.gz: 1d9db1a9dd14bfeb6e84646c91ff2c0804bff406d9e7716bf59f6b0dd2a4cbba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3147be5a9ab44a49eb9df20d69ea39a91218f8df5d182de13f141f1dcac2cc3f43ddf157dc1ef8f6088f1852aeeef130ce3b835476f317e2a41b8f331ac5b068
|
|
7
|
+
data.tar.gz: 847059de7300b33d2b9c84ab46d5505ca15b70211a9f1588418c8112ba4c20ac495f7964a7ea181e2246a3e9542acb26df149bad0097164332f8eb629cfc1a19
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,38 @@ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.10] - 2026-05-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Six new layouts join `tall`, `grid`, and `monocle`:
|
|
13
|
+
- **`wide`** (`w`) — master on top, slaves split across the bottom.
|
|
14
|
+
- **`columns`** (`|`) — equal-width, full-height vertical strips.
|
|
15
|
+
- **`rows`** (`-`) — equal-height, full-width horizontal strips.
|
|
16
|
+
- **`spiral`** (`f`) — Fibonacci spiral winding inward, each pane half
|
|
17
|
+
the size of the last.
|
|
18
|
+
- **`centered`** (`e`) — master in a centred column with slaves dealt
|
|
19
|
+
to both sides.
|
|
20
|
+
- **`stack`** (`S`) — accordion: the focused pane expands while the
|
|
21
|
+
others collapse to title slivers.
|
|
22
|
+
The `:layout` command resolves any unambiguous name prefix across all
|
|
23
|
+
nine layouts; `C-a Tab` / `Tab` cycles through them in order. README
|
|
24
|
+
screenshots cover every layout.
|
|
25
|
+
|
|
26
|
+
## [0.1.9] - 2026-05-29
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- `:layout` accepts short-form prefixes — `:layout t` / `g` / `m` map to
|
|
30
|
+
tall / grid / monocle via prefix matching. Full names still work; an
|
|
31
|
+
ambiguous prefix flashes the candidate layouts.
|
|
32
|
+
- README screenshots are now generated by [VHS](https://github.com/charmbracelet/vhs)
|
|
33
|
+
tapes under `docs/screenshots/tapes/`, regenerated with a single
|
|
34
|
+
`regenerate.sh` run. New captures cover scrollback `/` search and
|
|
35
|
+
movable-cursor visual selection.
|
|
36
|
+
|
|
37
|
+
### Documentation
|
|
38
|
+
- Documented that wrapped plain-text URLs are stamped with OSC 8
|
|
39
|
+
hyperlink ids (the 0.1.8 feature) in the README architecture section.
|
|
40
|
+
|
|
9
41
|
## [0.1.8] - 2026-05-22
|
|
10
42
|
|
|
11
43
|
### Added
|
data/README.md
CHANGED
|
@@ -33,17 +33,49 @@ with the border color — tracks the current [input mode](#modes).
|
|
|
33
33
|
|
|
34
34
|
## Screenshots
|
|
35
35
|
|
|
36
|
-
The
|
|
36
|
+
The built-in layouts (pick directly with the keys below in normal mode, or cycle with `Tab` / `C-a Tab`):
|
|
37
|
+
|
|
38
|
+
| Layout | Key | Geometry |
|
|
39
|
+
|--------|-----|----------|
|
|
40
|
+
| `tall` | `t` | master on the left, slaves stacked on the right |
|
|
41
|
+
| `wide` | `w` | master on top, slaves split across the bottom |
|
|
42
|
+
| `columns` | `\|` | equal-width full-height vertical strips |
|
|
43
|
+
| `rows` | `-` | equal-height full-width horizontal strips |
|
|
44
|
+
| `grid` | `g` | roughly-square even tiling |
|
|
45
|
+
| `spiral` | `f` | Fibonacci spiral winding inward (each pane half the last) |
|
|
46
|
+
| `centered` | `e` | master in a centred column, slaves dealt to both sides |
|
|
47
|
+
| `stack` | `S` | accordion — focused pane expands, others collapse to title slivers |
|
|
48
|
+
| `monocle` | `m` | focused pane fullscreen |
|
|
37
49
|
|
|
38
50
|
<table>
|
|
39
51
|
<tr>
|
|
40
52
|
<td align="center"><strong>tall</strong><br/>master + stacked slaves</td>
|
|
41
|
-
<td align="center"><strong>
|
|
42
|
-
<td align="center"><strong>
|
|
53
|
+
<td align="center"><strong>wide</strong><br/>master on top, slaves below</td>
|
|
54
|
+
<td align="center"><strong>columns</strong><br/>equal-width strips</td>
|
|
43
55
|
</tr>
|
|
44
56
|
<tr>
|
|
45
57
|
<td><img src="docs/screenshots/01-layout-tall.png" alt="tall layout"></td>
|
|
58
|
+
<td><img src="docs/screenshots/05-layout-wide.png" alt="wide layout"></td>
|
|
59
|
+
<td><img src="docs/screenshots/06-layout-columns.png" alt="columns layout"></td>
|
|
60
|
+
</tr>
|
|
61
|
+
<tr>
|
|
62
|
+
<td align="center"><strong>rows</strong><br/>equal-height strips</td>
|
|
63
|
+
<td align="center"><strong>grid</strong><br/>even tiling</td>
|
|
64
|
+
<td align="center"><strong>spiral</strong><br/>Fibonacci spiral</td>
|
|
65
|
+
</tr>
|
|
66
|
+
<tr>
|
|
67
|
+
<td><img src="docs/screenshots/07-layout-rows.png" alt="rows layout"></td>
|
|
46
68
|
<td><img src="docs/screenshots/02-layout-grid.png" alt="grid layout"></td>
|
|
69
|
+
<td><img src="docs/screenshots/08-layout-spiral.png" alt="spiral layout"></td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<td align="center"><strong>centered</strong><br/>master flanked by slaves</td>
|
|
73
|
+
<td align="center"><strong>stack</strong><br/>accordion of title slivers</td>
|
|
74
|
+
<td align="center"><strong>monocle</strong><br/>focused pane fullscreen</td>
|
|
75
|
+
</tr>
|
|
76
|
+
<tr>
|
|
77
|
+
<td><img src="docs/screenshots/09-layout-centered.png" alt="centered layout"></td>
|
|
78
|
+
<td><img src="docs/screenshots/10-layout-stack.png" alt="stack layout"></td>
|
|
47
79
|
<td><img src="docs/screenshots/03-layout-monocle.png" alt="monocle layout"></td>
|
|
48
80
|
</tr>
|
|
49
81
|
</table>
|
|
@@ -52,6 +84,17 @@ The Quake-style drawer overlay (`~` in normal mode, `C-a ~` in passthrough):
|
|
|
52
84
|
|
|
53
85
|

|
|
54
86
|
|
|
87
|
+
Scrollback / copy-mode (`s`) with `/` search — matches highlight in
|
|
88
|
+
yellow and the focused pane border turns orange:
|
|
89
|
+
|
|
90
|
+

|
|
91
|
+
|
|
92
|
+
Movable-cursor visual selection (`v` inside scrollback) — the border
|
|
93
|
+
turns magenta and the swept region is highlighted, ready to yank with
|
|
94
|
+
`y`:
|
|
95
|
+
|
|
96
|
+

|
|
97
|
+
|
|
55
98
|
## Install / run
|
|
56
99
|
|
|
57
100
|
```bash
|
|
@@ -89,7 +132,7 @@ muxr has two top-level input modes, modeled on vim:
|
|
|
89
132
|
|
|
90
133
|
- **Normal** (default at startup) — single keys act on the multiplexer.
|
|
91
134
|
`hjkl` moves focus between panes, `HJKL` moves the focused pane
|
|
92
|
-
itself, `c`/`x` create/close panes, `t`/`g`/`m` set the layout, etc.
|
|
135
|
+
itself, `c`/`x` create/close panes, `t`/`w`/`g`/`m` (and `|`/`-`/`f`/`e`/`S`) set the layout, etc.
|
|
93
136
|
No prefix needed.
|
|
94
137
|
- **Passthrough** (entered with `i`) — every keystroke is forwarded to
|
|
95
138
|
the focused pane, exactly like a regular terminal. muxr commands are
|
|
@@ -113,7 +156,8 @@ regardless of mode.
|
|
|
113
156
|
| `H` / `J` / `K` / `L`| move focused pane left / down / up / right |
|
|
114
157
|
| `i` | drop into passthrough mode |
|
|
115
158
|
| `c` / `x` | new / close focused pane (close asks `y/n`) |
|
|
116
|
-
| `t` / `g` / `m
|
|
159
|
+
| `t` / `w` / `g` / `m`| layout: tall / wide / grid / monocle |
|
|
160
|
+
| `\|` / `-` / `f` / `e` / `S` | layout: columns / rows / spiral / centered / stack |
|
|
117
161
|
| `Tab` / `Enter` | cycle layout / promote focused to master |
|
|
118
162
|
| `a` / `1` … `9` | toggle last pane / jump to pane by number |
|
|
119
163
|
| `s` | enter scrollback / copy-mode |
|
|
@@ -144,7 +188,7 @@ the move falls back to linear next/prev shuffling.
|
|
|
144
188
|
| `C-a a` | toggle last (previously focused) pane |
|
|
145
189
|
| `C-a 1` … `9` | jump to pane by its label |
|
|
146
190
|
| `C-a x` | close focused pane (asks `y/n`; hides drawer with no prompt) |
|
|
147
|
-
| `C-a Tab` | cycle layout (`tall` → `grid` → `monocle`)
|
|
191
|
+
| `C-a Tab` | cycle layout (`tall` → `wide` → `columns` → `rows` → `grid` → `spiral` → `centered` → `stack` → `monocle`) |
|
|
148
192
|
| `C-a Enter` | promote focused pane to master |
|
|
149
193
|
| `C-a ~` | toggle drawer (shell) |
|
|
150
194
|
| `C-a C` | toggle Claude Code drawer (MCP-aware) |
|
|
@@ -206,7 +250,10 @@ focused pane.
|
|
|
206
250
|
## Commands (typed after `:` in normal mode, or `C-a :` in passthrough)
|
|
207
251
|
|
|
208
252
|
```
|
|
209
|
-
layout {tall|grid|monocle}
|
|
253
|
+
layout {tall|wide|columns|rows|grid|spiral|centered|stack|monocle}
|
|
254
|
+
# any unambiguous name prefix works (t, w, r, g, m, …);
|
|
255
|
+
# ambiguous ones (c → columns/centered, s → spiral/stack)
|
|
256
|
+
# flash the candidates. layout (no arg) → cycle
|
|
210
257
|
drawer {toggle|show|hide|reset}
|
|
211
258
|
claude # toggle the Claude Code drawer
|
|
212
259
|
private # toggle private flag on focused pane
|
|
@@ -324,7 +371,11 @@ The per-pane `Terminal` is a real VT100 emulator (cursor movement, SGR
|
|
|
324
371
|
including 256-color/truecolor and underline subparameters, erase/insert/
|
|
325
372
|
delete, autowrap, scroll regions). Scrollback is composited into the
|
|
326
373
|
visible grid through a view-offset that auto-tracks new rows while
|
|
327
|
-
scrolled back, so reviewed content stays frozen.
|
|
374
|
+
scrolled back, so reviewed content stays frozen. Plain-text `http`/
|
|
375
|
+
`https`/`ftp` URLs that wrap across rows are re-stamped with matching
|
|
376
|
+
OSC 8 hyperlink ids after each feed, so terminals like Ghostty, iTerm2,
|
|
377
|
+
kitty, and WezTerm merge the wrapped halves back into one clickable
|
|
378
|
+
link (program-emitted OSC 8 payloads are left untouched).
|
|
328
379
|
|
|
329
380
|
## Session persistence
|
|
330
381
|
|
|
@@ -390,6 +441,22 @@ On-disk layout:
|
|
|
390
441
|
└─ logs/<name>.log server stdout/stderr
|
|
391
442
|
```
|
|
392
443
|
|
|
444
|
+
### Regenerating the README screenshots
|
|
445
|
+
|
|
446
|
+
The PNGs under `docs/screenshots/` are produced by [`vhs`](https://github.com/charmbracelet/vhs)
|
|
447
|
+
driving muxr itself — one `.tape` file per screenshot under
|
|
448
|
+
`docs/screenshots/tapes/`. After a UI change, refresh them with:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
brew install vhs # one-time
|
|
452
|
+
docs/screenshots/tapes/regenerate.sh # renders all six
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Each tape spawns a throwaway `shot` session, populates one or more panes
|
|
456
|
+
with `ls`/`git log`/`wc` output, drives the feature being shown (layout,
|
|
457
|
+
drawer, scrollback search, selection), and writes a single PNG via
|
|
458
|
+
`Screenshot`. Tweak the tape if the keybindings or status bar change.
|
|
459
|
+
|
|
393
460
|
## Contributing
|
|
394
461
|
|
|
395
462
|
Contributions are welcome from anyone, with one requirement: **the code
|
|
@@ -45,10 +45,12 @@ module Muxr
|
|
|
45
45
|
@app.cycle_layout
|
|
46
46
|
return
|
|
47
47
|
end
|
|
48
|
-
|
|
49
|
-
if
|
|
50
|
-
@app.session.window.set_layout(
|
|
48
|
+
matches = Window::LAYOUTS.select { |l| l.to_s.start_with?(name) }
|
|
49
|
+
if matches.length == 1
|
|
50
|
+
@app.session.window.set_layout(matches.first)
|
|
51
51
|
@app.invalidate
|
|
52
|
+
elsif matches.length > 1
|
|
53
|
+
@app.flash("ambiguous layout: #{name} (#{matches.join(", ")})")
|
|
52
54
|
else
|
|
53
55
|
@app.flash("unknown layout: #{name}")
|
|
54
56
|
end
|
data/lib/muxr/input_handler.rb
CHANGED
|
@@ -31,8 +31,14 @@ module Muxr
|
|
|
31
31
|
"c" => :new_pane,
|
|
32
32
|
"x" => :request_close,
|
|
33
33
|
"t" => [:set_layout, :tall],
|
|
34
|
+
"w" => [:set_layout, :wide],
|
|
34
35
|
"g" => [:set_layout, :grid],
|
|
35
36
|
"m" => [:set_layout, :monocle],
|
|
37
|
+
"|" => [:set_layout, :columns],
|
|
38
|
+
"-" => [:set_layout, :rows],
|
|
39
|
+
"f" => [:set_layout, :spiral],
|
|
40
|
+
"e" => [:set_layout, :centered],
|
|
41
|
+
"S" => [:set_layout, :stack],
|
|
36
42
|
"\t" => :cycle_layout,
|
|
37
43
|
"\r" => :promote_master,
|
|
38
44
|
"\n" => :promote_master,
|
data/lib/muxr/layout_manager.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Muxr
|
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
LAYOUTS = %i[tall grid monocle].freeze
|
|
13
|
+
LAYOUTS = %i[tall wide columns rows grid spiral centered stack monocle].freeze
|
|
14
14
|
|
|
15
15
|
module_function
|
|
16
16
|
|
|
@@ -19,9 +19,15 @@ module Muxr
|
|
|
19
19
|
master_index = master_index.clamp(0, count - 1)
|
|
20
20
|
focused_index = focused_index.clamp(0, count - 1)
|
|
21
21
|
case layout
|
|
22
|
-
when :tall
|
|
23
|
-
when :
|
|
24
|
-
when :
|
|
22
|
+
when :tall then tall(count, area, master_index)
|
|
23
|
+
when :wide then wide(count, area, master_index)
|
|
24
|
+
when :columns then columns(count, area)
|
|
25
|
+
when :rows then rows(count, area)
|
|
26
|
+
when :grid then grid(count, area)
|
|
27
|
+
when :spiral then spiral(count, area)
|
|
28
|
+
when :centered then centered(count, area, master_index)
|
|
29
|
+
when :stack then stack(count, area, focused_index)
|
|
30
|
+
when :monocle then monocle(count, area, focused_index)
|
|
25
31
|
else
|
|
26
32
|
raise ArgumentError, "Unknown layout: #{layout.inspect}"
|
|
27
33
|
end
|
|
@@ -52,6 +58,153 @@ module Muxr
|
|
|
52
58
|
rects
|
|
53
59
|
end
|
|
54
60
|
|
|
61
|
+
# The transpose of `tall`: master pane spans the full width across the top
|
|
62
|
+
# half; remaining panes sit side-by-side in the bottom half, dividing the
|
|
63
|
+
# remaining width evenly.
|
|
64
|
+
def wide(count, area, master_index = 0)
|
|
65
|
+
master_index = master_index.clamp(0, count - 1)
|
|
66
|
+
return [Rect.new(area.x, area.y, area.w, area.h)] if count == 1
|
|
67
|
+
|
|
68
|
+
master_h = [area.h / 2, 1].max
|
|
69
|
+
stack_h = [area.h - master_h, 1].max
|
|
70
|
+
others = (0...count).to_a - [master_index]
|
|
71
|
+
slave_count = others.length
|
|
72
|
+
base_w = area.w / slave_count
|
|
73
|
+
remainder = area.w - base_w * slave_count
|
|
74
|
+
|
|
75
|
+
rects = Array.new(count)
|
|
76
|
+
rects[master_index] = Rect.new(area.x, area.y, area.w, master_h)
|
|
77
|
+
|
|
78
|
+
x = area.x
|
|
79
|
+
others.each_with_index do |idx, i|
|
|
80
|
+
w = base_w + (i < remainder ? 1 : 0)
|
|
81
|
+
rects[idx] = Rect.new(x, area.y + master_h, w, stack_h)
|
|
82
|
+
x += w
|
|
83
|
+
end
|
|
84
|
+
rects
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Equal-width, full-height vertical strips, side by side. No master.
|
|
88
|
+
def columns(count, area)
|
|
89
|
+
base_w = area.w / count
|
|
90
|
+
rem = area.w - base_w * count
|
|
91
|
+
rects = []
|
|
92
|
+
x = area.x
|
|
93
|
+
count.times do |i|
|
|
94
|
+
w = base_w + (i < rem ? 1 : 0)
|
|
95
|
+
rects << Rect.new(x, area.y, w, area.h)
|
|
96
|
+
x += w
|
|
97
|
+
end
|
|
98
|
+
rects
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Equal-height, full-width horizontal strips, stacked. The dual of columns.
|
|
102
|
+
def rows(count, area)
|
|
103
|
+
base_h = area.h / count
|
|
104
|
+
rem = area.h - base_h * count
|
|
105
|
+
rects = []
|
|
106
|
+
y = area.y
|
|
107
|
+
count.times do |i|
|
|
108
|
+
h = base_h + (i < rem ? 1 : 0)
|
|
109
|
+
rects << Rect.new(area.x, y, area.w, h)
|
|
110
|
+
y += h
|
|
111
|
+
end
|
|
112
|
+
rects
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Fibonacci spiral: each pane takes half of the remaining region, splitting
|
|
116
|
+
# vertically then horizontally in alternation, so panes wind inward toward
|
|
117
|
+
# the bottom-right. The last pane fills whatever is left.
|
|
118
|
+
def spiral(count, area)
|
|
119
|
+
x, y, w, h = area.x, area.y, area.w, area.h
|
|
120
|
+
rects = []
|
|
121
|
+
count.times do |i|
|
|
122
|
+
if i == count - 1
|
|
123
|
+
rects << Rect.new(x, y, w, h)
|
|
124
|
+
elsif i.even?
|
|
125
|
+
left = [w / 2, 1].max
|
|
126
|
+
rects << Rect.new(x, y, left, h)
|
|
127
|
+
x += left
|
|
128
|
+
w = [w - left, 1].max
|
|
129
|
+
else
|
|
130
|
+
top = [h / 2, 1].max
|
|
131
|
+
rects << Rect.new(x, y, w, top)
|
|
132
|
+
y += top
|
|
133
|
+
h = [h - top, 1].max
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
rects
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Three-column master: master occupies the centre column full-height; the
|
|
140
|
+
# remaining panes are dealt alternately to a left and a right column and
|
|
141
|
+
# stacked within each. With a single slave there is no symmetry to keep, so
|
|
142
|
+
# it falls back to a simple master/slave vertical split (like `tall`).
|
|
143
|
+
def centered(count, area, master_index = 0)
|
|
144
|
+
master_index = master_index.clamp(0, count - 1)
|
|
145
|
+
return [Rect.new(area.x, area.y, area.w, area.h)] if count == 1
|
|
146
|
+
|
|
147
|
+
others = (0...count).to_a - [master_index]
|
|
148
|
+
rects = Array.new(count)
|
|
149
|
+
|
|
150
|
+
if others.length == 1
|
|
151
|
+
master_w = [area.w / 2, 1].max
|
|
152
|
+
rects[master_index] = Rect.new(area.x, area.y, master_w, area.h)
|
|
153
|
+
rects[others[0]] = Rect.new(area.x + master_w, area.y, [area.w - master_w, 1].max, area.h)
|
|
154
|
+
return rects
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
master_w = [area.w / 2, 1].max
|
|
158
|
+
side_w = area.w - master_w
|
|
159
|
+
left_w = [side_w / 2, 1].max
|
|
160
|
+
right_w = [side_w - left_w, 1].max
|
|
161
|
+
|
|
162
|
+
rects[master_index] = Rect.new(area.x + left_w, area.y, master_w, area.h)
|
|
163
|
+
left = others.select.with_index { |_, i| i.even? }
|
|
164
|
+
right = others.select.with_index { |_, i| i.odd? }
|
|
165
|
+
stack_column(rects, left, area.x, area.y, left_w, area.h)
|
|
166
|
+
stack_column(rects, right, area.x + left_w + master_w, area.y, right_w, area.h)
|
|
167
|
+
rects
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Accordion: the focused pane expands to fill the leftover height while the
|
|
171
|
+
# others collapse to short "title sliver" rows, all stacked vertically.
|
|
172
|
+
# Like monocle but the other panes stay visible (and spatially reachable).
|
|
173
|
+
def stack(count, area, focused_index = 0)
|
|
174
|
+
return [Rect.new(area.x, area.y, area.w, area.h)] if count == 1
|
|
175
|
+
focused_index = focused_index.clamp(0, count - 1)
|
|
176
|
+
|
|
177
|
+
others = count - 1
|
|
178
|
+
# Sliver is 3 rows so draw_box can still render the title; shrink it only
|
|
179
|
+
# when the terminal is too short to give the focused pane its own 3 rows.
|
|
180
|
+
sliver = [3, [area.h - 3, 0].max / others].min
|
|
181
|
+
sliver = [sliver, 1].max
|
|
182
|
+
focus_h = area.h - sliver * others
|
|
183
|
+
|
|
184
|
+
rects = Array.new(count)
|
|
185
|
+
y = area.y
|
|
186
|
+
count.times do |i|
|
|
187
|
+
h = (i == focused_index) ? focus_h : sliver
|
|
188
|
+
rects[i] = Rect.new(area.x, y, area.w, h)
|
|
189
|
+
y += h
|
|
190
|
+
end
|
|
191
|
+
rects
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Stack the given pane indices vertically within a single column, dividing
|
|
195
|
+
# the height evenly (remainder to the topmost panes). Used by `centered`.
|
|
196
|
+
def stack_column(rects, indices, x, y, w, total_h)
|
|
197
|
+
return if indices.empty?
|
|
198
|
+
base_h = total_h / indices.length
|
|
199
|
+
rem = total_h - base_h * indices.length
|
|
200
|
+
cy = y
|
|
201
|
+
indices.each_with_index do |idx, i|
|
|
202
|
+
h = base_h + (i < rem ? 1 : 0)
|
|
203
|
+
rects[idx] = Rect.new(x, cy, w, h)
|
|
204
|
+
cy += h
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
55
208
|
# Roughly square grid. Each row stretches its panes to fill the full width
|
|
56
209
|
# so an underfull bottom row doesn't leave gaps.
|
|
57
210
|
def grid(count, area)
|
data/lib/muxr/renderer.rb
CHANGED
|
@@ -391,7 +391,8 @@ module Muxr
|
|
|
391
391
|
" H / J / K / L move pane left / down / up / right",
|
|
392
392
|
" i drop into passthrough mode",
|
|
393
393
|
" c / x new / close pane (close asks y/n)",
|
|
394
|
-
" t / g / m
|
|
394
|
+
" t / w / g / m layout: tall / wide / grid / monocle",
|
|
395
|
+
" | - f e S layout: columns / rows / spiral / centered / stack",
|
|
395
396
|
" Tab / Enter cycle layout / promote to master",
|
|
396
397
|
" a / 1..9 last pane / jump by number",
|
|
397
398
|
" s enter scrollback",
|
|
@@ -401,7 +402,7 @@ module Muxr
|
|
|
401
402
|
"",
|
|
402
403
|
"PASSTHROUGH mode (keys reach the focused pane; prefix is Ctrl-a)",
|
|
403
404
|
" C-a Esc return to normal mode",
|
|
404
|
-
" C-a c x t g m
|
|
405
|
+
" C-a c x t w g m same as normal-mode bindings",
|
|
405
406
|
" C-a Tab Enter cycle layout / promote master",
|
|
406
407
|
" C-a n / p / a next / prev / last pane",
|
|
407
408
|
" C-a [ ] scrollback / paste buffer",
|
|
@@ -413,7 +414,8 @@ module Muxr
|
|
|
413
414
|
" cursor: h/j/k/l 0/^/$ w/e/b W/E/B H/M/L g/G",
|
|
414
415
|
" v select, C-v block, y/Enter yank, q/Esc cancel",
|
|
415
416
|
"",
|
|
416
|
-
"Commands: layout {tall|grid|
|
|
417
|
+
"Commands: layout {tall|wide|columns|rows|grid|spiral|centered|stack|monocle},",
|
|
418
|
+
" drawer {toggle|show|hide|reset},",
|
|
417
419
|
" claude, save, restore, sessions, quit, new, close, next, prev",
|
|
418
420
|
"",
|
|
419
421
|
"press any key to dismiss"
|
data/lib/muxr/version.rb
CHANGED