2015-01-01 19:49:30 +00:00
|
|
|
package fzf
|
|
|
|
|
|
|
|
import (
|
2017-01-11 13:13:40 +00:00
|
|
|
"bufio"
|
2015-01-18 07:59:04 +00:00
|
|
|
"bytes"
|
2015-01-01 19:49:30 +00:00
|
|
|
"fmt"
|
2019-10-27 14:50:12 +00:00
|
|
|
"io/ioutil"
|
2015-01-01 19:49:30 +00:00
|
|
|
"os"
|
2015-01-23 11:30:50 +00:00
|
|
|
"os/signal"
|
2015-01-01 19:49:30 +00:00
|
|
|
"regexp"
|
|
|
|
"sort"
|
2016-11-06 17:15:34 +00:00
|
|
|
"strconv"
|
2015-01-18 07:59:04 +00:00
|
|
|
"strings"
|
2015-01-01 19:49:30 +00:00
|
|
|
"sync"
|
2015-01-23 11:30:50 +00:00
|
|
|
"syscall"
|
2015-01-01 19:49:30 +00:00
|
|
|
"time"
|
2015-01-12 03:56:17 +00:00
|
|
|
|
2016-10-24 00:44:56 +00:00
|
|
|
"github.com/junegunn/fzf/src/tui"
|
2015-01-12 03:56:17 +00:00
|
|
|
"github.com/junegunn/fzf/src/util"
|
2015-01-01 19:49:30 +00:00
|
|
|
)
|
|
|
|
|
2016-09-07 00:58:18 +00:00
|
|
|
// import "github.com/pkg/profile"
|
|
|
|
|
2016-10-03 05:16:10 +00:00
|
|
|
var placeholder *regexp.Regexp
|
2020-08-02 01:02:11 +00:00
|
|
|
var numericPrefix *regexp.Regexp
|
2020-10-25 10:29:37 +00:00
|
|
|
var whiteSuffix *regexp.Regexp
|
2019-10-27 14:50:12 +00:00
|
|
|
var activeTempFiles []string
|
2016-10-03 05:16:10 +00:00
|
|
|
|
2020-03-11 13:35:24 +00:00
|
|
|
const ellipsis string = ".."
|
2020-10-23 12:37:20 +00:00
|
|
|
const clearCode string = "\x1b[2J"
|
2020-03-11 13:35:24 +00:00
|
|
|
|
2016-10-03 05:16:10 +00:00
|
|
|
func init() {
|
Improvements to code quality and readability (#1737)
* Remove 1 unused field and 3 unused functions
unused elements fount by running
golangci-lint run --disable-all --enable unused
src/result.go:19:2: field `index` is unused (unused)
index int32
^
src/tui/light.go:716:23: func `(*LightWindow).stderr` is unused (unused)
func (w *LightWindow) stderr(str string) {
^
src/terminal.go:1015:6: func `numLinesMax` is unused (unused)
func numLinesMax(str string, max int) int {
^
src/tui/tui.go:167:20: func `ColorPair.is24` is unused (unused)
func (p ColorPair) is24() bool {
^
* Address warnings from "gosimple" linter
src/options.go:389:83: S1003: should use strings.Contains(str, ",,,") instead (gosimple)
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Index(str, ",,,") >= 0 {
^
src/options.go:630:18: S1007: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (gosimple)
executeRegexp = regexp.MustCompile(
^
src/terminal.go:29:16: S1007: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (gosimple)
placeholder = regexp.MustCompile("\\\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\\+?f?nf?})")
^
src/terminal_test.go:92:10: S1007: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (gosimple)
regex = regexp.MustCompile("\\w+")
^
* Address warnings from "staticcheck" linter
src/algo/algo.go:374:2: SA4006: this value of `offset32` is never used (staticcheck)
offset32, T := alloc32(offset32, slab, N)
^
src/algo/algo.go:456:2: SA4006: this value of `offset16` is never used (staticcheck)
offset16, C := alloc16(offset16, slab, width*M)
^
src/tui/tui.go:119:2: SA9004: only the first constant in this group has an explicit type (staticcheck)
colUndefined Color = -2
^
2019-11-05 00:46:51 +00:00
|
|
|
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
2020-08-02 01:02:11 +00:00
|
|
|
numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`)
|
2020-10-25 10:29:37 +00:00
|
|
|
whiteSuffix = regexp.MustCompile(`\s*$`)
|
2019-10-27 14:50:12 +00:00
|
|
|
activeTempFiles = []string{}
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-17 17:06:52 +00:00
|
|
|
type jumpMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
jumpDisabled jumpMode = iota
|
|
|
|
jumpEnabled
|
|
|
|
jumpAcceptEnabled
|
|
|
|
)
|
|
|
|
|
2016-09-24 17:02:00 +00:00
|
|
|
type previewer struct {
|
2020-10-24 07:55:55 +00:00
|
|
|
version int64
|
2020-10-18 08:03:33 +00:00
|
|
|
lines []string
|
|
|
|
offset int
|
|
|
|
enabled bool
|
|
|
|
scrollable bool
|
|
|
|
final bool
|
|
|
|
spinner string
|
|
|
|
}
|
|
|
|
|
|
|
|
type previewed struct {
|
2020-10-24 07:55:55 +00:00
|
|
|
version int64
|
2020-10-18 08:03:33 +00:00
|
|
|
numLines int
|
|
|
|
offset int
|
|
|
|
filled bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type eachLine struct {
|
|
|
|
line string
|
|
|
|
err error
|
2016-09-24 17:02:00 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
type itemLine struct {
|
2017-01-07 17:29:31 +00:00
|
|
|
current bool
|
|
|
|
selected bool
|
|
|
|
label string
|
2017-02-23 17:30:11 +00:00
|
|
|
queryLen int
|
2017-01-15 17:26:36 +00:00
|
|
|
width int
|
2017-01-07 17:29:31 +00:00
|
|
|
result Result
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var emptyLine = itemLine{}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// Terminal represents terminal input/output
|
2015-01-01 19:49:30 +00:00
|
|
|
type Terminal struct {
|
2020-02-17 01:19:03 +00:00
|
|
|
initDelay time.Duration
|
|
|
|
infoStyle infoStyle
|
|
|
|
spinner []string
|
2020-07-05 07:16:46 +00:00
|
|
|
prompt func()
|
2020-02-17 01:19:03 +00:00
|
|
|
promptLen int
|
|
|
|
pointer string
|
|
|
|
pointerLen int
|
|
|
|
pointerEmpty string
|
|
|
|
marker string
|
|
|
|
markerLen int
|
|
|
|
markerEmpty string
|
|
|
|
queryLen [2]int
|
|
|
|
layout layoutType
|
|
|
|
fullscreen bool
|
2020-03-11 13:35:24 +00:00
|
|
|
keepRight bool
|
2020-02-17 01:19:03 +00:00
|
|
|
hscroll bool
|
|
|
|
hscrollOff 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[int]string
|
|
|
|
keymap map[int][]action
|
|
|
|
pressed string
|
|
|
|
printQuery bool
|
|
|
|
history *History
|
|
|
|
cycle bool
|
|
|
|
header []string
|
|
|
|
header0 []string
|
|
|
|
ansi bool
|
|
|
|
tabstop int
|
|
|
|
margin [4]sizeSpec
|
2020-11-09 11:34:08 +00:00
|
|
|
padding [4]sizeSpec
|
2020-02-17 01:19:03 +00:00
|
|
|
strong tui.Attr
|
|
|
|
unicode bool
|
2020-03-05 11:15:15 +00:00
|
|
|
borderShape tui.BorderShape
|
2020-02-17 01:19:03 +00:00
|
|
|
cleanExit bool
|
|
|
|
border tui.Window
|
|
|
|
window tui.Window
|
|
|
|
pborder tui.Window
|
|
|
|
pwindow tui.Window
|
|
|
|
count int
|
|
|
|
progress int
|
|
|
|
reading bool
|
|
|
|
failed *string
|
|
|
|
jumping jumpMode
|
|
|
|
jumpLabels string
|
|
|
|
printer func(string)
|
|
|
|
printsep string
|
|
|
|
merger *Merger
|
|
|
|
selected map[int32]selectedItem
|
|
|
|
version int64
|
|
|
|
reqBox *util.EventBox
|
|
|
|
preview previewOpts
|
|
|
|
previewer previewer
|
2020-10-18 08:03:33 +00:00
|
|
|
previewed previewed
|
2020-02-17 01:19:03 +00:00
|
|
|
previewBox *util.EventBox
|
|
|
|
eventBox *util.EventBox
|
|
|
|
mutex sync.Mutex
|
|
|
|
initFunc func()
|
|
|
|
prevLines []itemLine
|
|
|
|
suppress bool
|
|
|
|
startChan chan bool
|
|
|
|
killChan chan int
|
|
|
|
slab *util.Slab
|
|
|
|
theme *tui.ColorTheme
|
|
|
|
tui tui.Renderer
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-17 02:07:04 +00:00
|
|
|
type selectedItem struct {
|
|
|
|
at time.Time
|
2016-10-03 05:16:10 +00:00
|
|
|
item *Item
|
2015-01-17 02:07:04 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
type byTimeOrder []selectedItem
|
2015-01-17 02:07:04 +00:00
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
func (a byTimeOrder) Len() int {
|
2015-01-17 02:07:04 +00:00
|
|
|
return len(a)
|
|
|
|
}
|
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
func (a byTimeOrder) Swap(i, j int) {
|
2015-01-17 02:07:04 +00:00
|
|
|
a[i], a[j] = a[j], a[i]
|
|
|
|
}
|
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
func (a byTimeOrder) Less(i, j int) bool {
|
2015-01-17 02:07:04 +00:00
|
|
|
return a[i].at.Before(a[j].at)
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
const (
|
2015-01-12 03:56:17 +00:00
|
|
|
reqPrompt util.EventType = iota
|
2015-01-11 18:01:24 +00:00
|
|
|
reqInfo
|
2015-07-21 18:21:20 +00:00
|
|
|
reqHeader
|
2015-01-11 18:01:24 +00:00
|
|
|
reqList
|
2016-05-17 17:06:52 +00:00
|
|
|
reqJump
|
2015-01-11 18:01:24 +00:00
|
|
|
reqRefresh
|
2017-04-28 13:58:08 +00:00
|
|
|
reqReinit
|
2015-01-11 18:01:24 +00:00
|
|
|
reqRedraw
|
|
|
|
reqClose
|
2016-05-12 15:51:15 +00:00
|
|
|
reqPrintQuery
|
2016-06-11 10:59:12 +00:00
|
|
|
reqPreviewEnqueue
|
|
|
|
reqPreviewDisplay
|
2016-09-24 17:02:00 +00:00
|
|
|
reqPreviewRefresh
|
2020-10-18 08:03:33 +00:00
|
|
|
reqPreviewDelayed
|
2015-01-11 18:01:24 +00:00
|
|
|
reqQuit
|
2015-01-01 19:49:30 +00:00
|
|
|
)
|
|
|
|
|
2017-01-21 17:32:49 +00:00
|
|
|
type action struct {
|
|
|
|
t actionType
|
|
|
|
a string
|
|
|
|
}
|
|
|
|
|
2015-05-20 12:25:15 +00:00
|
|
|
type actionType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
actIgnore actionType = iota
|
|
|
|
actInvalid
|
|
|
|
actRune
|
|
|
|
actMouse
|
|
|
|
actBeginningOfLine
|
|
|
|
actAbort
|
|
|
|
actAccept
|
2017-12-01 17:27:02 +00:00
|
|
|
actAcceptNonEmpty
|
2015-05-20 12:25:15 +00:00
|
|
|
actBackwardChar
|
|
|
|
actBackwardDeleteChar
|
2020-02-27 17:38:32 +00:00
|
|
|
actBackwardDeleteCharEOF
|
2015-05-20 12:25:15 +00:00
|
|
|
actBackwardWord
|
2015-07-23 12:05:33 +00:00
|
|
|
actCancel
|
2015-05-20 12:25:15 +00:00
|
|
|
actClearScreen
|
2019-12-07 05:44:24 +00:00
|
|
|
actClearQuery
|
|
|
|
actClearSelection
|
2015-05-20 12:25:15 +00:00
|
|
|
actDeleteChar
|
2015-08-02 04:06:15 +00:00
|
|
|
actDeleteCharEOF
|
2015-05-20 12:25:15 +00:00
|
|
|
actEndOfLine
|
|
|
|
actForwardChar
|
|
|
|
actForwardWord
|
|
|
|
actKillLine
|
|
|
|
actKillWord
|
|
|
|
actUnixLineDiscard
|
|
|
|
actUnixWordRubout
|
|
|
|
actYank
|
|
|
|
actBackwardKillWord
|
2015-06-09 14:44:54 +00:00
|
|
|
actSelectAll
|
|
|
|
actDeselectAll
|
2015-05-20 16:35:35 +00:00
|
|
|
actToggle
|
2015-06-09 14:44:54 +00:00
|
|
|
actToggleAll
|
2015-05-20 12:25:15 +00:00
|
|
|
actToggleDown
|
|
|
|
actToggleUp
|
2016-01-13 17:35:43 +00:00
|
|
|
actToggleIn
|
|
|
|
actToggleOut
|
2015-05-20 12:25:15 +00:00
|
|
|
actDown
|
|
|
|
actUp
|
|
|
|
actPageUp
|
|
|
|
actPageDown
|
2017-01-16 02:58:13 +00:00
|
|
|
actHalfPageUp
|
|
|
|
actHalfPageDown
|
2016-05-17 17:06:52 +00:00
|
|
|
actJump
|
|
|
|
actJumpAccept
|
2016-05-12 15:51:15 +00:00
|
|
|
actPrintQuery
|
2020-06-20 13:04:09 +00:00
|
|
|
actRefreshPreview
|
2017-12-01 04:08:55 +00:00
|
|
|
actReplaceQuery
|
2015-05-20 12:25:15 +00:00
|
|
|
actToggleSort
|
2016-06-11 10:59:12 +00:00
|
|
|
actTogglePreview
|
2017-02-18 14:49:00 +00:00
|
|
|
actTogglePreviewWrap
|
2020-06-21 13:41:33 +00:00
|
|
|
actPreview
|
2016-09-24 17:02:00 +00:00
|
|
|
actPreviewUp
|
|
|
|
actPreviewDown
|
2016-09-24 19:12:44 +00:00
|
|
|
actPreviewPageUp
|
|
|
|
actPreviewPageDown
|
2020-10-05 12:58:56 +00:00
|
|
|
actPreviewHalfPageUp
|
|
|
|
actPreviewHalfPageDown
|
2015-06-13 15:43:44 +00:00
|
|
|
actPreviousHistory
|
|
|
|
actNextHistory
|
2015-06-14 03:25:08 +00:00
|
|
|
actExecute
|
2017-01-27 08:46:56 +00:00
|
|
|
actExecuteSilent
|
|
|
|
actExecuteMulti // Deprecated
|
2017-04-28 13:58:08 +00:00
|
|
|
actSigStop
|
2017-05-22 08:07:05 +00:00
|
|
|
actTop
|
2019-11-10 02:36:22 +00:00
|
|
|
actReload
|
2015-05-20 12:25:15 +00:00
|
|
|
)
|
|
|
|
|
2018-03-30 02:47:46 +00:00
|
|
|
type placeholderFlags struct {
|
|
|
|
plus bool
|
|
|
|
preserveSpace bool
|
2019-02-18 16:12:57 +00:00
|
|
|
number bool
|
2018-06-09 16:40:22 +00:00
|
|
|
query bool
|
2019-10-27 14:50:12 +00:00
|
|
|
file bool
|
2018-03-30 02:47:46 +00:00
|
|
|
}
|
|
|
|
|
2019-11-10 02:36:22 +00:00
|
|
|
type searchRequest struct {
|
|
|
|
sort bool
|
|
|
|
command *string
|
|
|
|
}
|
|
|
|
|
2020-06-21 13:41:33 +00:00
|
|
|
type previewRequest struct {
|
|
|
|
template string
|
2020-10-18 08:03:33 +00:00
|
|
|
pwindow tui.Window
|
2020-06-21 13:41:33 +00:00
|
|
|
list []*Item
|
|
|
|
}
|
|
|
|
|
2020-07-26 15:15:25 +00:00
|
|
|
type previewResult struct {
|
2020-10-24 07:55:55 +00:00
|
|
|
version int64
|
2020-10-18 08:03:33 +00:00
|
|
|
lines []string
|
2020-07-26 15:15:25 +00:00
|
|
|
offset int
|
2020-10-18 08:03:33 +00:00
|
|
|
spinner string
|
2020-07-26 15:15:25 +00:00
|
|
|
}
|
|
|
|
|
2017-01-21 17:32:49 +00:00
|
|
|
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[int][]action {
|
|
|
|
keymap := make(map[int][]action)
|
|
|
|
keymap[tui.Invalid] = toActions(actInvalid)
|
|
|
|
keymap[tui.Resize] = toActions(actClearScreen)
|
|
|
|
keymap[tui.CtrlA] = toActions(actBeginningOfLine)
|
|
|
|
keymap[tui.CtrlB] = toActions(actBackwardChar)
|
|
|
|
keymap[tui.CtrlC] = toActions(actAbort)
|
|
|
|
keymap[tui.CtrlG] = toActions(actAbort)
|
|
|
|
keymap[tui.CtrlQ] = toActions(actAbort)
|
|
|
|
keymap[tui.ESC] = toActions(actAbort)
|
|
|
|
keymap[tui.CtrlD] = toActions(actDeleteCharEOF)
|
|
|
|
keymap[tui.CtrlE] = toActions(actEndOfLine)
|
|
|
|
keymap[tui.CtrlF] = toActions(actForwardChar)
|
|
|
|
keymap[tui.CtrlH] = toActions(actBackwardDeleteChar)
|
|
|
|
keymap[tui.BSpace] = toActions(actBackwardDeleteChar)
|
|
|
|
keymap[tui.Tab] = toActions(actToggleDown)
|
|
|
|
keymap[tui.BTab] = toActions(actToggleUp)
|
|
|
|
keymap[tui.CtrlJ] = toActions(actDown)
|
|
|
|
keymap[tui.CtrlK] = toActions(actUp)
|
|
|
|
keymap[tui.CtrlL] = toActions(actClearScreen)
|
|
|
|
keymap[tui.CtrlM] = toActions(actAccept)
|
|
|
|
keymap[tui.CtrlN] = toActions(actDown)
|
|
|
|
keymap[tui.CtrlP] = toActions(actUp)
|
|
|
|
keymap[tui.CtrlU] = toActions(actUnixLineDiscard)
|
|
|
|
keymap[tui.CtrlW] = toActions(actUnixWordRubout)
|
|
|
|
keymap[tui.CtrlY] = toActions(actYank)
|
2017-04-28 13:58:08 +00:00
|
|
|
if !util.IsWindows() {
|
|
|
|
keymap[tui.CtrlZ] = toActions(actSigStop)
|
|
|
|
}
|
2017-01-21 17:32:49 +00:00
|
|
|
|
|
|
|
keymap[tui.AltB] = toActions(actBackwardWord)
|
|
|
|
keymap[tui.SLeft] = toActions(actBackwardWord)
|
|
|
|
keymap[tui.AltF] = toActions(actForwardWord)
|
|
|
|
keymap[tui.SRight] = toActions(actForwardWord)
|
|
|
|
keymap[tui.AltD] = toActions(actKillWord)
|
|
|
|
keymap[tui.AltBS] = toActions(actBackwardKillWord)
|
|
|
|
|
|
|
|
keymap[tui.Up] = toActions(actUp)
|
|
|
|
keymap[tui.Down] = toActions(actDown)
|
|
|
|
keymap[tui.Left] = toActions(actBackwardChar)
|
|
|
|
keymap[tui.Right] = toActions(actForwardChar)
|
|
|
|
|
|
|
|
keymap[tui.Home] = toActions(actBeginningOfLine)
|
|
|
|
keymap[tui.End] = toActions(actEndOfLine)
|
|
|
|
keymap[tui.Del] = toActions(actDeleteChar)
|
|
|
|
keymap[tui.PgUp] = toActions(actPageUp)
|
|
|
|
keymap[tui.PgDn] = toActions(actPageDown)
|
|
|
|
|
2018-02-15 10:56:11 +00:00
|
|
|
keymap[tui.SUp] = toActions(actPreviewUp)
|
|
|
|
keymap[tui.SDown] = toActions(actPreviewDown)
|
|
|
|
|
2017-01-21 17:32:49 +00:00
|
|
|
keymap[tui.Rune] = toActions(actRune)
|
|
|
|
keymap[tui.Mouse] = toActions(actMouse)
|
|
|
|
keymap[tui.DoubleClick] = toActions(actAccept)
|
2017-11-30 17:11:20 +00:00
|
|
|
keymap[tui.LeftClick] = toActions(actIgnore)
|
|
|
|
keymap[tui.RightClick] = toActions(actToggle)
|
2015-05-20 12:25:15 +00:00
|
|
|
return keymap
|
|
|
|
}
|
|
|
|
|
2017-08-09 14:25:32 +00:00
|
|
|
func trimQuery(query string) []rune {
|
|
|
|
return []rune(strings.Replace(query, "\t", " ", -1))
|
|
|
|
}
|
|
|
|
|
2020-06-21 13:41:33 +00:00
|
|
|
func hasPreviewAction(opts *Options) bool {
|
|
|
|
for _, actions := range opts.Keymap {
|
|
|
|
for _, action := range actions {
|
|
|
|
if action.t == actPreview {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
func makeSpinner(unicode bool) []string {
|
|
|
|
if unicode {
|
|
|
|
return []string{`⠋`, `⠙`, `⠹`, `⠸`, `⠼`, `⠴`, `⠦`, `⠧`, `⠇`, `⠏`}
|
|
|
|
}
|
|
|
|
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// NewTerminal returns new Terminal object
|
2015-01-12 03:56:17 +00:00
|
|
|
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
2017-08-09 14:25:32 +00:00
|
|
|
input := trimQuery(opts.Query)
|
2015-09-15 10:04:53 +00:00
|
|
|
var header []string
|
2018-06-09 16:41:50 +00:00
|
|
|
switch opts.Layout {
|
|
|
|
case layoutDefault, layoutReverseList:
|
2015-09-15 10:04:53 +00:00
|
|
|
header = reverseStringArray(opts.Header)
|
2018-06-09 16:41:50 +00:00
|
|
|
default:
|
|
|
|
header = opts.Header
|
2015-09-15 10:04:53 +00:00
|
|
|
}
|
2016-01-16 09:07:50 +00:00
|
|
|
var delay time.Duration
|
|
|
|
if opts.Tac {
|
|
|
|
delay = initialDelayTac
|
|
|
|
} else {
|
|
|
|
delay = initialDelay
|
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
var previewBox *util.EventBox
|
2020-06-21 13:41:33 +00:00
|
|
|
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) {
|
2016-06-11 10:59:12 +00:00
|
|
|
previewBox = util.NewEventBox()
|
|
|
|
}
|
2016-11-15 14:57:32 +00:00
|
|
|
strongAttr := tui.Bold
|
|
|
|
if !opts.Bold {
|
|
|
|
strongAttr = tui.AttrRegular
|
|
|
|
}
|
2017-01-07 16:30:31 +00:00
|
|
|
var renderer tui.Renderer
|
2017-04-28 13:58:08 +00:00
|
|
|
fullscreen := opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100
|
|
|
|
if fullscreen {
|
2020-10-30 17:53:10 +00:00
|
|
|
if tui.HasFullscreenRenderer() {
|
|
|
|
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
|
|
|
} else {
|
2017-03-04 05:09:36 +00:00
|
|
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
|
|
|
true, func(h int) int { return h })
|
|
|
|
}
|
|
|
|
} else {
|
2017-01-07 16:30:31 +00:00
|
|
|
maxHeightFunc := func(termHeight int) int {
|
|
|
|
var maxHeight int
|
|
|
|
if opts.Height.percent {
|
2017-02-04 12:51:22 +00:00
|
|
|
maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
|
2017-01-07 16:30:31 +00:00
|
|
|
} else {
|
2017-02-04 12:51:22 +00:00
|
|
|
maxHeight = int(opts.Height.size)
|
|
|
|
}
|
|
|
|
|
|
|
|
effectiveMinHeight := minHeight
|
|
|
|
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
|
|
|
|
effectiveMinHeight *= 2
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2019-11-14 15:39:29 +00:00
|
|
|
if opts.InfoStyle != infoDefault {
|
2020-03-05 11:15:15 +00:00
|
|
|
effectiveMinHeight--
|
2017-02-04 12:51:22 +00:00
|
|
|
}
|
2020-03-05 11:15:15 +00:00
|
|
|
if opts.BorderShape != tui.BorderNone {
|
2017-02-04 12:51:22 +00:00
|
|
|
effectiveMinHeight += 2
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2017-02-04 12:51:22 +00:00
|
|
|
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2017-03-04 05:09:36 +00:00
|
|
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2020-07-28 03:58:37 +00:00
|
|
|
wordRubout := "[^\\pL\\pN][\\pL\\pN]"
|
|
|
|
wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
|
2017-01-15 10:42:28 +00:00
|
|
|
if opts.FileWord {
|
|
|
|
sep := regexp.QuoteMeta(string(os.PathSeparator))
|
|
|
|
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
|
|
|
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
|
|
|
}
|
2017-05-26 10:02:49 +00:00
|
|
|
t := Terminal{
|
2020-03-05 11:15:15 +00:00
|
|
|
initDelay: delay,
|
|
|
|
infoStyle: opts.InfoStyle,
|
2020-10-18 08:03:33 +00:00
|
|
|
spinner: makeSpinner(opts.Unicode),
|
2020-03-05 11:15:15 +00:00
|
|
|
queryLen: [2]int{0, 0},
|
|
|
|
layout: opts.Layout,
|
|
|
|
fullscreen: fullscreen,
|
2020-03-11 13:35:24 +00:00
|
|
|
keepRight: opts.KeepRight,
|
2020-03-05 11:15:15 +00:00
|
|
|
hscroll: opts.Hscroll,
|
|
|
|
hscrollOff: opts.HscrollOff,
|
|
|
|
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,
|
2020-11-09 11:34:08 +00:00
|
|
|
padding: opts.Padding,
|
2020-03-05 11:15:15 +00:00
|
|
|
unicode: opts.Unicode,
|
|
|
|
borderShape: opts.BorderShape,
|
|
|
|
cleanExit: opts.ClearOnExit,
|
|
|
|
strong: strongAttr,
|
|
|
|
cycle: opts.Cycle,
|
|
|
|
header: header,
|
|
|
|
header0: header,
|
|
|
|
ansi: opts.Ansi,
|
|
|
|
tabstop: opts.Tabstop,
|
|
|
|
reading: true,
|
|
|
|
failed: nil,
|
|
|
|
jumping: jumpDisabled,
|
|
|
|
jumpLabels: opts.JumpLabels,
|
|
|
|
printer: opts.Printer,
|
|
|
|
printsep: opts.PrintSep,
|
|
|
|
merger: EmptyMerger,
|
|
|
|
selected: make(map[int32]selectedItem),
|
|
|
|
reqBox: util.NewEventBox(),
|
|
|
|
preview: opts.Preview,
|
2020-10-18 08:03:33 +00:00
|
|
|
previewer: previewer{0, []string{}, 0, previewBox != nil && !opts.Preview.hidden, false, true, ""},
|
|
|
|
previewed: previewed{0, 0, 0, false},
|
2020-03-05 11:15:15 +00:00
|
|
|
previewBox: previewBox,
|
|
|
|
eventBox: eventBox,
|
|
|
|
mutex: sync.Mutex{},
|
|
|
|
suppress: true,
|
|
|
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
|
|
|
theme: opts.Theme,
|
|
|
|
startChan: make(chan bool, 1),
|
|
|
|
killChan: make(chan int),
|
|
|
|
tui: renderer,
|
|
|
|
initFunc: func() { renderer.Init() }}
|
2020-07-05 07:16:46 +00:00
|
|
|
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
2020-02-17 01:19:03 +00:00
|
|
|
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)
|
|
|
|
|
2017-05-26 10:02:49 +00:00
|
|
|
return &t
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2020-07-05 07:16:46 +00:00
|
|
|
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}
|
2020-10-25 10:29:37 +00:00
|
|
|
|
|
|
|
// "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}}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2020-07-05 07:16:46 +00:00
|
|
|
output := func() {
|
|
|
|
t.printHighlighted(
|
2020-10-25 10:29:37 +00:00
|
|
|
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false)
|
2020-07-05 07:16:46 +00:00
|
|
|
}
|
|
|
|
_, promptLen := t.processTabs([]rune(trimmed), 0)
|
|
|
|
|
|
|
|
return output, promptLen
|
|
|
|
}
|
|
|
|
|
2019-11-14 15:39:29 +00:00
|
|
|
func (t *Terminal) noInfoLine() bool {
|
|
|
|
return t.infoStyle != infoDefault
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// Input returns current query string
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) Input() []rune {
|
|
|
|
t.mutex.Lock()
|
|
|
|
defer t.mutex.Unlock()
|
|
|
|
return copySlice(t.input)
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// UpdateCount updates the count information
|
2019-11-10 02:36:22 +00:00
|
|
|
func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Lock()
|
|
|
|
t.count = cnt
|
|
|
|
t.reading = !final
|
2019-11-10 02:36:22 +00:00
|
|
|
t.failed = failedCommand
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
2015-01-11 18:01:24 +00:00
|
|
|
t.reqBox.Set(reqInfo, nil)
|
2015-01-07 03:46:45 +00:00
|
|
|
if final {
|
2015-01-11 18:01:24 +00:00
|
|
|
t.reqBox.Set(reqRefresh, nil)
|
2015-01-07 03:46:45 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-09-15 10:04:53 +00:00
|
|
|
func reverseStringArray(input []string) []string {
|
|
|
|
size := len(input)
|
|
|
|
reversed := make([]string, size)
|
|
|
|
for idx, str := range input {
|
|
|
|
reversed[size-idx-1] = str
|
|
|
|
}
|
|
|
|
return reversed
|
|
|
|
}
|
|
|
|
|
2015-07-21 18:21:20 +00:00
|
|
|
// UpdateHeader updates the header
|
2015-09-15 10:04:53 +00:00
|
|
|
func (t *Terminal) UpdateHeader(header []string) {
|
2015-07-21 18:21:20 +00:00
|
|
|
t.mutex.Lock()
|
2015-09-15 10:04:53 +00:00
|
|
|
t.header = append(append([]string{}, t.header0...), header...)
|
2015-07-21 18:21:20 +00:00
|
|
|
t.mutex.Unlock()
|
|
|
|
t.reqBox.Set(reqHeader, nil)
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// UpdateProgress updates the search progress
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) UpdateProgress(progress float32) {
|
|
|
|
t.mutex.Lock()
|
2015-01-11 12:56:55 +00:00
|
|
|
newProgress := int(progress * 100)
|
|
|
|
changed := t.progress != newProgress
|
|
|
|
t.progress = newProgress
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
2015-01-11 12:56:55 +00:00
|
|
|
|
|
|
|
if changed {
|
2015-01-11 18:01:24 +00:00
|
|
|
t.reqBox.Set(reqInfo, nil)
|
2015-01-11 12:56:55 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// UpdateList updates Merger to display the list
|
2019-12-09 12:32:58 +00:00
|
|
|
func (t *Terminal) UpdateList(merger *Merger, reset bool) {
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Lock()
|
|
|
|
t.progress = 100
|
2015-01-09 16:06:08 +00:00
|
|
|
t.merger = merger
|
2019-12-09 12:32:58 +00:00
|
|
|
if reset {
|
|
|
|
t.selected = make(map[int32]selectedItem)
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
2015-01-11 18:01:24 +00:00
|
|
|
t.reqBox.Set(reqInfo, nil)
|
|
|
|
t.reqBox.Set(reqList, nil)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-09-15 04:21:51 +00:00
|
|
|
func (t *Terminal) output() bool {
|
2015-01-01 19:49:30 +00:00
|
|
|
if t.printQuery {
|
2016-09-17 19:52:47 +00:00
|
|
|
t.printer(string(t.input))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-03-28 17:59:32 +00:00
|
|
|
if len(t.expect) > 0 {
|
2016-09-17 19:52:47 +00:00
|
|
|
t.printer(t.pressed)
|
2015-03-28 17:59:32 +00:00
|
|
|
}
|
2015-09-15 04:21:51 +00:00
|
|
|
found := len(t.selected) > 0
|
|
|
|
if !found {
|
2017-01-27 07:38:42 +00:00
|
|
|
current := t.currentItem()
|
|
|
|
if current != nil {
|
|
|
|
t.printer(current.AsString(t.ansi))
|
2015-09-15 04:21:51 +00:00
|
|
|
found = true
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
} else {
|
2015-11-08 16:42:01 +00:00
|
|
|
for _, sel := range t.sortSelected() {
|
2016-10-03 05:16:10 +00:00
|
|
|
t.printer(sel.item.AsString(t.ansi))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-15 04:21:51 +00:00
|
|
|
return found
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-11-08 16:42:01 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) displayWidth(runes []rune) int {
|
2015-01-01 19:49:30 +00:00
|
|
|
l := 0
|
|
|
|
for _, r := range runes {
|
2017-01-07 16:30:31 +00:00
|
|
|
l += util.RuneWidth(r, l, t.tabstop)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
2016-06-11 10:59:12 +00:00
|
|
|
const (
|
2020-11-03 12:49:21 +00:00
|
|
|
minWidth = 4
|
2016-06-11 10:59:12 +00:00
|
|
|
minHeight = 4
|
2016-09-20 16:23:41 +00:00
|
|
|
|
|
|
|
maxDisplayWidthCalc = 1024
|
2016-06-11 10:59:12 +00:00
|
|
|
)
|
2015-07-26 14:02:04 +00:00
|
|
|
|
2020-10-06 10:37:33 +00:00
|
|
|
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
|
|
|
|
max := base - occupied
|
2016-06-11 10:59:12 +00:00
|
|
|
if size.percent {
|
|
|
|
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
|
|
|
|
}
|
2020-10-06 10:37:33 +00:00
|
|
|
return util.Constrain(int(size.size)+pad, minSize, max)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) resizeWindows() {
|
2017-01-07 16:30:31 +00:00
|
|
|
screenWidth := t.tui.MaxX()
|
|
|
|
screenHeight := t.tui.MaxY()
|
|
|
|
t.prevLines = make([]itemLine, screenHeight)
|
2020-11-09 11:34:08 +00:00
|
|
|
|
|
|
|
marginInt := [4]int{} // TRBL
|
|
|
|
paddingInt := [4]int{} // TRBL
|
|
|
|
sizeSpecToInt := func(index int, spec sizeSpec) int {
|
|
|
|
if spec.percent {
|
2016-06-11 10:59:12 +00:00
|
|
|
var max float64
|
2020-11-09 11:34:08 +00:00
|
|
|
if index%2 == 0 {
|
2016-06-11 10:59:12 +00:00
|
|
|
max = float64(screenHeight)
|
2015-07-26 14:02:04 +00:00
|
|
|
} else {
|
2016-06-11 10:59:12 +00:00
|
|
|
max = float64(screenWidth)
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
return int(max * spec.size * 0.01)
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
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 {
|
2020-03-05 11:15:15 +00:00
|
|
|
switch t.borderShape {
|
|
|
|
case tui.BorderHorizontal:
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx] += 1 - idx%2
|
2020-10-26 13:33:41 +00:00
|
|
|
case tui.BorderVertical:
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx] += 2 * (idx % 2)
|
2020-10-26 13:33:41 +00:00
|
|
|
case tui.BorderTop:
|
|
|
|
if idx == 0 {
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx]++
|
2020-10-26 13:33:41 +00:00
|
|
|
}
|
|
|
|
case tui.BorderRight:
|
|
|
|
if idx == 1 {
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx] += 2
|
2020-10-26 13:33:41 +00:00
|
|
|
}
|
|
|
|
case tui.BorderBottom:
|
|
|
|
if idx == 2 {
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx]++
|
2020-10-26 13:33:41 +00:00
|
|
|
}
|
|
|
|
case tui.BorderLeft:
|
|
|
|
if idx == 3 {
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx] += 2
|
2020-10-26 13:33:41 +00:00
|
|
|
}
|
2020-03-05 11:15:15 +00:00
|
|
|
case tui.BorderRounded, tui.BorderSharp:
|
2020-11-09 11:34:08 +00:00
|
|
|
extraMargin[idx] += 1 + idx%2
|
2017-02-04 12:51:22 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
|
2015-07-26 14:02:04 +00:00
|
|
|
adjust := func(idx1 int, idx2 int, max int, min int) {
|
|
|
|
if max >= min {
|
2020-11-09 11:34:08 +00:00
|
|
|
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
|
2015-07-26 14:02:04 +00:00
|
|
|
if max-margin < min {
|
|
|
|
desired := max - min
|
2020-11-09 11:34:08 +00:00
|
|
|
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)
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-23 15:23:16 +00:00
|
|
|
|
|
|
|
previewVisible := t.isPreviewEnabled() && t.preview.size.size > 0
|
2016-06-11 10:59:12 +00:00
|
|
|
minAreaWidth := minWidth
|
|
|
|
minAreaHeight := minHeight
|
2017-01-23 15:23:16 +00:00
|
|
|
if previewVisible {
|
2016-06-11 10:59:12 +00:00
|
|
|
switch t.preview.position {
|
|
|
|
case posUp, posDown:
|
|
|
|
minAreaHeight *= 2
|
|
|
|
case posLeft, posRight:
|
|
|
|
minAreaWidth *= 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
adjust(1, 3, screenWidth, minAreaWidth)
|
|
|
|
adjust(0, 2, screenHeight, minAreaHeight)
|
2017-02-04 12:51:22 +00:00
|
|
|
if t.border != nil {
|
|
|
|
t.border.Close()
|
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
if t.window != nil {
|
|
|
|
t.window.Close()
|
|
|
|
}
|
2017-02-04 12:51:22 +00:00
|
|
|
if t.pborder != nil {
|
|
|
|
t.pborder.Close()
|
2020-08-23 08:05:45 +00:00
|
|
|
}
|
|
|
|
if t.pwindow != nil {
|
2016-06-11 10:59:12 +00:00
|
|
|
t.pwindow.Close()
|
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
// Reset preview version so that full redraw occurs
|
|
|
|
t.previewed.version = 0
|
2016-06-11 10:59:12 +00:00
|
|
|
|
|
|
|
width := screenWidth - marginInt[1] - marginInt[3]
|
|
|
|
height := screenHeight - marginInt[0] - marginInt[2]
|
2020-03-05 11:15:15 +00:00
|
|
|
switch t.borderShape {
|
|
|
|
case tui.BorderHorizontal:
|
2017-02-04 12:51:22 +00:00
|
|
|
t.border = t.tui.NewWindow(
|
2020-10-26 13:33:41 +00:00
|
|
|
marginInt[0]-1, marginInt[3], width, height+2,
|
2019-12-12 14:03:17 +00:00
|
|
|
false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
|
2020-10-26 13:33:41 +00:00
|
|
|
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))
|
2020-03-05 11:15:15 +00:00
|
|
|
case tui.BorderRounded, tui.BorderSharp:
|
|
|
|
t.border = t.tui.NewWindow(
|
2020-10-26 13:33:41 +00:00
|
|
|
marginInt[0]-1, marginInt[3]-2, width+4, height+2,
|
2020-03-05 11:15:15 +00:00
|
|
|
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
2017-02-04 12:51:22 +00:00
|
|
|
}
|
2020-11-09 11:34:08 +00:00
|
|
|
|
|
|
|
// Add padding
|
|
|
|
for idx, val := range paddingInt {
|
|
|
|
marginInt[idx] += val
|
|
|
|
}
|
|
|
|
width = screenWidth - marginInt[1] - marginInt[3]
|
|
|
|
height = screenHeight - marginInt[0] - marginInt[2]
|
|
|
|
|
2019-03-28 17:11:03 +00:00
|
|
|
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
2017-01-23 15:23:16 +00:00
|
|
|
if previewVisible {
|
2016-06-11 10:59:12 +00:00
|
|
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
2020-08-23 08:05:45 +00:00
|
|
|
pwidth := w
|
|
|
|
pheight := h
|
|
|
|
if t.preview.border != tui.BorderNone {
|
|
|
|
previewBorder := tui.MakeBorderStyle(t.preview.border, t.unicode)
|
|
|
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
|
|
|
pwidth -= 4
|
|
|
|
pheight -= 2
|
|
|
|
x += 2
|
|
|
|
y += 1
|
|
|
|
} else {
|
|
|
|
previewBorder := tui.MakeTransparentBorder()
|
|
|
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
2020-08-23 08:17:57 +00:00
|
|
|
pwidth -= 4
|
|
|
|
x += 2
|
2019-11-15 02:37:52 +00:00
|
|
|
}
|
2020-08-23 08:05:45 +00:00
|
|
|
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2020-10-06 10:37:33 +00:00
|
|
|
verticalPad := 2
|
2020-11-03 12:49:21 +00:00
|
|
|
minPreviewHeight := 3
|
2020-10-06 10:37:33 +00:00
|
|
|
if t.preview.border == tui.BorderNone {
|
|
|
|
verticalPad = 0
|
2020-11-03 12:49:21 +00:00
|
|
|
minPreviewHeight = 1
|
2020-10-06 10:37:33 +00:00
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
switch t.preview.position {
|
|
|
|
case posUp:
|
2020-11-03 12:49:21 +00:00
|
|
|
pheight := calculateSize(height, t.preview.size, minHeight, minPreviewHeight, verticalPad)
|
2017-01-07 16:30:31 +00:00
|
|
|
t.window = t.tui.NewWindow(
|
2019-12-12 14:03:17 +00:00
|
|
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
2016-06-11 10:59:12 +00:00
|
|
|
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
|
|
|
case posDown:
|
2020-11-03 12:49:21 +00:00
|
|
|
pheight := calculateSize(height, t.preview.size, minHeight, minPreviewHeight, verticalPad)
|
2017-01-07 16:30:31 +00:00
|
|
|
t.window = t.tui.NewWindow(
|
2019-12-12 14:03:17 +00:00
|
|
|
marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
|
2016-06-11 10:59:12 +00:00
|
|
|
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
|
|
|
case posLeft:
|
2020-10-06 10:37:33 +00:00
|
|
|
pwidth := calculateSize(width, t.preview.size, minWidth, 5, 4)
|
2017-01-07 16:30:31 +00:00
|
|
|
t.window = t.tui.NewWindow(
|
2019-12-12 14:03:17 +00:00
|
|
|
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
|
2016-06-11 10:59:12 +00:00
|
|
|
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
|
|
|
case posRight:
|
2020-10-06 10:37:33 +00:00
|
|
|
pwidth := calculateSize(width, t.preview.size, minWidth, 5, 4)
|
2017-01-07 16:30:31 +00:00
|
|
|
t.window = t.tui.NewWindow(
|
2019-12-12 14:03:17 +00:00
|
|
|
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
|
2016-06-11 10:59:12 +00:00
|
|
|
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
|
|
|
}
|
|
|
|
} else {
|
2017-01-07 16:30:31 +00:00
|
|
|
t.window = t.tui.NewWindow(
|
2016-06-11 10:59:12 +00:00
|
|
|
marginInt[0],
|
|
|
|
marginInt[3],
|
|
|
|
width,
|
2019-12-12 14:03:17 +00:00
|
|
|
height, false, noBorder)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2017-09-09 04:50:07 +00:00
|
|
|
for i := 0; i < t.window.Height(); i++ {
|
|
|
|
t.window.MoveAndClear(i, 0)
|
2017-01-16 03:06:54 +00:00
|
|
|
}
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) move(y int, x int, clear bool) {
|
2018-06-09 16:41:50 +00:00
|
|
|
h := t.window.Height()
|
|
|
|
|
|
|
|
switch t.layout {
|
|
|
|
case layoutDefault:
|
|
|
|
y = h - y - 1
|
|
|
|
case layoutReverseList:
|
|
|
|
n := 2 + len(t.header)
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2018-06-09 16:41:50 +00:00
|
|
|
n--
|
|
|
|
}
|
|
|
|
if y < n {
|
|
|
|
y = h - y - 1
|
|
|
|
} else {
|
|
|
|
y -= n
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if clear {
|
2016-06-11 10:59:12 +00:00
|
|
|
t.window.MoveAndClear(y, x)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2016-06-11 10:59:12 +00:00
|
|
|
t.window.Move(y, x)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-25 10:07:12 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) placeCursor() {
|
2018-06-25 10:07:12 +00:00
|
|
|
t.move(0, t.promptLen+t.queryLen[0], false)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printPrompt() {
|
|
|
|
t.move(0, 0, true)
|
2020-07-05 07:16:46 +00:00
|
|
|
t.prompt()
|
2018-06-25 10:07:12 +00:00
|
|
|
|
|
|
|
before, after := t.updatePromptOffset()
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColInput, string(before))
|
|
|
|
t.window.CPrint(tui.ColInput, string(after))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) printInfo() {
|
2017-02-18 14:17:29 +00:00
|
|
|
pos := 0
|
2019-11-14 15:39:29 +00:00
|
|
|
switch t.infoStyle {
|
|
|
|
case infoDefault:
|
|
|
|
t.move(1, 0, true)
|
|
|
|
if t.reading {
|
|
|
|
duration := int64(spinnerDuration)
|
2020-01-15 01:43:09 +00:00
|
|
|
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColSpinner, t.spinner[idx])
|
2019-11-14 15:39:29 +00:00
|
|
|
}
|
|
|
|
t.move(1, 2, false)
|
|
|
|
pos = 2
|
|
|
|
case infoInline:
|
2018-06-25 10:07:12 +00:00
|
|
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
2017-02-18 14:17:29 +00:00
|
|
|
if pos+len(" < ") > t.window.Width() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.move(0, pos, true)
|
2015-04-21 14:50:53 +00:00
|
|
|
if t.reading {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColSpinner, " < ")
|
2015-04-21 14:50:53 +00:00
|
|
|
} else {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColPrompt, " < ")
|
2015-04-21 14:50:53 +00:00
|
|
|
}
|
2017-02-18 14:17:29 +00:00
|
|
|
pos += len(" < ")
|
2019-11-14 15:39:29 +00:00
|
|
|
case infoHidden:
|
|
|
|
return
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2019-11-10 02:36:22 +00:00
|
|
|
found := t.merger.Length()
|
|
|
|
total := util.Max(found, t.count)
|
|
|
|
output := fmt.Sprintf("%d/%d", found, total)
|
2015-05-20 12:25:15 +00:00
|
|
|
if t.toggleSort {
|
2015-04-16 05:44:41 +00:00
|
|
|
if t.sort {
|
2017-03-02 17:26:30 +00:00
|
|
|
output += " +S"
|
2015-04-16 05:44:41 +00:00
|
|
|
} else {
|
2017-03-02 17:26:30 +00:00
|
|
|
output += " -S"
|
2015-04-16 05:44:41 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-20 11:04:06 +00:00
|
|
|
if t.multi > 0 {
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi == maxMulti {
|
|
|
|
output += fmt.Sprintf(" (%d)", len(t.selected))
|
|
|
|
} else {
|
|
|
|
output += fmt.Sprintf(" (%d/%d)", len(t.selected), t.multi)
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
if t.progress > 0 && t.progress < 100 {
|
|
|
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
|
|
|
}
|
2019-11-10 02:36:22 +00:00
|
|
|
if t.failed != nil && t.count == 0 {
|
|
|
|
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
2017-06-30 16:13:15 +00:00
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
output = t.trimMessage(output, t.window.Width()-pos)
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColInfo, output)
|
2015-07-26 14:02:04 +00:00
|
|
|
}
|
|
|
|
|
2015-07-21 15:19:37 +00:00
|
|
|
func (t *Terminal) printHeader() {
|
|
|
|
if len(t.header) == 0 {
|
|
|
|
return
|
|
|
|
}
|
2017-01-07 16:30:31 +00:00
|
|
|
max := t.window.Height()
|
2015-07-22 05:19:45 +00:00
|
|
|
var state *ansiState
|
2015-07-21 15:19:37 +00:00
|
|
|
for idx, lineStr := range t.header {
|
2015-07-22 04:45:38 +00:00
|
|
|
line := idx + 2
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2015-08-02 04:06:15 +00:00
|
|
|
line--
|
2015-07-22 04:45:38 +00:00
|
|
|
}
|
|
|
|
if line >= max {
|
2015-07-22 12:22:59 +00:00
|
|
|
continue
|
2015-07-22 04:45:38 +00:00
|
|
|
}
|
2016-06-14 12:52:47 +00:00
|
|
|
trimmed, colors, newState := extractColor(lineStr, state, nil)
|
2015-07-22 05:19:45 +00:00
|
|
|
state = newState
|
2015-07-21 15:19:37 +00:00
|
|
|
item := &Item{
|
2017-07-21 08:29:14 +00:00
|
|
|
text: util.ToChars([]byte(trimmed)),
|
2016-08-18 17:39:32 +00:00
|
|
|
colors: colors}
|
2015-07-21 15:19:37 +00:00
|
|
|
|
|
|
|
t.move(line, 2, true)
|
2017-07-17 18:10:49 +00:00
|
|
|
t.printHighlighted(Result{item: item},
|
2020-10-25 10:29:37 +00:00
|
|
|
tui.ColHeader, tui.ColHeader, false, false)
|
2015-07-21 15:19:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) printList() {
|
|
|
|
t.constrain()
|
|
|
|
|
2015-04-21 14:50:53 +00:00
|
|
|
maxy := t.maxItems()
|
2015-01-09 16:06:08 +00:00
|
|
|
count := t.merger.Length() - t.offset
|
2017-01-07 16:30:31 +00:00
|
|
|
for j := 0; j < maxy; j++ {
|
|
|
|
i := j
|
2018-06-09 16:41:50 +00:00
|
|
|
if t.layout == layoutDefault {
|
2017-01-07 16:30:31 +00:00
|
|
|
i = maxy - 1 - j
|
|
|
|
}
|
2015-07-21 15:19:37 +00:00
|
|
|
line := i + 2 + len(t.header)
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2015-08-02 04:06:15 +00:00
|
|
|
line--
|
2015-04-21 14:50:53 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
if i < count {
|
2017-01-07 16:30:31 +00:00
|
|
|
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)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-17 18:10:49 +00:00
|
|
|
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
2016-08-18 17:39:32 +00:00
|
|
|
item := result.item
|
2016-01-13 12:36:44 +00:00
|
|
|
_, selected := t.selected[item.Index()]
|
2020-10-25 10:29:37 +00:00
|
|
|
label := ""
|
2016-05-17 17:06:52 +00:00
|
|
|
if t.jumping != jumpDisabled {
|
|
|
|
if i < len(t.jumpLabels) {
|
|
|
|
// Striped
|
|
|
|
current = i%2 == 0
|
2020-02-17 01:19:03 +00:00
|
|
|
label = t.jumpLabels[i:i+1] + strings.Repeat(" ", t.pointerLen-1)
|
2016-05-17 17:06:52 +00:00
|
|
|
}
|
|
|
|
} else if current {
|
2020-02-17 01:19:03 +00:00
|
|
|
label = t.pointer
|
2016-05-17 17:06:52 +00:00
|
|
|
}
|
2017-01-07 16:30:31 +00:00
|
|
|
|
|
|
|
// Avoid unnecessary redraw
|
2017-02-23 17:30:11 +00:00
|
|
|
newLine := itemLine{current: current, selected: selected, label: label,
|
2017-07-17 18:10:49 +00:00
|
|
|
result: result, queryLen: len(t.input), width: 0}
|
2017-01-15 17:26:36 +00:00
|
|
|
prevLine := t.prevLines[i]
|
|
|
|
if prevLine.current == newLine.current &&
|
|
|
|
prevLine.selected == newLine.selected &&
|
|
|
|
prevLine.label == newLine.label &&
|
2017-02-23 17:30:11 +00:00
|
|
|
prevLine.queryLen == newLine.queryLen &&
|
2017-01-15 17:26:36 +00:00
|
|
|
prevLine.result == newLine.result {
|
2017-01-07 16:30:31 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-09-09 04:50:07 +00:00
|
|
|
t.move(line, 0, false)
|
2015-01-01 19:49:30 +00:00
|
|
|
if current {
|
2020-10-25 10:29:37 +00:00
|
|
|
if len(label) == 0 {
|
|
|
|
t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
|
|
|
|
} else {
|
|
|
|
t.window.CPrint(tui.ColCurrentCursor, label)
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
if selected {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColCurrentSelected, t.marker)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2020-10-25 10:29:37 +00:00
|
|
|
newLine.width = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2020-10-25 10:29:37 +00:00
|
|
|
if len(label) == 0 {
|
|
|
|
t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
|
|
|
|
} else {
|
|
|
|
t.window.CPrint(tui.ColCursor, label)
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
if selected {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(tui.ColSelected, t.marker)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2020-02-17 01:19:03 +00:00
|
|
|
t.window.Print(t.markerEmpty)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2020-10-25 10:29:37 +00:00
|
|
|
newLine.width = t.printHighlighted(result, tui.ColNormal, tui.ColMatch, false, true)
|
2017-01-15 17:26:36 +00:00
|
|
|
}
|
2017-09-09 04:50:07 +00:00
|
|
|
fillSpaces := prevLine.width - newLine.width
|
|
|
|
if fillSpaces > 0 {
|
|
|
|
t.window.Print(strings.Repeat(" ", fillSpaces))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2017-01-15 17:26:36 +00:00
|
|
|
t.prevLines[i] = newLine
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) trimRight(runes []rune, width int) ([]rune, int) {
|
2015-01-18 07:59:04 +00:00
|
|
|
// We start from the beginning to handle tab characters
|
|
|
|
l := 0
|
|
|
|
for idx, r := range runes {
|
2017-01-07 16:30:31 +00:00
|
|
|
l += util.RuneWidth(r, l, t.tabstop)
|
2016-12-04 17:13:47 +00:00
|
|
|
if l > width {
|
2015-01-18 07:59:04 +00:00
|
|
|
return runes[:idx], len(runes) - idx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return runes, 0
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {
|
2015-01-18 07:59:04 +00:00
|
|
|
l := 0
|
|
|
|
for _, r := range runes {
|
2017-01-07 16:30:31 +00:00
|
|
|
l += util.RuneWidth(r, l+prefixWidth, t.tabstop)
|
2015-01-18 07:59:04 +00:00
|
|
|
if l > limit {
|
|
|
|
// Early exit
|
|
|
|
return l
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-18 07:59:04 +00:00
|
|
|
return l
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {
|
2016-09-20 16:23:41 +00:00
|
|
|
if len(runes) > maxDisplayWidthCalc && len(runes) > width {
|
|
|
|
trimmed := len(runes) - width
|
|
|
|
return runes[trimmed:], int32(trimmed)
|
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
currentWidth := t.displayWidth(runes)
|
2015-01-11 18:01:24 +00:00
|
|
|
var trimmed int32
|
2015-01-01 19:49:30 +00:00
|
|
|
|
|
|
|
for currentWidth > width && len(runes) > 0 {
|
|
|
|
runes = runes[1:]
|
2015-01-11 18:01:24 +00:00
|
|
|
trimmed++
|
2017-01-07 16:30:31 +00:00
|
|
|
currentWidth = t.displayWidthWithLimit(runes, 2, width)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
return runes, trimmed
|
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) overflow(runes []rune, max int) bool {
|
2017-01-15 17:26:36 +00:00
|
|
|
return t.displayWidthWithLimit(runes, 0, max) > max
|
2016-08-14 08:44:11 +00:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:29:37 +00:00
|
|
|
func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool) int {
|
2016-08-18 17:39:32 +00:00
|
|
|
item := result.item
|
2015-01-01 19:49:30 +00:00
|
|
|
|
|
|
|
// Overflow
|
2016-08-13 15:39:44 +00:00
|
|
|
text := make([]rune, item.text.Length())
|
|
|
|
copy(text, item.text.ToRunes())
|
2016-08-19 16:46:54 +00:00
|
|
|
matchOffsets := []Offset{}
|
2016-09-07 00:58:18 +00:00
|
|
|
var pos *[]int
|
2017-01-11 13:47:26 +00:00
|
|
|
if match && t.merger.pattern != nil {
|
2016-09-07 00:58:18 +00:00
|
|
|
_, 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))
|
2016-08-19 16:46:54 +00:00
|
|
|
}
|
|
|
|
var maxe int
|
2016-09-07 00:58:18 +00:00
|
|
|
for _, offset := range charOffsets {
|
2016-08-19 16:46:54 +00:00
|
|
|
maxe = util.Max(maxe, int(offset[1]))
|
|
|
|
}
|
2016-09-07 00:58:18 +00:00
|
|
|
|
2020-10-25 10:29:37 +00:00
|
|
|
offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
|
2020-09-24 02:06:20 +00:00
|
|
|
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
2016-03-01 18:06:21 +00:00
|
|
|
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
2017-01-15 17:26:36 +00:00
|
|
|
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
|
|
|
|
if displayWidth > maxWidth {
|
2020-03-23 10:05:06 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2015-04-16 03:56:01 +00:00
|
|
|
if t.hscroll {
|
2020-03-11 13:35:24 +00:00
|
|
|
if t.keepRight && pos == nil {
|
2020-03-23 10:05:06 +00:00
|
|
|
trimmed, diff := t.trimLeft(text, maxWidth-2)
|
|
|
|
transformOffsets(diff)
|
|
|
|
text = append([]rune(ellipsis), trimmed...)
|
2020-03-11 13:35:24 +00:00
|
|
|
} else if !t.overflow(text[:maxe], maxWidth-2) {
|
|
|
|
// Stri..
|
2017-01-07 16:30:31 +00:00
|
|
|
text, _ = t.trimRight(text, maxWidth-2)
|
2020-03-11 13:35:24 +00:00
|
|
|
text = append(text, []rune(ellipsis)...)
|
2015-04-16 03:56:01 +00:00
|
|
|
} else {
|
|
|
|
// Stri..
|
2017-01-07 16:30:31 +00:00
|
|
|
if t.overflow(text[maxe:], 2) {
|
2020-03-11 13:35:24 +00:00
|
|
|
text = append(text[:maxe], []rune(ellipsis)...)
|
2015-04-16 03:56:01 +00:00
|
|
|
}
|
|
|
|
// ..ri..
|
|
|
|
var diff int32
|
2017-01-07 16:30:31 +00:00
|
|
|
text, diff = t.trimLeft(text, maxWidth-2)
|
2015-04-16 03:56:01 +00:00
|
|
|
|
|
|
|
// Transform offsets
|
2020-03-23 10:05:06 +00:00
|
|
|
transformOffsets(diff)
|
2020-03-11 13:35:24 +00:00
|
|
|
text = append([]rune(ellipsis), text...)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-04-16 03:56:01 +00:00
|
|
|
} else {
|
2017-01-07 16:30:31 +00:00
|
|
|
text, _ = t.trimRight(text, maxWidth-2)
|
2020-03-11 13:35:24 +00:00
|
|
|
text = append(text, []rune(ellipsis)...)
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2015-03-18 16:59:14 +00:00
|
|
|
for idx, offset := range offsets {
|
2015-04-16 03:56:01 +00:00
|
|
|
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
|
|
|
|
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-24 17:39:49 +00:00
|
|
|
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
var index int32
|
2015-01-18 07:59:04 +00:00
|
|
|
var substr string
|
|
|
|
var prefixWidth int
|
2015-03-18 16:59:14 +00:00
|
|
|
maxOffset := int32(len(text))
|
2015-01-01 19:49:30 +00:00
|
|
|
for _, offset := range offsets {
|
2015-03-18 16:59:14 +00:00
|
|
|
b := util.Constrain32(offset.offset[0], index, maxOffset)
|
|
|
|
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
2015-01-18 07:59:04 +00:00
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(colBase, substr)
|
2015-01-18 07:59:04 +00:00
|
|
|
|
2015-03-19 03:14:26 +00:00
|
|
|
if b < e {
|
2017-01-07 16:30:31 +00:00
|
|
|
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(offset.color, substr)
|
2015-03-19 03:14:26 +00:00
|
|
|
}
|
2015-01-18 07:59:04 +00:00
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
index = e
|
2015-03-18 16:59:14 +00:00
|
|
|
if index >= maxOffset {
|
|
|
|
break
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
if index < maxOffset {
|
2017-01-07 16:30:31 +00:00
|
|
|
substr, _ = t.processTabs(text[index:], prefixWidth)
|
2020-10-25 10:29:37 +00:00
|
|
|
t.window.CPrint(colBase, substr)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2017-01-15 17:26:36 +00:00
|
|
|
return displayWidth
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
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)
|
2020-10-25 10:29:37 +00:00
|
|
|
t.pwindow.CPrint(tui.ColSpinner, spin)
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
|
|
|
} 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 {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.pwindow.CPrint(tui.ColSpinner, spin)
|
|
|
|
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
|
|
|
}
|
2016-09-28 17:53:05 +00:00
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
2017-01-11 13:13:40 +00:00
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
func (t *Terminal) renderPreviewText(unchanged bool) {
|
2017-01-11 13:13:40 +00:00
|
|
|
maxWidth := t.pwindow.Width()
|
|
|
|
lineNo := -t.previewer.offset
|
2017-07-19 13:46:16 +00:00
|
|
|
height := t.pwindow.Height()
|
2020-10-18 08:03:33 +00:00
|
|
|
if unchanged {
|
2020-10-25 10:29:37 +00:00
|
|
|
t.pwindow.MoveAndClear(0, 0)
|
2020-10-18 08:03:33 +00:00
|
|
|
} else {
|
|
|
|
t.previewed.filled = false
|
|
|
|
t.pwindow.Erase()
|
|
|
|
}
|
2017-05-27 17:26:42 +00:00
|
|
|
var ansi *ansiState
|
2020-10-18 08:03:33 +00:00
|
|
|
for _, line := range t.previewer.lines {
|
2020-11-03 12:31:19 +00:00
|
|
|
line = strings.TrimSuffix(line, "\n")
|
2019-03-29 09:28:45 +00:00
|
|
|
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
2020-10-18 08:03:33 +00:00
|
|
|
t.previewed.filled = true
|
2017-01-11 13:13:40 +00:00
|
|
|
break
|
2019-03-29 09:28:45 +00:00
|
|
|
} else if lineNo >= 0 {
|
2017-01-11 13:13:40 +00:00
|
|
|
var fillRet tui.FillReturn
|
2018-12-22 02:52:18 +00:00
|
|
|
prefixWidth := 0
|
2017-05-27 17:26:42 +00:00
|
|
|
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
2017-01-11 13:13:40 +00:00
|
|
|
trimmed := []rune(str)
|
|
|
|
if !t.preview.wrap {
|
|
|
|
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
|
|
|
}
|
2018-12-22 02:52:18 +00:00
|
|
|
str, width := t.processTabs(trimmed, prefixWidth)
|
|
|
|
prefixWidth += width
|
2020-10-25 10:29:37 +00:00
|
|
|
if t.theme.Colored && ansi != nil && ansi.colored() {
|
2017-01-11 13:13:40 +00:00
|
|
|
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
|
|
|
} else {
|
2019-12-12 14:03:17 +00:00
|
|
|
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
|
2017-01-11 13:13:40 +00:00
|
|
|
}
|
|
|
|
return fillRet == tui.FillContinue
|
|
|
|
})
|
2020-10-18 08:03:33 +00:00
|
|
|
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
2019-03-29 08:29:24 +00:00
|
|
|
if fillRet == tui.FillNextLine {
|
2017-01-11 13:13:40 +00:00
|
|
|
continue
|
2019-03-29 08:29:24 +00:00
|
|
|
} else if fillRet == tui.FillSuspend {
|
2020-10-18 08:03:33 +00:00
|
|
|
t.previewed.filled = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if unchanged && lineNo == 0 {
|
2017-01-11 13:13:40 +00:00
|
|
|
break
|
2017-01-07 16:30:31 +00:00
|
|
|
}
|
2020-11-03 12:31:19 +00:00
|
|
|
t.pwindow.Fill("\n")
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
lineNo++
|
2017-01-11 13:13:40 +00:00
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
if !unchanged {
|
|
|
|
t.pwindow.FinishFill()
|
2016-10-03 16:40:45 +00:00
|
|
|
}
|
2015-01-18 07:59:04 +00:00
|
|
|
}
|
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
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.renderPreviewText(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.renderPreviewText(true)
|
|
|
|
|
|
|
|
message := t.trimMessage("Loading ..", t.pwindow.Width())
|
|
|
|
pos := t.pwindow.Width() - len(message)
|
|
|
|
t.pwindow.Move(0, pos)
|
2020-10-25 10:29:37 +00:00
|
|
|
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), message)
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
|
2015-01-18 07:59:04 +00:00
|
|
|
var strbuf bytes.Buffer
|
|
|
|
l := prefixWidth
|
|
|
|
for _, r := range runes {
|
2017-01-07 16:30:31 +00:00
|
|
|
w := util.RuneWidth(r, l, t.tabstop)
|
2015-01-18 07:59:04 +00:00
|
|
|
l += w
|
|
|
|
if r == '\t' {
|
|
|
|
strbuf.WriteString(strings.Repeat(" ", w))
|
|
|
|
} else {
|
|
|
|
strbuf.WriteRune(r)
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-18 07:59:04 +00:00
|
|
|
return strbuf.String(), l
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printAll() {
|
2016-06-11 10:59:12 +00:00
|
|
|
t.resizeWindows()
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printList()
|
|
|
|
t.printPrompt()
|
2015-04-25 14:20:40 +00:00
|
|
|
t.printInfo()
|
2015-07-21 15:47:14 +00:00
|
|
|
t.printHeader()
|
2016-09-28 17:53:05 +00:00
|
|
|
t.printPreview()
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) refresh() {
|
2019-03-29 17:06:54 +00:00
|
|
|
t.placeCursor()
|
2015-01-07 03:46:45 +00:00
|
|
|
if !t.suppress {
|
2017-02-04 12:51:22 +00:00
|
|
|
windows := make([]tui.Window, 0, 4)
|
2020-03-05 11:15:15 +00:00
|
|
|
if t.borderShape != tui.BorderNone {
|
2017-02-04 12:51:22 +00:00
|
|
|
windows = append(windows, t.border)
|
|
|
|
}
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2020-08-23 08:05:45 +00:00
|
|
|
if t.pborder != nil {
|
|
|
|
windows = append(windows, t.pborder)
|
|
|
|
}
|
|
|
|
windows = append(windows, t.pwindow)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2017-02-04 12:51:22 +00:00
|
|
|
windows = append(windows, t.window)
|
|
|
|
t.tui.RefreshWindows(windows)
|
2015-01-07 03:46:45 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2020-07-28 03:58:37 +00:00
|
|
|
prefix := []rune(str[:locs[len(locs)-1][0]])
|
|
|
|
return len(prefix)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2020-07-28 03:58:37 +00:00
|
|
|
prefix := []rune(str[:loc[0]])
|
|
|
|
return len(prefix)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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...)
|
|
|
|
}
|
|
|
|
|
2016-10-24 00:44:56 +00:00
|
|
|
func keyMatch(key int, event tui.Event) bool {
|
2016-04-13 19:18:59 +00:00
|
|
|
return event.Type == key ||
|
2016-10-24 00:44:56 +00:00
|
|
|
event.Type == tui.Rune && int(event.Char) == key-tui.AltZ ||
|
|
|
|
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
|
2015-03-31 13:05:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-02 15:36:19 +00:00
|
|
|
func quoteEntryCmd(entry string) string {
|
|
|
|
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
|
|
|
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
|
|
|
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
|
|
|
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
|
|
|
return "^" + match
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-11-08 16:42:01 +00:00
|
|
|
func quoteEntry(entry string) string {
|
2016-11-06 17:15:34 +00:00
|
|
|
if util.IsWindows() {
|
2017-10-02 15:36:19 +00:00
|
|
|
return quoteEntryCmd(entry)
|
2016-11-06 17:15:34 +00:00
|
|
|
}
|
2016-06-07 15:54:21 +00:00
|
|
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
2015-11-08 16:42:01 +00:00
|
|
|
}
|
|
|
|
|
2018-03-30 02:47:46 +00:00
|
|
|
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++
|
2019-02-18 16:12:57 +00:00
|
|
|
case 'n':
|
|
|
|
flags.number = true
|
|
|
|
skipChars++
|
2019-10-27 14:50:12 +00:00
|
|
|
case 'f':
|
|
|
|
flags.file = true
|
|
|
|
skipChars++
|
2018-06-09 16:40:22 +00:00
|
|
|
case 'q':
|
|
|
|
flags.query = true
|
2018-03-30 02:47:46 +00:00
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
matchWithoutFlags := "{" + match[skipChars:]
|
|
|
|
|
|
|
|
return false, matchWithoutFlags, flags
|
|
|
|
}
|
|
|
|
|
2019-12-06 13:34:30 +00:00
|
|
|
func hasPreviewFlags(template string) (slot bool, plus bool, query bool) {
|
2017-01-27 07:38:42 +00:00
|
|
|
for _, match := range placeholder.FindAllString(template, -1) {
|
2018-03-30 02:47:46 +00:00
|
|
|
_, _, flags := parsePlaceholder(match)
|
|
|
|
if flags.plus {
|
2018-06-09 16:40:22 +00:00
|
|
|
plus = true
|
|
|
|
}
|
|
|
|
if flags.query {
|
|
|
|
query = true
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
2019-12-06 13:34:30 +00:00
|
|
|
slot = true
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
2018-06-09 16:40:22 +00:00
|
|
|
return
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 14:50:12 +00:00
|
|
|
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{}
|
|
|
|
}
|
|
|
|
|
2020-07-26 15:15:25 +00:00
|
|
|
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string {
|
|
|
|
return replacePlaceholder(
|
|
|
|
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ascii to positive integer
|
|
|
|
func atopi(s string) int {
|
2020-08-02 01:02:11 +00:00
|
|
|
matches := numericPrefix.FindStringSubmatch(s)
|
|
|
|
if len(matches) < 2 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
n, e := strconv.Atoi(matches[1])
|
2020-07-26 15:15:25 +00:00
|
|
|
if e != nil || n < 1 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
2020-08-23 06:57:49 +00:00
|
|
|
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
2020-07-26 15:15:25 +00:00
|
|
|
offsetExpr := t.replacePlaceholder(t.preview.scroll, false, "", list)
|
|
|
|
nums := strings.Split(offsetExpr, "-")
|
|
|
|
switch len(nums) {
|
|
|
|
case 0:
|
|
|
|
return 0
|
|
|
|
case 1, 2:
|
|
|
|
base := atopi(nums[0])
|
|
|
|
if base == 0 {
|
|
|
|
return 0
|
|
|
|
} else if len(nums) == 1 {
|
|
|
|
return base - 1
|
|
|
|
}
|
2020-08-23 06:57:49 +00:00
|
|
|
if nums[1][0] == '/' {
|
|
|
|
denom := atopi(nums[1][1:])
|
|
|
|
if denom == 0 {
|
|
|
|
return base
|
|
|
|
}
|
|
|
|
return base - height/denom
|
|
|
|
}
|
2020-07-26 15:15:25 +00:00
|
|
|
return base - atopi(nums[1]) - 1
|
|
|
|
default:
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-27 14:50:12 +00:00
|
|
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
2017-01-27 07:38:42 +00:00
|
|
|
current := allItems[:1]
|
|
|
|
selected := allItems[1:]
|
|
|
|
if current[0] == nil {
|
|
|
|
current = []*Item{}
|
|
|
|
}
|
|
|
|
if selected[0] == nil {
|
|
|
|
selected = []*Item{}
|
|
|
|
}
|
2016-10-03 05:16:10 +00:00
|
|
|
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
2018-03-30 02:47:46 +00:00
|
|
|
escaped, match, flags := parsePlaceholder(match)
|
|
|
|
|
|
|
|
if escaped {
|
|
|
|
return match
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Current query
|
|
|
|
if match == "{q}" {
|
|
|
|
return quoteEntry(query)
|
|
|
|
}
|
|
|
|
|
2017-01-27 07:38:42 +00:00
|
|
|
items := current
|
2018-03-30 02:47:46 +00:00
|
|
|
if flags.plus || forcePlus {
|
2017-01-27 07:38:42 +00:00
|
|
|
items = selected
|
|
|
|
}
|
|
|
|
|
2016-10-03 05:16:10 +00:00
|
|
|
replacements := make([]string, len(items))
|
|
|
|
|
|
|
|
if match == "{}" {
|
|
|
|
for idx, item := range items {
|
2019-02-18 16:12:57 +00:00
|
|
|
if flags.number {
|
|
|
|
n := int(item.text.Index)
|
|
|
|
if n < 0 {
|
|
|
|
replacements[idx] = ""
|
|
|
|
} else {
|
|
|
|
replacements[idx] = strconv.Itoa(n)
|
|
|
|
}
|
2019-10-27 14:50:12 +00:00
|
|
|
} else if flags.file {
|
|
|
|
replacements[idx] = item.AsString(stripAnsi)
|
2019-02-18 16:12:57 +00:00
|
|
|
} else {
|
|
|
|
replacements[idx] = quoteEntry(item.AsString(stripAnsi))
|
|
|
|
}
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
2019-10-27 14:50:12 +00:00
|
|
|
if flags.file {
|
|
|
|
return writeTemporaryFile(replacements, printsep)
|
|
|
|
}
|
2016-10-03 05:16:10 +00:00
|
|
|
return strings.Join(replacements, " ")
|
|
|
|
}
|
|
|
|
|
|
|
|
tokens := strings.Split(match[1:len(match)-1], ",")
|
|
|
|
ranges := make([]Range, len(tokens))
|
|
|
|
for idx, s := range tokens {
|
|
|
|
r, ok := ParseRange(&s)
|
|
|
|
if !ok {
|
|
|
|
// Invalid expression, just return the original string in the template
|
|
|
|
return match
|
|
|
|
}
|
|
|
|
ranges[idx] = r
|
|
|
|
}
|
|
|
|
|
|
|
|
for idx, item := range items {
|
2017-07-21 08:29:14 +00:00
|
|
|
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
2016-10-03 05:16:10 +00:00
|
|
|
trans := Transform(tokens, ranges)
|
2019-07-19 04:23:18 +00:00
|
|
|
str := joinTokens(trans)
|
2016-10-03 05:16:10 +00:00
|
|
|
if delimiter.str != nil {
|
|
|
|
str = strings.TrimSuffix(str, *delimiter.str)
|
|
|
|
} else if delimiter.regex != nil {
|
|
|
|
delims := delimiter.regex.FindAllStringIndex(str, -1)
|
|
|
|
if len(delims) > 0 && delims[len(delims)-1][1] == len(str) {
|
|
|
|
str = str[:delims[len(delims)-1][0]]
|
|
|
|
}
|
|
|
|
}
|
2018-03-30 02:47:46 +00:00
|
|
|
if !flags.preserveSpace {
|
|
|
|
str = strings.TrimSpace(str)
|
|
|
|
}
|
2019-10-27 14:50:12 +00:00
|
|
|
if !flags.file {
|
|
|
|
str = quoteEntry(str)
|
|
|
|
}
|
|
|
|
replacements[idx] = str
|
|
|
|
}
|
|
|
|
if flags.file {
|
|
|
|
return writeTemporaryFile(replacements, printsep)
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
|
|
|
return strings.Join(replacements, " ")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-04-28 13:58:08 +00:00
|
|
|
func (t *Terminal) redraw() {
|
|
|
|
t.tui.Clear()
|
|
|
|
t.tui.Refresh()
|
|
|
|
t.printAll()
|
|
|
|
}
|
|
|
|
|
2017-01-27 08:46:56 +00:00
|
|
|
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool) {
|
2017-01-27 07:38:42 +00:00
|
|
|
valid, list := t.buildPlusList(template, forcePlus)
|
|
|
|
if !valid {
|
|
|
|
return
|
|
|
|
}
|
2020-07-26 15:15:25 +00:00
|
|
|
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
2018-09-27 06:27:08 +00:00
|
|
|
cmd := util.ExecCommand(command, false)
|
2017-01-27 08:46:56 +00:00
|
|
|
if !background {
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
2017-04-28 13:58:08 +00:00
|
|
|
t.tui.Pause(true)
|
2017-01-27 08:46:56 +00:00
|
|
|
cmd.Run()
|
2020-05-17 17:33:37 +00:00
|
|
|
t.tui.Resume(true, false)
|
2017-04-28 13:58:08 +00:00
|
|
|
t.redraw()
|
2017-01-27 08:46:56 +00:00
|
|
|
t.refresh()
|
|
|
|
} else {
|
2018-09-27 02:10:49 +00:00
|
|
|
t.tui.Pause(false)
|
2017-01-27 08:46:56 +00:00
|
|
|
cmd.Run()
|
2020-05-17 17:33:37 +00:00
|
|
|
t.tui.Resume(false, false)
|
2017-01-27 08:46:56 +00:00
|
|
|
}
|
2019-10-27 14:50:12 +00:00
|
|
|
cleanTemporaryFiles()
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
|
2017-01-23 15:23:16 +00:00
|
|
|
func (t *Terminal) hasPreviewer() bool {
|
2016-06-11 10:59:12 +00:00
|
|
|
return t.previewBox != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) isPreviewEnabled() bool {
|
2017-01-23 15:23:16 +00:00
|
|
|
return t.hasPreviewer() && t.previewer.enabled
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) hasPreviewWindow() bool {
|
|
|
|
return t.pwindow != nil && t.isPreviewEnabled()
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
|
2016-10-03 05:16:10 +00:00
|
|
|
func (t *Terminal) currentItem() *Item {
|
2017-01-27 07:38:42 +00:00
|
|
|
cnt := t.merger.Length()
|
2019-11-10 02:36:22 +00:00
|
|
|
if t.cy >= 0 && cnt > 0 && cnt > t.cy {
|
2017-01-27 07:38:42 +00:00
|
|
|
return t.merger.Get(t.cy).item
|
|
|
|
}
|
|
|
|
return nil
|
2016-10-03 05:16:10 +00:00
|
|
|
}
|
|
|
|
|
2017-01-27 07:38:42 +00:00
|
|
|
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
|
|
|
current := t.currentItem()
|
2020-09-29 02:31:28 +00:00
|
|
|
slot, plus, query := hasPreviewFlags(template)
|
|
|
|
if !(!slot || query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
|
2017-01-27 07:38:42 +00:00
|
|
|
return current != nil, []*Item{current, current}
|
|
|
|
}
|
2018-06-09 16:40:22 +00:00
|
|
|
|
|
|
|
// 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 {
|
2019-02-18 16:12:57 +00:00
|
|
|
current = &minItem
|
2018-06-09 16:40:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
|
|
|
return true, sels
|
2015-06-14 03:25:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-02 03:55:26 +00:00
|
|
|
func (t *Terminal) selectItem(item *Item) bool {
|
|
|
|
if len(t.selected) >= t.multi {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if _, found := t.selected[item.Index()]; found {
|
2019-11-11 14:31:31 +00:00
|
|
|
return true
|
2019-11-02 03:55:26 +00:00
|
|
|
}
|
|
|
|
|
2017-07-27 16:39:25 +00:00
|
|
|
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
|
|
|
t.version++
|
2019-11-02 03:55:26 +00:00
|
|
|
|
|
|
|
return true
|
2017-07-27 16:39:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) deselectItem(item *Item) {
|
|
|
|
delete(t.selected, item.Index())
|
|
|
|
t.version++
|
|
|
|
}
|
|
|
|
|
2019-11-02 03:55:26 +00:00
|
|
|
func (t *Terminal) toggleItem(item *Item) bool {
|
2017-07-27 16:39:25 +00:00
|
|
|
if _, found := t.selected[item.Index()]; !found {
|
2019-11-02 03:55:26 +00:00
|
|
|
return t.selectItem(item)
|
2017-07-27 16:39:25 +00:00
|
|
|
}
|
2019-11-02 03:55:26 +00:00
|
|
|
t.deselectItem(item)
|
|
|
|
return true
|
2017-07-27 16:39:25 +00:00
|
|
|
}
|
|
|
|
|
2018-09-27 06:27:08 +00:00
|
|
|
func (t *Terminal) killPreview(code int) {
|
|
|
|
select {
|
|
|
|
case t.killChan <- code:
|
|
|
|
default:
|
|
|
|
if code != exitCancel {
|
|
|
|
os.Exit(code)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) cancelPreview() {
|
|
|
|
t.killPreview(exitCancel)
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// Loop is called to start Terminal I/O
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) Loop() {
|
2016-09-07 00:58:18 +00:00
|
|
|
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
2015-02-17 15:08:17 +00:00
|
|
|
<-t.startChan
|
2015-01-01 19:49:30 +00:00
|
|
|
{ // Late initialization
|
2015-10-05 14:19:26 +00:00
|
|
|
intChan := make(chan os.Signal, 1)
|
2019-07-19 04:24:46 +00:00
|
|
|
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
2015-10-05 14:19:26 +00:00
|
|
|
go func() {
|
|
|
|
<-intChan
|
|
|
|
t.reqBox.Set(reqQuit, nil)
|
|
|
|
}()
|
|
|
|
|
2017-04-28 13:58:08 +00:00
|
|
|
contChan := make(chan os.Signal, 1)
|
|
|
|
notifyOnCont(contChan)
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
<-contChan
|
|
|
|
t.reqBox.Set(reqReinit, nil)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2015-01-23 11:30:50 +00:00
|
|
|
resizeChan := make(chan os.Signal, 1)
|
2016-10-24 00:44:56 +00:00
|
|
|
notifyOnResize(resizeChan) // Non-portable
|
2015-01-23 11:30:50 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
<-resizeChan
|
|
|
|
t.reqBox.Set(reqRedraw, nil)
|
|
|
|
}
|
|
|
|
}()
|
2015-06-17 15:42:38 +00:00
|
|
|
|
2015-11-03 13:59:57 +00:00
|
|
|
t.mutex.Lock()
|
|
|
|
t.initFunc()
|
2016-06-11 10:59:12 +00:00
|
|
|
t.resizeWindows()
|
2015-11-03 13:59:57 +00:00
|
|
|
t.printPrompt()
|
|
|
|
t.printInfo()
|
|
|
|
t.printHeader()
|
2018-06-25 10:07:12 +00:00
|
|
|
t.refresh()
|
2015-11-03 13:59:57 +00:00
|
|
|
t.mutex.Unlock()
|
|
|
|
go func() {
|
2016-01-16 09:07:50 +00:00
|
|
|
timer := time.NewTimer(t.initDelay)
|
2015-11-03 13:59:57 +00:00
|
|
|
<-timer.C
|
|
|
|
t.reqBox.Set(reqRefresh, nil)
|
|
|
|
}()
|
|
|
|
|
2015-06-17 15:42:38 +00:00
|
|
|
// Keep the spinner spinning
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
t.mutex.Lock()
|
|
|
|
reading := t.reading
|
|
|
|
t.mutex.Unlock()
|
|
|
|
time.Sleep(spinnerDuration)
|
2019-11-10 02:36:22 +00:00
|
|
|
if reading {
|
|
|
|
t.reqBox.Set(reqInfo, nil)
|
|
|
|
}
|
2015-06-17 15:42:38 +00:00
|
|
|
}
|
|
|
|
}()
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewer() {
|
2016-06-11 10:59:12 +00:00
|
|
|
go func() {
|
2020-10-24 07:55:55 +00:00
|
|
|
var version int64
|
2016-06-11 10:59:12 +00:00
|
|
|
for {
|
2020-06-21 13:41:33 +00:00
|
|
|
var items []*Item
|
|
|
|
var commandTemplate string
|
2020-10-18 08:03:33 +00:00
|
|
|
var pwindow tui.Window
|
2016-06-11 10:59:12 +00:00
|
|
|
t.previewBox.Wait(func(events *util.Events) {
|
|
|
|
for req, value := range *events {
|
|
|
|
switch req {
|
|
|
|
case reqPreviewEnqueue:
|
2020-06-21 13:41:33 +00:00
|
|
|
request := value.(previewRequest)
|
|
|
|
commandTemplate = request.template
|
|
|
|
items = request.list
|
2020-10-18 08:03:33 +00:00
|
|
|
pwindow = request.pwindow
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
events.Clear()
|
|
|
|
})
|
2020-10-18 08:03:33 +00:00
|
|
|
version++
|
2017-01-27 07:38:42 +00:00
|
|
|
// We don't display preview window if no match
|
2020-06-21 13:41:33 +00:00
|
|
|
if items[0] != nil {
|
2020-07-26 15:15:25 +00:00
|
|
|
command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items)
|
2020-10-18 08:03:33 +00:00
|
|
|
initialOffset := 0
|
2018-09-27 06:27:08 +00:00
|
|
|
cmd := util.ExecCommand(command, true)
|
2020-10-18 08:03:33 +00:00
|
|
|
if pwindow != nil {
|
|
|
|
height := pwindow.Height()
|
|
|
|
initialOffset = util.Max(0, t.evaluateScrollOffset(items, height))
|
2017-11-30 18:27:58 +00:00
|
|
|
env := os.Environ()
|
2020-08-23 06:57:49 +00:00
|
|
|
lines := fmt.Sprintf("LINES=%d", height)
|
2020-10-18 08:03:33 +00:00
|
|
|
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
2019-02-22 05:33:29 +00:00
|
|
|
env = append(env, lines)
|
|
|
|
env = append(env, "FZF_PREVIEW_"+lines)
|
|
|
|
env = append(env, columns)
|
|
|
|
env = append(env, "FZF_PREVIEW_"+columns)
|
2017-11-30 18:27:58 +00:00
|
|
|
cmd.Env = env
|
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
|
|
|
|
out, _ := cmd.StdoutPipe()
|
|
|
|
cmd.Stderr = cmd.Stdout
|
|
|
|
reader := bufio.NewReader(out)
|
|
|
|
eofChan := make(chan bool)
|
|
|
|
finishChan := make(chan bool, 1)
|
2020-01-19 10:42:10 +00:00
|
|
|
err := cmd.Start()
|
2020-10-27 09:10:25 +00:00
|
|
|
if err == nil {
|
|
|
|
reapChan := make(chan bool)
|
2020-10-18 08:03:33 +00:00
|
|
|
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
|
|
|
|
}()
|
2020-10-27 09:10:25 +00:00
|
|
|
|
2020-10-18 08:03:33 +00:00
|
|
|
// Goroutine 2 periodically requests rendering
|
2020-10-24 07:55:55 +00:00
|
|
|
go func(version int64) {
|
2020-10-18 08:03:33 +00:00
|
|
|
lines := []string{}
|
|
|
|
spinner := makeSpinner(t.unicode)
|
|
|
|
spinnerIndex := -1 // Delay initial rendering by an extra tick
|
|
|
|
ticker := time.NewTicker(previewChunkDelay)
|
|
|
|
offset := initialOffset
|
|
|
|
Loop:
|
|
|
|
for {
|
2018-09-27 06:27:08 +00:00
|
|
|
select {
|
2020-10-18 08:03:33 +00:00
|
|
|
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})
|
|
|
|
offset = -1
|
|
|
|
}
|
|
|
|
spinnerIndex++
|
|
|
|
}
|
|
|
|
case eachLine := <-lineChan:
|
|
|
|
line := eachLine.line
|
|
|
|
err := eachLine.err
|
|
|
|
if len(line) > 0 {
|
2020-10-23 12:37:20 +00:00
|
|
|
clearIndex := strings.Index(line, clearCode)
|
|
|
|
if clearIndex >= 0 {
|
|
|
|
lines = []string{}
|
|
|
|
line = line[clearIndex+len(clearCode):]
|
|
|
|
version--
|
|
|
|
offset = 0
|
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
lines = append(lines, line)
|
|
|
|
}
|
|
|
|
if err != nil {
|
2020-10-23 14:52:05 +00:00
|
|
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, ""})
|
2020-10-18 08:03:33 +00:00
|
|
|
break Loop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ticker.Stop()
|
|
|
|
reapChan <- true
|
|
|
|
}(version)
|
2020-10-27 09:10:25 +00:00
|
|
|
|
|
|
|
// 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 {
|
2020-10-18 08:03:33 +00:00
|
|
|
util.KillCommand(cmd)
|
2020-10-27 09:10:25 +00:00
|
|
|
os.Exit(code)
|
|
|
|
} else {
|
|
|
|
timer := time.NewTimer(previewCancelWait)
|
|
|
|
select {
|
|
|
|
case <-timer.C:
|
|
|
|
util.KillCommand(cmd)
|
|
|
|
case <-finishChan:
|
|
|
|
}
|
|
|
|
timer.Stop()
|
2020-10-18 08:03:33 +00:00
|
|
|
}
|
2020-10-27 09:10:25 +00:00
|
|
|
break Loop
|
|
|
|
case <-finishChan:
|
|
|
|
break Loop
|
2018-09-27 06:27:08 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-27 09:10:25 +00:00
|
|
|
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
|
2020-10-18 08:03:33 +00:00
|
|
|
<-reapChan
|
2020-10-27 09:10:25 +00:00
|
|
|
} else {
|
|
|
|
// Failed to start the command. Report the error immediately.
|
|
|
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""})
|
2018-09-27 06:27:08 +00:00
|
|
|
}
|
2020-10-27 09:10:25 +00:00
|
|
|
|
2019-10-27 14:50:12 +00:00
|
|
|
cleanTemporaryFiles()
|
2016-06-15 04:03:42 +00:00
|
|
|
} else {
|
2020-10-18 08:03:33 +00:00
|
|
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""})
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2017-07-19 04:17:06 +00:00
|
|
|
exit := func(getCode func() int) {
|
2017-07-18 11:50:38 +00:00
|
|
|
t.tui.Close()
|
2017-07-19 04:17:06 +00:00
|
|
|
code := getCode()
|
2015-09-15 04:21:51 +00:00
|
|
|
if code <= exitNoMatch && t.history != nil {
|
2015-06-13 15:43:44 +00:00
|
|
|
t.history.append(string(t.input))
|
|
|
|
}
|
2016-09-07 00:58:18 +00:00
|
|
|
// prof.Stop()
|
2018-09-27 06:27:08 +00:00
|
|
|
t.killPreview(code)
|
2015-06-13 15:43:44 +00:00
|
|
|
}
|
|
|
|
|
2020-06-21 13:41:33 +00:00
|
|
|
refreshPreview := func(command string) {
|
|
|
|
if len(command) > 0 && t.isPreviewEnabled() {
|
|
|
|
_, list := t.buildPlusList(command, false)
|
2020-06-20 13:04:09 +00:00
|
|
|
t.cancelPreview()
|
2020-10-18 08:03:33 +00:00
|
|
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, list})
|
2020-06-20 13:04:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
go func() {
|
2018-12-13 01:58:57 +00:00
|
|
|
var focusedIndex int32 = minItem.Index()
|
2018-12-05 09:45:55 +00:00
|
|
|
var version int64 = -1
|
2015-01-01 19:49:30 +00:00
|
|
|
for {
|
2015-01-12 03:56:17 +00:00
|
|
|
t.reqBox.Wait(func(events *util.Events) {
|
2015-01-01 19:49:30 +00:00
|
|
|
defer events.Clear()
|
|
|
|
t.mutex.Lock()
|
2016-06-11 10:59:12 +00:00
|
|
|
for req, value := range *events {
|
2015-01-01 19:49:30 +00:00
|
|
|
switch req {
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqPrompt:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printPrompt()
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2015-04-21 14:50:53 +00:00
|
|
|
t.printInfo()
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqInfo:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printInfo()
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqList:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printList()
|
2018-12-13 01:58:57 +00:00
|
|
|
var currentIndex int32 = minItem.Index()
|
|
|
|
currentItem := t.currentItem()
|
|
|
|
if currentItem != nil {
|
|
|
|
currentIndex = currentItem.Index()
|
|
|
|
}
|
|
|
|
if focusedIndex != currentIndex || version != t.version {
|
2017-07-27 16:39:25 +00:00
|
|
|
version = t.version
|
2018-12-13 01:58:57 +00:00
|
|
|
focusedIndex = currentIndex
|
2020-06-21 13:41:33 +00:00
|
|
|
refreshPreview(t.preview.command)
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
2016-05-17 17:06:52 +00:00
|
|
|
case reqJump:
|
|
|
|
if t.merger.Length() == 0 {
|
|
|
|
t.jumping = jumpDisabled
|
|
|
|
}
|
|
|
|
t.printList()
|
2015-07-21 18:21:20 +00:00
|
|
|
case reqHeader:
|
|
|
|
t.printHeader()
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqRefresh:
|
2015-01-07 03:46:45 +00:00
|
|
|
t.suppress = false
|
2017-04-28 13:58:08 +00:00
|
|
|
case reqReinit:
|
2020-05-17 17:33:37 +00:00
|
|
|
t.tui.Resume(t.fullscreen, true)
|
2017-04-28 13:58:08 +00:00
|
|
|
t.redraw()
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqRedraw:
|
2017-04-28 13:58:08 +00:00
|
|
|
t.redraw()
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqClose:
|
2017-07-19 04:17:06 +00:00
|
|
|
exit(func() int {
|
|
|
|
if t.output() {
|
|
|
|
return exitOk
|
|
|
|
}
|
|
|
|
return exitNoMatch
|
|
|
|
})
|
2016-06-11 10:59:12 +00:00
|
|
|
case reqPreviewDisplay:
|
2020-07-26 15:15:25 +00:00
|
|
|
result := value.(previewResult)
|
2020-10-18 08:03:33 +00:00
|
|
|
t.previewer.version = result.version
|
|
|
|
t.previewer.lines = result.lines
|
|
|
|
t.previewer.spinner = result.spinner
|
|
|
|
if result.offset >= 0 {
|
|
|
|
t.previewer.offset = util.Constrain(result.offset, 0, len(t.previewer.lines)-1)
|
|
|
|
}
|
2016-09-24 17:02:00 +00:00
|
|
|
t.printPreview()
|
|
|
|
case reqPreviewRefresh:
|
2016-06-11 10:59:12 +00:00
|
|
|
t.printPreview()
|
2020-10-18 08:03:33 +00:00
|
|
|
case reqPreviewDelayed:
|
2020-10-24 07:55:55 +00:00
|
|
|
t.previewer.version = value.(int64)
|
2020-10-18 08:03:33 +00:00
|
|
|
t.printPreviewDelayed()
|
2016-05-12 15:51:15 +00:00
|
|
|
case reqPrintQuery:
|
2017-07-19 04:17:06 +00:00
|
|
|
exit(func() int {
|
|
|
|
t.printer(string(t.input))
|
|
|
|
return exitOk
|
|
|
|
})
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqQuit:
|
2017-07-19 04:17:06 +00:00
|
|
|
exit(func() int { return exitInterrupt })
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-25 10:07:12 +00:00
|
|
|
t.refresh()
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
looping := true
|
|
|
|
for looping {
|
2019-11-10 02:36:22 +00:00
|
|
|
var newCommand *string
|
|
|
|
changed := false
|
2020-06-07 14:07:03 +00:00
|
|
|
beof := false
|
2019-11-10 02:36:22 +00:00
|
|
|
queryChanged := false
|
|
|
|
|
2017-01-07 16:30:31 +00:00
|
|
|
event := t.tui.GetChar()
|
2015-01-01 19:49:30 +00:00
|
|
|
|
|
|
|
t.mutex.Lock()
|
|
|
|
previousInput := t.input
|
2019-03-29 06:02:31 +00:00
|
|
|
previousCx := t.cx
|
|
|
|
events := []util.EventType{}
|
2015-01-12 03:56:17 +00:00
|
|
|
req := func(evts ...util.EventType) {
|
2015-01-01 19:49:30 +00:00
|
|
|
for _, event := range evts {
|
|
|
|
events = append(events, event)
|
2015-01-11 18:01:24 +00:00
|
|
|
if event == reqClose || event == reqQuit {
|
2015-01-01 19:49:30 +00:00
|
|
|
looping = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-21 13:41:33 +00:00
|
|
|
togglePreview := func(enabled bool) {
|
|
|
|
if t.previewer.enabled != enabled {
|
|
|
|
t.previewer.enabled = enabled
|
|
|
|
t.tui.Clear()
|
|
|
|
t.resizeWindows()
|
|
|
|
req(reqPrompt, reqList, reqInfo, reqHeader)
|
|
|
|
}
|
|
|
|
}
|
2019-11-02 03:55:26 +00:00
|
|
|
toggle := func() bool {
|
|
|
|
if t.cy < t.merger.Length() && t.toggleItem(t.merger.Get(t.cy).item) {
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqInfo)
|
2019-11-02 03:55:26 +00:00
|
|
|
return true
|
2015-01-10 03:21:17 +00:00
|
|
|
}
|
2019-11-02 03:55:26 +00:00
|
|
|
return false
|
2015-01-10 03:21:17 +00:00
|
|
|
}
|
2016-09-24 19:12:44 +00:00
|
|
|
scrollPreview := func(amount int) {
|
2020-10-18 08:03:33 +00:00
|
|
|
if !t.previewer.scrollable {
|
2019-03-29 09:28:45 +00:00
|
|
|
return
|
|
|
|
}
|
2020-10-06 01:05:57 +00:00
|
|
|
newOffset := t.previewer.offset + amount
|
2020-10-18 08:03:33 +00:00
|
|
|
numLines := len(t.previewer.lines)
|
2020-10-06 01:05:57 +00:00
|
|
|
if t.preview.cycle {
|
2020-10-18 08:03:33 +00:00
|
|
|
newOffset = (newOffset + numLines) % numLines
|
2020-10-06 01:05:57 +00:00
|
|
|
}
|
2020-10-18 08:03:33 +00:00
|
|
|
newOffset = util.Constrain(newOffset, 0, numLines-1)
|
2019-03-29 06:12:46 +00:00
|
|
|
if t.previewer.offset != newOffset {
|
|
|
|
t.previewer.offset = newOffset
|
|
|
|
req(reqPreviewRefresh)
|
|
|
|
}
|
2016-09-24 19:12:44 +00:00
|
|
|
}
|
2015-06-18 15:31:48 +00:00
|
|
|
for key, ret := range t.expect {
|
2015-03-31 13:05:02 +00:00
|
|
|
if keyMatch(key, event) {
|
2015-06-18 15:31:48 +00:00
|
|
|
t.pressed = ret
|
2016-09-17 19:45:21 +00:00
|
|
|
t.reqBox.Set(reqClose, nil)
|
|
|
|
t.mutex.Unlock()
|
|
|
|
return
|
2015-03-28 17:59:32 +00:00
|
|
|
}
|
|
|
|
}
|
2015-05-20 12:25:15 +00:00
|
|
|
|
2017-01-21 17:32:49 +00:00
|
|
|
var doAction func(action, int) bool
|
|
|
|
doActions := func(actions []action, mapkey int) bool {
|
|
|
|
for _, action := range actions {
|
|
|
|
if !doAction(action, mapkey) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
doAction = func(a action, mapkey int) bool {
|
|
|
|
switch a.t {
|
2015-10-12 17:24:38 +00:00
|
|
|
case actIgnore:
|
2017-01-27 08:46:56 +00:00
|
|
|
case actExecute, actExecuteSilent:
|
|
|
|
t.executeCommand(a.a, false, a.t == actExecuteSilent)
|
2015-11-08 16:42:01 +00:00
|
|
|
case actExecuteMulti:
|
2017-01-27 08:46:56 +00:00
|
|
|
t.executeCommand(a.a, true, false)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actInvalid:
|
|
|
|
t.mutex.Unlock()
|
|
|
|
return false
|
2016-06-11 10:59:12 +00:00
|
|
|
case actTogglePreview:
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewer() {
|
2020-06-21 13:41:33 +00:00
|
|
|
togglePreview(!t.previewer.enabled)
|
2017-01-27 07:38:42 +00:00
|
|
|
if t.previewer.enabled {
|
|
|
|
valid, list := t.buildPlusList(t.preview.command, false)
|
|
|
|
if valid {
|
2018-09-27 06:27:08 +00:00
|
|
|
t.cancelPreview()
|
2020-06-21 13:41:33 +00:00
|
|
|
t.previewBox.Set(reqPreviewEnqueue,
|
2020-10-18 08:03:33 +00:00
|
|
|
previewRequest{t.preview.command, t.pwindow, list})
|
2017-01-27 07:38:42 +00:00
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-18 14:49:00 +00:00
|
|
|
case actTogglePreviewWrap:
|
|
|
|
if t.hasPreviewWindow() {
|
|
|
|
t.preview.wrap = !t.preview.wrap
|
|
|
|
req(reqPreviewRefresh)
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actToggleSort:
|
|
|
|
t.sort = !t.sort
|
2019-11-10 02:36:22 +00:00
|
|
|
changed = true
|
2016-09-24 17:02:00 +00:00
|
|
|
case actPreviewUp:
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2016-09-24 19:12:44 +00:00
|
|
|
scrollPreview(-1)
|
2016-09-24 17:02:00 +00:00
|
|
|
}
|
|
|
|
case actPreviewDown:
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2016-09-24 19:12:44 +00:00
|
|
|
scrollPreview(1)
|
|
|
|
}
|
|
|
|
case actPreviewPageUp:
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2017-01-07 16:30:31 +00:00
|
|
|
scrollPreview(-t.pwindow.Height())
|
2016-09-24 19:12:44 +00:00
|
|
|
}
|
|
|
|
case actPreviewPageDown:
|
2017-01-23 15:23:16 +00:00
|
|
|
if t.hasPreviewWindow() {
|
2017-01-07 16:30:31 +00:00
|
|
|
scrollPreview(t.pwindow.Height())
|
2016-09-24 17:02:00 +00:00
|
|
|
}
|
2020-10-05 12:58:56 +00:00
|
|
|
case actPreviewHalfPageUp:
|
|
|
|
if t.hasPreviewWindow() {
|
2020-10-06 01:05:57 +00:00
|
|
|
scrollPreview(-t.pwindow.Height() / 2)
|
2020-10-05 12:58:56 +00:00
|
|
|
}
|
|
|
|
case actPreviewHalfPageDown:
|
|
|
|
if t.hasPreviewWindow() {
|
2020-10-06 01:05:57 +00:00
|
|
|
scrollPreview(t.pwindow.Height() / 2)
|
2020-10-05 12:58:56 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actBeginningOfLine:
|
2015-07-23 12:05:33 +00:00
|
|
|
t.cx = 0
|
2015-10-12 17:24:38 +00:00
|
|
|
case actBackwardChar:
|
|
|
|
if t.cx > 0 {
|
|
|
|
t.cx--
|
2015-06-09 14:44:54 +00:00
|
|
|
}
|
2016-05-12 15:51:15 +00:00
|
|
|
case actPrintQuery:
|
|
|
|
req(reqPrintQuery)
|
2020-06-21 13:41:33 +00:00
|
|
|
case actPreview:
|
|
|
|
togglePreview(true)
|
|
|
|
refreshPreview(a.a)
|
2020-06-20 13:04:09 +00:00
|
|
|
case actRefreshPreview:
|
2020-06-21 13:41:33 +00:00
|
|
|
refreshPreview(t.preview.command)
|
2017-12-01 04:08:55 +00:00
|
|
|
case actReplaceQuery:
|
2017-12-03 14:48:59 +00:00
|
|
|
if t.cy >= 0 && t.cy < t.merger.Length() {
|
2017-12-01 04:08:55 +00:00
|
|
|
t.input = t.merger.Get(t.cy).item.text.ToRunes()
|
|
|
|
t.cx = len(t.input)
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actAbort:
|
|
|
|
req(reqQuit)
|
|
|
|
case actDeleteChar:
|
|
|
|
t.delChar()
|
|
|
|
case actDeleteCharEOF:
|
|
|
|
if !t.delChar() && t.cx == 0 {
|
|
|
|
req(reqQuit)
|
2015-06-09 14:44:54 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
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
|
2015-06-09 14:44:54 +00:00
|
|
|
}
|
2020-02-27 17:38:32 +00:00
|
|
|
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--
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actForwardChar:
|
|
|
|
if t.cx < len(t.input) {
|
|
|
|
t.cx++
|
|
|
|
}
|
|
|
|
case actBackwardDeleteChar:
|
2020-06-07 14:07:03 +00:00
|
|
|
beof = len(t.input) == 0
|
2015-10-12 17:24:38 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
|
|
|
|
t.cx--
|
|
|
|
}
|
|
|
|
case actSelectAll:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 {
|
2015-10-12 17:24:38 +00:00
|
|
|
for i := 0; i < t.merger.Length(); i++ {
|
2019-11-02 03:55:26 +00:00
|
|
|
if !t.selectItem(t.merger.Get(i).item) {
|
|
|
|
break
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
|
|
|
case actDeselectAll:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 {
|
2019-11-02 10:41:59 +00:00
|
|
|
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
|
2019-11-02 10:55:05 +00:00
|
|
|
t.deselectItem(t.merger.Get(i).item)
|
2019-11-02 10:41:59 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
|
|
|
case actToggle:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
case actToggleAll:
|
2019-11-02 03:55:26 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-12 17:24:38 +00:00
|
|
|
for i := 0; i < t.merger.Length(); i++ {
|
2019-11-02 03:55:26 +00:00
|
|
|
if _, found := prevIndexes[i]; !found {
|
|
|
|
item := t.merger.Get(i).item
|
|
|
|
if !t.selectItem(item) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
2016-01-13 17:35:43 +00:00
|
|
|
case actToggleIn:
|
2018-06-09 16:41:50 +00:00
|
|
|
if t.layout != layoutDefault {
|
2017-01-21 17:32:49 +00:00
|
|
|
return doAction(action{t: actToggleUp}, mapkey)
|
2016-01-13 17:35:43 +00:00
|
|
|
}
|
2017-01-21 17:32:49 +00:00
|
|
|
return doAction(action{t: actToggleDown}, mapkey)
|
2016-01-13 17:35:43 +00:00
|
|
|
case actToggleOut:
|
2018-06-09 16:41:50 +00:00
|
|
|
if t.layout != layoutDefault {
|
2017-01-21 17:32:49 +00:00
|
|
|
return doAction(action{t: actToggleDown}, mapkey)
|
2016-01-13 17:35:43 +00:00
|
|
|
}
|
2017-01-21 17:32:49 +00:00
|
|
|
return doAction(action{t: actToggleUp}, mapkey)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actToggleDown:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(-1, true)
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
case actToggleUp:
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(1, true)
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
case actDown:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(-1, true)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actUp:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(1, true)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actAccept:
|
|
|
|
req(reqClose)
|
2017-12-01 17:27:02 +00:00
|
|
|
case actAcceptNonEmpty:
|
|
|
|
if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
|
|
|
|
req(reqClose)
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actClearScreen:
|
|
|
|
req(reqRedraw)
|
2019-12-07 05:44:24 +00:00
|
|
|
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)
|
|
|
|
}
|
2017-05-22 08:07:05 +00:00
|
|
|
case actTop:
|
|
|
|
t.vset(0)
|
|
|
|
req(reqList)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actUnixLineDiscard:
|
2020-06-07 14:07:03 +00:00
|
|
|
beof = len(t.input) == 0
|
2015-10-12 17:24:38 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.yanked = copySlice(t.input[:t.cx])
|
|
|
|
t.input = t.input[t.cx:]
|
|
|
|
t.cx = 0
|
2015-07-26 15:06:44 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actUnixWordRubout:
|
2020-06-07 14:07:03 +00:00
|
|
|
beof = len(t.input) == 0
|
2015-10-12 17:24:38 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.rubout("\\s\\S")
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actBackwardKillWord:
|
2020-06-07 14:07:03 +00:00
|
|
|
beof = len(t.input) == 0
|
2015-10-12 17:24:38 +00:00
|
|
|
if t.cx > 0 {
|
2017-01-15 10:42:28 +00:00
|
|
|
t.rubout(t.wordRubout)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
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:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(t.maxItems()-1, false)
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
|
|
|
case actPageDown:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(-(t.maxItems() - 1), false)
|
2015-10-12 17:24:38 +00:00
|
|
|
req(reqList)
|
2017-01-16 02:58:13 +00:00
|
|
|
case actHalfPageUp:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(t.maxItems()/2, false)
|
2017-01-16 02:58:13 +00:00
|
|
|
req(reqList)
|
|
|
|
case actHalfPageDown:
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(-(t.maxItems() / 2), false)
|
2017-01-16 02:58:13 +00:00
|
|
|
req(reqList)
|
2016-05-17 17:06:52 +00:00
|
|
|
case actJump:
|
|
|
|
t.jumping = jumpEnabled
|
|
|
|
req(reqJump)
|
|
|
|
case actJumpAccept:
|
|
|
|
t.jumping = jumpAcceptEnabled
|
|
|
|
req(reqJump)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actBackwardWord:
|
2017-01-15 10:42:28 +00:00
|
|
|
t.cx = findLastMatch(t.wordRubout, string(t.input[:t.cx])) + 1
|
2015-10-12 17:24:38 +00:00
|
|
|
case actForwardWord:
|
2017-01-15 10:42:28 +00:00
|
|
|
t.cx += findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
|
2015-10-12 17:24:38 +00:00
|
|
|
case actKillWord:
|
|
|
|
ncx := t.cx +
|
2017-01-15 10:42:28 +00:00
|
|
|
findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
|
2015-10-12 17:24:38 +00:00
|
|
|
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))
|
2017-08-09 14:25:32 +00:00
|
|
|
t.input = trimQuery(t.history.previous())
|
2015-10-12 17:24:38 +00:00
|
|
|
t.cx = len(t.input)
|
|
|
|
}
|
|
|
|
case actNextHistory:
|
|
|
|
if t.history != nil {
|
|
|
|
t.history.override(string(t.input))
|
2017-08-09 14:25:32 +00:00
|
|
|
t.input = trimQuery(t.history.next())
|
2015-10-12 17:24:38 +00:00
|
|
|
t.cx = len(t.input)
|
|
|
|
}
|
2017-04-28 13:58:08 +00:00
|
|
|
case actSigStop:
|
|
|
|
p, err := os.FindProcess(os.Getpid())
|
|
|
|
if err == nil {
|
|
|
|
t.tui.Clear()
|
|
|
|
t.tui.Pause(t.fullscreen)
|
|
|
|
notifyStop(p)
|
|
|
|
t.mutex.Unlock()
|
|
|
|
return false
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actMouse:
|
|
|
|
me := event.MouseEvent
|
|
|
|
mx, my := me.X, me.Y
|
|
|
|
if me.S != 0 {
|
|
|
|
// Scroll
|
2016-06-11 10:59:12 +00:00
|
|
|
if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.multi > 0 && me.Mod {
|
2015-07-26 14:02:04 +00:00
|
|
|
toggle()
|
|
|
|
}
|
2017-05-23 17:43:39 +00:00
|
|
|
t.vmove(me.S, true)
|
2015-07-26 14:02:04 +00:00
|
|
|
req(reqList)
|
2017-01-23 15:23:16 +00:00
|
|
|
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
|
2016-09-24 19:12:44 +00:00
|
|
|
scrollPreview(-me.S)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2016-06-11 10:59:12 +00:00
|
|
|
} else if t.window.Enclose(my, mx) {
|
2017-01-07 16:30:31 +00:00
|
|
|
mx -= t.window.Left()
|
|
|
|
my -= t.window.Top()
|
2017-05-26 10:02:49 +00:00
|
|
|
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
2015-10-12 17:24:38 +00:00
|
|
|
min := 2 + len(t.header)
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2015-10-12 17:24:38 +00:00
|
|
|
min--
|
|
|
|
}
|
2018-06-09 16:41:50 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
if me.Double {
|
|
|
|
// Double-click
|
|
|
|
if my >= min {
|
|
|
|
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
2017-01-21 17:32:49 +00:00
|
|
|
return doActions(t.keymap[tui.DoubleClick], tui.DoubleClick)
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if me.Down {
|
|
|
|
if my == 0 && mx >= 0 {
|
|
|
|
// Prompt
|
2018-06-25 10:07:12 +00:00
|
|
|
t.cx = mx + t.xoffset
|
2015-10-12 17:24:38 +00:00
|
|
|
} else if my >= min {
|
|
|
|
// List
|
2019-11-02 03:55:26 +00:00
|
|
|
if t.vset(t.offset+my-min) && t.multi > 0 && me.Mod {
|
2015-10-12 17:24:38 +00:00
|
|
|
toggle()
|
|
|
|
}
|
|
|
|
req(reqList)
|
2017-11-30 17:11:20 +00:00
|
|
|
if me.Left {
|
|
|
|
return doActions(t.keymap[tui.LeftClick], tui.LeftClick)
|
|
|
|
}
|
|
|
|
return doActions(t.keymap[tui.RightClick], tui.RightClick)
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2019-11-10 02:36:22 +00:00
|
|
|
case actReload:
|
|
|
|
t.failed = nil
|
|
|
|
|
|
|
|
valid, list := t.buildPlusList(a.a, false)
|
|
|
|
if !valid {
|
2019-12-06 13:34:30 +00:00
|
|
|
// 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
|
2019-11-10 02:36:22 +00:00
|
|
|
}
|
|
|
|
if valid {
|
2020-07-26 15:15:25 +00:00
|
|
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
2019-11-10 02:36:22 +00:00
|
|
|
newCommand = &command
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-10-12 17:36:11 +00:00
|
|
|
mapkey := event.Type
|
2016-05-17 17:06:52 +00:00
|
|
|
if t.jumping == jumpDisabled {
|
2017-01-21 17:32:49 +00:00
|
|
|
actions := t.keymap[mapkey]
|
2016-10-24 00:44:56 +00:00
|
|
|
if mapkey == tui.Rune {
|
|
|
|
mapkey = int(event.Char) + int(tui.AltZ)
|
2016-05-17 17:06:52 +00:00
|
|
|
if act, prs := t.keymap[mapkey]; prs {
|
2017-01-21 17:32:49 +00:00
|
|
|
actions = act
|
2016-05-17 17:06:52 +00:00
|
|
|
}
|
2015-10-12 17:36:11 +00:00
|
|
|
}
|
2017-01-21 17:32:49 +00:00
|
|
|
if !doActions(actions, mapkey) {
|
2016-05-17 17:06:52 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-02-18 14:17:29 +00:00
|
|
|
t.truncateQuery()
|
2019-11-10 02:36:22 +00:00
|
|
|
queryChanged = string(previousInput) != string(t.input)
|
|
|
|
changed = changed || queryChanged
|
|
|
|
if onChanges, prs := t.keymap[tui.Change]; queryChanged && prs {
|
2017-05-22 08:07:05 +00:00
|
|
|
if !doActions(onChanges, tui.Change) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2020-06-07 14:07:03 +00:00
|
|
|
if onEOFs, prs := t.keymap[tui.BackwardEOF]; beof && prs {
|
|
|
|
if !doActions(onEOFs, tui.BackwardEOF) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2016-05-17 17:06:52 +00:00
|
|
|
} else {
|
2016-10-24 00:44:56 +00:00
|
|
|
if mapkey == tui.Rune {
|
2016-05-18 13:45:34 +00:00
|
|
|
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
2016-05-17 17:06:52 +00:00
|
|
|
t.cy = idx + t.offset
|
|
|
|
if t.jumping == jumpAcceptEnabled {
|
|
|
|
req(reqClose)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t.jumping = jumpDisabled
|
|
|
|
req(reqList)
|
2015-10-12 17:36:11 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2019-11-10 02:36:22 +00:00
|
|
|
if queryChanged {
|
2018-06-09 16:40:22 +00:00
|
|
|
if t.isPreviewEnabled() {
|
2019-12-06 13:34:30 +00:00
|
|
|
_, _, q := hasPreviewFlags(t.preview.command)
|
2018-06-09 16:40:22 +00:00
|
|
|
if q {
|
|
|
|
t.version++
|
|
|
|
}
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2019-03-29 06:02:31 +00:00
|
|
|
|
2019-11-10 02:36:22 +00:00
|
|
|
if queryChanged || t.cx != previousCx {
|
2019-03-29 06:02:31 +00:00
|
|
|
req(reqPrompt)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
2019-03-29 17:06:54 +00:00
|
|
|
|
2019-11-10 02:36:22 +00:00
|
|
|
if changed || newCommand != nil {
|
|
|
|
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, command: newCommand})
|
2019-03-29 17:06:54 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
for _, event := range events {
|
|
|
|
t.reqBox.Set(event, nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) constrain() {
|
2015-01-09 16:06:08 +00:00
|
|
|
count := t.merger.Length()
|
2015-04-21 14:50:53 +00:00
|
|
|
height := t.maxItems()
|
2015-01-01 19:49:30 +00:00
|
|
|
diffpos := t.cy - t.offset
|
|
|
|
|
2015-01-12 03:56:17 +00:00
|
|
|
t.cy = util.Constrain(t.cy, 0, count-1)
|
2016-02-17 16:46:18 +00:00
|
|
|
t.offset = util.Constrain(t.offset, t.cy-height+1, t.cy)
|
2015-01-01 19:49:30 +00:00
|
|
|
// Adjustment
|
|
|
|
if count-t.offset < height {
|
2015-01-12 03:56:17 +00:00
|
|
|
t.offset = util.Max(0, count-height)
|
|
|
|
t.cy = util.Constrain(t.offset+diffpos, 0, count-1)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-07-22 04:45:38 +00:00
|
|
|
t.offset = util.Max(0, t.offset)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2017-05-23 17:43:39 +00:00
|
|
|
func (t *Terminal) vmove(o int, allowCycle bool) {
|
2018-06-09 16:41:50 +00:00
|
|
|
if t.layout != layoutDefault {
|
2015-06-16 14:14:57 +00:00
|
|
|
o *= -1
|
|
|
|
}
|
|
|
|
dest := t.cy + o
|
2017-05-23 17:43:39 +00:00
|
|
|
if t.cycle && allowCycle {
|
2015-06-16 14:14:57 +00:00
|
|
|
max := t.merger.Length() - 1
|
|
|
|
if dest > max {
|
|
|
|
if t.cy == max {
|
|
|
|
dest = 0
|
|
|
|
}
|
|
|
|
} else if dest < 0 {
|
|
|
|
if t.cy == 0 {
|
|
|
|
dest = max
|
|
|
|
}
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-06-16 14:14:57 +00:00
|
|
|
t.vset(dest)
|
2015-01-10 05:50:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) vset(o int) bool {
|
2015-01-12 03:56:17 +00:00
|
|
|
t.cy = util.Constrain(o, 0, t.merger.Length()-1)
|
2015-01-10 05:50:24 +00:00
|
|
|
return t.cy == o
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-04-21 14:50:53 +00:00
|
|
|
func (t *Terminal) maxItems() int {
|
2017-01-07 16:30:31 +00:00
|
|
|
max := t.window.Height() - 2 - len(t.header)
|
2019-11-14 15:39:29 +00:00
|
|
|
if t.noInfoLine() {
|
2015-08-02 04:06:15 +00:00
|
|
|
max++
|
2015-04-21 14:50:53 +00:00
|
|
|
}
|
2015-07-22 04:45:38 +00:00
|
|
|
return util.Max(max, 0)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|