mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-12-23 03:19:01 +00:00
Make pointer and multi-select marker customizable (#1844)
Add --pointer and --marker option which can provide additional context to the user
This commit is contained in:
parent
d61ac32d7b
commit
2a60edcd52
@ -231,6 +231,12 @@ A synonym for \fB--info=hidden\fB
|
||||
.BI "--prompt=" "STR"
|
||||
Input prompt (default: '> ')
|
||||
.TP
|
||||
.BI "--pointer=" "STR"
|
||||
Pointer to the current line (default: '>')
|
||||
.TP
|
||||
.BI "--marker=" "STR"
|
||||
Multi-select marker (default: '>')
|
||||
.TP
|
||||
.BI "--header=" "STR"
|
||||
The given string will be printed as the sticky header. The lines are displayed
|
||||
in the given order from top to bottom regardless of \fB--layout\fR option, and
|
||||
|
@ -72,6 +72,8 @@ _fzf_opts_completion() {
|
||||
--margin
|
||||
--inline-info
|
||||
--prompt
|
||||
--pointer
|
||||
--marker
|
||||
--header
|
||||
--header-lines
|
||||
--ansi
|
||||
|
@ -6,12 +6,14 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/junegunn/fzf/src/algo"
|
||||
"github.com/junegunn/fzf/src/tui"
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/mattn/go-shellwords"
|
||||
)
|
||||
|
||||
@ -59,6 +61,8 @@ const usage = `usage: fzf [options]
|
||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||
--info=STYLE Finder info style [default|inline|hidden]
|
||||
--prompt=STR Input prompt (default: '> ')
|
||||
--pointer=STR Pointer to the current line (default: '>')
|
||||
--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
|
||||
|
||||
@ -189,6 +193,8 @@ type Options struct {
|
||||
InfoStyle infoStyle
|
||||
JumpLabels string
|
||||
Prompt string
|
||||
Pointer string
|
||||
Marker string
|
||||
Query string
|
||||
Select1 bool
|
||||
Exit0 bool
|
||||
@ -242,6 +248,8 @@ func defaultOptions() *Options {
|
||||
InfoStyle: infoDefault,
|
||||
JumpLabels: defaultJumpLabels,
|
||||
Prompt: "> ",
|
||||
Pointer: ">",
|
||||
Marker: ">",
|
||||
Query: "",
|
||||
Select1: false,
|
||||
Exit0: false,
|
||||
@ -1041,6 +1049,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
}
|
||||
}
|
||||
validateJumpLabels := false
|
||||
validatePointer := false
|
||||
validateMarker := false
|
||||
for i := 0; i < len(allArgs); i++ {
|
||||
arg := allArgs[i]
|
||||
switch arg {
|
||||
@ -1189,6 +1199,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.PrintQuery = false
|
||||
case "--prompt":
|
||||
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
||||
case "--pointer":
|
||||
opts.Pointer = nextString(allArgs, &i, "pointer sign string required")
|
||||
validatePointer = true
|
||||
case "--marker":
|
||||
opts.Marker = nextString(allArgs, &i, "selected sign string required")
|
||||
validateMarker = true
|
||||
case "--sync":
|
||||
opts.Sync = true
|
||||
case "--no-sync":
|
||||
@ -1255,6 +1271,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Delimiter = delimiterRegexp(value)
|
||||
} else if match, value := optString(arg, "--prompt="); match {
|
||||
opts.Prompt = value
|
||||
} else if match, value := optString(arg, "--pointer="); match {
|
||||
opts.Pointer = value
|
||||
validatePointer = true
|
||||
} else if match, value := optString(arg, "--marker="); match {
|
||||
opts.Marker = value
|
||||
validateMarker = true
|
||||
} else if match, value := optString(arg, "-n", "--nth="); match {
|
||||
opts.Nth = splitNth(value)
|
||||
} else if match, value := optString(arg, "--with-nth="); match {
|
||||
@ -1333,6 +1355,35 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if validatePointer {
|
||||
if err := validateSign(opts.Pointer, "pointer"); err != nil {
|
||||
errorExit(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if validateMarker {
|
||||
if err := validateSign(opts.Marker, "marker"); err != nil {
|
||||
errorExit(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateSign(sign string, signOptName string) error {
|
||||
if sign == "" {
|
||||
return fmt.Errorf("%v cannot be empty", signOptName)
|
||||
}
|
||||
widthSum := 0
|
||||
for _, r := range sign {
|
||||
if !unicode.IsGraphic(r) {
|
||||
return fmt.Errorf("invalid character in %v", signOptName)
|
||||
}
|
||||
widthSum += runewidth.RuneWidth(r)
|
||||
if widthSum > 2 {
|
||||
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postProcessOptions(opts *Options) {
|
||||
|
@ -422,3 +422,29 @@ func TestAdditiveExpect(t *testing.T) {
|
||||
t.Error(opts.Expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSign(t *testing.T) {
|
||||
testCases := []struct {
|
||||
inputSign string
|
||||
isValid bool
|
||||
}{
|
||||
{"> ", true},
|
||||
{"아", true},
|
||||
{"😀", true},
|
||||
{"", false},
|
||||
{">>>", false},
|
||||
{"\n", false},
|
||||
{"\t", false},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
err := validateSign(testCase.inputSign, "")
|
||||
if testCase.isValid && err != nil {
|
||||
t.Errorf("Input sign `%s` caused error", testCase.inputSign)
|
||||
}
|
||||
|
||||
if !testCase.isValid && err == nil {
|
||||
t.Errorf("Input sign `%s` did not cause error", testCase.inputSign)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
158
src/terminal.go
158
src/terminal.go
@ -59,72 +59,78 @@ var emptyLine = itemLine{}
|
||||
|
||||
// Terminal represents terminal input/output
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
spinner []string
|
||||
prompt string
|
||||
promptLen int
|
||||
queryLen [2]int
|
||||
layout layoutType
|
||||
fullscreen bool
|
||||
hscroll bool
|
||||
hscrollOff int
|
||||
wordRubout string
|
||||
wordNext string
|
||||
cx int
|
||||
cy int
|
||||
offset int
|
||||
xoffset int
|
||||
yanked []rune
|
||||
input []rune
|
||||
multi int
|
||||
sort bool
|
||||
toggleSort bool
|
||||
delimiter Delimiter
|
||||
expect map[int]string
|
||||
keymap map[int][]action
|
||||
pressed string
|
||||
printQuery bool
|
||||
history *History
|
||||
cycle bool
|
||||
header []string
|
||||
header0 []string
|
||||
ansi bool
|
||||
tabstop int
|
||||
margin [4]sizeSpec
|
||||
strong tui.Attr
|
||||
unicode bool
|
||||
bordered bool
|
||||
cleanExit bool
|
||||
border tui.Window
|
||||
window tui.Window
|
||||
pborder tui.Window
|
||||
pwindow tui.Window
|
||||
count int
|
||||
progress int
|
||||
reading bool
|
||||
failed *string
|
||||
jumping jumpMode
|
||||
jumpLabels string
|
||||
printer func(string)
|
||||
printsep string
|
||||
merger *Merger
|
||||
selected map[int32]selectedItem
|
||||
version int64
|
||||
reqBox *util.EventBox
|
||||
preview previewOpts
|
||||
previewer previewer
|
||||
previewBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
mutex sync.Mutex
|
||||
initFunc func()
|
||||
prevLines []itemLine
|
||||
suppress bool
|
||||
startChan chan bool
|
||||
killChan chan int
|
||||
slab *util.Slab
|
||||
theme *tui.ColorTheme
|
||||
tui tui.Renderer
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
spinner []string
|
||||
prompt string
|
||||
promptLen int
|
||||
pointer string
|
||||
pointerLen int
|
||||
pointerEmpty string
|
||||
marker string
|
||||
markerLen int
|
||||
markerEmpty string
|
||||
queryLen [2]int
|
||||
layout layoutType
|
||||
fullscreen bool
|
||||
hscroll bool
|
||||
hscrollOff int
|
||||
wordRubout string
|
||||
wordNext string
|
||||
cx int
|
||||
cy int
|
||||
offset int
|
||||
xoffset int
|
||||
yanked []rune
|
||||
input []rune
|
||||
multi int
|
||||
sort bool
|
||||
toggleSort bool
|
||||
delimiter Delimiter
|
||||
expect map[int]string
|
||||
keymap map[int][]action
|
||||
pressed string
|
||||
printQuery bool
|
||||
history *History
|
||||
cycle bool
|
||||
header []string
|
||||
header0 []string
|
||||
ansi bool
|
||||
tabstop int
|
||||
margin [4]sizeSpec
|
||||
strong tui.Attr
|
||||
unicode bool
|
||||
bordered bool
|
||||
cleanExit bool
|
||||
border tui.Window
|
||||
window tui.Window
|
||||
pborder tui.Window
|
||||
pwindow tui.Window
|
||||
count int
|
||||
progress int
|
||||
reading bool
|
||||
failed *string
|
||||
jumping jumpMode
|
||||
jumpLabels string
|
||||
printer func(string)
|
||||
printsep string
|
||||
merger *Merger
|
||||
selected map[int32]selectedItem
|
||||
version int64
|
||||
reqBox *util.EventBox
|
||||
preview previewOpts
|
||||
previewer previewer
|
||||
previewBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
mutex sync.Mutex
|
||||
initFunc func()
|
||||
prevLines []itemLine
|
||||
suppress bool
|
||||
startChan chan bool
|
||||
killChan chan int
|
||||
slab *util.Slab
|
||||
theme *tui.ColorTheme
|
||||
tui tui.Renderer
|
||||
}
|
||||
|
||||
type selectedItem struct {
|
||||
@ -441,6 +447,12 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
tui: renderer,
|
||||
initFunc: func() { renderer.Init() }}
|
||||
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
|
||||
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
||||
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
||||
// Pre-calculated empty pointer and marker signs
|
||||
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
||||
t.markerEmpty = strings.Repeat(" ", t.markerLen)
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
@ -852,15 +864,15 @@ func (t *Terminal) printList() {
|
||||
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
||||
item := result.item
|
||||
_, selected := t.selected[item.Index()]
|
||||
label := " "
|
||||
label := t.pointerEmpty
|
||||
if t.jumping != jumpDisabled {
|
||||
if i < len(t.jumpLabels) {
|
||||
// Striped
|
||||
current = i%2 == 0
|
||||
label = t.jumpLabels[i : i+1]
|
||||
label = t.jumpLabels[i:i+1] + strings.Repeat(" ", t.pointerLen-1)
|
||||
}
|
||||
} else if current {
|
||||
label = ">"
|
||||
label = t.pointer
|
||||
}
|
||||
|
||||
// Avoid unnecessary redraw
|
||||
@ -879,17 +891,17 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
||||
if current {
|
||||
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
|
||||
if selected {
|
||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, ">")
|
||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.marker)
|
||||
} else {
|
||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, " ")
|
||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.markerEmpty)
|
||||
}
|
||||
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
|
||||
} else {
|
||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||
if selected {
|
||||
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
||||
t.window.CPrint(tui.ColSelected, t.strong, t.marker)
|
||||
} else {
|
||||
t.window.Print(" ")
|
||||
t.window.Print(t.markerEmpty)
|
||||
}
|
||||
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
||||
}
|
||||
|
@ -1407,6 +1407,39 @@ class TestGoFZF < TestBase
|
||||
assert_equal '3', readonce.chomp
|
||||
end
|
||||
|
||||
def test_pointer
|
||||
pointer = '>>'
|
||||
tmux.send_keys "seq 10 | #{fzf "--pointer '#{pointer}'"}", :Enter
|
||||
tmux.until { |lines| lines[-2] == ' 10/10' }
|
||||
lines = tmux.capture
|
||||
# Assert that specified pointer is displayed
|
||||
assert_equal "#{pointer} 1", lines[-3]
|
||||
end
|
||||
|
||||
def test_pointer_with_jump
|
||||
pointer = '>>'
|
||||
tmux.send_keys "seq 10 | #{fzf "--multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '#{pointer}'"}", :Enter
|
||||
tmux.until { |lines| lines[-2] == ' 10/10' }
|
||||
tmux.send_keys 'C-j'
|
||||
# Correctly padded jump label should appear
|
||||
tmux.until { |lines| lines[-7] == '5 5' }
|
||||
tmux.until { |lines| lines[-8] == ' 6' }
|
||||
tmux.send_keys '5'
|
||||
lines = tmux.capture
|
||||
# Assert that specified pointer is displayed
|
||||
assert_equal "#{pointer} 5", lines[-7]
|
||||
end
|
||||
|
||||
def test_marker
|
||||
marker = '>>'
|
||||
tmux.send_keys "seq 10 | #{fzf "--multi --marker '#{marker}'"}", :Enter
|
||||
tmux.until { |lines| lines[-2] == ' 10/10' }
|
||||
tmux.send_keys :BTab
|
||||
lines = tmux.capture
|
||||
# Assert that specified marker is displayed
|
||||
assert_equal " #{marker}1", lines[-3]
|
||||
end
|
||||
|
||||
def test_preview
|
||||
tmux.send_keys %(seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview), :Enter
|
||||
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
||||
|
Loading…
Reference in New Issue
Block a user