package fzf

import (

"bufio"
"fmt"
"io/ioutil"
"os"
"os/signal"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"syscall"
"time"

"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"

"github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util"

)

// import “github.com/pkg/profile”

/*

Placeholder regex is used to extract placeholders from fzf's template
strings. Acts as input validation for parsePlaceholder function.
Describes the syntax, but it is fairly lenient.

The following pseudo regex has been reverse engineered from the
implementation. It is overly strict, but better describes whats possible.
As such it is not useful for validation, but rather to generate test
cases for example.

\\?(?:                                      # escaped type
    {\+?s?f?RANGE(?:,RANGE)*}               # token type
    |{q}                                    # query type
    |{\+?n?f?}                              # item type (notice no mandatory element inside brackets)
)
RANGE = (?:
    (?:-?[0-9]+)?\.\.(?:-?[0-9]+)?          # ellipsis syntax for token range (x..y)
    |-?[0-9]+                               # shorthand syntax (x..x)
)

*/ var placeholder *regexp.Regexp var whiteSuffix *regexp.Regexp var offsetComponentRegex *regexp.Regexp var offsetTrimCharsRegex *regexp.Regexp var activeTempFiles []string

const ellipsis string = “..” const clearCode string = “x1b[2J”

func init() {

placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
whiteSuffix = regexp.MustCompile(`\s*$`)
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
activeTempFiles = []string{}

}

type jumpMode int

const (

jumpDisabled jumpMode = iota
jumpEnabled
jumpAcceptEnabled

)

type previewer struct {

version    int64
lines      []string
offset     int
enabled    bool
scrollable bool
final      bool
following  bool
spinner    string

}

type previewed struct {

version  int64
numLines int
offset   int
filled   bool

}

type eachLine struct {

line string
err  error

}

type itemLine struct {

current  bool
selected bool
label    string
queryLen int
width    int
result   Result

}

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

}

type selectedItem struct {

at   time.Time
item *Item

}

type byTimeOrder []selectedItem

func (a byTimeOrder) Len() int {

return len(a)

}

func (a byTimeOrder) Swap(i, j int) {

a[i], a[j] = a[j], a[i]

}

func (a byTimeOrder) Less(i, j int) bool {

return a[i].at.Before(a[j].at)

}

const (

reqPrompt util.EventType = iota
reqInfo
reqHeader
reqList
reqJump
reqRefresh
reqReinit
reqRedraw
reqClose
reqPrintQuery
reqPreviewEnqueue
reqPreviewDisplay
reqPreviewRefresh
reqPreviewDelayed
reqQuit

)

type action struct {

t actionType
a string

}

type actionType int

const (

actIgnore actionType = iota
actInvalid
actRune
actMouse
actBeginningOfLine
actAbort
actAccept
actAcceptNonEmpty
actBackwardChar
actBackwardDeleteChar
actBackwardDeleteCharEOF
actBackwardWord
actCancel
actChangePrompt
actClearScreen
actClearQuery
actClearSelection
actClose
actDeleteChar
actDeleteCharEOF
actEndOfLine
actForwardChar
actForwardWord
actKillLine
actKillWord
actUnixLineDiscard
actUnixWordRubout
actYank
actBackwardKillWord
actSelectAll
actDeselectAll
actToggle
actToggleSearch
actToggleAll
actToggleDown
actToggleUp
actToggleIn
actToggleOut
actDown
actUp
actPageUp
actPageDown
actHalfPageUp
actHalfPageDown
actJump
actJumpAccept
actPrintQuery
actRefreshPreview
actReplaceQuery
actToggleSort
actTogglePreview
actTogglePreviewWrap
actPreview
actPreviewTop
actPreviewBottom
actPreviewUp
actPreviewDown
actPreviewPageUp
actPreviewPageDown
actPreviewHalfPageUp
actPreviewHalfPageDown
actPreviousHistory
actNextHistory
actExecute
actExecuteSilent
actExecuteMulti // Deprecated
actSigStop
actFirst
actLast
actReload
actDisableSearch
actEnableSearch
actSelect
actDeselect
actUnbind

)

type placeholderFlags struct {

plus          bool
preserveSpace bool
number        bool
query         bool
file          bool

}

type searchRequest struct {

sort    bool
command *string

}

type previewRequest struct {

template string
pwindow  tui.Window
list     []*Item

}

type previewResult struct {

version int64
lines   []string
offset  int
spinner string

}

func toActions(types …actionType) []action {

actions := make([]action, len(types))
for idx, t := range types {
        actions[idx] = action{t: t, a: ""}
}
return actions

}

func defaultKeymap() map[]action {

keymap := make(map[tui.Event][]action)
add := func(e tui.EventType, a actionType) {
        keymap[e.AsEvent()] = toActions(a)
}
addEvent := func(e tui.Event, a actionType) {
        keymap[e] = toActions(a)
}

add(tui.Invalid, actInvalid)
add(tui.Resize, actClearScreen)
add(tui.CtrlA, actBeginningOfLine)
add(tui.CtrlB, actBackwardChar)
add(tui.CtrlC, actAbort)
add(tui.CtrlG, actAbort)
add(tui.CtrlQ, actAbort)
add(tui.ESC, actAbort)
add(tui.CtrlD, actDeleteCharEOF)
add(tui.CtrlE, actEndOfLine)
add(tui.CtrlF, actForwardChar)
add(tui.CtrlH, actBackwardDeleteChar)
add(tui.BSpace, actBackwardDeleteChar)
add(tui.Tab, actToggleDown)
add(tui.BTab, actToggleUp)
add(tui.CtrlJ, actDown)
add(tui.CtrlK, actUp)
add(tui.CtrlL, actClearScreen)
add(tui.CtrlM, actAccept)
add(tui.CtrlN, actDown)
add(tui.CtrlP, actUp)
add(tui.CtrlU, actUnixLineDiscard)
add(tui.CtrlW, actUnixWordRubout)
add(tui.CtrlY, actYank)
if !util.IsWindows() {
        add(tui.CtrlZ, actSigStop)
}

addEvent(tui.AltKey('b'), actBackwardWord)
add(tui.SLeft, actBackwardWord)
addEvent(tui.AltKey('f'), actForwardWord)
add(tui.SRight, actForwardWord)
addEvent(tui.AltKey('d'), actKillWord)
add(tui.AltBS, actBackwardKillWord)

add(tui.Up, actUp)
add(tui.Down, actDown)
add(tui.Left, actBackwardChar)
add(tui.Right, actForwardChar)

add(tui.Home, actBeginningOfLine)
add(tui.End, actEndOfLine)
add(tui.Del, actDeleteChar)
add(tui.PgUp, actPageUp)
add(tui.PgDn, actPageDown)

add(tui.SUp, actPreviewUp)
add(tui.SDown, actPreviewDown)

add(tui.Mouse, actMouse)
add(tui.DoubleClick, actAccept)
add(tui.LeftClick, actIgnore)
add(tui.RightClick, actToggle)
return keymap

}

func trimQuery(query string) []rune {

return []rune(strings.Replace(query, "\t", " ", -1))

}

func hasPreviewAction(opts *Options) bool {

for _, actions := range opts.Keymap {
        for _, action := range actions {
                if action.t == actPreview {
                        return true
                }
        }
}
return false

}

func makeSpinner(unicode bool) []string {

if unicode {
        return []string{`⠋`, `⠙`, `⠹`, `⠸`, `⠼`, `⠴`, `⠦`, `⠧`, `⠇`, `⠏`}
}
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}

}

// NewTerminal returns new Terminal object func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {

input := trimQuery(opts.Query)
var header []string
switch opts.Layout {
case layoutDefault, layoutReverseList:
        header = reverseStringArray(opts.Header)
default:
        header = opts.Header
}
var delay time.Duration
if opts.Tac {
        delay = initialDelayTac
} else {
        delay = initialDelay
}
var previewBox *util.EventBox
showPreviewWindow := len(opts.Preview.command) > 0 && !opts.Preview.hidden
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) {
        previewBox = util.NewEventBox()
}
strongAttr := tui.Bold
if !opts.Bold {
        strongAttr = tui.AttrRegular
}
var renderer tui.Renderer
fullscreen := opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100
if fullscreen {
        if tui.HasFullscreenRenderer() {
                renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
        } else {
                renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
                        true, func(h int) int { return h })
        }
} else {
        maxHeightFunc := func(termHeight int) int {
                var maxHeight int
                if opts.Height.percent {
                        maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
                } else {
                        maxHeight = int(opts.Height.size)
                }

                effectiveMinHeight := minHeight
                if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
                        effectiveMinHeight *= 2
                }
                if opts.InfoStyle != infoDefault {
                        effectiveMinHeight--
                }
                if opts.BorderShape != tui.BorderNone {
                        effectiveMinHeight += 2
                }
                return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
        }
        renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
}
wordRubout := "[^\\pL\\pN][\\pL\\pN]"
wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
if opts.FileWord {
        sep := regexp.QuoteMeta(string(os.PathSeparator))
        wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
        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)}
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)
// Pre-calculated empty pointer and marker signs
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
t.markerEmpty = strings.Repeat(" ", t.markerLen)

