mirror of
https://github.com/junegunn/fzf
synced 2024-11-10 13:10:44 +00:00
Add --header-lines option
This commit is contained in:
parent
18469b6954
commit
f469c25730
@ -259,8 +259,11 @@ Maximum number of entries in the history file (default: 1000). The file is
|
||||
automatically truncated when the number of the lines exceeds the value.
|
||||
.TP
|
||||
.BI "--header-file=" "FILE"
|
||||
The content of the file will be printed as the "sticky" header. The file can
|
||||
The content of the file will be printed as the sticky header. The file can
|
||||
span multiple lines and can contain ANSI color codes.
|
||||
.TP
|
||||
.BI "--header-lines=" "N"
|
||||
The first N lines of the input are treated as the sticky header.
|
||||
.SS Scripting
|
||||
.TP
|
||||
.BI "-q, --query=" "STR"
|
||||
|
@ -26,8 +26,13 @@ func NewChunkList(trans ItemBuilder) *ChunkList {
|
||||
trans: trans}
|
||||
}
|
||||
|
||||
func (c *Chunk) push(trans ItemBuilder, data *string, index int) {
|
||||
*c = append(*c, trans(data, index))
|
||||
func (c *Chunk) push(trans ItemBuilder, data *string, index int) bool {
|
||||
item := trans(data, index)
|
||||
if item != nil {
|
||||
*c = append(*c, item)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsFull returns true if the Chunk is full
|
||||
@ -48,7 +53,7 @@ func CountItems(cs []*Chunk) int {
|
||||
}
|
||||
|
||||
// Push adds the item to the list
|
||||
func (cl *ChunkList) Push(data string) {
|
||||
func (cl *ChunkList) Push(data string) bool {
|
||||
cl.mutex.Lock()
|
||||
defer cl.mutex.Unlock()
|
||||
|
||||
@ -57,8 +62,11 @@ func (cl *ChunkList) Push(data string) {
|
||||
cl.chunks = append(cl.chunks, &newChunk)
|
||||
}
|
||||
|
||||
cl.lastChunk().push(cl.trans, &data, cl.count)
|
||||
cl.count++
|
||||
if cl.lastChunk().push(cl.trans, &data, cl.count) {
|
||||
cl.count++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Snapshot returns immutable snapshot of the ChunkList
|
||||
|
@ -44,5 +44,6 @@ const (
|
||||
EvtSearchNew
|
||||
EvtSearchProgress
|
||||
EvtSearchFin
|
||||
EvtHeader
|
||||
EvtClose
|
||||
)
|
||||
|
24
src/core.go
24
src/core.go
@ -44,6 +44,7 @@ Reader -> EvtReadNew -> Matcher (restart)
|
||||
Terminal -> EvtSearchNew:bool -> Matcher (restart)
|
||||
Matcher -> EvtSearchProgress -> Terminal (update info)
|
||||
Matcher -> EvtSearchFin -> Terminal (update list)
|
||||
Matcher -> EvtHeader -> Terminal (update header)
|
||||
*/
|
||||
|
||||
// Run starts fzf
|
||||
@ -83,8 +84,14 @@ func Run(opts *Options) {
|
||||
|
||||
// Chunk list
|
||||
var chunkList *ChunkList
|
||||
header := make([]string, 0, opts.HeaderLines)
|
||||
if len(opts.WithNth) == 0 {
|
||||
chunkList = NewChunkList(func(data *string, index int) *Item {
|
||||
if len(header) < opts.HeaderLines {
|
||||
header = append(header, *data)
|
||||
eventBox.Set(EvtHeader, header)
|
||||
return nil
|
||||
}
|
||||
data, colors := ansiProcessor(data)
|
||||
return &Item{
|
||||
text: data,
|
||||
@ -94,6 +101,11 @@ func Run(opts *Options) {
|
||||
})
|
||||
} else {
|
||||
chunkList = NewChunkList(func(data *string, index int) *Item {
|
||||
if len(header) < opts.HeaderLines {
|
||||
header = append(header, *data)
|
||||
eventBox.Set(EvtHeader, header)
|
||||
return nil
|
||||
}
|
||||
tokens := Tokenize(data, opts.Delimiter)
|
||||
trans := Transform(tokens, opts.WithNth)
|
||||
item := Item{
|
||||
@ -113,7 +125,9 @@ func Run(opts *Options) {
|
||||
// Reader
|
||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||
if !streamingFilter {
|
||||
reader := Reader{func(str string) { chunkList.Push(str) }, eventBox, opts.ReadZero}
|
||||
reader := Reader{func(str string) bool {
|
||||
return chunkList.Push(str)
|
||||
}, eventBox, opts.ReadZero}
|
||||
go reader.ReadSource()
|
||||
}
|
||||
|
||||
@ -134,11 +148,12 @@ func Run(opts *Options) {
|
||||
|
||||
if streamingFilter {
|
||||
reader := Reader{
|
||||
func(str string) {
|
||||
func(str string) bool {
|
||||
item := chunkList.trans(&str, 0)
|
||||
if pattern.MatchItem(item) {
|
||||
if item != nil && pattern.MatchItem(item) {
|
||||
fmt.Println(*item.text)
|
||||
}
|
||||
return false
|
||||
}, eventBox, opts.ReadZero}
|
||||
reader.ReadSource()
|
||||
} else {
|
||||
@ -206,6 +221,9 @@ func Run(opts *Options) {
|
||||
terminal.UpdateProgress(val)
|
||||
}
|
||||
|
||||
case EvtHeader:
|
||||
terminal.UpdateHeader(value.([]string), opts.HeaderLines)
|
||||
|
||||
case EvtSearchFin:
|
||||
switch val := value.(type) {
|
||||
case *Merger:
|
||||
|
144
src/options.go
144
src/options.go
@ -46,6 +46,7 @@ const usage = `usage: fzf [options]
|
||||
--history=FILE History file
|
||||
--history-size=N Maximum number of history entries (default: 1000)
|
||||
--header-file=FILE The file whose content to be printed as header
|
||||
--header-lines=N The first N lines of the input are treated as header
|
||||
|
||||
Scripting
|
||||
-q, --query=STR Start the finder with the given query
|
||||
@ -94,38 +95,39 @@ const (
|
||||
|
||||
// Options stores the values of command-line options
|
||||
type Options struct {
|
||||
Mode Mode
|
||||
Case Case
|
||||
Nth []Range
|
||||
WithNth []Range
|
||||
Delimiter *regexp.Regexp
|
||||
Sort int
|
||||
Tac bool
|
||||
Tiebreak tiebreak
|
||||
Multi bool
|
||||
Ansi bool
|
||||
Mouse bool
|
||||
Theme *curses.ColorTheme
|
||||
Black bool
|
||||
Reverse bool
|
||||
Cycle bool
|
||||
Hscroll bool
|
||||
InlineInfo bool
|
||||
Prompt string
|
||||
Query string
|
||||
Select1 bool
|
||||
Exit0 bool
|
||||
Filter *string
|
||||
ToggleSort bool
|
||||
Expect map[int]string
|
||||
Keymap map[int]actionType
|
||||
Execmap map[int]string
|
||||
PrintQuery bool
|
||||
ReadZero bool
|
||||
Sync bool
|
||||
History *History
|
||||
Header []string
|
||||
Version bool
|
||||
Mode Mode
|
||||
Case Case
|
||||
Nth []Range
|
||||
WithNth []Range
|
||||
Delimiter *regexp.Regexp
|
||||
Sort int
|
||||
Tac bool
|
||||
Tiebreak tiebreak
|
||||
Multi bool
|
||||
Ansi bool
|
||||
Mouse bool
|
||||
Theme *curses.ColorTheme
|
||||
Black bool
|
||||
Reverse bool
|
||||
Cycle bool
|
||||
Hscroll bool
|
||||
InlineInfo bool
|
||||
Prompt string
|
||||
Query string
|
||||
Select1 bool
|
||||
Exit0 bool
|
||||
Filter *string
|
||||
ToggleSort bool
|
||||
Expect map[int]string
|
||||
Keymap map[int]actionType
|
||||
Execmap map[int]string
|
||||
PrintQuery bool
|
||||
ReadZero bool
|
||||
Sync bool
|
||||
History *History
|
||||
Header []string
|
||||
HeaderLines int
|
||||
Version bool
|
||||
}
|
||||
|
||||
func defaultTheme() *curses.ColorTheme {
|
||||
@ -137,38 +139,39 @@ func defaultTheme() *curses.ColorTheme {
|
||||
|
||||
func defaultOptions() *Options {
|
||||
return &Options{
|
||||
Mode: ModeFuzzy,
|
||||
Case: CaseSmart,
|
||||
Nth: make([]Range, 0),
|
||||
WithNth: make([]Range, 0),
|
||||
Delimiter: nil,
|
||||
Sort: 1000,
|
||||
Tac: false,
|
||||
Tiebreak: byLength,
|
||||
Multi: false,
|
||||
Ansi: false,
|
||||
Mouse: true,
|
||||
Theme: defaultTheme(),
|
||||
Black: false,
|
||||
Reverse: false,
|
||||
Cycle: false,
|
||||
Hscroll: true,
|
||||
InlineInfo: false,
|
||||
Prompt: "> ",
|
||||
Query: "",
|
||||
Select1: false,
|
||||
Exit0: false,
|
||||
Filter: nil,
|
||||
ToggleSort: false,
|
||||
Expect: make(map[int]string),
|
||||
Keymap: defaultKeymap(),
|
||||
Execmap: make(map[int]string),
|
||||
PrintQuery: false,
|
||||
ReadZero: false,
|
||||
Sync: false,
|
||||
History: nil,
|
||||
Header: make([]string, 0),
|
||||
Version: false}
|
||||
Mode: ModeFuzzy,
|
||||
Case: CaseSmart,
|
||||
Nth: make([]Range, 0),
|
||||
WithNth: make([]Range, 0),
|
||||
Delimiter: nil,
|
||||
Sort: 1000,
|
||||
Tac: false,
|
||||
Tiebreak: byLength,
|
||||
Multi: false,
|
||||
Ansi: false,
|
||||
Mouse: true,
|
||||
Theme: defaultTheme(),
|
||||
Black: false,
|
||||
Reverse: false,
|
||||
Cycle: false,
|
||||
Hscroll: true,
|
||||
InlineInfo: false,
|
||||
Prompt: "> ",
|
||||
Query: "",
|
||||
Select1: false,
|
||||
Exit0: false,
|
||||
Filter: nil,
|
||||
ToggleSort: false,
|
||||
Expect: make(map[int]string),
|
||||
Keymap: defaultKeymap(),
|
||||
Execmap: make(map[int]string),
|
||||
PrintQuery: false,
|
||||
ReadZero: false,
|
||||
Sync: false,
|
||||
History: nil,
|
||||
Header: make([]string, 0),
|
||||
HeaderLines: 0,
|
||||
Version: false}
|
||||
}
|
||||
|
||||
func help(ok int) {
|
||||
@ -724,9 +727,18 @@ 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 "--no-header-file":
|
||||
opts.Header = []string{}
|
||||
case "--no-header-lines":
|
||||
opts.HeaderLines = 0
|
||||
case "--header-file":
|
||||
opts.Header = readHeaderFile(
|
||||
nextString(allArgs, &i, "header file name required"))
|
||||
opts.HeaderLines = 0
|
||||
case "--header-lines":
|
||||
opts.Header = []string{}
|
||||
opts.HeaderLines = atoi(
|
||||
nextString(allArgs, &i, "number of header lines required"))
|
||||
case "--version":
|
||||
opts.Version = true
|
||||
default:
|
||||
@ -762,6 +774,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
setHistoryMax(atoi(value))
|
||||
} else if match, value := optString(arg, "--header-file="); match {
|
||||
opts.Header = readHeaderFile(value)
|
||||
opts.HeaderLines = 0
|
||||
} else if match, value := optString(arg, "--header-lines="); match {
|
||||
opts.Header = []string{}
|
||||
opts.HeaderLines = atoi(value)
|
||||
} else {
|
||||
errorExit("unknown option: " + arg)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
|
||||
// Reader reads from command or standard input
|
||||
type Reader struct {
|
||||
pusher func(string)
|
||||
pusher func(string) bool
|
||||
eventBox *util.EventBox
|
||||
delimNil bool
|
||||
}
|
||||
@ -43,8 +43,9 @@ func (r *Reader) feed(src io.Reader) {
|
||||
if err == nil {
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
r.pusher(line)
|
||||
r.eventBox.Set(EvtReadNew, nil)
|
||||
if r.pusher(line) {
|
||||
r.eventBox.Set(EvtReadNew, nil)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
|
@ -10,7 +10,7 @@ func TestReadFromCommand(t *testing.T) {
|
||||
strs := []string{}
|
||||
eb := util.NewEventBox()
|
||||
reader := Reader{
|
||||
pusher: func(s string) { strs = append(strs, s) },
|
||||
pusher: func(s string) bool { strs = append(strs, s); return true },
|
||||
eventBox: eb}
|
||||
|
||||
// Check EventBox
|
||||
|
@ -79,6 +79,7 @@ var _runeWidths = make(map[rune]int)
|
||||
const (
|
||||
reqPrompt util.EventType = iota
|
||||
reqInfo
|
||||
reqHeader
|
||||
reqList
|
||||
reqRefresh
|
||||
reqRedraw
|
||||
@ -231,6 +232,22 @@ func (t *Terminal) UpdateCount(cnt int, final bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateHeader updates the header
|
||||
func (t *Terminal) UpdateHeader(header []string, lines int) {
|
||||
t.mutex.Lock()
|
||||
t.header = make([]string, lines)
|
||||
copy(t.header, header)
|
||||
if !t.reverse {
|
||||
reversed := make([]string, lines)
|
||||
for idx, str := range t.header {
|
||||
reversed[lines-idx-1] = str
|
||||
}
|
||||
t.header = reversed
|
||||
}
|
||||
t.mutex.Unlock()
|
||||
t.reqBox.Set(reqHeader, nil)
|
||||
}
|
||||
|
||||
// UpdateProgress updates the search progress
|
||||
func (t *Terminal) UpdateProgress(progress float32) {
|
||||
t.mutex.Lock()
|
||||
@ -686,6 +703,8 @@ func (t *Terminal) Loop() {
|
||||
t.printInfo()
|
||||
case reqList:
|
||||
t.printList()
|
||||
case reqHeader:
|
||||
t.printHeader()
|
||||
case reqRefresh:
|
||||
t.suppress = false
|
||||
case reqRedraw:
|
||||
|
Loading…
Reference in New Issue
Block a user