mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-09 00:22:02 -07:00
Implement change-preview and change-preview-window actions
The new actions are named with 'change-' prefix to differentiate from the pre-existing, one-off 'preview(...)' action. Fix #2360 Fix #2505 Fix #2666 Related #2435 Related #2376 - Can set up multiple bindings with different change-preview-window actions - Not possible to "rotate" through the options with a single binding - Enlarge or shrink not possible
This commit is contained in:
373
src/terminal.go
373
src/terminal.go
@@ -104,88 +104,89 @@ var emptyLine = itemLine{}
|
||||
|
||||
// Terminal represents terminal input/output
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
spinner []string
|
||||
prompt func()
|
||||
promptLen int
|
||||
pointer string
|
||||
pointerLen int
|
||||
pointerEmpty string
|
||||
marker string
|
||||
markerLen int
|
||||
markerEmpty string
|
||||
queryLen [2]int
|
||||
layout layoutType
|
||||
fullscreen bool
|
||||
keepRight bool
|
||||
hscroll bool
|
||||
hscrollOff int
|
||||
scrollOff 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[tui.Event]string
|
||||
keymap map[tui.Event][]action
|
||||
pressed string
|
||||
printQuery bool
|
||||
history *History
|
||||
cycle bool
|
||||
headerFirst bool
|
||||
headerLines int
|
||||
header []string
|
||||
header0 []string
|
||||
ansi bool
|
||||
tabstop int
|
||||
margin [4]sizeSpec
|
||||
padding [4]sizeSpec
|
||||
strong tui.Attr
|
||||
unicode bool
|
||||
borderShape tui.BorderShape
|
||||
cleanExit bool
|
||||
paused bool
|
||||
border tui.Window
|
||||
window tui.Window
|
||||
pborder tui.Window
|
||||
pwindow tui.Window
|
||||
count int
|
||||
progress int
|
||||
reading bool
|
||||
running bool
|
||||
failed *string
|
||||
jumping jumpMode
|
||||
jumpLabels string
|
||||
printer func(string)
|
||||
printsep string
|
||||
merger *Merger
|
||||
selected map[int32]selectedItem
|
||||
version int64
|
||||
reqBox *util.EventBox
|
||||
previewOpts previewOpts
|
||||
previewer previewer
|
||||
previewed previewed
|
||||
previewBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
mutex sync.Mutex
|
||||
initFunc func()
|
||||
prevLines []itemLine
|
||||
suppress bool
|
||||
sigstop bool
|
||||
startChan chan bool
|
||||
killChan chan int
|
||||
slab *util.Slab
|
||||
theme *tui.ColorTheme
|
||||
tui tui.Renderer
|
||||
executing *util.AtomicBool
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
spinner []string
|
||||
prompt func()
|
||||
promptLen int
|
||||
pointer string
|
||||
pointerLen int
|
||||
pointerEmpty string
|
||||
marker string
|
||||
markerLen int
|
||||
markerEmpty string
|
||||
queryLen [2]int
|
||||
layout layoutType
|
||||
fullscreen bool
|
||||
keepRight bool
|
||||
hscroll bool
|
||||
hscrollOff int
|
||||
scrollOff 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[tui.Event]string
|
||||
keymap map[tui.Event][]action
|
||||
pressed string
|
||||
printQuery bool
|
||||
history *History
|
||||
cycle bool
|
||||
headerFirst bool
|
||||
headerLines int
|
||||
header []string
|
||||
header0 []string
|
||||
ansi bool
|
||||
tabstop int
|
||||
margin [4]sizeSpec
|
||||
padding [4]sizeSpec
|
||||
strong tui.Attr
|
||||
unicode bool
|
||||
borderShape tui.BorderShape
|
||||
cleanExit bool
|
||||
paused bool
|
||||
border tui.Window
|
||||
window tui.Window
|
||||
pborder tui.Window
|
||||
pwindow tui.Window
|
||||
count int
|
||||
progress int
|
||||
reading bool
|
||||
running bool
|
||||
failed *string
|
||||
jumping jumpMode
|
||||
jumpLabels string
|
||||
printer func(string)
|
||||
printsep string
|
||||
merger *Merger
|
||||
selected map[int32]selectedItem
|
||||
version int64
|
||||
reqBox *util.EventBox
|
||||
initialPreviewOpts previewOpts
|
||||
previewOpts previewOpts
|
||||
previewer previewer
|
||||
previewed previewed
|
||||
previewBox *util.EventBox
|
||||
eventBox *util.EventBox
|
||||
mutex sync.Mutex
|
||||
initFunc func()
|
||||
prevLines []itemLine
|
||||
suppress bool
|
||||
sigstop bool
|
||||
startChan chan bool
|
||||
killChan chan int
|
||||
slab *util.Slab
|
||||
theme *tui.ColorTheme
|
||||
tui tui.Renderer
|
||||
executing *util.AtomicBool
|
||||
}
|
||||
|
||||
type selectedItem struct {
|
||||
@@ -286,6 +287,8 @@ const (
|
||||
actTogglePreview
|
||||
actTogglePreviewWrap
|
||||
actPreview
|
||||
actChangePreview
|
||||
actChangePreviewWindow
|
||||
actPreviewTop
|
||||
actPreviewBottom
|
||||
actPreviewUp
|
||||
@@ -324,9 +327,10 @@ type searchRequest struct {
|
||||
}
|
||||
|
||||
type previewRequest struct {
|
||||
template string
|
||||
pwindow tui.Window
|
||||
list []*Item
|
||||
template string
|
||||
pwindow tui.Window
|
||||
scrollOffset int
|
||||
list []*Item
|
||||
}
|
||||
|
||||
type previewResult struct {
|
||||
@@ -416,7 +420,7 @@ func trimQuery(query string) []rune {
|
||||
func hasPreviewAction(opts *Options) bool {
|
||||
for _, actions := range opts.Keymap {
|
||||
for _, action := range actions {
|
||||
if action.t == actPreview {
|
||||
if action.t == actPreview || action.t == actChangePreview {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -496,72 +500,73 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
||||
}
|
||||
t := Terminal{
|
||||
initDelay: delay,
|
||||
infoStyle: opts.InfoStyle,
|
||||
spinner: makeSpinner(opts.Unicode),
|
||||
queryLen: [2]int{0, 0},
|
||||
layout: opts.Layout,
|
||||
fullscreen: fullscreen,
|
||||
keepRight: opts.KeepRight,
|
||||
hscroll: opts.Hscroll,
|
||||
hscrollOff: opts.HscrollOff,
|
||||
scrollOff: opts.ScrollOff,
|
||||
wordRubout: wordRubout,
|
||||
wordNext: wordNext,
|
||||
cx: len(input),
|
||||
cy: 0,
|
||||
offset: 0,
|
||||
xoffset: 0,
|
||||
yanked: []rune{},
|
||||
input: input,
|
||||
multi: opts.Multi,
|
||||
sort: opts.Sort > 0,
|
||||
toggleSort: opts.ToggleSort,
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
keymap: opts.Keymap,
|
||||
pressed: "",
|
||||
printQuery: opts.PrintQuery,
|
||||
history: opts.History,
|
||||
margin: opts.Margin,
|
||||
padding: opts.Padding,
|
||||
unicode: opts.Unicode,
|
||||
borderShape: opts.BorderShape,
|
||||
cleanExit: opts.ClearOnExit,
|
||||
paused: opts.Phony,
|
||||
strong: strongAttr,
|
||||
cycle: opts.Cycle,
|
||||
headerFirst: opts.HeaderFirst,
|
||||
headerLines: opts.HeaderLines,
|
||||
header: header,
|
||||
header0: header,
|
||||
ansi: opts.Ansi,
|
||||
tabstop: opts.Tabstop,
|
||||
reading: true,
|
||||
running: true,
|
||||
failed: nil,
|
||||
jumping: jumpDisabled,
|
||||
jumpLabels: opts.JumpLabels,
|
||||
printer: opts.Printer,
|
||||
printsep: opts.PrintSep,
|
||||
merger: EmptyMerger,
|
||||
selected: make(map[int32]selectedItem),
|
||||
reqBox: util.NewEventBox(),
|
||||
previewOpts: opts.Preview,
|
||||
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
|
||||
previewed: previewed{0, 0, 0, false},
|
||||
previewBox: previewBox,
|
||||
eventBox: eventBox,
|
||||
mutex: sync.Mutex{},
|
||||
suppress: true,
|
||||
sigstop: false,
|
||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||
theme: opts.Theme,
|
||||
startChan: make(chan bool, 1),
|
||||
killChan: make(chan int),
|
||||
tui: renderer,
|
||||
initFunc: func() { renderer.Init() },
|
||||
executing: util.NewAtomicBool(false)}
|
||||
initDelay: delay,
|
||||
infoStyle: opts.InfoStyle,
|
||||
spinner: makeSpinner(opts.Unicode),
|
||||
queryLen: [2]int{0, 0},
|
||||
layout: opts.Layout,
|
||||
fullscreen: fullscreen,
|
||||
keepRight: opts.KeepRight,
|
||||
hscroll: opts.Hscroll,
|
||||
hscrollOff: opts.HscrollOff,
|
||||
scrollOff: opts.ScrollOff,
|
||||
wordRubout: wordRubout,
|
||||
wordNext: wordNext,
|
||||
cx: len(input),
|
||||
cy: 0,
|
||||
offset: 0,
|
||||
xoffset: 0,
|
||||
yanked: []rune{},
|
||||
input: input,
|
||||
multi: opts.Multi,
|
||||
sort: opts.Sort > 0,
|
||||
toggleSort: opts.ToggleSort,
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
keymap: opts.Keymap,
|
||||
pressed: "",
|
||||
printQuery: opts.PrintQuery,
|
||||
history: opts.History,
|
||||
margin: opts.Margin,
|
||||
padding: opts.Padding,
|
||||
unicode: opts.Unicode,
|
||||
borderShape: opts.BorderShape,
|
||||
cleanExit: opts.ClearOnExit,
|
||||
paused: opts.Phony,
|
||||
strong: strongAttr,
|
||||
cycle: opts.Cycle,
|
||||
headerFirst: opts.HeaderFirst,
|
||||
headerLines: opts.HeaderLines,
|
||||
header: header,
|
||||
header0: header,
|
||||
ansi: opts.Ansi,
|
||||
tabstop: opts.Tabstop,
|
||||
reading: true,
|
||||
running: true,
|
||||
failed: nil,
|
||||
jumping: jumpDisabled,
|
||||
jumpLabels: opts.JumpLabels,
|
||||
printer: opts.Printer,
|
||||
printsep: opts.PrintSep,
|
||||
merger: EmptyMerger,
|
||||
selected: make(map[int32]selectedItem),
|
||||
reqBox: util.NewEventBox(),
|
||||
initialPreviewOpts: opts.Preview,
|
||||
previewOpts: opts.Preview,
|
||||
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
|
||||
previewed: previewed{0, 0, 0, false},
|
||||
previewBox: previewBox,
|
||||
eventBox: eventBox,
|
||||
mutex: sync.Mutex{},
|
||||
suppress: true,
|
||||
sigstop: false,
|
||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||
theme: opts.Theme,
|
||||
startChan: make(chan bool, 1),
|
||||
killChan: make(chan int),
|
||||
tui: renderer,
|
||||
initFunc: func() { renderer.Init() },
|
||||
executing: util.NewAtomicBool(false)}
|
||||
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
||||
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
||||
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
||||
@@ -1642,9 +1647,14 @@ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input str
|
||||
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
||||
}
|
||||
|
||||
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
||||
func (t *Terminal) evaluateScrollOffset() int {
|
||||
if t.pwindow == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// We only need the current item to calculate the scroll offset
|
||||
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
||||
t.replacePlaceholder(t.previewOpts.scroll, false, "", list), "")
|
||||
t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil}), "")
|
||||
|
||||
atoi := func(s string) int {
|
||||
n, e := strconv.Atoi(s)
|
||||
@@ -1655,20 +1665,21 @@ func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
||||
}
|
||||
|
||||
base := -1
|
||||
height := util.Max(0, t.pwindow.Height()-t.previewOpts.headerLines)
|
||||
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
||||
if strings.HasPrefix(component, "-/") {
|
||||
component = component[1:]
|
||||
}
|
||||
if component[0] == '/' {
|
||||
denom := atoi(component[1:])
|
||||
if denom == 0 {
|
||||
return base
|
||||
if denom != 0 {
|
||||
base -= height / denom
|
||||
}
|
||||
return base - height/denom
|
||||
break
|
||||
}
|
||||
base += atoi(component)
|
||||
}
|
||||
return base
|
||||
return util.Max(0, base)
|
||||
}
|
||||
|
||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||
@@ -1972,12 +1983,14 @@ func (t *Terminal) Loop() {
|
||||
var items []*Item
|
||||
var commandTemplate string
|
||||
var pwindow tui.Window
|
||||
initialOffset := 0
|
||||
t.previewBox.Wait(func(events *util.Events) {
|
||||
for req, value := range *events {
|
||||
switch req {
|
||||
case reqPreviewEnqueue:
|
||||
request := value.(previewRequest)
|
||||
commandTemplate = request.template
|
||||
initialOffset = request.scrollOffset
|
||||
items = request.list
|
||||
pwindow = request.pwindow
|
||||
}
|
||||
@@ -1989,11 +2002,9 @@ func (t *Terminal) Loop() {
|
||||
if items[0] != nil {
|
||||
_, query := t.Input()
|
||||
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
||||
initialOffset := 0
|
||||
cmd := util.ExecCommand(command, true)
|
||||
if pwindow != nil {
|
||||
height := pwindow.Height()
|
||||
initialOffset = util.Max(0, t.evaluateScrollOffset(items, util.Max(0, height-t.previewOpts.headerLines)))
|
||||
env := os.Environ()
|
||||
lines := fmt.Sprintf("LINES=%d", height)
|
||||
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
||||
@@ -2128,7 +2139,7 @@ func (t *Terminal) Loop() {
|
||||
if len(command) > 0 && t.isPreviewEnabled() {
|
||||
_, list := t.buildPlusList(command, false)
|
||||
t.cancelPreview()
|
||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, list})
|
||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2253,13 +2264,16 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
togglePreview := func(enabled bool) {
|
||||
togglePreview := func(enabled bool) bool {
|
||||
if t.previewer.enabled != enabled {
|
||||
t.previewer.enabled = enabled
|
||||
// We need to immediately update t.pwindow so we don't use reqRedraw
|
||||
t.tui.Clear()
|
||||
t.resizeWindows()
|
||||
req(reqPrompt, reqList, reqInfo, reqHeader)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
toggle := func() bool {
|
||||
current := t.currentItem()
|
||||
@@ -2327,7 +2341,7 @@ func (t *Terminal) Loop() {
|
||||
if valid {
|
||||
t.cancelPreview()
|
||||
t.previewBox.Set(reqPreviewEnqueue,
|
||||
previewRequest{t.previewOpts.command, t.pwindow, list})
|
||||
previewRequest{t.previewOpts.command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2707,6 +2721,39 @@ func (t *Terminal) Loop() {
|
||||
for key := range keys {
|
||||
delete(t.keymap, key)
|
||||
}
|
||||
case actChangePreview:
|
||||
if t.previewOpts.command != a.a {
|
||||
togglePreview(len(a.a) > 0)
|
||||
t.previewOpts.command = a.a
|
||||
refreshPreview(t.previewOpts.command)
|
||||
}
|
||||
case actChangePreviewWindow:
|
||||
currentPreviewOpts := t.previewOpts
|
||||
|
||||
// Reset preview options and apply the additional options
|
||||
t.previewOpts = t.initialPreviewOpts
|
||||
parsePreviewWindow(&t.previewOpts, a.a)
|
||||
|
||||
if t.previewOpts.hidden {
|
||||
togglePreview(false)
|
||||
} else {
|
||||
// Full redraw
|
||||
if !currentPreviewOpts.sameLayout(t.previewOpts) {
|
||||
if togglePreview(true) {
|
||||
refreshPreview(t.previewOpts.command)
|
||||
} else {
|
||||
req(reqRedraw)
|
||||
}
|
||||
} else if !currentPreviewOpts.sameContentLayout(t.previewOpts) {
|
||||
t.previewed.version = 0
|
||||
req(reqPreviewRefresh)
|
||||
}
|
||||
|
||||
// Adjust scroll offset
|
||||
if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.previewOpts.scroll {
|
||||
scrollPreviewTo(t.evaluateScrollOffset())
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
Reference in New Issue
Block a user