Make preview windows scrollable

Close #669

You can use your mouse or binadble preview-up and preview-down actions
to scroll the content of the preview window.

    fzf --preview 'highlight -O ansi {}' --bind alt-j:preview-down,alt-k:preview-up
This commit is contained in:
Junegunn Choi 2016-09-25 02:02:00 +09:00
parent 7fa5e6c861
commit 66d55fd893
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
2 changed files with 67 additions and 9 deletions

View File

@ -663,6 +663,10 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
keymap[key] = actTogglePreview keymap[key] = actTogglePreview
case "toggle-sort": case "toggle-sort":
keymap[key] = actToggleSort keymap[key] = actToggleSort
case "preview-up":
keymap[key] = actPreviewUp
case "preview-down":
keymap[key] = actPreviewDown
default: default:
if isExecuteAction(actLower) { if isExecuteAction(actLower) {
var offset int var offset int

View File

@ -28,6 +28,13 @@ const (
jumpAcceptEnabled jumpAcceptEnabled
) )
type previewer struct {
text string
lines int
offset int
enabled bool
}
// Terminal represents terminal input/output // Terminal represents terminal input/output
type Terminal struct { type Terminal struct {
initDelay time.Duration initDelay time.Duration
@ -68,8 +75,7 @@ type Terminal struct {
selected map[int32]selectedItem selected map[int32]selectedItem
reqBox *util.EventBox reqBox *util.EventBox
preview previewOpts preview previewOpts
previewing bool previewer previewer
previewTxt string
previewBox *util.EventBox previewBox *util.EventBox
eventBox *util.EventBox eventBox *util.EventBox
mutex sync.Mutex mutex sync.Mutex
@ -119,6 +125,7 @@ const (
reqPrintQuery reqPrintQuery
reqPreviewEnqueue reqPreviewEnqueue
reqPreviewDisplay reqPreviewDisplay
reqPreviewRefresh
reqQuit reqQuit
) )
@ -165,6 +172,8 @@ const (
actPrintQuery actPrintQuery
actToggleSort actToggleSort
actTogglePreview actTogglePreview
actPreviewUp
actPreviewDown
actPreviousHistory actPreviousHistory
actNextHistory actNextHistory
actExecute actExecute
@ -275,8 +284,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
selected: make(map[int32]selectedItem), selected: make(map[int32]selectedItem),
reqBox: util.NewEventBox(), reqBox: util.NewEventBox(),
preview: opts.Preview, preview: opts.Preview,
previewing: previewBox != nil && !opts.Preview.hidden, previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden},
previewTxt: "",
previewBox: previewBox, previewBox: previewBox,
eventBox: eventBox, eventBox: eventBox,
mutex: sync.Mutex{}, mutex: sync.Mutex{},
@ -772,9 +780,35 @@ func (t *Terminal) printHighlighted(result *Result, bold bool, col1 int, col2 in
} }
} }
func numLinesMax(str string, max int) int {
lines := 0
for lines < max {
idx := strings.Index(str, "\n")
if idx < 0 {
break
}
str = str[idx+1:]
lines++
}
return lines
}
func (t *Terminal) printPreview() { func (t *Terminal) printPreview() {
t.pwindow.Erase() t.pwindow.Erase()
extractColor(t.previewTxt, nil, func(str string, ansi *ansiState) bool { skip := t.previewer.offset
extractColor(t.previewer.text, nil, func(str string, ansi *ansiState) bool {
if skip > 0 {
newlines := numLinesMax(str, skip)
if skip <= newlines {
for i := 0; i < skip; i++ {
str = str[strings.Index(str, "\n")+1:]
}
skip = 0
} else {
skip -= newlines
return true
}
}
if ansi != nil && ansi.colored() { if ansi != nil && ansi.colored() {
return t.pwindow.CFill(str, ansi.fg, ansi.bg, ansi.bold) return t.pwindow.CFill(str, ansi.fg, ansi.bg, ansi.bold)
} }
@ -891,7 +925,7 @@ func (t *Terminal) hasPreviewWindow() bool {
} }
func (t *Terminal) isPreviewEnabled() bool { func (t *Terminal) isPreviewEnabled() bool {
return t.previewBox != nil && t.previewing return t.previewBox != nil && t.previewer.enabled
} }
func (t *Terminal) current() string { func (t *Terminal) current() string {
@ -1033,7 +1067,11 @@ func (t *Terminal) Loop() {
} }
exit(exitNoMatch) exit(exitNoMatch)
case reqPreviewDisplay: case reqPreviewDisplay:
t.previewTxt = value.(string) t.previewer.text = value.(string)
t.previewer.lines = strings.Count(t.previewer.text, "\n")
t.previewer.offset = 0
t.printPreview()
case reqPreviewRefresh:
t.printPreview() t.printPreview()
case reqPrintQuery: case reqPrintQuery:
C.Close() C.Close()
@ -1118,10 +1156,10 @@ func (t *Terminal) Loop() {
return false return false
case actTogglePreview: case actTogglePreview:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
t.previewing = !t.previewing t.previewer.enabled = !t.previewer.enabled
t.resizeWindows() t.resizeWindows()
cnt := t.merger.Length() cnt := t.merger.Length()
if t.previewing && cnt > 0 && cnt > t.cy { if t.previewer.enabled && cnt > 0 && cnt > t.cy {
t.previewBox.Set(reqPreviewEnqueue, previewRequest{true, t.current()}) t.previewBox.Set(reqPreviewEnqueue, previewRequest{true, t.current()})
} }
req(reqList, reqInfo) req(reqList, reqInfo)
@ -1131,6 +1169,18 @@ func (t *Terminal) Loop() {
t.eventBox.Set(EvtSearchNew, t.sort) t.eventBox.Set(EvtSearchNew, t.sort)
t.mutex.Unlock() t.mutex.Unlock()
return false return false
case actPreviewUp:
if t.isPreviewEnabled() {
t.previewer.offset = util.Constrain(
t.previewer.offset-1, 0, t.previewer.lines-t.pwindow.Height)
req(reqPreviewRefresh)
}
case actPreviewDown:
if t.isPreviewEnabled() {
t.previewer.offset = util.Constrain(
t.previewer.offset+1, 0, t.previewer.lines-t.pwindow.Height)
req(reqPreviewRefresh)
}
case actBeginningOfLine: case actBeginningOfLine:
t.cx = 0 t.cx = 0
case actBackwardChar: case actBackwardChar:
@ -1299,6 +1349,10 @@ func (t *Terminal) Loop() {
} }
t.vmove(me.S) t.vmove(me.S)
req(reqList) req(reqList)
} else if t.isPreviewEnabled() && t.pwindow.Enclose(my, mx) {
t.previewer.offset = util.Constrain(
t.previewer.offset-me.S, 0, t.previewer.lines-t.pwindow.Height)
req(reqPreviewRefresh)
} }
} else if t.window.Enclose(my, mx) { } else if t.window.Enclose(my, mx) {
mx -= t.window.Left mx -= t.window.Left