return &t

}

func (t *Terminal) parsePrompt(prompt string) (func(), int) {

var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil)
item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors}

// "Prompt>  "
//  -------    // Do not apply ANSI attributes to the trailing whitespaces
//             // unless the part has a non-default ANSI state
loc := whiteSuffix.FindStringIndex(trimmed)
if loc != nil {
        blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear, -1}}
        if item.colors != nil {
                lastColor := (*item.colors)[len(*item.colors)-1]
                if lastColor.offset[1] < int32(loc[1]) {
                        blankState.offset[0] = lastColor.offset[1]
                        colors := append(*item.colors, blankState)
                        item.colors = &colors
                }
        } else {
                colors := []ansiOffset{blankState}
                item.colors = &colors
        }
}
output := func() {
        t.printHighlighted(
                Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false)
}
_, promptLen := t.processTabs([]rune(trimmed), 0)

return output, promptLen

}

func (t *Terminal) noInfoLine() bool {

return t.infoStyle != infoDefault

}

// Input returns current query string func (t *Terminal) Input() (bool, []rune) {

t.mutex.Lock()
defer t.mutex.Unlock()
return t.paused, copySlice(t.input)

}

// UpdateCount updates the count information func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {

t.mutex.Lock()
t.count = cnt
t.reading = !final
t.failed = failedCommand
t.mutex.Unlock()
t.reqBox.Set(reqInfo, nil)
if final {
        t.reqBox.Set(reqRefresh, nil)
}

}

func reverseStringArray(input []string) []string {

size := len(input)
reversed := make([]string, size)
for idx, str := range input {
        reversed[size-idx-1] = str
}
return reversed

}

// UpdateHeader updates the header func (t *Terminal) UpdateHeader(header []string) {

t.mutex.Lock()
t.header = append(append([]string{}, t.header0...), header...)
t.mutex.Unlock()
t.reqBox.Set(reqHeader, nil)

}

// UpdateProgress updates the search progress func (t *Terminal) UpdateProgress(progress float32) {

t.mutex.Lock()
newProgress := int(progress * 100)
changed := t.progress != newProgress
t.progress = newProgress
t.mutex.Unlock()

if changed {
        t.reqBox.Set(reqInfo, nil)
}

}

// UpdateList updates Merger to display the list func (t *Terminal) UpdateList(merger *Merger, reset bool) {

t.mutex.Lock()
t.progress = 100
t.merger = merger
if reset {
        t.selected = make(map[int32]selectedItem)
}
t.mutex.Unlock()
t.reqBox.Set(reqInfo, nil)
t.reqBox.Set(reqList, nil)

}

func (t *Terminal) output() bool {

if t.printQuery {
        t.printer(string(t.input))
}
if len(t.expect) > 0 {
        t.printer(t.pressed)
}
found := len(t.selected) > 0
if !found {
        current := t.currentItem()
        if current != nil {
                t.printer(current.AsString(t.ansi))
                found = true
        }
} else {
        for _, sel := range t.sortSelected() {
                t.printer(sel.item.AsString(t.ansi))
        }
}
return found

}

func (t *Terminal) sortSelected() []selectedItem {

sels := make([]selectedItem, 0, len(t.selected))
for _, sel := range t.selected {
        sels = append(sels, sel)
}
sort.Sort(byTimeOrder(sels))
return sels

}

func (t *Terminal) displayWidth(runes []rune) int {

width, _ := util.RunesWidth(runes, 0, t.tabstop, 0)
return width

}

const (

minWidth  = 4
minHeight = 4

)

func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {

max := base - occupied
if size.percent {
        return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
}
return util.Constrain(int(size.size)+pad, minSize, max)

}

func (t *Terminal) resizeWindows() {

screenWidth := t.tui.MaxX()
screenHeight := t.tui.MaxY()
t.prevLines = make([]itemLine, screenHeight)

marginInt := [4]int{}  // TRBL
paddingInt := [4]int{} // TRBL
sizeSpecToInt := func(index int, spec sizeSpec) int {
        if spec.percent {
                var max float64
                if index%2 == 0 {
                        max = float64(screenHeight)
                } else {
                        max = float64(screenWidth)
                }
                return int(max * spec.size * 0.01)
        }
        return int(spec.size)
}
for idx, sizeSpec := range t.padding {
        paddingInt[idx] = sizeSpecToInt(idx, sizeSpec)
}

extraMargin := [4]int{} // TRBL
for idx, sizeSpec := range t.margin {
        switch t.borderShape {
        case tui.BorderHorizontal:
                extraMargin[idx] += 1 - idx%2
        case tui.BorderVertical:
                extraMargin[idx] += 2 * (idx % 2)
        case tui.BorderTop:
                if idx == 0 {
                        extraMargin[idx]++
                }
        case tui.BorderRight:
                if idx == 1 {
                        extraMargin[idx] += 2
                }
        case tui.BorderBottom:
                if idx == 2 {
                        extraMargin[idx]++
                }
        case tui.BorderLeft:
                if idx == 3 {
                        extraMargin[idx] += 2
                }
        case tui.BorderRounded, tui.BorderSharp:
                extraMargin[idx] += 1 + idx%2
        }
        marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
}

adjust := func(idx1 int, idx2 int, max int, min int) {
        if max >= min {
                margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
                if max-margin < min {
                        desired := max - min
                        paddingInt[idx1] = desired * paddingInt[idx1] / margin
                        paddingInt[idx2] = desired * paddingInt[idx2] / margin
                        marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin)
                        marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
                }
        }
}

previewVisible := t.isPreviewEnabled() && t.previewOpts.size.size > 0
minAreaWidth := minWidth
minAreaHeight := minHeight
if previewVisible {
        switch t.previewOpts.position {
        case posUp, posDown:
                minAreaHeight *= 2
        case posLeft, posRight:
                minAreaWidth *= 2
        }
}
adjust(1, 3, screenWidth, minAreaWidth)
adjust(0, 2, screenHeight, minAreaHeight)
if t.border != nil {
        t.border.Close()
}
if t.window != nil {
        t.window.Close()
}
if t.pborder != nil {
        t.pborder.Close()
}
if t.pwindow != nil {
        t.pwindow.Close()
}
// Reset preview version so that full redraw occurs
t.previewed.version = 0

