mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-23 08:23:50 -07:00
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:
@@ -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
|
||||
|
@@ -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: '> ')
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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{} }
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user