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}'
This commit is contained in:
Junegunn Choi
2025-01-30 00:50:46 +09:00
committed by GitHub
parent 6b5d461411
commit 6c0ca4a64a
9 changed files with 126 additions and 23 deletions

View File

@@ -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

View File

@@ -620,6 +620,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: '> ')

View File

@@ -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 {

View File

@@ -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,10 +1129,16 @@ func (t *Terminal) visibleHeaderLinesInList() int {
// Extra number of lines needed to display fzf
func (t *Terminal) extraLines() int {
extra := 1
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
}
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
}

View File

@@ -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{} }

View File

@@ -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
}

View File

@@ -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 {
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
}

View File

@@ -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
}

View File

@@ -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