width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]
switch t.borderShape {
case tui.BorderHorizontal:
        t.border = t.tui.NewWindow(
                marginInt[0]-1, marginInt[3], width, height+2,
                false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
case tui.BorderVertical:
        t.border = t.tui.NewWindow(
                marginInt[0], marginInt[3]-2, width+4, height,
                false, tui.MakeBorderStyle(tui.BorderVertical, t.unicode))
case tui.BorderTop:
        t.border = t.tui.NewWindow(
                marginInt[0]-1, marginInt[3], width, height+1,
                false, tui.MakeBorderStyle(tui.BorderTop, t.unicode))
case tui.BorderBottom:
        t.border = t.tui.NewWindow(
                marginInt[0], marginInt[3], width, height+1,
                false, tui.MakeBorderStyle(tui.BorderBottom, t.unicode))
case tui.BorderLeft:
        t.border = t.tui.NewWindow(
                marginInt[0], marginInt[3]-2, width+2, height,
                false, tui.MakeBorderStyle(tui.BorderLeft, t.unicode))
case tui.BorderRight:
        t.border = t.tui.NewWindow(
                marginInt[0], marginInt[3], width+2, height,
                false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
case tui.BorderRounded, tui.BorderSharp:
        t.border = t.tui.NewWindow(
                marginInt[0]-1, marginInt[3]-2, width+4, height+2,
                false, tui.MakeBorderStyle(t.borderShape, t.unicode))
}

// Add padding
for idx, val := range paddingInt {
        marginInt[idx] += val
}
width = screenWidth - marginInt[1] - marginInt[3]
height = screenHeight - marginInt[0] - marginInt[2]

noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible {
        createPreviewWindow := func(y int, x int, w int, h int) {
                pwidth := w
                pheight := h
                var previewBorder tui.BorderStyle
                if t.previewOpts.border == tui.BorderNone {
                        previewBorder = tui.MakeTransparentBorder()
                } else {
                        previewBorder = tui.MakeBorderStyle(t.previewOpts.border, t.unicode)
                }
                t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
                switch t.previewOpts.border {
                case tui.BorderSharp, tui.BorderRounded:
                        pwidth -= 4
                        pheight -= 2
                        x += 2
                        y += 1
                case tui.BorderLeft:
                        pwidth -= 2
                        x += 2
                case tui.BorderRight:
                        pwidth -= 2
                case tui.BorderTop:
                        pheight -= 1
                        y += 1
                case tui.BorderBottom:
                        pheight -= 1
                case tui.BorderHorizontal:
                        pheight -= 2
                        y += 1
                case tui.BorderVertical:
                        pwidth -= 4
                        x += 2
                }
                t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
        }
        verticalPad := 2
        minPreviewHeight := 3
        switch t.previewOpts.border {
        case tui.BorderNone, tui.BorderVertical, tui.BorderLeft, tui.BorderRight:
                verticalPad = 0
                minPreviewHeight = 1
        case tui.BorderTop, tui.BorderBottom:
                verticalPad = 1
                minPreviewHeight = 2
        }
        switch t.previewOpts.position {
        case posUp:
                pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
                t.window = t.tui.NewWindow(
                        marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
                createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
        case posDown:
                pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
                t.window = t.tui.NewWindow(
                        marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
                createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
        case posLeft:
                pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
                t.window = t.tui.NewWindow(
                        marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
                createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
        case posRight:
                pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
                t.window = t.tui.NewWindow(
                        marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
                createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
        }
} else {
        t.window = t.tui.NewWindow(
                marginInt[0],
                marginInt[3],
                width,
                height, false, noBorder)
}
for i := 0; i < t.window.Height(); i++ {
        t.window.MoveAndClear(i, 0)
}

}

func (t *Terminal) move(y int, x int, clear bool) {

h := t.window.Height()

switch t.layout {
case layoutDefault:
        y = h - y - 1
case layoutReverseList:
        n := 2 + len(t.header)
        if t.noInfoLine() {
                n--
        }
        if y < n {
                y = h - y - 1
        } else {
                y -= n
        }
}

if clear {
        t.window.MoveAndClear(y, x)
} else {
        t.window.Move(y, x)
}

}

func (t *Terminal) truncateQuery() {

t.input, _ = t.trimRight(t.input, maxPatternLength)
t.cx = util.Constrain(t.cx, 0, len(t.input))

}

func (t *Terminal) updatePromptOffset() ([]rune, []rune) {

maxWidth := util.Max(1, t.window.Width()-t.promptLen-1)

_, overflow := t.trimLeft(t.input[:t.cx], maxWidth)
minOffset := int(overflow)
maxOffset := util.Min(util.Min(len(t.input), minOffset+maxWidth), t.cx)

t.xoffset = util.Constrain(t.xoffset, minOffset, maxOffset)
before, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth)
beforeLen := t.displayWidth(before)
after, _ := t.trimRight(t.input[t.cx:], maxWidth-beforeLen)
afterLen := t.displayWidth(after)
t.queryLen = [2]int{beforeLen, afterLen}
return before, after

}

func (t *Terminal) promptLine() int {

if t.headerFirst {
        max := t.window.Height() - 1
        if !t.noInfoLine() {
                max--
        }
        return util.Min(len(t.header0)+t.headerLines, max)
}
return 0

}

func (t *Terminal) placeCursor() {

t.move(t.promptLine(), t.promptLen+t.queryLen[0], false)

}

func (t *Terminal) printPrompt() {

t.move(t.promptLine(), 0, true)
t.prompt()

before, after := t.updatePromptOffset()
color := tui.ColInput
if t.paused {
        color = tui.ColDisabled
}
t.window.CPrint(color, string(before))
t.window.CPrint(color, string(after))

}

func (t *Terminal) trimMessage(message string, maxWidth int) string {

if len(message) <= maxWidth {
        return message
}
runes, _ := t.trimRight([]rune(message), maxWidth-2)
return string(runes) + strings.Repeat(".", util.Constrain(maxWidth, 0, 2))

}

func (t *Terminal) printInfo() {

pos := 0
line := t.promptLine()
switch t.infoStyle {
case infoDefault:
        t.move(line+1, 0, true)
        if t.reading {
                duration := int64(spinnerDuration)
                idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
                t.window.CPrint(tui.ColSpinner, t.spinner[idx])
        }
        t.move(line+1, 2, false)
        pos = 2
case infoInline:
        pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
        if pos+len(" < ") > t.window.Width() {
                return
        }
        t.move(line, pos, true)
        if t.reading {
                t.window.CPrint(tui.ColSpinner, " < ")
        } else {
                t.window.CPrint(tui.ColPrompt, " < ")
        }
        pos += len(" < ")
case infoHidden:
        return
}

found := t.merger.Length()
total := util.Max(found, t.count)
output := fmt.Sprintf("%d/%d", found, total)
if t.toggleSort {
        if t.sort {
                output += " +S"
        } else {
                output += " -S"
        }
}
if t.multi > 0 {
        if t.multi == maxMulti {
                output += fmt.Sprintf(" (%d)", len(t.selected))
        } else {
                output += fmt.Sprintf(" (%d/%d)", len(t.selected), t.multi)
        }
}
if t.progress > 0 && t.progress < 100 {
        output += fmt.Sprintf(" (%d%%)", t.progress)
}
if t.failed != nil && t.count == 0 {
        output = fmt.Sprintf("[Command failed: %s]", *t.failed)
}
output = t.trimMessage(output, t.window.Width()-pos)
t.window.CPrint(tui.ColInfo, output)

}

func (t *Terminal) printHeader() {

if len(t.header) == 0 {
        return
}
max := t.window.Height()
if t.headerFirst {
        max--
        if !t.noInfoLine() {
                max--
        }
}
var state *ansiState
for idx, lineStr := range t.header {
        line := idx
        if !t.headerFirst {
                line++
                if !t.noInfoLine() {
                        line++
                }
        }
        if line >= max {
                continue
        }
        trimmed, colors, newState := extractColor(lineStr, state, nil)
        state = newState
        item := &Item{
                text:   util.ToChars([]byte(trimmed)),
                colors: colors}

        t.move(line, 2, true)
        t.printHighlighted(Result{item: item},
                tui.ColHeader, tui.ColHeader, false, false)
}

}

func (t *Terminal) printList() {

t.constrain()

maxy := t.maxItems()
count := t.merger.Length() - t.offset
for j := 0; j < maxy; j++ {
        i := j
        if t.layout == layoutDefault {
                i = maxy - 1 - j
        }
        line := i + 2 + len(t.header)
        if t.noInfoLine() {
                line--
        }
        if i < count {
                t.printItem(t.merger.Get(i+t.offset), line, i, i == t.cy-t.offset)
        } else if t.prevLines[i] != emptyLine {
                t.prevLines[i] = emptyLine
                t.move(line, 0, true)
        }
}

}

func (t *Terminal) printItem(result Result, line int, i int, current bool) {

item := result.item
_, selected := t.selected[item.Index()]
label := ""
if t.jumping != jumpDisabled {
        if i < len(t.jumpLabels) {
                // Striped
                current = i%2 == 0
                label = t.jumpLabels[i:i+1] + strings.Repeat(" ", t.pointerLen-1)
        }
} else if current {
        label = t.pointer
}

// Avoid unnecessary redraw
newLine := itemLine{current: current, selected: selected, label: label,
        result: result, queryLen: len(t.input), width: 0}
prevLine := t.prevLines[i]
if prevLine.current == newLine.current &&
        prevLine.selected == newLine.selected &&
        prevLine.label == newLine.label &&
        prevLine.queryLen == newLine.queryLen &&
        prevLine.result == newLine.result {
        return
}

t.move(line, 0, false)
if current {
        if len(label) == 0 {
                t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
        } else {
                t.window.CPrint(tui.ColCurrentCursor, label)
        }
        if selected {
                t.window.CPrint(tui.ColCurrentSelected, t.marker)
        } else {
                t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
        }
        newLine.width = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else {
        if len(label) == 0 {
                t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
        } else {
                t.window.CPrint(tui.ColCursor, label)
        }
        if selected {
                t.window.CPrint(tui.ColSelected, t.marker)
        } else {
                t.window.Print(t.markerEmpty)
        }
        newLine.width = t.printHighlighted(result, tui.ColNormal, tui.ColMatch, false, true)
}
fillSpaces := prevLine.width - newLine.width
if fillSpaces > 0 {
        t.window.Print(strings.Repeat(" ", fillSpaces))
}
t.prevLines[i] = newLine

}

func (t *Terminal) trimRight(runes []rune, width int) ([]rune, bool) {

// We start from the beginning to handle tab characters
width, overflowIdx := util.RunesWidth(runes, 0, t.tabstop, width)
if overflowIdx >= 0 {
        return runes[:overflowIdx], true
}
return runes, false

}

func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {

width, _ := util.RunesWidth(runes, prefixWidth, t.tabstop, limit)
return width

}

func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {

width = util.Max(0, width)
var trimmed int32
// Assume that each rune takes at least one column on screen
if len(runes) > width+2 {
        diff := len(runes) - width - 2
        trimmed = int32(diff)
        runes = runes[diff:]
}

currentWidth := t.displayWidth(runes)

for currentWidth > width && len(runes) > 0 {
        runes = runes[1:]
        trimmed++
        currentWidth = t.displayWidthWithLimit(runes, 2, width)
}
return runes, trimmed

}

func (t *Terminal) overflow(runes []rune, max int) bool {

return t.displayWidthWithLimit(runes, 0, max) > max

}

func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool) int {

item := result.item

// Overflow
text := make([]rune, item.text.Length())
copy(text, item.text.ToRunes())
matchOffsets := []Offset{}
var pos *[]int
if match && t.merger.pattern != nil {
        _, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab)
}
charOffsets := matchOffsets
if pos != nil {
        charOffsets = make([]Offset, len(*pos))
        for idx, p := range *pos {
                offset := Offset{int32(p), int32(p + 1)}
                charOffsets[idx] = offset
        }
        sort.Sort(ByOrder(charOffsets))
}
var maxe int
for _, offset := range charOffsets {
        maxe = util.Max(maxe, int(offset[1]))
}

offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
if displayWidth > maxWidth {
        transformOffsets := func(diff int32) {
                for idx, offset := range offsets {
                        b, e := offset.offset[0], offset.offset[1]
                        b += 2 - diff
                        e += 2 - diff
                        b = util.Max32(b, 2)
                        offsets[idx].offset[0] = b
                        offsets[idx].offset[1] = util.Max32(b, e)
                }
        }
        if t.hscroll {
                if t.keepRight && pos == nil {
                        trimmed, diff := t.trimLeft(text, maxWidth-2)
                        transformOffsets(diff)
                        text = append([]rune(ellipsis), trimmed...)
                } else if !t.overflow(text[:maxe], maxWidth-2) {
                        // Stri..
                        text, _ = t.trimRight(text, maxWidth-2)
                        text = append(text, []rune(ellipsis)...)
                } else {
                        // Stri..
                        if t.overflow(text[maxe:], 2) {
                                text = append(text[:maxe], []rune(ellipsis)...)
                        }
                        // ..ri..
                        var diff int32
                        text, diff = t.trimLeft(text, maxWidth-2)

                        // Transform offsets
                        transformOffsets(diff)
                        text = append([]rune(ellipsis), text...)
                }
        } else {
                text, _ = t.trimRight(text, maxWidth-2)
                text = append(text, []rune(ellipsis)...)

                for idx, offset := range offsets {
                        offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
                        offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
                }
        }
        displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
}

var index int32
var substr string
var prefixWidth int
maxOffset := int32(len(text))
for _, offset := range offsets {
        b := util.Constrain32(offset.offset[0], index, maxOffset)
        e := util.Constrain32(offset.offset[1], index, maxOffset)

        substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
        t.window.CPrint(colBase, substr)

        if b < e {
                substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
                t.window.CPrint(offset.color, substr)
        }

        index = e
        if index >= maxOffset {
                break
        }
}
if index < maxOffset {
        substr, _ = t.processTabs(text[index:], prefixWidth)
        t.window.CPrint(colBase, substr)
}
return displayWidth

}

func (t *Terminal) renderPreviewSpinner() {

numLines := len(t.previewer.lines)
spin := t.previewer.spinner
if len(spin) > 0 || t.previewer.scrollable {
        maxWidth := t.pwindow.Width()
        if !t.previewer.scrollable {
                if maxWidth > 0 {
                        t.pwindow.Move(0, maxWidth-1)
                        t.pwindow.CPrint(tui.ColSpinner, spin)
                }
        } else {
                offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
                if len(spin) > 0 {
                        spin += " "
                        maxWidth -= 2
                }
                offsetRunes, _ := t.trimRight([]rune(offsetString), maxWidth)
                pos := maxWidth - t.displayWidth(offsetRunes)
                t.pwindow.Move(0, pos)
                if maxWidth > 0 {
                        t.pwindow.CPrint(tui.ColSpinner, spin)
                        t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
                }
        }
}

}

func (t *Terminal) renderPreviewArea(unchanged bool) {

if unchanged {
        t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
} else {
        t.previewed.filled = false
        t.pwindow.Erase()
}

height := t.pwindow.Height()
header := []string{}
body := t.previewer.lines
headerLines := t.previewOpts.headerLines
// Do not enable preview header lines if it's value is too large
if headerLines > 0 && headerLines < util.Min(len(body), height) {
        header = t.previewer.lines[0:headerLines]
        body = t.previewer.lines[headerLines:]
        // Always redraw header
        t.renderPreviewText(height, header, 0, false)
        t.pwindow.MoveAndClear(t.pwindow.Y(), 0)
}
t.renderPreviewText(height, body, -t.previewer.offset+headerLines, unchanged)

if !unchanged {
        t.pwindow.FinishFill()
}

}

func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {

maxWidth := t.pwindow.Width()
var ansi *ansiState
for _, line := range lines {
        var lbg tui.Color = -1
        if ansi != nil {
                ansi.lbg = -1
        }
        line = strings.TrimSuffix(line, "\n")
        if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
                t.previewed.filled = true
                break
        } else if lineNo >= 0 {
                var fillRet tui.FillReturn
                prefixWidth := 0
                _, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
                        trimmed := []rune(str)
                        isTrimmed := false
                        if !t.previewOpts.wrap {
                                trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
                        }
                        str, width := t.processTabs(trimmed, prefixWidth)
                        prefixWidth += width
                        if t.theme.Colored && ansi != nil && ansi.colored() {
                                lbg = ansi.lbg
                                fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
                        } else {
                                fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
                        }
                        return !isTrimmed &&
                                (fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
                })
                t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
                if fillRet == tui.FillNextLine {
                        continue
                } else if fillRet == tui.FillSuspend {
                        t.previewed.filled = true
                        break
                }
                if unchanged && lineNo == 0 {
                        break
                }
                if lbg >= 0 {
                        t.pwindow.CFill(-1, lbg, tui.AttrRegular,
                                strings.Repeat(" ", t.pwindow.Width()-t.pwindow.X())+"\n")
                } else {
                        t.pwindow.Fill("\n")
                }
        }
        lineNo++
}

}

func (t *Terminal) printPreview() {

if !t.hasPreviewWindow() {
        return
}
numLines := len(t.previewer.lines)
height := t.pwindow.Height()
unchanged := (t.previewed.filled || numLines == t.previewed.numLines) &&
        t.previewer.version == t.previewed.version &&
        t.previewer.offset == t.previewed.offset
t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
t.renderPreviewArea(unchanged)
t.renderPreviewSpinner()
t.previewed.numLines = numLines
t.previewed.version = t.previewer.version
t.previewed.offset = t.previewer.offset

}

func (t *Terminal) printPreviewDelayed() {

if !t.hasPreviewWindow() || len(t.previewer.lines) > 0 && t.previewed.version == t.previewer.version {
        return
}

t.previewer.scrollable = false
t.renderPreviewArea(true)

message := t.trimMessage("Loading ..", t.pwindow.Width())
pos := t.pwindow.Width() - len(message)
t.pwindow.Move(0, pos)
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), message)

}

