summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-01-30 00:50:46 +0900
committerGitHub <noreply@github.com>2025-01-30 00:50:46 +0900
commit6c0ca4a64a4e2f8697dfa830dcae56c1d7ddca51 (patch)
treeb3cb6eefec8009c815d0ce0a4bea3032f8bc0cf8
parent6b5d4614114f1a7a34a50c21ed2952f94a5ee81e (diff)
downloadfzf-6c0ca4a64a4e2f8697dfa830dcae56c1d7ddca51.tar.gz
Add --no-input to hide the input section (#4210)
Close #2890 Close #1396 You can't type in queries in this mode, and the only way to trigger an fzf search is to use `search(...)` action. # Click header to trigger search fzf --header '[src] [test]' --no-input --layout reverse \ --header-border bottom --input-border \ --bind 'click-header:transform-search:echo ${FZF_CLICK_HEADER_WORD:1:-1}'
-rw-r--r--CHANGELOG.md7
-rw-r--r--man/man1/fzf.15
-rw-r--r--src/options.go19
-rw-r--r--src/terminal.go63
-rw-r--r--src/tui/dummy.go1
-rw-r--r--src/tui/light.go19
-rw-r--r--src/tui/tcell.go15
-rw-r--r--src/tui/tui.go5
-rw-r--r--test/test_layout.rb15
9 files changed, 126 insertions, 23 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4d51c75..c4250b42 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,13 @@ CHANGELOG
```
- `$FZF_KEY` was updated to expose the type of the click. e.g. `click`, `ctrl-click`, etc. You can use it to implement a more sophisticated behavior.
- `kill` completion for bash and zsh were updated to use this feature
+- Added `--no-input` option to completely disable and hide the input section
+ ```sh
+ # Click header to trigger search
+ fzf --header '[src] [test]' --no-input --layout reverse \
+ --header-border bottom --input-border \
+ --bind 'click-header:transform-search:echo ${FZF_CLICK_HEADER_WORD:1:-1}'
+ ```
- Extended `{q}` placeholder to support ranges. e.g. `{q:1}`, `{q:2..}`, etc.
- Added `search(...)` and `transform-search(...)` action to trigger an fzf search with an arbitrary query string. This can be used to extend the search syntax of fzf. In the following example, fzf will use the first word of the query to trigger ripgrep search, and use the rest of the query to perform fzf search within the result.
```sh
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index ad205920..3a8a107f 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -621,6 +621,11 @@ Position of the list label
.SS INPUT SECTION
.TP
+.B "\-\-no\-input"
+Disable and hide the input section. You can no longer type in queries. To
+trigger a search, use \fBsearch\fR action.
+
+.TP
.BI "\-\-prompt=" "STR"
Input prompt (default: '> ')
.TP
diff --git a/src/options.go b/src/options.go
index cc627ee1..fec9596c 100644
--- a/src/options.go
+++ b/src/options.go
@@ -127,6 +127,7 @@ Usage: fzf [options]
(default: 0 or center)
INPUT SECTION
+ --no-input Disable and hide the input section
--prompt=STR Input prompt (default: '> ')
--info=STYLE Finder info style
[default|right|hidden|inline[-right][:PREFIX]]
@@ -538,6 +539,7 @@ type Options struct {
Scheme string
Extended bool
Phony bool
+ Inputless bool
Case Case
Normalize bool
Nth []Range
@@ -659,6 +661,7 @@ func defaultOptions() *Options {
Scheme: "", // Unknown
Extended: true,
Phony: false,
+ Inputless: false,
Case: CaseSmart,
Normalize: true,
Nth: make([]Range, 0),
@@ -2315,6 +2318,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.Phony = false
case "--disabled", "--phony":
opts.Phony = true
+ case "--no-input":
+ opts.Inputless = true
case "--tiebreak":
str, err := nextString("sort criterion required")
if err != nil {
@@ -3064,6 +3069,9 @@ func noSeparatorLine(style infoStyle, separator bool) bool {
}
func (opts *Options) noSeparatorLine() bool {
+ if opts.Inputless {
+ return true
+ }
sep := opts.Separator == nil && !opts.InputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0
return noSeparatorLine(opts.InfoStyle, sep)
}
@@ -3235,7 +3243,13 @@ func postProcessOptions(opts *Options) error {
// Sets --min-height automatically
if opts.Height.size > 0 && opts.Height.percent && opts.MinHeight < 0 {
- opts.MinHeight = -opts.MinHeight + 1 + borderLines(opts.BorderShape) + borderLines(opts.ListBorderShape) + borderLines(opts.InputBorderShape)
+ opts.MinHeight = -opts.MinHeight + borderLines(opts.BorderShape) + borderLines(opts.ListBorderShape)
+ if !opts.Inputless {
+ opts.MinHeight += 1 + borderLines(opts.InputBorderShape)
+ if !opts.noSeparatorLine() {
+ opts.MinHeight++
+ }
+ }
if len(opts.Header) > 0 {
opts.MinHeight += borderLines(opts.HeaderBorderShape) + len(opts.Header)
}
@@ -3246,9 +3260,6 @@ func postProcessOptions(opts *Options) error {
}
opts.MinHeight += borderLines(borderShape) + opts.HeaderLines
}
- if !opts.noSeparatorLine() {
- opts.MinHeight++
- }
if len(opts.Preview.command) > 0 && (opts.Preview.position == posUp || opts.Preview.position == posDown) && opts.Preview.Visible() && opts.Preview.position == posUp {
borderShape := opts.Preview.border
if opts.Preview.border == tui.BorderLine {
diff --git a/src/terminal.go b/src/terminal.go
index da3b863c..b63af45b 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -324,6 +324,7 @@ type Terminal struct {
cleanExit bool
executor *util.Executor
paused bool
+ inputless bool
border tui.Window
window tui.Window
inputWindow tui.Window
@@ -810,6 +811,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
if err != nil {
return nil, err
}
+ if opts.Inputless {
+ renderer.HideCursor()
+ }
wordRubout := "[^\\pL\\pN][\\pL\\pN]"
wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
if opts.FileWord {
@@ -887,6 +891,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
cleanExit: opts.ClearOnExit,
executor: executor,
paused: opts.Phony,
+ inputless: opts.Inputless,
cycle: opts.Cycle,
highlightLine: opts.CursorLine,
headerVisible: true,
@@ -1124,9 +1129,15 @@ func (t *Terminal) visibleHeaderLinesInList() int {
// Extra number of lines needed to display fzf
func (t *Terminal) extraLines() int {
- extra := 1
- if t.inputBorderShape.Visible() {
- extra += borderLines(t.inputBorderShape)
+ extra := 0
+ if !t.inputless {
+ extra++
+ if !t.noSeparatorLine() {
+ extra++
+ }
+ if t.inputBorderShape.Visible() {
+ extra += borderLines(t.inputBorderShape)
+ }
}
if t.listBorderShape.Visible() {
extra += borderLines(t.listBorderShape)
@@ -1141,9 +1152,6 @@ func (t *Terminal) extraLines() int {
}
extra += t.headerLines
}
- if !t.noSeparatorLine() {
- extra++
- }
return extra
}
@@ -1265,7 +1273,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
}
func (t *Terminal) noSeparatorLine() bool {
- return noSeparatorLine(t.infoStyle, t.separatorLen > 0)
+ return t.inputless || noSeparatorLine(t.infoStyle, t.separatorLen > 0)
}
func getScrollbar(perLine int, total int, height int, offset int) (int, int) {
@@ -1350,7 +1358,10 @@ func (t *Terminal) Input() (bool, []rune) {
t.mutex.Lock()
defer t.mutex.Unlock()
paused := t.paused
- src := t.input
+ var src []rune
+ if !t.inputless {
+ src = t.input
+ }
if t.inputOverride != nil {
paused = false
src = *t.inputOverride
@@ -1635,8 +1646,11 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
minAreaWidth := minWidth
minAreaHeight := minHeight
+ if t.inputless {
+ minAreaHeight--
+ }
if t.noSeparatorLine() {
- minAreaHeight -= 1
+ minAreaHeight--
}
if t.needPreviewWindow() {
minPreviewWidth, minPreviewHeight := t.minPreviewSize(t.activePreviewOpts)
@@ -1756,7 +1770,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
shrink := 0
hasHeaderWindow := t.hasHeaderWindow()
hasHeaderLinesWindow := t.hasHeaderLinesWindow()
- hasInputWindow := t.inputBorderShape.Visible() || hasHeaderWindow || hasHeaderLinesWindow
+ hasInputWindow := !t.inputless && (t.inputBorderShape.Visible() || hasHeaderWindow || hasHeaderLinesWindow)
if hasInputWindow {
inputWindowHeight := 2
if t.noSeparatorLine() {
@@ -1873,6 +1887,9 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
switch previewOpts.position {
case posUp, posDown:
minWindowHeight := minHeight
+ if t.inputless {
+ minWindowHeight--
+ }
if t.noSeparatorLine() {
minWindowHeight--
}
@@ -2227,6 +2244,9 @@ func (t *Terminal) promptLine() int {
}
func (t *Terminal) placeCursor() {
+ if t.inputless {
+ return
+ }
if t.inputWindow != nil {
y := t.inputWindow.Height() - 1
if t.layout == layoutReverse {
@@ -2239,6 +2259,9 @@ func (t *Terminal) placeCursor() {
}
func (t *Terminal) printPrompt() {
+ if t.inputless {
+ return
+ }
w := t.window
if t.inputWindow != nil {
w = t.inputWindow
@@ -2266,6 +2289,9 @@ func (t *Terminal) trimMessage(message string, maxWidth int) string {
}
func (t *Terminal) printInfo() {
+ if t.inputless {
+ return
+ }
t.withWindow(t.inputWindow, func() { t.printInfoImpl() })
}
@@ -2509,7 +2535,7 @@ func (t *Terminal) headerIndent(borderShape tui.BorderShape) int {
func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShape, lines1 []string, lines2 []string) {
max := t.window.Height()
- if t.inputWindow == nil && window == nil && t.headerFirst {
+ if !t.inputless && t.inputWindow == nil && window == nil && t.headerFirst {
max--
if !t.noSeparatorLine() {
max--
@@ -2539,7 +2565,7 @@ func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShap
if needReverse && idx < len(lines1) {
line = len(lines1) - idx - 1
}
- if t.inputWindow == nil && window == nil && !t.headerFirst {
+ if !t.inputless && t.inputWindow == nil && window == nil && !t.headerFirst {
line++
if !t.noSeparatorLine() {
line++
@@ -5681,7 +5707,7 @@ func (t *Terminal) Loop() error {
// Header
numLines := t.visibleHeaderLinesInList()
lineOffset := 0
- if t.inputWindow == nil && !t.headerFirst {
+ if !t.inputless && t.inputWindow == nil && !t.headerFirst {
// offset for info line
if t.noSeparatorLine() {
lineOffset = 1
@@ -5829,7 +5855,13 @@ func (t *Terminal) Loop() error {
} else if !doActions(actions) {
continue
}
- t.truncateQuery()
+ if t.inputless {
+ // Always just discard the change
+ t.input = previousInput
+ t.cx = len(t.input)
+ } else {
+ t.truncateQuery()
+ }
queryChanged = string(previousInput) != string(t.input)
if queryChanged {
t.inputOverride = nil
@@ -6016,6 +6048,9 @@ func (t *Terminal) vset(o int) bool {
// Number of prompt lines in the list window
func (t *Terminal) promptLines() int {
+ if t.inputless {
+ return 0
+ }
if t.inputWindow != nil {
return 0
}
diff --git a/src/tui/dummy.go b/src/tui/dummy.go
index 1cfb292e..8dd58457 100644
--- a/src/tui/dummy.go
+++ b/src/tui/dummy.go
@@ -45,6 +45,7 @@ func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool { return false }
func (r *FullscreenRenderer) Bell() {}
+func (r *FullscreenRenderer) HideCursor() {}
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
diff --git a/src/tui/light.go b/src/tui/light.go
index 54c38c18..7b40efbb 100644
--- a/src/tui/light.go
+++ b/src/tui/light.go
@@ -77,7 +77,13 @@ func (r *LightRenderer) csi(code string) string {
func (r *LightRenderer) flush() {
if r.queued.Len() > 0 {
- r.flushRaw("\x1b[?7l\x1b[?25l" + r.queued.String() + "\x1b[?25h\x1b[?7h")
+ raw := "\x1b[?7l\x1b[?25l" + r.queued.String()
+ if r.showCursor {
+ raw += "\x1b[?25h\x1b[?7h"
+ } else {
+ raw += "\x1b[?7h"
+ }
+ r.flushRaw(raw)
r.queued.Reset()
}
}
@@ -110,6 +116,7 @@ type LightRenderer struct {
y int
x int
maxHeightFunc func(int) int
+ showCursor bool
// Windows only
ttyinChannel chan byte
@@ -152,7 +159,8 @@ func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse
tabstop: tabstop,
fullscreen: fullscreen,
upOneLine: false,
- maxHeightFunc: maxHeightFunc}
+ maxHeightFunc: maxHeightFunc,
+ showCursor: true}
return &r, nil
}
@@ -759,6 +767,9 @@ func (r *LightRenderer) Close() {
} else if !r.fullscreen {
r.csi("u")
}
+ if !r.showCursor {
+ r.csi("?25h")
+ }
r.disableMouse()
r.flush()
r.closePlatform()
@@ -1214,3 +1225,7 @@ func (w *LightWindow) Erase() {
func (w *LightWindow) EraseMaybe() bool {
return false
}
+
+func (r *LightRenderer) HideCursor() {
+ r.showCursor = false
+}
diff --git a/src/tui/tcell.go b/src/tui/tcell.go
index 3738214a..9d6fde80 100644
--- a/src/tui/tcell.go
+++ b/src/tui/tcell.go
@@ -52,6 +52,7 @@ type TcellWindow struct {
borderStyle BorderStyle
uri *string
params *string
+ showCursor bool
}
func (w *TcellWindow) Top() int {
@@ -72,7 +73,9 @@ func (w *TcellWindow) Height() int {
func (w *TcellWindow) Refresh() {
if w.moveCursor {
- _screen.ShowCursor(w.left+w.lastX, w.top+w.lastY)
+ if w.showCursor {
+ _screen.ShowCursor(w.left+w.lastX, w.top+w.lastY)
+ }
w.moveCursor = false
}
w.lastX = 0
@@ -104,6 +107,10 @@ func (r *FullscreenRenderer) Bell() {
_screen.Beep()
}
+func (r *FullscreenRenderer) HideCursor() {
+ r.showCursor = false
+}
+
func (r *FullscreenRenderer) PassThrough(str string) {
// No-op
// https://github.com/gdamore/tcell/pull/650#issuecomment-1806442846
@@ -168,6 +175,9 @@ func (r *FullscreenRenderer) getScreen() (tcell.Screen, error) {
if e != nil {
return nil, e
}
+ if !r.showCursor {
+ s.HideCursor()
+ }
_screen = s
}
return _screen, nil
@@ -590,7 +600,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
width: width,
height: height,
normal: normal,
- borderStyle: borderStyle}
+ borderStyle: borderStyle,
+ showCursor: r.showCursor}
w.Erase()
return w
}
diff --git a/src/tui/tui.go b/src/tui/tui.go
index fe8fc243..0c778ad6 100644
--- a/src/tui/tui.go
+++ b/src/tui/tui.go
@@ -615,6 +615,7 @@ type Renderer interface {
NeedScrollbarRedraw() bool
ShouldEmitResizeEvent() bool
Bell()
+ HideCursor()
GetChar() Event
@@ -662,6 +663,7 @@ type FullscreenRenderer struct {
forceBlack bool
prevDownTime time.Time
clicks [][2]int
+ showCursor bool
}
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
@@ -670,7 +672,8 @@ func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Rende
mouse: mouse,
forceBlack: forceBlack,
prevDownTime: time.Unix(0, 0),
- clicks: [][2]int{}}
+ clicks: [][2]int{},
+ showCursor: true}
return r
}
diff --git a/test/test_layout.rb b/test/test_layout.rb
index dcfe1d78..6e844527 100644
--- a/test/test_layout.rb
+++ b/test/test_layout.rb
@@ -876,4 +876,19 @@ class TestLayout < TestInteractive
BLOCK
tmux.until { assert_block(block, _1) }
end
+
+ def test_min_height_auto_no_input
+ tmux.send_keys %(seq 100 | #{FZF} --style full:sharp --no-input --height 1% --min-height 5+), :Enter
+
+ block = <<~BLOCK
+ ┌─────────
+ │ 5
+ │ 4
+ │ 3
+ │ 2
+ │ > 1
+ └─────────
+ BLOCK
+ tmux.until { assert_block(block, _1) }
+ end
end