mirror of
https://github.com/junegunn/fzf
synced 2026-07-17 16:03:17 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d229762751 |
@@ -28,7 +28,7 @@ jobs:
|
|||||||
go-version: "1.23"
|
go-version: "1.23"
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1
|
uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.4.6
|
ruby-version: 3.4.6
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
go-version: "1.23"
|
go-version: "1.23"
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1
|
uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -96,25 +96,6 @@ archives:
|
|||||||
files:
|
files:
|
||||||
- non-existent*
|
- 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:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: junegunn
|
owner: junegunn
|
||||||
|
|||||||
@@ -1,24 +1,6 @@
|
|||||||
CHANGELOG
|
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
|
0.74.0
|
||||||
------
|
------
|
||||||
_Release highlights: https://junegunn.github.io/fzf/releases/0.74.0/_
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.74.0/_
|
||||||
|
|||||||
@@ -823,10 +823,6 @@ A synonym for \fB\-\-info=hidden\fB
|
|||||||
The given string will be repeated to form the horizontal separator on the info
|
The given string will be repeated to form the horizontal separator on the info
|
||||||
line (default: '─' or '\-' depending on \fB\-\-no\-unicode\fR).
|
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.
|
ANSI color codes are supported.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
|
|||||||
+1
-2
@@ -116,8 +116,7 @@ def __fzf_list_hosts [] {
|
|||||||
(
|
(
|
||||||
# Process ssh config files
|
# Process ssh config files
|
||||||
$ssh_configs | append $ssh_configs_d | append $ssh_config_global
|
$ssh_configs | append $ssh_configs_d | append $ssh_config_global
|
||||||
# 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 downcase | str starts-with 'host') or ($it | str downcase | str starts-with 'hostname') }
|
||||||
| 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
|
| parse --regex '^\s*host(?:name)?\s+(?<hosts>.+)' # Extract hosts after keyword
|
||||||
| default { hosts: null } # Handle lines that don't match regex
|
| default { hosts: null } # Handle lines that don't match regex
|
||||||
| get hosts
|
| get hosts
|
||||||
|
|||||||
@@ -3696,9 +3696,6 @@ func (opts *Options) noSeparatorLine() bool {
|
|||||||
if opts.Inputless {
|
if opts.Inputless {
|
||||||
return true
|
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
|
sep := opts.Separator == nil && !opts.InputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0
|
||||||
return noSeparatorLine(opts.InfoStyle, sep)
|
return noSeparatorLine(opts.InfoStyle, sep)
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-83
@@ -271,7 +271,6 @@ type Terminal struct {
|
|||||||
ghost string
|
ghost string
|
||||||
separator labelPrinter
|
separator labelPrinter
|
||||||
separatorLen int
|
separatorLen int
|
||||||
separatorAuto bool
|
|
||||||
spinner []string
|
spinner []string
|
||||||
promptString string
|
promptString string
|
||||||
prompt func()
|
prompt func()
|
||||||
@@ -1300,16 +1299,14 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The default separator can be suppressed at runtime depending on the
|
// Disable separator by default if input border is set
|
||||||
// window layout (see separatorLength)
|
if opts.Separator == nil && !t.inputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0 {
|
||||||
t.separatorAuto = opts.Separator == nil
|
bar := "─"
|
||||||
bar := "─"
|
if opts.Separator != nil {
|
||||||
if opts.Separator != nil {
|
bar = *opts.Separator
|
||||||
bar = *opts.Separator
|
} else if !t.unicode {
|
||||||
} else if !t.unicode {
|
bar = "-"
|
||||||
bar = "-"
|
}
|
||||||
}
|
|
||||||
if len(bar) > 0 {
|
|
||||||
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
|
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1698,72 +1695,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) noSeparatorLine() bool {
|
func (t *Terminal) noSeparatorLine() bool {
|
||||||
return t.inputless || noSeparatorLine(t.infoStyle, t.separatorLength() > 0)
|
return t.inputless || noSeparatorLine(t.infoStyle, t.separatorLen > 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) {
|
func getScrollbar(perLine int, total int, height int, offset int) (int, int) {
|
||||||
@@ -3398,7 +3330,6 @@ func (t *Terminal) printInfoImpl() {
|
|||||||
}
|
}
|
||||||
pos := 0
|
pos := 0
|
||||||
line := 0
|
line := 0
|
||||||
separatorLen := t.separatorLength()
|
|
||||||
maxHeight := t.window.Height()
|
maxHeight := t.window.Height()
|
||||||
move := func(y int, x int, clear bool) bool {
|
move := func(y int, x int, clear bool) bool {
|
||||||
if y < 0 || y >= maxHeight {
|
if y < 0 || y >= maxHeight {
|
||||||
@@ -3426,7 +3357,7 @@ func (t *Terminal) printInfoImpl() {
|
|||||||
str = string(trimmed)
|
str = string(trimmed)
|
||||||
width = maxWidth
|
width = maxWidth
|
||||||
}
|
}
|
||||||
move(line, pos, separatorLen == 0)
|
move(line, pos, t.separatorLen == 0)
|
||||||
if t.reading {
|
if t.reading {
|
||||||
t.window.CPrint(tui.ColSpinner, str)
|
t.window.CPrint(tui.ColSpinner, str)
|
||||||
} else {
|
} else {
|
||||||
@@ -3435,7 +3366,7 @@ func (t *Terminal) printInfoImpl() {
|
|||||||
pos += width
|
pos += width
|
||||||
}
|
}
|
||||||
printSeparator := func(fillLength int, pad bool) {
|
printSeparator := func(fillLength int, pad bool) {
|
||||||
if separatorLen > 0 {
|
if t.separatorLen > 0 {
|
||||||
t.separator(t.window, fillLength)
|
t.separator(t.window, fillLength)
|
||||||
t.window.Print(" ")
|
t.window.Print(" ")
|
||||||
} else if pad {
|
} else if pad {
|
||||||
@@ -3444,7 +3375,7 @@ func (t *Terminal) printInfoImpl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if t.infoStyle == infoHidden {
|
if t.infoStyle == infoHidden {
|
||||||
if separatorLen > 0 {
|
if t.separatorLen > 0 {
|
||||||
if !move(line+1, 0, false) {
|
if !move(line+1, 0, false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3507,7 +3438,7 @@ func (t *Terminal) printInfoImpl() {
|
|||||||
}
|
}
|
||||||
switch t.infoStyle {
|
switch t.infoStyle {
|
||||||
case infoDefault:
|
case infoDefault:
|
||||||
if !move(line+1, 0, separatorLen == 0) {
|
if !move(line+1, 0, t.separatorLen == 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
printSpinner()
|
printSpinner()
|
||||||
@@ -3591,7 +3522,7 @@ func (t *Terminal) printInfoImpl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if t.infoStyle == infoInlineRight {
|
if t.infoStyle == infoInlineRight {
|
||||||
if separatorLen > 0 {
|
if t.separatorLen > 0 {
|
||||||
if !move(line+1, 0, false) {
|
if !move(line+1, 0, false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
-10
@@ -13,16 +13,6 @@ import (
|
|||||||
// Returns the size of the current window if the tmux server supports
|
// Returns the size of the current window if the tmux server supports
|
||||||
// floating panes (tmux 3.7 or above)
|
// floating panes (tmux 3.7 or above)
|
||||||
func tmuxFloatingPaneInfo() (int, int, bool) {
|
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")
|
target := os.Getenv("TMUX_PANE")
|
||||||
if target == "" {
|
if target == "" {
|
||||||
return 0, 0, false
|
return 0, 0, false
|
||||||
|
|||||||
+33
-57
@@ -39,7 +39,6 @@ func (r *LightRenderer) Bell() {
|
|||||||
|
|
||||||
func (r *LightRenderer) PassThrough(str string) {
|
func (r *LightRenderer) PassThrough(str string) {
|
||||||
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
||||||
r.invalidateSGR()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) stderr(str string) {
|
func (r *LightRenderer) stderr(str string) {
|
||||||
@@ -91,36 +90,14 @@ func (r *LightRenderer) csi(code string) string {
|
|||||||
return fullcode
|
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() {
|
func (r *LightRenderer) flush() {
|
||||||
if r.queued.Len() > 0 {
|
if r.queued.Len() > 0 {
|
||||||
// Leave the terminal in the default SGR state between frames
|
raw := "\x1b[?7l\x1b[?25l" + r.queued.String()
|
||||||
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 {
|
if r.showCursor {
|
||||||
raw += "\x1b[?25h"
|
raw += "\x1b[?25h\x1b[?7h"
|
||||||
|
} else {
|
||||||
|
raw += "\x1b[?7h"
|
||||||
}
|
}
|
||||||
raw += "\x1b[?7h\x1b[?2026l"
|
|
||||||
r.flushRaw(raw)
|
r.flushRaw(raw)
|
||||||
r.queued.Reset()
|
r.queued.Reset()
|
||||||
}
|
}
|
||||||
@@ -151,7 +128,6 @@ type LightRenderer struct {
|
|||||||
fullscreen bool
|
fullscreen bool
|
||||||
upOneLine bool
|
upOneLine bool
|
||||||
queued strings.Builder
|
queued strings.Builder
|
||||||
sgr string
|
|
||||||
y int
|
y int
|
||||||
x int
|
x int
|
||||||
maxHeightFunc func(int) int
|
maxHeightFunc func(int) int
|
||||||
@@ -256,7 +232,7 @@ func (r *LightRenderer) Init() error {
|
|||||||
r.csi("G")
|
r.csi("G")
|
||||||
r.csi("K")
|
r.csi("K")
|
||||||
if !r.clearOnExit && !r.fullscreen {
|
if !r.clearOnExit && !r.fullscreen {
|
||||||
r.stderr("\x1b7") // DECSC: save cursor position
|
r.csi("s")
|
||||||
}
|
}
|
||||||
if !r.fullscreen && r.mouse {
|
if !r.fullscreen && r.mouse {
|
||||||
r.yoffset, _ = r.findOffset()
|
r.yoffset, _ = r.findOffset()
|
||||||
@@ -274,15 +250,15 @@ func (r *LightRenderer) makeSpace() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) move(y int, x int) {
|
func (r *LightRenderer) move(y int, x int) {
|
||||||
|
// w.csi("u")
|
||||||
if r.y < y {
|
if r.y < y {
|
||||||
r.csi(fmt.Sprintf("%dB", y-r.y))
|
r.csi(fmt.Sprintf("%dB", y-r.y))
|
||||||
} else if r.y > y {
|
} else if r.y > y {
|
||||||
r.csi(fmt.Sprintf("%dA", r.y-y))
|
r.csi(fmt.Sprintf("%dA", r.y-y))
|
||||||
}
|
}
|
||||||
if x == 0 {
|
r.stderr("\r")
|
||||||
r.stderr("\r")
|
if x > 0 {
|
||||||
} else {
|
r.csi(fmt.Sprintf("%dC", x))
|
||||||
r.csi(fmt.Sprintf("%dG", x+1))
|
|
||||||
}
|
}
|
||||||
r.y = y
|
r.y = y
|
||||||
r.x = x
|
r.x = x
|
||||||
@@ -1024,9 +1000,6 @@ func (r *LightRenderer) disableModes() {
|
|||||||
|
|
||||||
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
||||||
r.setupTerminal()
|
r.setupTerminal()
|
||||||
// The programs that ran in the meantime may have left the terminal in an
|
|
||||||
// arbitrary SGR state
|
|
||||||
r.invalidateSGR()
|
|
||||||
if clear {
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.smcup()
|
r.smcup()
|
||||||
@@ -1048,6 +1021,7 @@ func (r *LightRenderer) Clear() {
|
|||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.csi("H")
|
r.csi("H")
|
||||||
}
|
}
|
||||||
|
// r.csi("u")
|
||||||
r.origin()
|
r.origin()
|
||||||
r.csi("J")
|
r.csi("J")
|
||||||
r.flush()
|
r.flush()
|
||||||
@@ -1070,6 +1044,7 @@ func (r *LightRenderer) Refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Close() {
|
func (r *LightRenderer) Close() {
|
||||||
|
// r.csi("u")
|
||||||
if r.clearOnExit {
|
if r.clearOnExit {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
@@ -1081,7 +1056,7 @@ func (r *LightRenderer) Close() {
|
|||||||
r.csi("J")
|
r.csi("J")
|
||||||
}
|
}
|
||||||
} else if !r.fullscreen {
|
} else if !r.fullscreen {
|
||||||
r.stderr("\x1b8") // DECRC: restore cursor position
|
r.csi("u")
|
||||||
}
|
}
|
||||||
if !r.showCursor {
|
if !r.showCursor {
|
||||||
r.csi("?25h")
|
r.csi("?25h")
|
||||||
@@ -1298,6 +1273,10 @@ 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) {
|
func (w *LightWindow) stderrInternal(str string, allowNLCR bool, resetCode string) {
|
||||||
w.renderer.stderrInternal(str, allowNLCR, resetCode)
|
w.renderer.stderrInternal(str, allowNLCR, resetCode)
|
||||||
}
|
}
|
||||||
@@ -1436,18 +1415,13 @@ func ulColorCode(c Color) string {
|
|||||||
return ""
|
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) {
|
func (w *LightWindow) csiColor(fg Color, bg Color, ul Color, attr Attr) (bool, string) {
|
||||||
codes := append(attrCodes(attr), colorCodes(fg, bg)...)
|
codes := append(attrCodes(attr), colorCodes(fg, bg)...)
|
||||||
if ulCode := ulColorCode(ul); ulCode != "" {
|
if ulCode := ulColorCode(ul); ulCode != "" {
|
||||||
codes = append(codes, ulCode)
|
codes = append(codes, ulCode)
|
||||||
}
|
}
|
||||||
if len(codes) == 0 {
|
code := w.csi(";" + strings.Join(codes, ";") + "m")
|
||||||
return false, "\x1b[0m"
|
return len(codes) > 0, code
|
||||||
}
|
|
||||||
return true, "\x1b[;" + strings.Join(codes, ";") + "m"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Print(text string) {
|
func (w *LightWindow) Print(text string) {
|
||||||
@@ -1460,13 +1434,15 @@ func cleanse(str string) string {
|
|||||||
|
|
||||||
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
||||||
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Ul(), pair.Attr())
|
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Ul(), pair.Attr())
|
||||||
w.renderer.setSGR(code)
|
|
||||||
w.stderrInternal(cleanse(text), false, code)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
|
w.csi("0m")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
||||||
_, code := w.csiColor(fg, bg, colDefault, attr)
|
hasColors, code := w.csiColor(fg, bg, colDefault, attr)
|
||||||
w.renderer.setSGR(code)
|
if hasColors {
|
||||||
|
defer w.csi("0m")
|
||||||
|
}
|
||||||
w.stderrInternal(cleanse(text), false, code)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1487,7 +1463,7 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
|
|||||||
}
|
}
|
||||||
w.MoveAndClear(w.posy, w.posx)
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
w.Move(w.posy+1, 0)
|
w.Move(w.posy+1, 0)
|
||||||
w.renderer.setSGR(resetCode)
|
w.renderer.stderr(resetCode)
|
||||||
if len(lines) > 1 {
|
if len(lines) > 1 {
|
||||||
sign := w.wrapSign
|
sign := w.wrapSign
|
||||||
width := w.wrapSignWidth
|
width := w.wrapSignWidth
|
||||||
@@ -1497,8 +1473,7 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
|
|||||||
width = truncatedWidth
|
width = truncatedWidth
|
||||||
}
|
}
|
||||||
w.stderrInternal(DIM+sign, false, resetCode)
|
w.stderrInternal(DIM+sign, false, resetCode)
|
||||||
w.renderer.invalidateSGR()
|
w.renderer.stderr(resetCode)
|
||||||
w.renderer.setSGR(resetCode)
|
|
||||||
w.Move(w.posy, width)
|
w.Move(w.posy, width)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1509,19 +1484,20 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
|
|||||||
return FillSuspend
|
return FillSuspend
|
||||||
}
|
}
|
||||||
w.Move(w.posy+1, 0)
|
w.Move(w.posy+1, 0)
|
||||||
w.renderer.setSGR(resetCode)
|
w.renderer.stderr(resetCode)
|
||||||
return FillNextLine
|
return FillNextLine
|
||||||
}
|
}
|
||||||
return FillContinue
|
return FillContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) setBg() string {
|
func (w *LightWindow) setBg() string {
|
||||||
// The plain reset code for the default background is still required to
|
if w.bg != colDefault {
|
||||||
// clear the dim attribute after ␍ in the preview window
|
_, code := w.csiColor(colDefault, w.bg, colDefault, AttrRegular)
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
// Should clear dim attribute after ␍ in the preview window
|
||||||
// e.g. printf "foo\rbar" | fzf --ansi --preview 'printf "foo\rbar"'
|
// e.g. printf "foo\rbar" | fzf --ansi --preview 'printf "foo\rbar"'
|
||||||
_, code := w.csiColor(colDefault, w.bg, colDefault, AttrRegular)
|
return "\x1b[m"
|
||||||
w.renderer.setSGR(code)
|
|
||||||
return code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) LinkBegin(uri string, params string) {
|
func (w *LightWindow) LinkBegin(uri string, params string) {
|
||||||
@@ -1547,7 +1523,7 @@ func (w *LightWindow) CFill(fg Color, bg Color, ul Color, attr Attr, text string
|
|||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
if hasColors, resetCode := w.csiColor(fg, bg, ul, attr); hasColors {
|
if hasColors, resetCode := w.csiColor(fg, bg, ul, attr); hasColors {
|
||||||
w.renderer.setSGR(resetCode)
|
defer w.csi("0m")
|
||||||
return w.fill(text, resetCode)
|
return w.fill(text, resetCode)
|
||||||
}
|
}
|
||||||
return w.fill(text, w.setBg())
|
return w.fill(text, w.setBg())
|
||||||
|
|||||||
@@ -419,61 +419,3 @@ func TestLightRendererScrollWheel(t *testing.T) {
|
|||||||
assertScroll("\x1b[<94;1;1M", 0, "ctrl-alt-shift") // ctrl+alt+shift + left (ignored)
|
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)
|
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
@@ -597,7 +597,7 @@ class TestLayout < TestInteractive
|
|||||||
│ │ 2
|
│ │ 2
|
||||||
│ │ 1
|
│ │ 1
|
||||||
│ └header──
|
│ └header──
|
||||||
│ 19/97
|
│ 19/97 ─
|
||||||
│ > 1
|
│ > 1
|
||||||
│
|
│
|
||||||
╰────────────
|
╰────────────
|
||||||
@@ -628,7 +628,7 @@ class TestLayout < TestInteractive
|
|||||||
╭────────────
|
╭────────────
|
||||||
│ hello
|
│ hello
|
||||||
╰────────────
|
╰────────────
|
||||||
100/100
|
100/100 ─
|
||||||
>
|
>
|
||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block2, it) }
|
tmux.until { assert_block(block2, it) }
|
||||||
@@ -648,7 +648,7 @@ class TestLayout < TestInteractive
|
|||||||
│ 2
|
│ 2
|
||||||
│ 1
|
│ 1
|
||||||
╰──────────
|
╰──────────
|
||||||
98/98
|
98/98 ─
|
||||||
>
|
>
|
||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block1, it) }
|
tmux.until { assert_block(block1, it) }
|
||||||
@@ -663,7 +663,7 @@ class TestLayout < TestInteractive
|
|||||||
│ 1
|
│ 1
|
||||||
│ hello
|
│ hello
|
||||||
╰──────────
|
╰──────────
|
||||||
98/98
|
98/98 ─
|
||||||
>
|
>
|
||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block2, it) }
|
tmux.until { assert_block(block2, it) }
|
||||||
@@ -679,7 +679,7 @@ class TestLayout < TestInteractive
|
|||||||
│ 4
|
│ 4
|
||||||
│ > 3
|
│ > 3
|
||||||
╰──────────
|
╰──────────
|
||||||
98/98
|
98/98 ─
|
||||||
>
|
>
|
||||||
╭──────────
|
╭──────────
|
||||||
│ 2
|
│ 2
|
||||||
@@ -718,7 +718,7 @@ class TestLayout < TestInteractive
|
|||||||
║ 2
|
║ 2
|
||||||
║ 1
|
║ 1
|
||||||
╚══════════
|
╚══════════
|
||||||
98/98
|
98/98 ─
|
||||||
>
|
>
|
||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block1, it) }
|
tmux.until { assert_block(block1, it) }
|
||||||
@@ -734,7 +734,7 @@ class TestLayout < TestInteractive
|
|||||||
╭──────────
|
╭──────────
|
||||||
│ hello
|
│ hello
|
||||||
╰──────────
|
╰──────────
|
||||||
98/98
|
98/98 ─
|
||||||
>
|
>
|
||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block2, it) }
|
tmux.until { assert_block(block2, it) }
|
||||||
@@ -750,7 +750,7 @@ class TestLayout < TestInteractive
|
|||||||
│ 4
|
│ 4
|
||||||
│ > 3
|
│ > 3
|
||||||
╰──────────
|
╰──────────
|
||||||
98/98
|
98/98 ─
|
||||||
>
|
>
|
||||||
╔══════════
|
╔══════════
|
||||||
║ 2
|
║ 2
|
||||||
@@ -767,7 +767,7 @@ class TestLayout < TestInteractive
|
|||||||
║ 2
|
║ 2
|
||||||
║ 1
|
║ 1
|
||||||
╚══════════
|
╚══════════
|
||||||
98/98
|
98/98 ─
|
||||||
>
|
>
|
||||||
╭──────────
|
╭──────────
|
||||||
│ hello
|
│ hello
|
||||||
@@ -810,7 +810,7 @@ class TestLayout < TestInteractive
|
|||||||
│ │ 2
|
│ │ 2
|
||||||
│ │ 1
|
│ │ 1
|
||||||
│ └header────
|
│ └header────
|
||||||
│ 19/97
|
│ 19/97 ─
|
||||||
│ > 1
|
│ > 1
|
||||||
│
|
│
|
||||||
╰──────────────
|
╰──────────────
|
||||||
@@ -825,7 +825,7 @@ class TestLayout < TestInteractive
|
|||||||
│ ║ 11
|
│ ║ 11
|
||||||
│ ║ > 10
|
│ ║ > 10
|
||||||
│ ╚list══════
|
│ ╚list══════
|
||||||
│ 19/97
|
│ 19/97 ─
|
||||||
│ > 1
|
│ > 1
|
||||||
│ ┌──────────
|
│ ┌──────────
|
||||||
│ │ 3
|
│ │ 3
|
||||||
@@ -1039,9 +1039,9 @@ class TestLayout < TestInteractive
|
|||||||
└──────── │ 103 │ 101 │ 103 │ 101 │ │ 102 │ ┌────── │ │ 102 │ ┌────── │ 2/2 ─ │ ┌─────── │ ┌───── │ 103 │ ┌─────── │ ┌───────
|
└──────── │ 103 │ 101 │ 103 │ 101 │ │ 102 │ ┌────── │ │ 102 │ ┌────── │ 2/2 ─ │ ┌─────── │ ┌───── │ 103 │ ┌─────── │ ┌───────
|
||||||
│ 5/5 ─ │ 102 │ 2/2 ─ │ 102 │ │ 103 │ │ 101 │ │ 103 │ │ 101 │ > │ │ 2/2 │ │ 101 │ ┌─────── │ │ 101 │ │ >
|
│ 5/5 ─ │ 102 │ 2/2 ─ │ 102 │ │ 103 │ │ 101 │ │ 103 │ │ 101 │ > │ │ 2/2 │ │ 101 │ ┌─────── │ │ 101 │ │ >
|
||||||
│ > │ 103 │ > │ 103 │ └────── │ │ 102 │ └────── │ │ 102 │ ┌────── │ │ > │ │ 102 │ │ 5/5 │ │ 102 │ └───────
|
│ > │ 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
|
│ > │ └────── │ > │ └────── │ │ 102 │ ┌─────── │ └───── │ └─────── │ └─────── │ │ 101
|
||||||
└──────── └──────── └──────── │ 2/2 │ │ 103 │ │ 101 └─────── └───────── │ ┌─────── │ │ 102
|
└──────── └──────── └──────── │ 2/2 ─ │ │ 103 │ │ 101 └─────── └───────── │ ┌─────── │ │ 102
|
||||||
│ > │ └────── │ │ 102 │ │ > │ │ 103
|
│ > │ └────── │ │ 102 │ │ > │ │ 103
|
||||||
└──────── └──────── │ │ 103 │ └─────── │ └───────
|
└──────── └──────── │ │ 103 │ └─────── │ └───────
|
||||||
│ └─────── └───────── └─────────
|
│ └─────── └───────── └─────────
|
||||||
@@ -1099,9 +1099,9 @@ class TestLayout < TestInteractive
|
|||||||
│ > │ 102 │ > │ 102 │ > │ │ 101 │ > │ │ 101 │ │ 101 │ │ 101 │ └────── │ └─────── │ └────── │ 101 │ │ > 1
|
│ > │ 102 │ > │ 102 │ > │ │ 101 │ > │ │ 101 │ │ 101 │ │ 101 │ └────── │ └─────── │ └────── │ 101 │ │ > 1
|
||||||
└──────── │ 3/3 ─ │ 101 │ 1/1 ─ │ 101 │ │ 102 │ ┌────── │ │ 102 │ │ 102 │ │ 102 │ ┌────── │ ┌─────── │ ┌────── │ 102 │ └───────
|
└──────── │ 3/3 ─ │ 101 │ 1/1 ─ │ 101 │ │ 102 │ ┌────── │ │ 102 │ │ 102 │ │ 102 │ ┌────── │ ┌─────── │ ┌────── │ 102 │ └───────
|
||||||
│ > │ 102 │ > │ 102 │ └─HEAD─ │ │ 101 │ └─HEAD─ │ └─HEAD─ │ └─HEAD─ │ │ 101 │ │ 1/1 │ │ 101 │ ─────── │ ┌───────
|
│ > │ 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─ │ > │ └───────
|
│ > │ └─HEAD─ │ > │ > │ > │ └─HEAD─ │ └─────── │ └─HEAD─ │ > │ └───────
|
||||||
└──────── └──────── └──────── └──────── └──────── │ 1/1 │ ┌─────── └──────── └──────── │ ┌───────
|
└──────── └──────── └──────── └──────── └──────── │ 1/1 ─ │ ┌─────── └──────── └──────── │ ┌───────
|
||||||
│ > │ │ 101 │ │ 101
|
│ > │ │ 101 │ │ 101
|
||||||
└──────── │ │ 102 │ │ 102
|
└──────── │ │ 102 │ │ 102
|
||||||
│ └─HEAD── │ └─HEAD──
|
│ └─HEAD── │ └─HEAD──
|
||||||
@@ -1159,9 +1159,9 @@ class TestLayout < TestInteractive
|
|||||||
│ > │ 102 │ > │ 102 │ > │ │ 101 │ > │ │ 101 │ > 3 │ > 3 │ > 3 │ 101 │ │ 3
|
│ > │ 102 │ > │ 102 │ > │ │ 101 │ > │ │ 101 │ > 3 │ > 3 │ > 3 │ 101 │ │ 3
|
||||||
└──────── │ 3/3 ─ │ 101 │ 1/1 ─ │ 101 │ │ 102 │ ┌────── │ │ 102 │ ┌────── │ ┌─────── │ ┌────── │ 102 │ └───────
|
└──────── │ 3/3 ─ │ 101 │ 1/1 ─ │ 101 │ │ 102 │ ┌────── │ │ 102 │ ┌────── │ ┌─────── │ ┌────── │ 102 │ └───────
|
||||||
│ > │ 102 │ > │ 102 │ └─HEAD─ │ │ 101 │ └─HEAD─ │ │ 101 │ │ 1/1 │ │ 101 │ ─────── │ ┌───────
|
│ > │ 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─ │ > │ └───────
|
│ > │ └─HEAD─ │ > │ └─HEAD─ │ └─────── │ └─HEAD─ │ > │ └───────
|
||||||
└──────── └──────── └──────── │ 1/1 │ ┌─────── └──────── └──────── │ ┌───────
|
└──────── └──────── └──────── │ 1/1 ─ │ ┌─────── └──────── └──────── │ ┌───────
|
||||||
│ > │ │ 101 │ │ 101
|
│ > │ │ 101 │ │ 101
|
||||||
└──────── │ │ 102 │ │ 102
|
└──────── │ │ 102 │ │ 102
|
||||||
│ └─HEAD── │ └─HEAD──
|
│ └─HEAD── │ └─HEAD──
|
||||||
@@ -1191,7 +1191,7 @@ class TestLayout < TestInteractive
|
|||||||
│ ┌─label──
|
│ ┌─label──
|
||||||
│ │ header
|
│ │ header
|
||||||
│ └────────
|
│ └────────
|
||||||
│ 10/10
|
│ 10/10 ─
|
||||||
│ >
|
│ >
|
||||||
└──────────
|
└──────────
|
||||||
BLOCK
|
BLOCK
|
||||||
@@ -1234,7 +1234,7 @@ class TestLayout < TestInteractive
|
|||||||
┌──────
|
┌──────
|
||||||
│ 1
|
│ 1
|
||||||
└──────
|
└──────
|
||||||
9/9
|
9/9 ─
|
||||||
>
|
>
|
||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block, it) }
|
tmux.until { assert_block(block, it) }
|
||||||
|
|||||||
Reference in New Issue
Block a user