mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-06-03 07:50:49 +00: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:
parent
c0a83b27eb
commit
0f9cb5590e
|
@ -26,6 +26,13 @@ CHANGELOG
|
||||||
# Preview window hidden by default, it appears when you first hit '?'
|
# Preview window hidden by default, it appears when you first hit '?'
|
||||||
fzf --bind '?:preview:cat {}' --preview-window hidden
|
fzf --bind '?:preview:cat {}' --preview-window hidden
|
||||||
```
|
```
|
||||||
|
- Added preview window option for setting the initial scroll offset
|
||||||
|
```sh
|
||||||
|
# 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
|
||||||
|
```
|
||||||
- Added support for ANSI colors in `--prompt` string
|
- Added support for ANSI colors in `--prompt` string
|
||||||
- Vim plugin
|
- Vim plugin
|
||||||
- `tmux` layout option for using fzf-tmux
|
- `tmux` layout option for using fzf-tmux
|
||||||
|
|
|
@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Jun 2020" "fzf 0.22.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jul 2020" "fzf 0.22.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
|
@ -381,7 +381,7 @@ Preview window will be updated even when there is no match for the current
|
||||||
query if any of the placeholder expressions evaluates to a non-empty string.
|
query if any of the placeholder expressions evaluates to a non-empty string.
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:noborder][:wrap][:hidden]"
|
.BI "--preview-window=" "[POSITION][:SIZE[%]][:noborder][:wrap][:hidden][:+SCROLL[-OFFSET]]"
|
||||||
Determines the layout of the preview window. If the argument contains
|
Determines the layout of the preview window. If the argument contains
|
||||||
\fB:hidden\fR, the preview window will be hidden by default until
|
\fB:hidden\fR, the preview window will be hidden by default until
|
||||||
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
||||||
|
@ -390,6 +390,12 @@ Line wrap can be enabled with \fB:wrap\fR flag.
|
||||||
If size is given as 0, preview window will not be visible, but fzf will still
|
If size is given as 0, preview window will not be visible, but fzf will still
|
||||||
execute the command in the background.
|
execute the command in the background.
|
||||||
|
|
||||||
|
\fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
|
||||||
|
window. \fBSCROLL\fR can be either a numeric integer or a single-field index
|
||||||
|
expression that refers to a numeric integer. The optional \fB-OFFSET\fR part is
|
||||||
|
for adjusting the base offset so that you can see the text above it. It should
|
||||||
|
be given as a numeric integer.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B POSITION: (default: right)
|
.B POSITION: (default: right)
|
||||||
\fBup
|
\fBup
|
||||||
|
@ -400,8 +406,15 @@ execute the command in the background.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g.
|
e.g.
|
||||||
\fBfzf --preview="head {}" --preview-window=up:30%
|
\fB# Non-default scroll window positions and sizes
|
||||||
fzf --preview="file {}" --preview-window=down:1\fR
|
fzf --preview="head {}" --preview-window=up:30%
|
||||||
|
fzf --preview="file {}" --preview-window=down:1
|
||||||
|
|
||||||
|
# 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\fR
|
||||||
|
|
||||||
.RE
|
.RE
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
.TP
|
.TP
|
||||||
|
|
|
@ -80,7 +80,7 @@ const usage = `usage: fzf [options]
|
||||||
Preview
|
Preview
|
||||||
--preview=COMMAND Command to preview highlighted line ({})
|
--preview=COMMAND Command to preview highlighted line ({})
|
||||||
--preview-window=OPT Preview window layout (default: right:50%)
|
--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
|
Scripting
|
||||||
-q, --query=STR Start the finder with the given query
|
-q, --query=STR Start the finder with the given query
|
||||||
|
@ -159,6 +159,7 @@ type previewOpts struct {
|
||||||
command string
|
command string
|
||||||
position windowPosition
|
position windowPosition
|
||||||
size sizeSpec
|
size sizeSpec
|
||||||
|
scroll string
|
||||||
hidden bool
|
hidden bool
|
||||||
wrap bool
|
wrap bool
|
||||||
border bool
|
border bool
|
||||||
|
@ -260,7 +261,7 @@ func defaultOptions() *Options {
|
||||||
ToggleSort: false,
|
ToggleSort: false,
|
||||||
Expect: make(map[int]string),
|
Expect: make(map[int]string),
|
||||||
Keymap: make(map[int][]action),
|
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,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
Printer: func(str string) { fmt.Println(str) },
|
Printer: func(str string) { fmt.Println(str) },
|
||||||
|
@ -994,6 +995,7 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
|
|
||||||
tokens := strings.Split(input, ":")
|
tokens := strings.Split(input, ":")
|
||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
|
offsetRegex := regexp.MustCompile("^\\+([0-9]+|{[0-9]+})(-[0-9]+)?$")
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
switch token {
|
switch token {
|
||||||
case "":
|
case "":
|
||||||
|
@ -1016,8 +1018,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
default:
|
default:
|
||||||
if sizeRegex.MatchString(token) {
|
if sizeRegex.MatchString(token) {
|
||||||
opts.size = parseSize(token, 99, "window size")
|
opts.size = parseSize(token, 99, "window size")
|
||||||
|
} else if offsetRegex.MatchString(token) {
|
||||||
|
opts.scroll = token[1:]
|
||||||
} else {
|
} 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 = ""
|
opts.Preview.command = ""
|
||||||
case "--preview-window":
|
case "--preview-window":
|
||||||
parsePreviewWindow(&opts.Preview,
|
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":
|
case "--height":
|
||||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||||
case "--min-height":
|
case "--min-height":
|
||||||
|
|
|
@ -262,6 +262,11 @@ type previewRequest struct {
|
||||||
list []*Item
|
list []*Item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type previewResult struct {
|
||||||
|
content string
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
actions := make([]action, len(types))
|
actions := make([]action, len(types))
|
||||||
for idx, t := range types {
|
for idx, t := range types {
|
||||||
|
@ -1347,6 +1352,39 @@ func cleanTemporaryFiles() {
|
||||||
activeTempFiles = []string{}
|
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 {
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
current := allItems[:1]
|
current := allItems[:1]
|
||||||
selected := allItems[1:]
|
selected := allItems[1:]
|
||||||
|
@ -1445,7 +1483,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
||||||
if !valid {
|
if !valid {
|
||||||
return
|
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)
|
cmd := util.ExecCommand(command, false)
|
||||||
if !background {
|
if !background {
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
|
@ -1629,8 +1667,8 @@ func (t *Terminal) Loop() {
|
||||||
})
|
})
|
||||||
// We don't display preview window if no match
|
// We don't display preview window if no match
|
||||||
if items[0] != nil {
|
if items[0] != nil {
|
||||||
command := replacePlaceholder(commandTemplate,
|
command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items)
|
||||||
t.ansi, t.delimiter, t.printsep, false, string(t.Input()), items)
|
offset := t.evaluateScrollOffset(items)
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := util.ExecCommand(command, true)
|
||||||
if t.pwindow != nil {
|
if t.pwindow != nil {
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
|
@ -1673,11 +1711,11 @@ func (t *Terminal) Loop() {
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
finishChan <- true
|
finishChan <- true
|
||||||
if out.Len() > 0 || !<-updateChan {
|
if out.Len() > 0 || !<-updateChan {
|
||||||
t.reqBox.Set(reqPreviewDisplay, out.String())
|
t.reqBox.Set(reqPreviewDisplay, previewResult{out.String(), offset})
|
||||||
}
|
}
|
||||||
cleanTemporaryFiles()
|
cleanTemporaryFiles()
|
||||||
} else {
|
} else {
|
||||||
t.reqBox.Set(reqPreviewDisplay, "")
|
t.reqBox.Set(reqPreviewDisplay, previewResult{"", 0})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -1751,9 +1789,10 @@ func (t *Terminal) Loop() {
|
||||||
return exitNoMatch
|
return exitNoMatch
|
||||||
})
|
})
|
||||||
case reqPreviewDisplay:
|
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.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()
|
t.printPreview()
|
||||||
case reqPreviewRefresh:
|
case reqPreviewRefresh:
|
||||||
t.printPreview()
|
t.printPreview()
|
||||||
|
@ -2172,8 +2211,7 @@ func (t *Terminal) Loop() {
|
||||||
valid = !slot || query
|
valid = !slot || query
|
||||||
}
|
}
|
||||||
if valid {
|
if valid {
|
||||||
command := replacePlaceholder(a.a,
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
t.ansi, t.delimiter, t.printsep, false, string(t.input), list)
|
|
||||||
newCommand = &command
|
newCommand = &command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1787,6 +1787,24 @@ class TestGoFZF < TestBase
|
||||||
tmux.until { |lines| refute_includes lines[1], '2' }
|
tmux.until { |lines| refute_includes lines[1], '2' }
|
||||||
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_scroll_begin_constant
|
||||||
|
tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+123", :Enter
|
||||||
|
tmux.until { |lines| lines.item_count == 1 }
|
||||||
|
tmux.until { |lines| assert_match %r{123.*123/1000}, lines[1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_scroll_begin_expr
|
||||||
|
tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+{3}", :Enter
|
||||||
|
tmux.until { |lines| lines.item_count == 1 }
|
||||||
|
tmux.until { |lines| assert_match %r{321.*321/1000}, lines[1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_scroll_begin_and_offset
|
||||||
|
tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+{2}-2", :Enter
|
||||||
|
tmux.until { |lines| lines.item_count == 1 }
|
||||||
|
tmux.until { |lines| assert_match %r{121.*121/1000}, lines[1] }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
|
Loading…
Reference in New Issue
Block a user