func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {

var strbuf strings.Builder
l := prefixWidth
gr := uniseg.NewGraphemes(string(runes))
for gr.Next() {
        rs := gr.Runes()
        str := string(rs)
        var w int
        if len(rs) == 1 && rs[0] == '\t' {
                w = t.tabstop - l%t.tabstop
                strbuf.WriteString(strings.Repeat(" ", w))
        } else {
                w = runewidth.StringWidth(str)
                strbuf.WriteString(str)
        }
        l += w
}
return strbuf.String(), l

}

func (t *Terminal) printAll() {

t.resizeWindows()
t.printList()
t.printPrompt()
t.printInfo()
t.printHeader()
t.printPreview()

}

func (t *Terminal) refresh() {

t.placeCursor()
if !t.suppress {
        windows := make([]tui.Window, 0, 4)
        if t.borderShape != tui.BorderNone {
                windows = append(windows, t.border)
        }
        if t.hasPreviewWindow() {
                if t.pborder != nil {
                        windows = append(windows, t.pborder)
                }
                windows = append(windows, t.pwindow)
        }
        windows = append(windows, t.window)
        t.tui.RefreshWindows(windows)
}

}

func (t *Terminal) delChar() bool {

if len(t.input) > 0 && t.cx < len(t.input) {
        t.input = append(t.input[:t.cx], t.input[t.cx+1:]...)
        return true
}
return false

}

