From 6c0ca4a64a4e2f8697dfa830dcae56c1d7ddca51 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Thu, 30 Jan 2025 00:50:46 +0900 Subject: 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}' --- src/options.go | 19 +++++++++++++---- src/terminal.go | 63 +++++++++++++++++++++++++++++++++++++++++++------------- src/tui/dummy.go | 1 + src/tui/light.go | 19 +++++++++++++++-- src/tui/tcell.go | 15 ++++++++++++-- src/tui/tui.go | 5 ++++- 6 files changed, 99 insertions(+), 23 deletions(-) (limited to 'src') 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 } -- cgit v1.2.3