From 7bff4661f6961fa9a7aad83f8cabc1bc6eb5c787 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 3 Nov 2021 21:19:22 +0900 Subject: Add --header-first option to display header before prompt line Close #2422 --- src/options.go | 7 +++++++ src/terminal.go | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/options.go b/src/options.go index daa4fe07..e2c03dd9 100644 --- a/src/options.go +++ b/src/options.go @@ -69,6 +69,7 @@ const usage = `usage: fzf [options] --marker=STR Multi-select marker (default: '>') --header=STR String to print as header --header-lines=N The first N lines of the input are treated as header + --header-first Print header before the prompt line Display --ansi Enable processing of ANSI color codes @@ -225,6 +226,7 @@ type Options struct { History *History Header []string HeaderLines int + HeaderFirst bool Margin [4]sizeSpec Padding [4]sizeSpec BorderShape tui.BorderShape @@ -287,6 +289,7 @@ func defaultOptions() *Options { History: nil, Header: make([]string, 0), HeaderLines: 0, + HeaderFirst: false, Margin: defaultMargin(), Padding: defaultMargin(), Unicode: true, @@ -1427,6 +1430,10 @@ func parseOptions(opts *Options, allArgs []string) { case "--header-lines": opts.HeaderLines = atoi( nextString(allArgs, &i, "number of header lines required")) + case "--header-first": + opts.HeaderFirst = true + case "--no-header-first": + opts.HeaderFirst = false case "--preview": opts.Preview.command = nextString(allArgs, &i, "preview command required") case "--no-preview": diff --git a/src/terminal.go b/src/terminal.go index 1af399a0..2bad5d75 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -140,6 +140,8 @@ type Terminal struct { printQuery bool history *History cycle bool + headerFirst bool + headerLines int header []string header0 []string ansi bool @@ -529,6 +531,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { paused: opts.Phony, strong: strongAttr, cycle: opts.Cycle, + headerFirst: opts.HeaderFirst, + headerLines: opts.HeaderLines, header: header, header0: header, ansi: opts.Ansi, @@ -976,12 +980,23 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) { return before, after } +func (t *Terminal) promptLine() int { + if t.headerFirst { + max := t.window.Height() - 1 + if !t.noInfoLine() { + max-- + } + return util.Min(len(t.header0)+t.headerLines, max) + } + return 0 +} + func (t *Terminal) placeCursor() { - t.move(0, t.promptLen+t.queryLen[0], false) + t.move(t.promptLine(), t.promptLen+t.queryLen[0], false) } func (t *Terminal) printPrompt() { - t.move(0, 0, true) + t.move(t.promptLine(), 0, true) t.prompt() before, after := t.updatePromptOffset() @@ -1003,22 +1018,23 @@ func (t *Terminal) trimMessage(message string, maxWidth int) string { func (t *Terminal) printInfo() { pos := 0 + line := t.promptLine() switch t.infoStyle { case infoDefault: - t.move(1, 0, true) + t.move(line+1, 0, true) if t.reading { duration := int64(spinnerDuration) idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration t.window.CPrint(tui.ColSpinner, t.spinner[idx]) } - t.move(1, 2, false) + t.move(line+1, 2, false) pos = 2 case infoInline: pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1 if pos+len(" < ") > t.window.Width() { return } - t.move(0, pos, true) + t.move(line, pos, true) if t.reading { t.window.CPrint(tui.ColSpinner, " < ") } else { @@ -1061,11 +1077,20 @@ func (t *Terminal) printHeader() { return } max := t.window.Height() + if t.headerFirst { + max-- + if !t.noInfoLine() { + max-- + } + } var state *ansiState for idx, lineStr := range t.header { - line := idx + 2 - if t.noInfoLine() { - line-- + line := idx + if !t.headerFirst { + line++ + if !t.noInfoLine() { + line++ + } } if line >= max { continue @@ -2644,7 +2669,7 @@ func (t *Terminal) Loop() { } } } else if me.Down { - if my == 0 && mx >= 0 { + if my == t.promptLine() && mx >= 0 { // Prompt t.cx = mx + t.xoffset } else if my >= min { -- cgit v1.2.3