func findLastMatch(pattern string, str string) int {

rx, err := regexp.Compile(pattern)
if err != nil {
        return -1
}
locs := rx.FindAllStringIndex(str, -1)
if locs == nil {
        return -1
}
prefix := []rune(str[:locs[len(locs)-1][0]])
return len(prefix)

}

func findFirstMatch(pattern string, str string) int {

rx, err := regexp.Compile(pattern)
if err != nil {
        return -1
}
loc := rx.FindStringIndex(str)
if loc == nil {
        return -1
}
prefix := []rune(str[:loc[0]])
return len(prefix)

}

func copySlice(slice []rune) []rune {

ret := make([]rune, len(slice))
copy(ret, slice)
return ret

}

func (t *Terminal) rubout(pattern string) {

pcx := t.cx
after := t.input[t.cx:]
t.cx = findLastMatch(pattern, string(t.input[:t.cx])) + 1
t.yanked = copySlice(t.input[t.cx:pcx])
t.input = append(t.input[:t.cx], after...)

}

func keyMatch(key tui.Event, event tui.Event) bool {

return event.Type == key.Type && event.Char == key.Char ||
        key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double

}

func parsePlaceholder(match string) (bool, string, placeholderFlags) {

flags := placeholderFlags{}

if match[0] == '\\' {
        // Escaped placeholder pattern
        return true, match[1:], flags
}

skipChars := 1
for _, char := range match[1:] {
        switch char {
        case '+':
                flags.plus = true
                skipChars++
        case 's':
                flags.preserveSpace = true
                skipChars++
        case 'n':
                flags.number = true
                skipChars++
        case 'f':
                flags.file = true
                skipChars++
        case 'q':
                flags.query = true
                // query flag is not skipped
        default:
                break
        }
}

matchWithoutFlags := "{" + match[skipChars:]

return false, matchWithoutFlags, flags

}

func hasPreviewFlags(template string) (slot bool, plus bool, query bool) {

for _, match := range placeholder.FindAllString(template, -1) {
        _, _, flags := parsePlaceholder(match)
        if flags.plus {
                plus = true
        }
        if flags.query {
                query = true
        }
        slot = true
}
return

}

func writeTemporaryFile(data []string, printSep string) string {

f, err := ioutil.TempFile("", "fzf-preview-*")
if err != nil {
        errorExit("Unable to create temporary file")
}
defer f.Close()

f.WriteString(strings.Join(data, printSep))
f.WriteString(printSep)
activeTempFiles = append(activeTempFiles, f.Name())
return f.Name()

}

func cleanTemporaryFiles() {

for _, filename := range activeTempFiles {
        os.Remove(filename)
}
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)

}

func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {

offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
        t.replacePlaceholder(t.previewOpts.scroll, false, "", list), "")

atoi := func(s string) int {
        n, e := strconv.Atoi(s)
        if e != nil {
                return 0
        }
        return n
}

base := -1
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
                }
                return base - height/denom
        }
        base += atoi(component)
}
return base

}

func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {

current := allItems[:1]
selected := allItems[1:]
if current[0] == nil {
        current = []*Item{}
}
if selected[0] == nil {
        selected = []*Item{}
}

// replace placeholders one by one
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
        escaped, match, flags := parsePlaceholder(match)

        // this function implements the effects a placeholder has on items
        var replace func(*Item) string

        // placeholder types (escaped, query type, item type, token type)
        switch {
        case escaped:
                return match
        case match == "{q}":
                return quoteEntry(query)
        case match == "{}":
                replace = func(item *Item) string {
                        switch {
                        case flags.number:
                                n := int(item.text.Index)
                                if n < 0 {
                                        return ""
                                }
                                return strconv.Itoa(n)
                        case flags.file:
                                return item.AsString(stripAnsi)
                        default:
                                return quoteEntry(item.AsString(stripAnsi))
                        }
                }
        default:
                // token type and also failover (below)
                rangeExpressions := strings.Split(match[1:len(match)-1], ",")
                ranges := make([]Range, len(rangeExpressions))
                for idx, s := range rangeExpressions {
                        r, ok := ParseRange(&s) // ellipsis (x..y) and shorthand (x..x) range syntax
                        if !ok {
                                // Invalid expression, just return the original string in the template
                                return match
                        }
                        ranges[idx] = r
                }

                replace = func(item *Item) string {
                        tokens := Tokenize(item.AsString(stripAnsi), delimiter)
                        trans := Transform(tokens, ranges)
                        str := joinTokens(trans)

                        // trim the last delimiter
                        if delimiter.str != nil {
                                str = strings.TrimSuffix(str, *delimiter.str)
                        } else if delimiter.regex != nil {
                                delims := delimiter.regex.FindAllStringIndex(str, -1)
                                // make sure the delimiter is at the very end of the string
                                if len(delims) > 0 && delims[len(delims)-1][1] == len(str) {
                                        str = str[:delims[len(delims)-1][0]]
                                }
                        }

                        if !flags.preserveSpace {
                                str = strings.TrimSpace(str)
                        }
                        if !flags.file {
                                str = quoteEntry(str)
                        }
                        return str
                }
        }

        // apply 'replace' function over proper set of items and return result

        items := current
        if flags.plus || forcePlus {
                items = selected
        }
        replacements := make([]string, len(items))

        for idx, item := range items {
                replacements[idx] = replace(item)
        }

        if flags.file {
                return writeTemporaryFile(replacements, printsep)
        }
        return strings.Join(replacements, " ")
})

}

func (t *Terminal) redraw() {

t.tui.Clear()
t.tui.Refresh()
t.printAll()

}

func (t *Terminal) executeCommand(template string, forcePlus bool, background bool) {

valid, list := t.buildPlusList(template, forcePlus)
if !valid {
        return
}
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
cmd := util.ExecCommand(command, false)
t.executing.Set(true)
if !background {
        cmd.Stdin = tui.TtyIn()
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        t.tui.Pause(true)
        cmd.Run()
        t.tui.Resume(true, false)
        t.redraw()
        t.refresh()
} else {
        t.tui.Pause(false)
        cmd.Run()
        t.tui.Resume(false, false)
}
t.executing.Set(false)
cleanTemporaryFiles()

}

func (t *Terminal) hasPreviewer() bool {

return t.previewBox != nil

}

func (t *Terminal) isPreviewEnabled() bool {

return t.hasPreviewer() && t.previewer.enabled

}

func (t *Terminal) hasPreviewWindow() bool {

return t.pwindow != nil && t.isPreviewEnabled()

}

func (t *Terminal) currentItem() *Item {

cnt := t.merger.Length()
if t.cy >= 0 && cnt > 0 && cnt > t.cy {
        return t.merger.Get(t.cy).item
}
return nil

}

func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {

current := t.currentItem()
slot, plus, query := hasPreviewFlags(template)
if !(!slot || query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
        return current != nil, []*Item{current, current}
}

// We would still want to update preview window even if there is no match if
//   1. command template contains {q} and the query string is not empty
//   2. or it contains {+} and we have more than one item already selected.
// To do so, we pass an empty Item instead of nil to trigger an update.
if current == nil {
        current = &minItem
}

var sels []*Item
if len(t.selected) == 0 {
        sels = []*Item{current, current}
} else {
        sels = make([]*Item, len(t.selected)+1)
        sels[0] = current
        for i, sel := range t.sortSelected() {
                sels[i+1] = sel.item
        }
}
return true, sels

}

func (t *Terminal) selectItem(item *Item) bool {

if len(t.selected) >= t.multi {
        return false
}
if _, found := t.selected[item.Index()]; found {
        return true
}

t.selected[item.Index()] = selectedItem{time.Now(), item}
t.version++

return true

}

func (t *Terminal) selectItemChanged(item *Item) bool {

if _, found := t.selected[item.Index()]; found {
        return false
}
return t.selectItem(item)

}

func (t *Terminal) deselectItem(item *Item) {

delete(t.selected, item.Index())
t.version++

}

func (t *Terminal) deselectItemChanged(item *Item) bool {

if _, found := t.selected[item.Index()]; found {
        t.deselectItem(item)
        return true
}
return false

}

func (t *Terminal) toggleItem(item *Item) bool {

if _, found := t.selected[item.Index()]; !found {
        return t.selectItem(item)
}
t.deselectItem(item)
return true

}

func (t *Terminal) killPreview(code int) {

select {
case t.killChan <- code:
default:
        if code != exitCancel {
                t.eventBox.Set(EvtQuit, code)
        }
}

}

