Compare commits

..

16 Commits

Author SHA1 Message Date
Junegunn Choi 083045ad53 Clarify comment on height overestimation 2026-07-18 00:52:44 +09:00
Junegunn Choi 235ef3c95c Compute separator length once in printInfoImpl
separatorLength now derives value from window layout on each call;
hoist to local as it cannot change within single repaint
2026-07-17 23:47:58 +09:00
Junegunn Choi f7f8b35f6c Check threshold alternatives for preview at 'next' position
Alternative layout in --preview-window spec can also place preview next
to input section, e.g. 'up,40%,<30(next)'
2026-07-17 23:47:50 +09:00
Junegunn Choi e098a796bb Ignore inline header border when hiding separator
Inline header is drawn inside list border, not adjacent to input
section. Decide based on section actually next to input: header lines
window or list border.

  # Bare header lines next to input; separator needed
  fzf --list-border --header foo --header-lines 2 \
      --header-border inline --header-lines-border none
2026-07-17 23:47:36 +09:00
Junegunn Choi a62fc80f1a Keep separator when preview window is at 'next' position
Preview window at 'next' position sits right next to input section, so
border of header or list section is not adjacent to it. Suppressing
separator there left no divider when preview border is none.
2026-07-17 23:26:48 +09:00
Junegunn Choi 5e31fd25e7 Hide default separator when a border separates input from list
- Applies when adjacent section (header, header lines, or list) draws
  border line facing input section

    # No separator below header border
    fzf --style full --input-border none --header foo

    # Separator shown; nothing separates input from list
    fzf --style full --input-border none --header foo --no-header-border

- Evaluated dynamically so change-header/toggle-header update it
2026-07-17 23:18:00 +09:00
Junegunn Choi e877b6f846 Update CHANGELOG 2026-07-17 14:44:20 +09:00
Junegunn Choi b9cbe3a6b8 Publish .deb package for each release
Close #4859
2026-07-17 14:44:20 +09:00
Junegunn Choi 7e134f0a78 Update CHANGELOG 2026-07-17 10:47:56 +09:00
Junegunn Choi 3c0de84dab Skip redundant SGR sequences in light renderer
Track the last emitted SGR sequence and skip no-op transitions.
Reduces rendering output by 10-23% depending on the scenario.
2026-07-17 10:34:42 +09:00
Junegunn Choi 374b01242d Wrap frames in synchronized update mode (2026)
Reduce flickering during rendering on supported terminals.
2026-07-16 23:20:29 +09:00
Junegunn Choi c0860259fa Use DECSC/DECRC instead of CSI s/u
Honored by more terminals, e.g. fixes cursor restoration with
--height --no-clear inside nvim terminal.
2026-07-16 23:00:39 +09:00
Junegunn Choi 1462f1358d Comment cleanup 2026-07-15 18:30:17 +09:00
Junegunn Choi 8e340dabbc Use CHA for absolute horizontal cursor movement
Related:
- #4858
- https://github.com/zellij-org/zellij/issues/5370
2026-07-15 18:23:08 +09:00
Junegunn Choi 4dcedc01df Comment why floating pane requires TMUX_PANE
Blocking run-shell suspends client key processing until the command
exits, so a floating pane deadlocks; popup input bypasses the suspended
path. Fall back to display-popup when TMUX_PANE is not set.
2026-07-15 17:45:08 +09:00
Simon Désaulniers 24832e97ef nushell: fix 0.114.0 nushell str downcase deprecated error
We switch to `str lowercase` and add a note for the Nushell<0.114.0 user
that should display at runtime.

