m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-18 08:13:40 -05:00

Add preview window option for setting the initial scroll offset

Close #1057
Close #2120

  # Initial scroll offset is set to the line number of each line of
  # git grep output *minus* 5 lines
  git grep --line-number '' |
    fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5
This commit is contained in:
Junegunn Choi
2020-07-27 00:15:25 +09:00
parent c0a83b27eb
commit 0f9cb5590e
5 changed files with 97 additions and 17 deletions

View File

@@ -80,7 +80,7 @@ const usage = `usage: fzf [options]
Preview
--preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][:SIZE[%]][:wrap][:hidden]
[up|down|left|right][:SIZE[%]][:wrap][:hidden][:+SCROLL[-OFFSET]]
Scripting
-q, --query=STR Start the finder with the given query
@@ -159,6 +159,7 @@ type previewOpts struct {
command string
position windowPosition
size sizeSpec
scroll string
hidden bool
wrap bool
border bool
@@ -260,7 +261,7 @@ func defaultOptions() *Options {
ToggleSort: false,
Expect: make(map[int]string),
Keymap: make(map[int][]action),
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false, true},
Preview: previewOpts{"", posRight, sizeSpec{50, true}, "", false, false, true},
PrintQuery: false,
ReadZero: false,
Printer: func(str string) { fmt.Println(str) },
@@ -994,6 +995,7 @@ func parsePreviewWindow(opts *previewOpts, input string) {
tokens := strings.Split(input, ":")
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
offsetRegex := regexp.MustCompile("^\\+([0-9]+|{[0-9]+})(-[0-9]+)?$")
for _, token := range tokens {
switch token {
case "":
@@ -1016,8 +1018,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
default:
if sizeRegex.MatchString(token) {
opts.size = parseSize(token, 99, "window size")
} else if offsetRegex.MatchString(token) {
opts.scroll = token[1:]
} else {
errorExit("invalid preview window layout: " + input)
errorExit("invalid preview window option: " + token)
}
}
}
@@ -1270,7 +1274,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Preview.command = ""
case "--preview-window":
parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:noborder][:wrap][:hidden]"))
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:noborder][:wrap][:hidden][:+SCROLL[-OFFSET]]"))
case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
case "--min-height":

View File

@@ -262,6 +262,11 @@ type previewRequest struct {
list []*Item
}
type previewResult struct {
content string
offset int
}
func toActions(types ...actionType) []action {
actions := make([]action, len(types))
for idx, t := range types {
@@ -1347,6 +1352,39 @@ func cleanTemporaryFiles() {
activeTempFiles = []string{}
}
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string {
return replacePlaceholder(
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
}
// Ascii to positive integer
func atopi(s string) int {
n, e := strconv.Atoi(strings.ReplaceAll(s, "'", ""))
if e != nil || n < 1 {
return 0
}
return n
}
func (t *Terminal) evaluateScrollOffset(list []*Item) int {
offsetExpr := t.replacePlaceholder(t.preview.scroll, false, "", list)
nums := strings.Split(offsetExpr, "-")
switch len(nums) {
case 0:
return 0
case 1, 2:
base := atopi(nums[0])
if base == 0 {
return 0
} else if len(nums) == 1 {
return base - 1
}
return base - atopi(nums[1]) - 1
default:
return 0
}
}
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
current := allItems[:1]
selected := allItems[1:]
@@ -1445,7 +1483,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
if !valid {
return
}
command := replacePlaceholder(template, t.ansi, t.delimiter, t.printsep, forcePlus, string(t.input), list)
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
cmd := util.ExecCommand(command, false)
if !background {
cmd.Stdin = os.Stdin
@@ -1629,8 +1667,8 @@ func (t *Terminal) Loop() {
})
// We don't display preview window if no match
if items[0] != nil {
command := replacePlaceholder(commandTemplate,
t.ansi, t.delimiter, t.printsep, false, string(t.Input()), items)
command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items)
offset := t.evaluateScrollOffset(items)
cmd := util.ExecCommand(command, true)
if t.pwindow != nil {
env := os.Environ()
@@ -1673,11 +1711,11 @@ func (t *Terminal) Loop() {
cmd.Wait()
finishChan <- true
if out.Len() > 0 || !<-updateChan {
t.reqBox.Set(reqPreviewDisplay, out.String())
t.reqBox.Set(reqPreviewDisplay, previewResult{out.String(), offset})
}
cleanTemporaryFiles()
} else {
t.reqBox.Set(reqPreviewDisplay, "")
t.reqBox.Set(reqPreviewDisplay, previewResult{"", 0})
}
}
}()
@@ -1751,9 +1789,10 @@ func (t *Terminal) Loop() {
return exitNoMatch
})
case reqPreviewDisplay:
t.previewer.text = value.(string)
result := value.(previewResult)
t.previewer.text = result.content
t.previewer.lines = strings.Count(t.previewer.text, "\n")
t.previewer.offset = 0
t.previewer.offset = util.Constrain(result.offset, 0, t.previewer.lines-1)
t.printPreview()
case reqPreviewRefresh:
t.printPreview()
@@ -2172,8 +2211,7 @@ func (t *Terminal) Loop() {
valid = !slot || query
}
if valid {
command := replacePlaceholder(a.a,
t.ansi, t.delimiter, t.printsep, false, string(t.input), list)
command := t.replacePlaceholder(a.a, false, string(t.input), list)
newCommand = &command
}
}