func (t *Terminal) cancelPreview() {

t.killPreview(exitCancel)

}

// Loop is called to start Terminal I/O func (t *Terminal) Loop() {

// prof := profile.Start(profile.ProfilePath("/tmp/"))
<-t.startChan
{ // Late initialization
        intChan := make(chan os.Signal, 1)
        signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
        go func() {
                for s := range intChan {
                        // Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
                        if !(s == os.Interrupt && t.executing.Get()) {
                                t.reqBox.Set(reqQuit, nil)
                        }
                }
        }()

        contChan := make(chan os.Signal, 1)
        notifyOnCont(contChan)
        go func() {
                for {
                        <-contChan
                        t.reqBox.Set(reqReinit, nil)
                }
        }()

        resizeChan := make(chan os.Signal, 1)
        notifyOnResize(resizeChan) // Non-portable
        go func() {
                for {
                        <-resizeChan
                        t.reqBox.Set(reqRedraw, nil)
                }
        }()

        t.mutex.Lock()
        t.initFunc()
        t.resizeWindows()
        t.printPrompt()
        t.printInfo()
        t.printHeader()
        t.refresh()
        t.mutex.Unlock()
        go func() {
                timer := time.NewTimer(t.initDelay)
                <-timer.C
                t.reqBox.Set(reqRefresh, nil)
        }()

        // Keep the spinner spinning
        go func() {
                for {
                        t.mutex.Lock()
                        reading := t.reading
                        t.mutex.Unlock()
                        time.Sleep(spinnerDuration)
                        if reading {
                                t.reqBox.Set(reqInfo, nil)
                        }
                }
        }()
}

if t.hasPreviewer() {
        go func() {
                var version int64
                for {
                        var items []*Item
                        var commandTemplate string
                        var pwindow tui.Window
                        t.previewBox.Wait(func(events *util.Events) {
                                for req, value := range *events {
                                        switch req {
                                        case reqPreviewEnqueue:
                                                request := value.(previewRequest)
                                                commandTemplate = request.template
                                                items = request.list
                                                pwindow = request.pwindow
                                        }
                                }
                                events.Clear()
                        })
                        version++
                        // We don't display preview window if no match
                        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())
                                        env = append(env, lines)
                                        env = append(env, "FZF_PREVIEW_"+lines)
                                        env = append(env, columns)
                                        env = append(env, "FZF_PREVIEW_"+columns)
                                        cmd.Env = env
                                }

                                out, _ := cmd.StdoutPipe()
                                cmd.Stderr = cmd.Stdout
                                reader := bufio.NewReader(out)
                                eofChan := make(chan bool)
                                finishChan := make(chan bool, 1)
                                err := cmd.Start()
                                if err == nil {
                                        reapChan := make(chan bool)
                                        lineChan := make(chan eachLine)
                                        // Goroutine 1 reads process output
                                        go func() {
                                                for {
                                                        line, err := reader.ReadString('\n')
                                                        lineChan <- eachLine{line, err}
                                                        if err != nil {
                                                                break
                                                        }
                                                }
                                                eofChan <- true
                                        }()

                                        // Goroutine 2 periodically requests rendering
                                        rendered := util.NewAtomicBool(false)
                                        go func(version int64) {
                                                lines := []string{}
                                                spinner := makeSpinner(t.unicode)
                                                spinnerIndex := -1 // Delay initial rendering by an extra tick
                                                ticker := time.NewTicker(previewChunkDelay)
                                                offset := initialOffset
                                        Loop:
                                                for {
                                                        select {
                                                        case <-ticker.C:
                                                                if len(lines) > 0 && len(lines) >= initialOffset {
                                                                        if spinnerIndex >= 0 {
                                                                                spin := spinner[spinnerIndex%len(spinner)]
                                                                                t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, spin})
                                                                                rendered.Set(true)
                                                                                offset = -1
                                                                        }
                                                                        spinnerIndex++
                                                                }
                                                        case eachLine := <-lineChan:
                                                                line := eachLine.line
                                                                err := eachLine.err
                                                                if len(line) > 0 {
                                                                        clearIndex := strings.Index(line, clearCode)
                                                                        if clearIndex >= 0 {
                                                                                lines = []string{}
                                                                                line = line[clearIndex+len(clearCode):]
                                                                                version--
                                                                                offset = 0
                                                                        }
                                                                        lines = append(lines, line)
                                                                }
                                                                if err != nil {
                                                                        t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, ""})
                                                                        rendered.Set(true)
                                                                        break Loop
                                                                }
                                                        }
                                                }
                                                ticker.Stop()
                                                reapChan <- true
                                        }(version)

                                        // Goroutine 3 is responsible for cancelling running preview command
                                        go func(version int64) {
                                                timer := time.NewTimer(previewDelayed)
                                        Loop:
                                                for {
                                                        select {
                                                        case <-timer.C:
                                                                t.reqBox.Set(reqPreviewDelayed, version)
                                                        case code := <-t.killChan:
                                                                if code != exitCancel {
                                                                        util.KillCommand(cmd)
                                                                        t.eventBox.Set(EvtQuit, code)
                                                                } else {
                                                                        // We can immediately kill a long-running preview program
                                                                        // once we started rendering its partial output
                                                                        delay := previewCancelWait
                                                                        if rendered.Get() {
                                                                                delay = 0
                                                                        }
                                                                        timer := time.NewTimer(delay)
                                                                        select {
                                                                        case <-timer.C:
                                                                                util.KillCommand(cmd)
                                                                        case <-finishChan:
                                                                        }
                                                                        timer.Stop()
                                                                }
                                                                break Loop
                                                        case <-finishChan:
                                                                break Loop
                                                        }
                                                }
                                                timer.Stop()
                                                reapChan <- true
                                        }(version)

                                        <-eofChan          // Goroutine 1 finished
                                        cmd.Wait()         // NOTE: We should not call Wait before EOF
                                        finishChan <- true // Tell Goroutine 3 to stop
                                        <-reapChan         // Goroutine 2 and 3 finished
                                        <-reapChan
                                } else {
                                        // Failed to start the command. Report the error immediately.
                                        t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""})
                                }

                                cleanTemporaryFiles()
                        } else {
                                t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""})
                        }
                }
        }()
}

refreshPreview := func(command string) {
        if len(command) > 0 && t.isPreviewEnabled() {
                _, list := t.buildPlusList(command, false)
                t.cancelPreview()
                t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, list})
        }
}

go func() {
        var focusedIndex int32 = minItem.Index()
        var version int64 = -1
        running := true
        code := exitError
        exit := func(getCode func() int) {
                t.tui.Close()
                code = getCode()
                if code <= exitNoMatch && t.history != nil {
                        t.history.append(string(t.input))
                }
                running = false
                t.mutex.Unlock()
        }

        for running {
                t.reqBox.Wait(func(events *util.Events) {
                        defer events.Clear()
                        t.mutex.Lock()
                        for req, value := range *events {
                                switch req {
                                case reqPrompt:
                                        t.printPrompt()
                                        if t.noInfoLine() {
                                                t.printInfo()
                                        }
                                case reqInfo:
                                        t.printInfo()
                                case reqList:
                                        t.printList()
                                        var currentIndex int32 = minItem.Index()
                                        currentItem := t.currentItem()
                                        if currentItem != nil {
                                                currentIndex = currentItem.Index()
                                        }
                                        if focusedIndex != currentIndex || version != t.version {
                                                version = t.version
                                                focusedIndex = currentIndex
                                                refreshPreview(t.previewOpts.command)
                                        }
                                case reqJump:
                                        if t.merger.Length() == 0 {
                                                t.jumping = jumpDisabled
                                        }
                                        t.printList()
                                case reqHeader:
                                        t.printHeader()
                                case reqRefresh:
                                        t.suppress = false
                                case reqReinit:
                                        t.tui.Resume(t.fullscreen, t.sigstop)
                                        t.redraw()
                                case reqRedraw:
                                        t.redraw()
                                case reqClose:
                                        exit(func() int {
                                                if t.output() {
                                                        return exitOk
                                                }
                                                return exitNoMatch
                                        })
                                        return
                                case reqPreviewDisplay:
                                        result := value.(previewResult)
                                        if t.previewer.version != result.version {
                                                t.previewer.version = result.version
                                                t.previewer.following = t.previewOpts.follow
                                        }
                                        t.previewer.lines = result.lines
                                        t.previewer.spinner = result.spinner
                                        if t.previewer.following {
                                                t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height()
                                        } else if result.offset >= 0 {
                                                t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1)
                                        }
                                        t.printPreview()
                                case reqPreviewRefresh:
                                        t.printPreview()
                                case reqPreviewDelayed:
                                        t.previewer.version = value.(int64)
                                        t.printPreviewDelayed()
                                case reqPrintQuery:
                                        exit(func() int {
                                                t.printer(string(t.input))
                                                return exitOk
                                        })
                                        return
                                case reqQuit:
                                        exit(func() int { return exitInterrupt })
                                        return
                                }
                        }
                        t.refresh()
                        t.mutex.Unlock()
                })
        }
        // prof.Stop()
        t.killPreview(code)
}()