Close #4857
2026-07-10 13:49:47 +09:00
10 changed files with 273 additions and 67 deletions
+19
View File
@@ -96,6 +96,25 @@ archives:
files:
- non-existent*
nfpms:
- package_name: fzf
file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
vendor: junegunn
homepage: https://github.com/junegunn/fzf
maintainer: Junegunn Choi <junegunn.c@gmail.com>
description: Command-line fuzzy finder
license: MIT
section: utils
formats:
- deb
ids:
- fzf
contents:
- src: man/man1/fzf.1
dst: /usr/share/man/man1/fzf.1
- src: LICENSE
dst: /usr/share/doc/fzf/copyright
release:
github:
owner: junegunn
+18
View File
@@ -1,6 +1,24 @@
CHANGELOG
=========
0.74.1
------
- The default separator on the info line is no longer shown when the input section is already visually separated from the list section by a border line
```sh
# No separator shown below the header border
fzf --style full --input-border none --header foo
# Separator shown; no border separates the input section from the list section
fzf --style full --input-border none --header foo --no-header-border
```
- Rendering improvements
- Each frame is now wrapped in synchronized update mode (mode 2026) to reduce flickering on supported terminals
- Reduced rendering output by 10-23% by skipping redundant SGR sequences
- Fixed ghost characters and misplaced colors inside Zellij by using CHA instead of CR + CUF for horizontal cursor movement (#4858, zellij-org/zellij#5370)
- Fixed cursor restoration on exit with `--height --no-clear` inside Neovim terminal by using DECSC/DECRC instead of `CSI s`/`CSI u`
- nushell: fixed deprecation error of `str downcase` on nushell 0.114.0 or above (#4857) (@sim590)
- Each release now includes `.deb` packages for easy installation on Debian-based distros (#4859)
0.74.0
------
_Release highlights: https://junegunn.github.io/fzf/releases/0.74.0/_
+4
View File
@@ -823,6 +823,10 @@ A synonym for \fB\-\-info=hidden\fB
The given string will be repeated to form the horizontal separator on the info
line (default: '─' or '\-' depending on \fB\-\-no\-unicode\fR).
Unless explicitly specified, the separator is not displayed if the input
section is already visually separated from the list section by a border line
(e.g. \fB\-\-input\-border\fR or \fB\-\-header\-border\fR).
ANSI color codes are supported.
.TP
+2 -1
View File
@@ -116,7 +116,8 @@ def __fzf_list_hosts [] {
(
# Process ssh config files
$ssh_configs | append $ssh_configs_d | append $ssh_config_global
| where {|it| ($it | str downcase | str starts-with 'host') or ($it | str downcase | str starts-with 'hostname') }
# NOTE: str lowercase is new from Nushell 0.114.0. Please upgrade to Nushell >=0.114.0 if you are seeing `extra positional argument` error regarding `str lowercase` in this block.
| where {|it| ($it | str lowercase | str starts-with 'host') or ($it | str lowercase | str starts-with 'hostname') }
| parse --regex '^\s*host(?:name)?\s+(?<hosts>.+)' # Extract hosts after keyword
| default { hosts: null } # Handle lines that don't match regex
| get hosts
+3
View File
@@ -3696,6 +3696,9 @@ func (opts *Options) noSeparatorLine() bool {
if opts.Inputless {
return true
}
// NOTE: This does not know that the default separator can be suppressed at
// runtime (see Terminal.separatorLength), so the minimum heights derived
// from it can be one line taller than strictly necessary.
sep := opts.Separator == nil && !opts.InputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0
return noSeparatorLine(opts.InfoStyle, sep)
}
+83 -14
View File
@@ -271,6 +271,7 @@ type Terminal struct {
ghost string
separator labelPrinter
separatorLen int
separatorAuto bool
spinner []string
promptString string
prompt func()
@@ -1299,14 +1300,16 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
}
}
// Disable separator by default if input border is set
if opts.Separator == nil && !t.inputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0 {
bar := "─"
if opts.Separator != nil {
bar = *opts.Separator
} else if !t.unicode {
bar = "-"
}
// The default separator can be suppressed at runtime depending on the
// window layout (see separatorLength)
t.separatorAuto = opts.Separator == nil
bar := "─"
if opts.Separator != nil {
bar = *opts.Separator
} else if !t.unicode {
bar = "-"
}
if len(bar) > 0 {
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
}
@@ -1695,7 +1698,72 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
}
func (t *Terminal) noSeparatorLine() bool {
return t.inputless || noSeparatorLine(t.infoStyle, t.separatorLen > 0)
return t.inputless || noSeparatorLine(t.infoStyle, t.separatorLength() > 0)
}
// Effective length of the horizontal separator. The default separator is
// suppressed when the input section is already visually separated from the
// list section by a border line.
func (t *Terminal) separatorLength() int {
if t.separatorAuto && t.separatedByBorder() {
return 0
}
return t.separatorLen
}
// The default horizontal separator is redundant if the input section is
// already visually separated from the list section by a border line
func (t *Terminal) separatedByBorder() bool {
if t.inputBorderShape.Visible() {
return true
}
for po := &t.previewOpts; po != nil; po = po.alternative {
if po.position == posNext {
// A preview window at 'next' position sits right next to the input
// section instead. Keep the separator, as the visibility and the
// border of the preview window can change at runtime.
return false
}
}
hasHeaderLinesWindow, headerLinesShape := t.determineHeaderLinesShape()
hasHeaderWindow := t.hasHeaderWindow()
if !hasHeaderWindow && !hasHeaderLinesWindow {
// No separate window is created for the input section in this case
// (see hasInputWindow in resizeWindows), i.e. the input section is
// embedded in the list window and no border separates the two
return false
}
// The input section has its own window. Determine which section it is
// facing, and check if the border of that section draws a line toward it.
if hasHeaderWindow && !t.headerFirst && t.headerBorderShape != tui.BorderInline {
// Header section; an inline header is not a separate section, it is
// drawn inside the list border, so it falls through to the branches
// below
return t.borderFacingInput(t.headerBorderShape)
}
// The header lines section is next to the input section, except in
// reverse-list layout where it is on the other side of the list section,
// or with --header-first and no header section where it is moved past the
// input section
if hasHeaderLinesWindow && t.layout != layoutReverseList && (hasHeaderWindow || !t.headerFirst) {
return t.borderFacingInput(headerLinesShape)
}
// List section
return t.borderFacingInput(t.listBorderShape)
}
// Whether the shape draws a horizontal line facing the info line when the
// section is placed between the input section and the list section
func (t *Terminal) borderFacingInput(shape tui.BorderShape) bool {
if shape == tui.BorderInline {
// Embedded between the horizontal lines of the list border
return true
}
if t.layout == layoutReverse {
return shape.HasTop()
}
return shape.HasBottom()
}
func getScrollbar(perLine int, total int, height int, offset int) (int, int) {
@@ -3330,6 +3398,7 @@ func (t *Terminal) printInfoImpl() {
}
pos := 0
line := 0
separatorLen := t.separatorLength()
maxHeight := t.window.Height()
move := func(y int, x int, clear bool) bool {
if y < 0 || y >= maxHeight {
@@ -3357,7 +3426,7 @@ func (t *Terminal) printInfoImpl() {
str = string(trimmed)
width = maxWidth
}
move(line, pos, t.separatorLen == 0)
move(line, pos, separatorLen == 0)
if t.reading {
t.window.CPrint(tui.ColSpinner, str)
} else {
@@ -3366,7 +3435,7 @@ func (t *Terminal) printInfoImpl() {
pos += width
}
printSeparator := func(fillLength int, pad bool) {
if t.separatorLen > 0 {
if separatorLen > 0 {
t.separator(t.window, fillLength)
t.window.Print(" ")
} else if pad {
@@ -3375,7 +3444,7 @@ func (t *Terminal) printInfoImpl() {
}
if t.infoStyle == infoHidden {
if t.separatorLen > 0 {
if separatorLen > 0 {
if !move(line+1, 0, false) {
return
}
@@ -3438,7 +3507,7 @@ func (t *Terminal) printInfoImpl() {
}
switch t.infoStyle {
case infoDefault:
if !move(line+1, 0, t.separatorLen == 0) {
if !move(line+1, 0, separatorLen == 0) {
return
}
printSpinner()
@@ -3522,7 +3591,7 @@ func (t *Terminal) printInfoImpl() {
}
if t.infoStyle == infoInlineRight {
if t.separatorLen > 0 {
if separatorLen > 0 {
if !move(line+1, 0, false) {
return
}
+10
View File
@@ -13,6 +13,16 @@ import (
// Returns the size of the current window if the tmux server supports
// floating panes (tmux 3.7 or above)
func tmuxFloatingPaneInfo() (int, int, bool) {
// TMUX_PANE is not set when fzf is not started from within a pane
// (e.g. 'bind-key 0 run-shell "fzf --popup"'). Do not use a floating
// pane there; a blocking run-shell suspends key processing for the
// client until the command exits, so fzf in a floating pane would
// never receive a key and the client would appear frozen until the
// process is killed externally. A popup is unaffected; its input is
// handled at the client overlay level, bypassing the suspended path.
// The right approach for such bindings is 'run-shell -b', which does
// not suspend key processing, as discussed in:
// https://github.com/tmux/tmux/issues/5384
target := os.Getenv("TMUX_PANE")
if target == "" {
return 0, 0, false
+57 -33
View File
@@ -39,6 +39,7 @@ func (r *LightRenderer) Bell() {
func (r *LightRenderer) PassThrough(str string) {
r.queued.WriteString("\x1b7" + str + "\x1b8")
r.invalidateSGR()
}
func (r *LightRenderer) stderr(str string) {
@@ -90,14 +91,36 @@ func (r *LightRenderer) csi(code string) string {
return fullcode
}
// setSGR emits the given SGR sequence only when the terminal is not already
// in that state. Sequences built by csiColor start with a reset parameter,
// so each of them fully determines the state on its own. The zero value of
// r.sgr never matches a sequence, so the first update after Init, Resume, or
// invalidateSGR is always emitted.
func (r *LightRenderer) setSGR(code string) {
if code != r.sgr {
r.stderr(code)
r.sgr = code
}
}
// invalidateSGR marks the terminal SGR state as unknown, e.g. after raw
// output that may have changed it behind the renderer's back.
func (r *LightRenderer) invalidateSGR() {
r.sgr = ""
}
func (r *LightRenderer) flush() {
if r.queued.Len() > 0 {
raw := "\x1b[?7l\x1b[?25l" + r.queued.String()
// Leave the terminal in the default SGR state between frames
r.setSGR("\x1b[0m")
// Wrap the frame in synchronized update mode (2026) so that the
// terminal applies it atomically. Terminals without support ignore
// the unknown private mode and behave as before.
raw := "\x1b[?2026h\x1b[?7l\x1b[?25l" + r.queued.String()
if r.showCursor {
raw += "\x1b[?25h\x1b[?7h"
} else {
raw += "\x1b[?7h"
raw += "\x1b[?25h"
}
raw += "\x1b[?7h\x1b[?2026l"
r.flushRaw(raw)
r.queued.Reset()
}
@@ -128,6 +151,7 @@ type LightRenderer struct {
fullscreen bool
upOneLine bool
queued strings.Builder
sgr string
y int
x int
maxHeightFunc func(int) int
@@ -232,7 +256,7 @@ func (r *LightRenderer) Init() error {
r.csi("G")
r.csi("K")
if !r.clearOnExit && !r.fullscreen {
r.csi("s")
r.stderr("\x1b7") // DECSC: save cursor position
}
if !r.fullscreen && r.mouse {
r.yoffset, _ = r.findOffset()
@@ -250,15 +274,15 @@ func (r *LightRenderer) makeSpace() {
}
func (r *LightRenderer) move(y int, x int) {
// w.csi("u")
if r.y < y {
r.csi(fmt.Sprintf("%dB", y-r.y))
} else if r.y > y {
r.csi(fmt.Sprintf("%dA", r.y-y))
}
r.stderr("\r")
if x > 0 {
r.csi(fmt.Sprintf("%dC", x))
if x == 0 {
r.stderr("\r")
} else {
r.csi(fmt.Sprintf("%dG", x+1))
}
r.y = y
r.x = x
@@ -1000,6 +1024,9 @@ func (r *LightRenderer) disableModes() {
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
r.setupTerminal()
// The programs that ran in the meantime may have left the terminal in an
// arbitrary SGR state
r.invalidateSGR()
if clear {
if r.fullscreen {
r.smcup()
@@ -1021,7 +1048,6 @@ func (r *LightRenderer) Clear() {
if r.fullscreen {
r.csi("H")
}
// r.csi("u")
r.origin()
r.csi("J")
r.flush()
@@ -1044,7 +1070,6 @@ func (r *LightRenderer) Refresh() {
}
func (r *LightRenderer) Close() {
// r.csi("u")
if r.clearOnExit {
if r.fullscreen {
r.rmcup()
@@ -1056,7 +1081,7 @@ func (r *LightRenderer) Close() {
r.csi("J")
}
} else if !r.fullscreen {
r.csi("u")
r.stderr("\x1b8") // DECRC: restore cursor position
}
if !r.showCursor {
r.csi("?25h")
@@ -1273,10 +1298,6 @@ func (w *LightWindow) drawBorder(onlyHorizontal bool) {
}
}
func (w *LightWindow) csi(code string) string {
return w.renderer.csi(code)
}
func (w *LightWindow) stderrInternal(str string, allowNLCR bool, resetCode string) {
w.renderer.stderrInternal(str, allowNLCR, resetCode)
}
@@ -1415,13 +1436,18 @@ func ulColorCode(c Color) string {
return ""
}
// csiColor builds the SGR sequence for the given colors and attributes
// without emitting it. The sequence starts with a reset parameter, so it
// fully determines the SGR state on its own.
func (w *LightWindow) csiColor(fg Color, bg Color, ul Color, attr Attr) (bool, string) {
codes := append(attrCodes(attr), colorCodes(fg, bg)...)
if ulCode := ulColorCode(ul); ulCode != "" {
codes = append(codes, ulCode)
}
code := w.csi(";" + strings.Join(codes, ";") + "m")
return len(codes) > 0, code
if len(codes) == 0 {
return false, "\x1b[0m"
}
return true, "\x1b[;" + strings.Join(codes, ";") + "m"
}
func (w *LightWindow) Print(text string) {
@@ -1434,15 +1460,13 @@ func cleanse(str string) string {
func (w *LightWindow) CPrint(pair ColorPair, text string) {
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Ul(), pair.Attr())
w.renderer.setSGR(code)
w.stderrInternal(cleanse(text), false, code)
w.csi("0m")
}
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
hasColors, code := w.csiColor(fg, bg, colDefault, attr)
if hasColors {
defer w.csi("0m")
}
_, code := w.csiColor(fg, bg, colDefault, attr)
w.renderer.setSGR(code)
w.stderrInternal(cleanse(text), false, code)
}
@@ -1463,7 +1487,7 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
}
w.MoveAndClear(w.posy, w.posx)
w.Move(w.posy+1, 0)
w.renderer.stderr(resetCode)
w.renderer.setSGR(resetCode)
if len(lines) > 1 {
sign := w.wrapSign
width := w.wrapSignWidth
@@ -1473,7 +1497,8 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
width = truncatedWidth
}
w.stderrInternal(DIM+sign, false, resetCode)
w.renderer.stderr(resetCode)
w.renderer.invalidateSGR()
w.renderer.setSGR(resetCode)
w.Move(w.posy, width)
}
}
@@ -1484,20 +1509,19 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
return FillSuspend
}
w.Move(w.posy+1, 0)
w.renderer.stderr(resetCode)
w.renderer.setSGR(resetCode)
return FillNextLine
}
return FillContinue
}
func (w *LightWindow) setBg() string {
if w.bg != colDefault {
_, code := w.csiColor(colDefault, w.bg, colDefault, AttrRegular)
return code
}
// Should clear dim attribute after ␍ in the preview window
// The plain reset code for the default background is still required to
// clear the dim attribute after ␍ in the preview window
// e.g. printf "foo\rbar" | fzf --ansi --preview 'printf "foo\rbar"'
return "\x1b[m"
_, code := w.csiColor(colDefault, w.bg, colDefault, AttrRegular)
w.renderer.setSGR(code)
return code
}
func (w *LightWindow) LinkBegin(uri string, params string) {
@@ -1523,7 +1547,7 @@ func (w *LightWindow) CFill(fg Color, bg Color, ul Color, attr Attr, text string
bg = w.bg
}
if hasColors, resetCode := w.csiColor(fg, bg, ul, attr); hasColors {
defer w.csi("0m")
w.renderer.setSGR(resetCode)
return w.fill(text, resetCode)
}
return w.fill(text, w.setBg())
+58
View File
@@ -419,3 +419,61 @@ func TestLightRendererScrollWheel(t *testing.T) {
assertScroll("\x1b[<94;1;1M", 0, "ctrl-alt-shift") // ctrl+alt+shift + left (ignored)
assertScroll("\x1b[<95;1;1M", 0, "ctrl-alt-shift") // ctrl+alt+shift + right (ignored)
}
// Assert the exact byte stream emitted for a scripted rendering sequence,
// locking down the SGR deduplication behavior of the light renderer.
func TestLightRendererSGRDeduplication(t *testing.T) {
renderer, _ := NewLightRenderer(
"", nil, &ColorTheme{}, true, false, 8, false, true,
func(h int) int { return h })
r := renderer.(*LightRenderer)
r.width = 80
r.height = 24
out, err := os.CreateTemp(t.TempDir(), "fzf-ttyout")
if err != nil {
t.Fatal(err)
}
r.ttyout = out
w := r.NewWindow(0, 0, 40, 4, WindowList, MakeBorderStyle(BorderNone, true), false).(*LightWindow)
w.fg = colDefault
w.bg = colDefault
r.queued.Reset()
red := NewColorPair(196, colDefault, AttrRegular)
blue := NewColorPair(21, colDefault, AttrRegular)
w.Move(0, 0)
w.CPrint(red, "ab")
w.CPrint(red, "cd") // same pair; no SGR expected
w.CPrint(blue, "ef")
w.Print("gh") // default colors; one reset expected
w.Print("ij") // still default; no SGR expected
w.Move(1, 2)
w.CPrint(red, "kl")
r.invalidateSGR()
w.CPrint(red, "mn") // re-emitted after invalidation
r.flush() // adds a trailing reset to leave the default state
expected := "\x1b[?2026h\x1b[?7l\x1b[?25l" +
"\r" +
"\x1b[;38;5;196mab" +
"cd" +
"\x1b[;38;5;21mef" +
"\x1b[0mgh" +
"ij" +
"\x1b[1B\x1b[3G" +
"\x1b[;38;5;196mkl" +
"\x1b[;38;5;196mmn" +
"\x1b[0m" +
"\x1b[?25h\x1b[?7h\x1b[?2026l"
bytes, err := os.ReadFile(out.Name())
if err != nil {
t.Fatal(err)
}
if string(bytes) != expected {
t.Errorf("expected: %q\n got: %q", expected, string(bytes))
}
}
+19 -19
View File
@@ -597,7 +597,7 @@ class TestLayout < TestInteractive
2
1
header
19/97
19/97
> 1
@@ -628,7 +628,7 @@ class TestLayout < TestInteractive
hello
100/100
100/100
>
BLOCK
tmux.until { assert_block(block2, it) }
@@ -648,7 +648,7 @@ class TestLayout < TestInteractive
2
1
98/98
98/98
>
BLOCK
tmux.until { assert_block(block1, it) }
@@ -663,7 +663,7 @@ class TestLayout < TestInteractive
1
hello
98/98
98/98
>
BLOCK
tmux.until { assert_block(block2, it) }
@@ -679,7 +679,7 @@ class TestLayout < TestInteractive
4
> 3
98/98
98/98
>
2
@@ -718,7 +718,7 @@ class TestLayout < TestInteractive
2
1
98/98
98/98
>
BLOCK
tmux.until { assert_block(block1, it) }
@@ -734,7 +734,7 @@ class TestLayout < TestInteractive
hello
98/98
98/98
>
BLOCK
tmux.until { assert_block(block2, it) }
@@ -750,7 +750,7 @@ class TestLayout < TestInteractive
4
> 3
98/98
98/98
>
2
@@ -767,7 +767,7 @@ class TestLayout < TestInteractive
2
1
98/98
98/98
>
hello
@@ -810,7 +810,7 @@ class TestLayout < TestInteractive
2
1
header
19/97
19/97
> 1
@@ -825,7 +825,7 @@ class TestLayout < TestInteractive
11
> 10
list
19/97
19/97
> 1
3
@@ -1039,9 +1039,9 @@ class TestLayout < TestInteractive
103 101 103 101 102 102 2/2 103
5/5 102 2/2 102 103 101 103 101 > 2/2 101 101 >
> 103 > 103 102 102 > 102 5/5 102
5/5 103 2/2 103 101 103 > 103
5/5 103 2/2 103 101 103 > 103
> > 102 101
2/2 103 101 102
2/2 103 101 102
> 102 > 103
103
@@ -1099,9 +1099,9 @@ class TestLayout < TestInteractive
> 102 > 102 > 101 > 101 101 101 101 > 1
3/3 101 1/1 101 102 102 102 102 102
> 102 > 102 HEAD 101 HEAD HEAD HEAD 101 1/1 101
3/3 102 1/1 1/1 1/1 102 > 102 3/3 >
3/3 102 1/1 1/1 1/1 102 > 102 3/3 >
> HEAD > > > HEAD HEAD >
1/1
1/1
> 101 101
102 102
HEAD HEAD
@@ -1159,9 +1159,9 @@ class TestLayout < TestInteractive
> 102 > 102 > 101 > 101 > 3 > 3 > 3 101 3
3/3 101 1/1 101 102 102 102
> 102 > 102 HEAD 101 HEAD 101 1/1 101
3/3 102 1/1 102 > 102 3/3 >
3/3 102 1/1 102 > 102 3/3 >
> HEAD > HEAD HEAD >
1/1
1/1
> 101 101
102 102
HEAD HEAD
@@ -1191,7 +1191,7 @@ class TestLayout < TestInteractive
label
header
10/10
10/10
>
BLOCK
@@ -1234,7 +1234,7 @@ class TestLayout < TestInteractive
1
9/9
9/9
>
BLOCK
tmux.until { assert_block(block, it) }