diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 532e0df..5d369e7 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -122,6 +122,7 @@ e.g. \fBfzf --color=bg+:24\fR \fBpointer \fRPointer to the current line \fBmarker \fRMulti-select marker \fBspinner \fRStreaming input indicator + \fBheader \fRHeader .RE .TP .B "--black" diff --git a/src/curses/curses.go b/src/curses/curses.go index 4dde288..95c6ad9 100644 --- a/src/curses/curses.go +++ b/src/curses/curses.go @@ -94,6 +94,7 @@ const ( ColInfo ColCursor ColSelected + ColHeader ColUser ) @@ -114,6 +115,7 @@ type ColorTheme struct { Info int16 Cursor int16 Selected int16 + Header int16 } type Event struct { @@ -164,7 +166,8 @@ func init() { Spinner: C.COLOR_GREEN, Info: C.COLOR_WHITE, Cursor: C.COLOR_RED, - Selected: C.COLOR_MAGENTA} + Selected: C.COLOR_MAGENTA, + Header: C.COLOR_CYAN} Dark256 = &ColorTheme{ UseDefault: true, Fg: 15, @@ -177,7 +180,8 @@ func init() { Spinner: 148, Info: 144, Cursor: 161, - Selected: 168} + Selected: 168, + Header: 110} Light256 = &ColorTheme{ UseDefault: true, Fg: 15, @@ -190,7 +194,8 @@ func init() { Spinner: 65, Info: 101, Cursor: 161, - Selected: 168} + Selected: 168, + Header: 31} } func attrColored(pair int, bold bool) C.int { @@ -308,6 +313,7 @@ func initPairs(theme *ColorTheme, black bool) { C.init_pair(ColInfo, C.short(theme.Info), bg) C.init_pair(ColCursor, C.short(theme.Cursor), darkBG) C.init_pair(ColSelected, C.short(theme.Selected), darkBG) + C.init_pair(ColHeader, C.short(theme.Header), bg) } func Close() { diff --git a/src/options.go b/src/options.go index 82dc6d9..4c6e9a0 100644 --- a/src/options.go +++ b/src/options.go @@ -2,6 +2,7 @@ package fzf import ( "fmt" + "io/ioutil" "os" "regexp" "strconv" @@ -44,6 +45,7 @@ const usage = `usage: fzf [options] --bind=KEYBINDS Custom key bindings. Refer to the man page. --history=FILE History file --history-size=N Maximum number of history entries (default: 1000) + --header-file=N Header file Scripting -q, --query=STR Start the finder with the given query @@ -122,6 +124,7 @@ type Options struct { ReadZero bool Sync bool History *History + Header []string Version bool } @@ -164,6 +167,7 @@ func defaultOptions() *Options { ReadZero: false, Sync: false, History: nil, + Header: make([]string, 0), Version: false} } @@ -413,6 +417,8 @@ func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme theme.Cursor = ansi case "marker": theme.Selected = ansi + case "header": + theme.Header = ansi default: fail() } @@ -571,6 +577,14 @@ func checkToggleSort(keymap map[int]actionType, str string) map[int]actionType { return keymap } +func readHeaderFile(filename string) []string { + content, err := ioutil.ReadFile(filename) + if err != nil { + errorExit("failed to read header file: " + filename) + } + return strings.Split(strings.TrimSuffix(string(content), "\n"), "\n") +} + func parseOptions(opts *Options, allArgs []string) { keymap := make(map[int]actionType) var historyMax int @@ -710,6 +724,9 @@ func parseOptions(opts *Options, allArgs []string) { setHistory(nextString(allArgs, &i, "history file path required")) case "--history-size": setHistoryMax(nextInt(allArgs, &i, "history max size required")) + case "--header-file": + opts.Header = readHeaderFile( + nextString(allArgs, &i, "header file name required")) case "--version": opts.Version = true default: @@ -743,6 +760,8 @@ func parseOptions(opts *Options, allArgs []string) { setHistory(value) } else if match, value := optString(arg, "--history-size="); match { setHistoryMax(atoi(value)) + } else if match, value := optString(arg, "--header-file="); match { + opts.Header = readHeaderFile(value) } else { errorExit("unknown option: " + arg) } diff --git a/src/terminal.go b/src/terminal.go index 9c73197..e6e3c51 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -40,6 +40,7 @@ type Terminal struct { printQuery bool history *History cycle bool + header []string count int progress int reading bool @@ -197,6 +198,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { printQuery: opts.PrintQuery, history: opts.History, cycle: opts.Cycle, + header: opts.Header, reading: true, merger: EmptyMerger, selected: make(map[uint32]selectedItem), @@ -354,17 +356,39 @@ func (t *Terminal) printInfo() { C.CPrint(C.ColInfo, false, output) } +func (t *Terminal) printHeader() { + if len(t.header) == 0 { + return + } + for idx, lineStr := range t.header { + if !t.reverse { + idx = len(t.header) - idx - 1 + } + trimmed, colors := extractColor(&lineStr) + item := &Item{ + text: trimmed, + index: 0, + colors: colors, + rank: Rank{0, 0, 0}} + + line := idx + 2 + if t.inlineInfo { + line -= 1 + } + t.move(line, 2, true) + t.printHighlighted(item, false, C.ColHeader, 0, false) + } +} + func (t *Terminal) printList() { t.constrain() maxy := t.maxItems() count := t.merger.Length() - t.offset for i := 0; i < maxy; i++ { - var line int + line := i + 2 + len(t.header) if t.inlineInfo { - line = i + 1 - } else { - line = i + 2 + line -= 1 } t.move(line, 0, true) if i < count { @@ -606,6 +630,7 @@ func (t *Terminal) Loop() { t.placeCursor() C.Refresh() t.printInfo() + t.printHeader() t.mutex.Unlock() go func() { timer := time.NewTimer(initialDelay) @@ -883,9 +908,9 @@ func (t *Terminal) Loop() { if !t.reverse { my = C.MaxY() - my - 1 } - min := 2 + min := 2 + len(t.header) if t.inlineInfo { - min = 1 + min -= 1 } if me.S != 0 { // Scroll @@ -977,7 +1002,7 @@ func (t *Terminal) vset(o int) bool { func (t *Terminal) maxItems() int { if t.inlineInfo { - return C.MaxY() - 1 + return C.MaxY() - 1 - len(t.header) } - return C.MaxY() - 2 + return C.MaxY() - 2 - len(t.header) }