looping := true
for looping {
        var newCommand *string
        changed := false
        beof := false
        queryChanged := false

        event := t.tui.GetChar()

        t.mutex.Lock()
        previousInput := t.input
        previousCx := t.cx
        events := []util.EventType{}
        req := func(evts ...util.EventType) {
                for _, event := range evts {
                        events = append(events, event)
                        if event == reqClose || event == reqQuit {
                                looping = false
                        }
                }
        }
        togglePreview := func(enabled bool) {
                if t.previewer.enabled != enabled {
                        t.previewer.enabled = enabled
                        t.tui.Clear()
                        t.resizeWindows()
                        req(reqPrompt, reqList, reqInfo, reqHeader)
                }
        }
        toggle := func() bool {
                current := t.currentItem()
                if current != nil && t.toggleItem(current) {
                        req(reqInfo)
                        return true
                }
                return false
        }
        scrollPreviewTo := func(newOffset int) {
                if !t.previewer.scrollable {
                        return
                }
                t.previewer.following = false
                numLines := len(t.previewer.lines)
                if t.previewOpts.cycle {
                        newOffset = (newOffset + numLines) % numLines
                }
                newOffset = util.Constrain(newOffset, t.previewOpts.headerLines, numLines-1)
                if t.previewer.offset != newOffset {
                        t.previewer.offset = newOffset
                        req(reqPreviewRefresh)
                }
        }
        scrollPreviewBy := func(amount int) {
                scrollPreviewTo(t.previewer.offset + amount)
        }
        for key, ret := range t.expect {
                if keyMatch(key, event) {
                        t.pressed = ret
                        t.reqBox.Set(reqClose, nil)
                        t.mutex.Unlock()
                        return
                }
        }

        actionsFor := func(eventType tui.EventType) []action {
                return t.keymap[eventType.AsEvent()]
        }

        var doAction func(action) bool
        doActions := func(actions []action) bool {
                for _, action := range actions {
                        if !doAction(action) {
                                return false
                        }
                }
                return true
        }
        doAction = func(a action) bool {
                switch a.t {
                case actIgnore:
                case actExecute, actExecuteSilent:
                        t.executeCommand(a.a, false, a.t == actExecuteSilent)
                case actExecuteMulti:
                        t.executeCommand(a.a, true, false)
                case actInvalid:
                        t.mutex.Unlock()
                        return false
                case actTogglePreview:
                        if t.hasPreviewer() {
                                togglePreview(!t.previewer.enabled)
                                if t.previewer.enabled {
                                        valid, list := t.buildPlusList(t.previewOpts.command, false)
                                        if valid {
                                                t.cancelPreview()
                                                t.previewBox.Set(reqPreviewEnqueue,
                                                        previewRequest{t.previewOpts.command, t.pwindow, list})
                                        }
                                }
                        }
                case actTogglePreviewWrap:
                        if t.hasPreviewWindow() {
                                t.previewOpts.wrap = !t.previewOpts.wrap
                                // Reset preview version so that full redraw occurs
                                t.previewed.version = 0
                                req(reqPreviewRefresh)
                        }
                case actToggleSort:
                        t.sort = !t.sort
                        changed = true
                case actPreviewTop:
                        if t.hasPreviewWindow() {
                                scrollPreviewTo(0)
                        }
                case actPreviewBottom:
                        if t.hasPreviewWindow() {
                                scrollPreviewTo(len(t.previewer.lines) - t.pwindow.Height())
                        }
                case actPreviewUp:
                        if t.hasPreviewWindow() {
                                scrollPreviewBy(-1)
                        }
                case actPreviewDown:
                        if t.hasPreviewWindow() {
                                scrollPreviewBy(1)
                        }
                case actPreviewPageUp:
                        if t.hasPreviewWindow() {
                                scrollPreviewBy(-t.pwindow.Height())
                        }
                case actPreviewPageDown:
                        if t.hasPreviewWindow() {
                                scrollPreviewBy(t.pwindow.Height())
                        }
                case actPreviewHalfPageUp:
                        if t.hasPreviewWindow() {
                                scrollPreviewBy(-t.pwindow.Height() / 2)
                        }
                case actPreviewHalfPageDown:
                        if t.hasPreviewWindow() {
                                scrollPreviewBy(t.pwindow.Height() / 2)
                        }
                case actBeginningOfLine:
                        t.cx = 0
                case actBackwardChar:
                        if t.cx > 0 {
                                t.cx--
                        }
                case actPrintQuery:
                        req(reqPrintQuery)
                case actChangePrompt:
                        t.prompt, t.promptLen = t.parsePrompt(a.a)
                        req(reqPrompt)
                case actPreview:
                        togglePreview(true)
                        refreshPreview(a.a)
                case actRefreshPreview:
                        refreshPreview(t.previewOpts.command)
                case actReplaceQuery:
                        current := t.currentItem()
                        if current != nil {
                                t.input = current.text.ToRunes()
                                t.cx = len(t.input)
                        }
                case actAbort:
                        req(reqQuit)
                case actDeleteChar:
                        t.delChar()
                case actDeleteCharEOF:
                        if !t.delChar() && t.cx == 0 {
                                req(reqQuit)
                        }
                case actEndOfLine:
                        t.cx = len(t.input)
                case actCancel:
                        if len(t.input) == 0 {
                                req(reqQuit)
                        } else {
                                t.yanked = t.input
                                t.input = []rune{}
                                t.cx = 0
                        }
                case actBackwardDeleteCharEOF:
                        if len(t.input) == 0 {
                                req(reqQuit)
                        } else if t.cx > 0 {
                                t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
                                t.cx--
                        }
                case actForwardChar:
                        if t.cx < len(t.input) {
                                t.cx++
                        }
                case actBackwardDeleteChar:
                        beof = len(t.input) == 0
                        if t.cx > 0 {
                                t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
                                t.cx--
                        }
                case actSelectAll:
                        if t.multi > 0 {
                                for i := 0; i < t.merger.Length(); i++ {
                                        if !t.selectItem(t.merger.Get(i).item) {
                                                break
                                        }
                                }
                                req(reqList, reqInfo)
                        }
                case actDeselectAll:
                        if t.multi > 0 {
                                for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
                                        t.deselectItem(t.merger.Get(i).item)
                                }
                                req(reqList, reqInfo)
                        }
                case actClose:
                        if t.isPreviewEnabled() {
                                togglePreview(false)
                        } else {
                                req(reqQuit)
                        }
                case actSelect:
                        current := t.currentItem()
                        if t.multi > 0 && current != nil && t.selectItemChanged(current) {
                                req(reqList, reqInfo)
                        }
                case actDeselect:
                        current := t.currentItem()
                        if t.multi > 0 && current != nil && t.deselectItemChanged(current) {
                                req(reqList, reqInfo)
                        }
                case actToggle:
                        if t.multi > 0 && t.merger.Length() > 0 && toggle() {
                                req(reqList)
                        }
                case actToggleAll:
                        if t.multi > 0 {
                                prevIndexes := make(map[int]struct{})
                                for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
                                        item := t.merger.Get(i).item
                                        if _, found := t.selected[item.Index()]; found {
                                                prevIndexes[i] = struct{}{}
                                                t.deselectItem(item)
                                        }
                                }

                                for i := 0; i < t.merger.Length(); i++ {
                                        if _, found := prevIndexes[i]; !found {
                                                item := t.merger.Get(i).item
                                                if !t.selectItem(item) {
                                                        break
                                                }
                                        }
                                }
                                req(reqList, reqInfo)
                        }
                case actToggleIn:
                        if t.layout != layoutDefault {
                                return doAction(action{t: actToggleUp})
                        }
                        return doAction(action{t: actToggleDown})
                case actToggleOut:
                        if t.layout != layoutDefault {
                                return doAction(action{t: actToggleDown})
                        }
                        return doAction(action{t: actToggleUp})
                case actToggleDown:
                        if t.multi > 0 && t.merger.Length() > 0 && toggle() {
                                t.vmove(-1, true)
                                req(reqList)
                        }
                case actToggleUp:
                        if t.multi > 0 && t.merger.Length() > 0 && toggle() {
                                t.vmove(1, true)
                                req(reqList)
                        }
                case actDown:
                        t.vmove(-1, true)
                        req(reqList)
                case actUp:
                        t.vmove(1, true)
                        req(reqList)
                case actAccept:
                        req(reqClose)
                case actAcceptNonEmpty:
                        if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
                                req(reqClose)
                        }
                case actClearScreen:
                        req(reqRedraw)
                case actClearQuery:
                        t.input = []rune{}
                        t.cx = 0
                case actClearSelection:
                        if t.multi > 0 {
                                t.selected = make(map[int32]selectedItem)
                                t.version++
                                req(reqList, reqInfo)
                        }
                case actFirst:
                        t.vset(0)
                        req(reqList)
                case actLast:
                        t.vset(t.merger.Length() - 1)
                        req(reqList)
                case actUnixLineDiscard:
                        beof = len(t.input) == 0
                        if t.cx > 0 {
                                t.yanked = copySlice(t.input[:t.cx])
                                t.input = t.input[t.cx:]
                                t.cx = 0
                        }
                case actUnixWordRubout:
                        beof = len(t.input) == 0
                        if t.cx > 0 {
                                t.rubout("\\s\\S")
                        }
                case actBackwardKillWord:
                        beof = len(t.input) == 0
                        if t.cx > 0 {
                                t.rubout(t.wordRubout)
                        }
                case actYank:
                        suffix := copySlice(t.input[t.cx:])
                        t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
                        t.cx += len(t.yanked)
                case actPageUp:
                        t.vmove(t.maxItems()-1, false)
                        req(reqList)
                case actPageDown:
                        t.vmove(-(t.maxItems() - 1), false)
                        req(reqList)
                case actHalfPageUp:
                        t.vmove(t.maxItems()/2, false)
                        req(reqList)
                case actHalfPageDown:
                        t.vmove(-(t.maxItems() / 2), false)
                        req(reqList)
                case actJump:
                        t.jumping = jumpEnabled
                        req(reqJump)
                case actJumpAccept:
                        t.jumping = jumpAcceptEnabled
                        req(reqJump)
                case actBackwardWord:
                        t.cx = findLastMatch(t.wordRubout, string(t.input[:t.cx])) + 1
                case actForwardWord:
                        t.cx += findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
                case actKillWord:
                        ncx := t.cx +
                                findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
                        if ncx > t.cx {
                                t.yanked = copySlice(t.input[t.cx:ncx])
                                t.input = append(t.input[:t.cx], t.input[ncx:]...)
                        }
                case actKillLine:
                        if t.cx < len(t.input) {
                                t.yanked = copySlice(t.input[t.cx:])
                                t.input = t.input[:t.cx]
                        }
                case actRune:
                        prefix := copySlice(t.input[:t.cx])
                        t.input = append(append(prefix, event.Char), t.input[t.cx:]...)
                        t.cx++
                case actPreviousHistory:
                        if t.history != nil {
                                t.history.override(string(t.input))
                                t.input = trimQuery(t.history.previous())
                                t.cx = len(t.input)
                        }
                case actNextHistory:
                        if t.history != nil {
                                t.history.override(string(t.input))
                                t.input = trimQuery(t.history.next())
                                t.cx = len(t.input)
                        }
                case actToggleSearch:
                        t.paused = !t.paused
                        changed = !t.paused
                        req(reqPrompt)
                case actEnableSearch:
                        t.paused = false
                        changed = true
                        req(reqPrompt)
                case actDisableSearch:
                        t.paused = true
                        req(reqPrompt)
                case actSigStop:
                        p, err := os.FindProcess(os.Getpid())
                        if err == nil {
                                t.sigstop = true
                                t.tui.Clear()
                                t.tui.Pause(t.fullscreen)
                                notifyStop(p)
                                t.mutex.Unlock()
                                return false
                        }
                case actMouse:
                        me := event.MouseEvent
                        mx, my := me.X, me.Y
                        if me.S != 0 {
                                // Scroll
                                if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
                                        if t.multi > 0 && me.Mod {
                                                toggle()
                                        }
                                        t.vmove(me.S, true)
                                        req(reqList)
                                } else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
                                        scrollPreviewBy(-me.S)
                                }
                        } else if t.window.Enclose(my, mx) {
                                mx -= t.window.Left()
                                my -= t.window.Top()
                                mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
                                min := 2 + len(t.header)
                                if t.noInfoLine() {
                                        min--
                                }
                                h := t.window.Height()
                                switch t.layout {
                                case layoutDefault:
                                        my = h - my - 1
                                case layoutReverseList:
                                        if my < h-min {
                                                my += min
                                        } else {
                                                my = h - my - 1
                                        }
                                }
                                if me.Double {
                                        // Double-click
                                        if my >= min {
                                                if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
                                                        return doActions(actionsFor(tui.DoubleClick))
                                                }
                                        }
                                } else if me.Down {
                                        if my == t.promptLine() && mx >= 0 {
                                                // Prompt
                                                t.cx = mx + t.xoffset
                                        } else if my >= min {
                                                // List
                                                if t.vset(t.offset+my-min) && t.multi > 0 && me.Mod {
                                                        toggle()
                                                }
                                                req(reqList)
                                                if me.Left {
                                                        return doActions(actionsFor(tui.LeftClick))
                                                }
                                                return doActions(actionsFor(tui.RightClick))
                                        }
                                }
                        }
                case actReload:
                        t.failed = nil

                        valid, list := t.buildPlusList(a.a, false)
                        if !valid {
                                // We run the command even when there's no match
                                // 1. If the template doesn't have any slots
                                // 2. If the template has {q}
                                slot, _, query := hasPreviewFlags(a.a)
                                valid = !slot || query
                        }
                        if valid {
                                command := t.replacePlaceholder(a.a, false, string(t.input), list)
                                newCommand = &command
                                t.reading = true
                                t.version++
                        }
                case actUnbind:
                        keys := parseKeyChords(a.a, "PANIC")
                        for key := range keys {
                                delete(t.keymap, key)
                        }
                }
                return true
        }

        if t.jumping == jumpDisabled {
                actions := t.keymap[event.Comparable()]
                if len(actions) == 0 && event.Type == tui.Rune {
                        doAction(action{t: actRune})
                } else if !doActions(actions) {
                        continue
                }
                t.truncateQuery()
                queryChanged = string(previousInput) != string(t.input)
                changed = changed || queryChanged
                if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
                        if !doActions(onChanges) {
                                continue
                        }
                }
                if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
                        if !doActions(onEOFs) {
                                continue
                        }
                }
        } else {
                if event.Type == tui.Rune {
                        if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
                                t.cy = idx + t.offset
                                if t.jumping == jumpAcceptEnabled {
                                        req(reqClose)
                                }
                        }
                }
                t.jumping = jumpDisabled
                req(reqList)
        }

        if queryChanged {
                if t.isPreviewEnabled() {
                        _, _, q := hasPreviewFlags(t.previewOpts.command)
                        if q {
                                t.version++
                        }
                }
        }

        if queryChanged || t.cx != previousCx {
                req(reqPrompt)
        }

        t.mutex.Unlock() // Must be unlocked before touching reqBox

        if changed || newCommand != nil {
                t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, command: newCommand})
        }
        for _, event := range events {
                t.reqBox.Set(event, nil)
        }
}

}

func (t *Terminal) constrain() {

// count of items to display allowed by filtering
count := t.merger.Length()
// count of lines can be displayed
height := t.maxItems()

t.cy = util.Constrain(t.cy, 0, count-1)

minOffset := util.Max(t.cy-height+1, 0)
maxOffset := util.Max(util.Min(count-height, t.cy), 0)
t.offset = util.Constrain(t.offset, minOffset, maxOffset)
if t.scrollOff == 0 {
        return
}

scrollOff := util.Min(height/2, t.scrollOff)
for {
        prevOffset := t.offset
        if t.cy-t.offset < scrollOff {
                t.offset = util.Max(minOffset, t.offset-1)
        }
        if t.cy-t.offset >= height-scrollOff {
                t.offset = util.Min(maxOffset, t.offset+1)
        }
        if t.offset == prevOffset {
                break
        }
}

}

func (t *Terminal) vmove(o int, allowCycle bool) {

if t.layout != layoutDefault {
        o *= -1
}
dest := t.cy + o
if t.cycle && allowCycle {
        max := t.merger.Length() - 1
        if dest > max {
                if t.cy == max {
                        dest = 0
                }
        } else if dest < 0 {
                if t.cy == 0 {
                        dest = max
                }
        }
}
t.vset(dest)

}

func (t *Terminal) vset(o int) bool {

t.cy = util.Constrain(o, 0, t.merger.Length()-1)
return t.cy == o

}

func (t *Terminal) maxItems() int {

max := t.window.Height() - 2 - len(t.header)
if t.noInfoLine() {
        max++
}
return util.Max(max, 0)

}