2015-01-01 19:49:30 +00:00
|
|
|
package fzf
|
|
|
|
|
|
|
|
import (
|
2015-01-18 07:59:04 +00:00
|
|
|
"bytes"
|
2015-01-01 19:49:30 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2015-06-14 03:25:08 +00:00
|
|
|
"os/exec"
|
2015-01-23 11:30:50 +00:00
|
|
|
"os/signal"
|
2015-01-01 19:49:30 +00:00
|
|
|
"regexp"
|
|
|
|
"sort"
|
2015-07-26 14:02:04 +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
|
|
|
|
|
|
|
C "github.com/junegunn/fzf/src/curses"
|
|
|
|
"github.com/junegunn/fzf/src/util"
|
|
|
|
|
|
|
|
"github.com/junegunn/go-runewidth"
|
2015-01-01 19:49:30 +00:00
|
|
|
)
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// Terminal represents terminal input/output
|
2015-01-01 19:49:30 +00:00
|
|
|
type Terminal struct {
|
2015-04-21 14:50:53 +00:00
|
|
|
inlineInfo bool
|
2015-01-01 19:49:30 +00:00
|
|
|
prompt string
|
|
|
|
reverse bool
|
2015-04-16 03:56:01 +00:00
|
|
|
hscroll bool
|
2015-01-01 19:49:30 +00:00
|
|
|
cx int
|
|
|
|
cy int
|
|
|
|
offset int
|
|
|
|
yanked []rune
|
|
|
|
input []rune
|
|
|
|
multi bool
|
2015-04-16 05:44:41 +00:00
|
|
|
sort bool
|
2015-05-20 12:25:15 +00:00
|
|
|
toggleSort bool
|
2015-06-18 15:31:48 +00:00
|
|
|
expect map[int]string
|
2015-05-20 12:25:15 +00:00
|
|
|
keymap map[int]actionType
|
2015-06-14 03:25:08 +00:00
|
|
|
execmap map[int]string
|
2015-06-18 15:31:48 +00:00
|
|
|
pressed string
|
2015-01-01 19:49:30 +00:00
|
|
|
printQuery bool
|
2015-06-13 15:43:44 +00:00
|
|
|
history *History
|
2015-06-16 14:14:57 +00:00
|
|
|
cycle bool
|
2015-07-21 15:19:37 +00:00
|
|
|
header []string
|
2015-09-15 10:04:53 +00:00
|
|
|
header0 []string
|
2015-08-28 12:23:10 +00:00
|
|
|
ansi bool
|
2015-07-26 14:02:04 +00:00
|
|
|
margin [4]string
|
|
|
|
marginInt [4]int
|
2015-01-01 19:49:30 +00:00
|
|
|
count int
|
|
|
|
progress int
|
|
|
|
reading bool
|
2015-01-09 16:06:08 +00:00
|
|
|
merger *Merger
|
2016-01-12 18:07:42 +00:00
|
|
|
selected map[int32]selectedItem
|
2015-01-12 03:56:17 +00:00
|
|
|
reqBox *util.EventBox
|
|
|
|
eventBox *util.EventBox
|
2015-01-01 19:49:30 +00:00
|
|
|
mutex sync.Mutex
|
|
|
|
initFunc func()
|
2015-01-07 03:46:45 +00:00
|
|
|
suppress bool
|
2015-02-17 15:08:17 +00:00
|
|
|
startChan chan bool
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-17 02:07:04 +00:00
|
|
|
type selectedItem struct {
|
|
|
|
at time.Time
|
|
|
|
text *string
|
|
|
|
}
|
|
|
|
|
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-11 18:01:24 +00:00
|
|
|
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
2015-01-24 03:28:00 +00:00
|
|
|
var _runeWidths = make(map[rune]int)
|
2015-11-30 08:35:03 +00:00
|
|
|
var _tabStop int
|
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
|
|
|
|
reqRefresh
|
|
|
|
reqRedraw
|
|
|
|
reqClose
|
|
|
|
reqQuit
|
2015-01-01 19:49:30 +00:00
|
|
|
)
|
|
|
|
|
2015-05-20 12:25:15 +00:00
|
|
|
type actionType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
actIgnore actionType = iota
|
|
|
|
actInvalid
|
|
|
|
actRune
|
|
|
|
actMouse
|
|
|
|
actBeginningOfLine
|
|
|
|
actAbort
|
|
|
|
actAccept
|
|
|
|
actBackwardChar
|
|
|
|
actBackwardDeleteChar
|
|
|
|
actBackwardWord
|
2015-07-23 12:05:33 +00:00
|
|
|
actCancel
|
2015-05-20 12:25:15 +00:00
|
|
|
actClearScreen
|
|
|
|
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
|
|
|
|
actDown
|
|
|
|
actUp
|
|
|
|
actPageUp
|
|
|
|
actPageDown
|
|
|
|
actToggleSort
|
2015-06-13 15:43:44 +00:00
|
|
|
actPreviousHistory
|
|
|
|
actNextHistory
|
2015-06-14 03:25:08 +00:00
|
|
|
actExecute
|
2015-11-08 16:42:01 +00:00
|
|
|
actExecuteMulti
|
2015-05-20 12:25:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func defaultKeymap() map[int]actionType {
|
|
|
|
keymap := make(map[int]actionType)
|
|
|
|
keymap[C.Invalid] = actInvalid
|
|
|
|
keymap[C.CtrlA] = actBeginningOfLine
|
|
|
|
keymap[C.CtrlB] = actBackwardChar
|
|
|
|
keymap[C.CtrlC] = actAbort
|
|
|
|
keymap[C.CtrlG] = actAbort
|
|
|
|
keymap[C.CtrlQ] = actAbort
|
|
|
|
keymap[C.ESC] = actAbort
|
2015-08-02 04:06:15 +00:00
|
|
|
keymap[C.CtrlD] = actDeleteCharEOF
|
2015-05-20 12:25:15 +00:00
|
|
|
keymap[C.CtrlE] = actEndOfLine
|
|
|
|
keymap[C.CtrlF] = actForwardChar
|
|
|
|
keymap[C.CtrlH] = actBackwardDeleteChar
|
2015-06-15 17:18:49 +00:00
|
|
|
keymap[C.BSpace] = actBackwardDeleteChar
|
2015-05-20 12:25:15 +00:00
|
|
|
keymap[C.Tab] = actToggleDown
|
|
|
|
keymap[C.BTab] = actToggleUp
|
|
|
|
keymap[C.CtrlJ] = actDown
|
|
|
|
keymap[C.CtrlK] = actUp
|
|
|
|
keymap[C.CtrlL] = actClearScreen
|
|
|
|
keymap[C.CtrlM] = actAccept
|
|
|
|
keymap[C.CtrlN] = actDown
|
|
|
|
keymap[C.CtrlP] = actUp
|
|
|
|
keymap[C.CtrlU] = actUnixLineDiscard
|
|
|
|
keymap[C.CtrlW] = actUnixWordRubout
|
|
|
|
keymap[C.CtrlY] = actYank
|
|
|
|
|
|
|
|
keymap[C.AltB] = actBackwardWord
|
|
|
|
keymap[C.SLeft] = actBackwardWord
|
|
|
|
keymap[C.AltF] = actForwardWord
|
|
|
|
keymap[C.SRight] = actForwardWord
|
|
|
|
keymap[C.AltD] = actKillWord
|
|
|
|
keymap[C.AltBS] = actBackwardKillWord
|
|
|
|
|
|
|
|
keymap[C.Up] = actUp
|
|
|
|
keymap[C.Down] = actDown
|
|
|
|
keymap[C.Left] = actBackwardChar
|
|
|
|
keymap[C.Right] = actForwardChar
|
|
|
|
|
|
|
|
keymap[C.Home] = actBeginningOfLine
|
|
|
|
keymap[C.End] = actEndOfLine
|
2015-07-22 15:56:03 +00:00
|
|
|
keymap[C.Del] = actDeleteChar
|
2015-05-20 12:25:15 +00:00
|
|
|
keymap[C.PgUp] = actPageUp
|
|
|
|
keymap[C.PgDn] = actPageDown
|
|
|
|
|
|
|
|
keymap[C.Rune] = actRune
|
|
|
|
keymap[C.Mouse] = actMouse
|
2015-10-12 17:24:38 +00:00
|
|
|
keymap[C.DoubleClick] = actAccept
|
2015-05-20 12:25:15 +00:00
|
|
|
return keymap
|
|
|
|
}
|
|
|
|
|
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 {
|
2015-01-01 19:49:30 +00:00
|
|
|
input := []rune(opts.Query)
|
2015-09-15 10:04:53 +00:00
|
|
|
var header []string
|
|
|
|
if opts.Reverse {
|
|
|
|
header = opts.Header
|
|
|
|
} else {
|
|
|
|
header = reverseStringArray(opts.Header)
|
|
|
|
}
|
2015-11-30 08:35:03 +00:00
|
|
|
_tabStop = opts.Tabstop
|
2015-01-01 19:49:30 +00:00
|
|
|
return &Terminal{
|
2015-04-21 14:50:53 +00:00
|
|
|
inlineInfo: opts.InlineInfo,
|
2015-01-01 19:49:30 +00:00
|
|
|
prompt: opts.Prompt,
|
|
|
|
reverse: opts.Reverse,
|
2015-04-16 03:56:01 +00:00
|
|
|
hscroll: opts.Hscroll,
|
2015-01-24 04:26:33 +00:00
|
|
|
cx: len(input),
|
2015-01-01 19:49:30 +00:00
|
|
|
cy: 0,
|
|
|
|
offset: 0,
|
|
|
|
yanked: []rune{},
|
|
|
|
input: input,
|
|
|
|
multi: opts.Multi,
|
2015-04-16 05:44:41 +00:00
|
|
|
sort: opts.Sort > 0,
|
2015-03-31 13:05:02 +00:00
|
|
|
toggleSort: opts.ToggleSort,
|
2015-03-28 17:59:32 +00:00
|
|
|
expect: opts.Expect,
|
2015-05-20 12:25:15 +00:00
|
|
|
keymap: opts.Keymap,
|
2015-06-14 03:25:08 +00:00
|
|
|
execmap: opts.Execmap,
|
2015-06-18 15:31:48 +00:00
|
|
|
pressed: "",
|
2015-01-01 19:49:30 +00:00
|
|
|
printQuery: opts.PrintQuery,
|
2015-06-13 15:43:44 +00:00
|
|
|
history: opts.History,
|
2015-07-26 14:02:04 +00:00
|
|
|
margin: opts.Margin,
|
|
|
|
marginInt: [4]int{0, 0, 0, 0},
|
2015-06-16 14:14:57 +00:00
|
|
|
cycle: opts.Cycle,
|
2015-09-15 10:04:53 +00:00
|
|
|
header: header,
|
|
|
|
header0: header,
|
2015-08-28 12:23:10 +00:00
|
|
|
ansi: opts.Ansi,
|
2015-06-17 15:42:38 +00:00
|
|
|
reading: true,
|
2015-01-09 16:06:08 +00:00
|
|
|
merger: EmptyMerger,
|
2016-01-12 18:07:42 +00:00
|
|
|
selected: make(map[int32]selectedItem),
|
2015-01-12 03:56:17 +00:00
|
|
|
reqBox: util.NewEventBox(),
|
2015-01-01 19:49:30 +00:00
|
|
|
eventBox: eventBox,
|
|
|
|
mutex: sync.Mutex{},
|
2015-01-07 03:46:45 +00:00
|
|
|
suppress: true,
|
2015-02-17 15:08:17 +00:00
|
|
|
startChan: make(chan bool, 1),
|
2015-01-01 19:49:30 +00:00
|
|
|
initFunc: func() {
|
2015-04-17 17:52:30 +00:00
|
|
|
C.Init(opts.Theme, opts.Black, opts.Mouse)
|
2015-01-01 19:49:30 +00:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
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
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) UpdateCount(cnt int, final bool) {
|
|
|
|
t.mutex.Lock()
|
|
|
|
t.count = cnt
|
|
|
|
t.reading = !final
|
|
|
|
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
|
2015-01-09 16:06:08 +00:00
|
|
|
func (t *Terminal) UpdateList(merger *Merger) {
|
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
|
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 {
|
|
|
|
fmt.Println(string(t.input))
|
|
|
|
}
|
2015-03-28 17:59:32 +00:00
|
|
|
if len(t.expect) > 0 {
|
2015-06-18 15:31:48 +00:00
|
|
|
fmt.Println(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 {
|
2015-01-14 21:06:22 +00:00
|
|
|
cnt := t.merger.Length()
|
|
|
|
if cnt > 0 && cnt > t.cy {
|
2015-08-28 12:23:10 +00:00
|
|
|
fmt.Println(t.merger.Get(t.cy).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() {
|
2015-01-17 02:07:04 +00:00
|
|
|
fmt.Println(*sel.text)
|
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
|
|
|
|
}
|
|
|
|
|
2015-01-18 07:59:04 +00:00
|
|
|
func runeWidth(r rune, prefixWidth int) int {
|
|
|
|
if r == '\t' {
|
2015-11-30 08:35:03 +00:00
|
|
|
return _tabStop - prefixWidth%_tabStop
|
2015-01-24 03:28:00 +00:00
|
|
|
} else if w, found := _runeWidths[r]; found {
|
|
|
|
return w
|
2015-01-18 07:59:04 +00:00
|
|
|
} else {
|
2015-01-24 03:28:00 +00:00
|
|
|
w := runewidth.RuneWidth(r)
|
|
|
|
_runeWidths[r] = w
|
|
|
|
return w
|
2015-01-18 07:59:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func displayWidth(runes []rune) int {
|
|
|
|
l := 0
|
|
|
|
for _, r := range runes {
|
2015-01-18 07:59:04 +00:00
|
|
|
l += runeWidth(r, l)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
2015-07-26 14:02:04 +00:00
|
|
|
const minWidth = 16
|
|
|
|
const minHeight = 4
|
|
|
|
|
|
|
|
func (t *Terminal) calculateMargins() {
|
|
|
|
screenWidth := C.MaxX()
|
|
|
|
screenHeight := C.MaxY()
|
|
|
|
for idx, str := range t.margin {
|
|
|
|
if str == "0" {
|
|
|
|
t.marginInt[idx] = 0
|
|
|
|
} else if strings.HasSuffix(str, "%") {
|
|
|
|
num, _ := strconv.ParseFloat(str[:len(str)-1], 64)
|
|
|
|
var val float64
|
|
|
|
if idx%2 == 0 {
|
|
|
|
val = float64(screenHeight)
|
|
|
|
} else {
|
|
|
|
val = float64(screenWidth)
|
|
|
|
}
|
|
|
|
t.marginInt[idx] = int(val * num * 0.01)
|
|
|
|
} else {
|
|
|
|
num, _ := strconv.Atoi(str)
|
|
|
|
t.marginInt[idx] = num
|
|
|
|
}
|
|
|
|
}
|
|
|
|
adjust := func(idx1 int, idx2 int, max int, min int) {
|
|
|
|
if max >= min {
|
|
|
|
margin := t.marginInt[idx1] + t.marginInt[idx2]
|
|
|
|
if max-margin < min {
|
|
|
|
desired := max - min
|
|
|
|
t.marginInt[idx1] = desired * t.marginInt[idx1] / margin
|
|
|
|
t.marginInt[idx2] = desired * t.marginInt[idx2] / margin
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
adjust(1, 3, screenWidth, minWidth)
|
|
|
|
adjust(0, 2, screenHeight, minHeight)
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
func (t *Terminal) move(y int, x int, clear bool) {
|
2015-07-26 14:02:04 +00:00
|
|
|
x += t.marginInt[3]
|
2015-01-01 19:49:30 +00:00
|
|
|
maxy := C.MaxY()
|
|
|
|
if !t.reverse {
|
2015-07-26 14:02:04 +00:00
|
|
|
y = maxy - y - 1 - t.marginInt[2]
|
|
|
|
} else {
|
|
|
|
y += t.marginInt[0]
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if clear {
|
|
|
|
C.MoveAndClear(y, x)
|
|
|
|
} else {
|
|
|
|
C.Move(y, x)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) placeCursor() {
|
2015-11-09 13:01:40 +00:00
|
|
|
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input[:t.cx]), false)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printPrompt() {
|
|
|
|
t.move(0, 0, true)
|
2015-01-11 18:01:24 +00:00
|
|
|
C.CPrint(C.ColPrompt, true, t.prompt)
|
|
|
|
C.CPrint(C.ColNormal, true, string(t.input))
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printInfo() {
|
2015-04-21 14:50:53 +00:00
|
|
|
if t.inlineInfo {
|
2015-11-09 13:01:40 +00:00
|
|
|
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
|
2015-04-21 14:50:53 +00:00
|
|
|
if t.reading {
|
|
|
|
C.CPrint(C.ColSpinner, true, " < ")
|
|
|
|
} else {
|
|
|
|
C.CPrint(C.ColPrompt, true, " < ")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
t.move(1, 0, true)
|
|
|
|
if t.reading {
|
|
|
|
duration := int64(spinnerDuration)
|
|
|
|
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
|
|
|
C.CPrint(C.ColSpinner, true, _spinner[idx])
|
|
|
|
}
|
|
|
|
t.move(1, 2, false)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-09 16:06:08 +00:00
|
|
|
output := fmt.Sprintf("%d/%d", t.merger.Length(), t.count)
|
2015-05-20 12:25:15 +00:00
|
|
|
if t.toggleSort {
|
2015-04-16 05:44:41 +00:00
|
|
|
if t.sort {
|
|
|
|
output += "/S"
|
|
|
|
} else {
|
|
|
|
output += " "
|
|
|
|
}
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
if t.multi && len(t.selected) > 0 {
|
|
|
|
output += fmt.Sprintf(" (%d)", len(t.selected))
|
|
|
|
}
|
|
|
|
if t.progress > 0 && t.progress < 100 {
|
|
|
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
C.CPrint(C.ColInfo, false, output)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-07-26 14:02:04 +00:00
|
|
|
func (t *Terminal) maxHeight() int {
|
|
|
|
return C.MaxY() - t.marginInt[0] - t.marginInt[2]
|
|
|
|
}
|
|
|
|
|
2015-07-21 15:19:37 +00:00
|
|
|
func (t *Terminal) printHeader() {
|
|
|
|
if len(t.header) == 0 {
|
|
|
|
return
|
|
|
|
}
|
2015-07-26 14:02:04 +00:00
|
|
|
max := t.maxHeight()
|
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
|
|
|
|
if t.inlineInfo {
|
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
|
|
|
}
|
2015-08-02 05:00:18 +00:00
|
|
|
trimmed, colors, newState := extractColor(lineStr, state)
|
2015-07-22 05:19:45 +00:00
|
|
|
state = newState
|
2015-07-21 15:19:37 +00:00
|
|
|
item := &Item{
|
2015-08-02 05:00:18 +00:00
|
|
|
text: []rune(trimmed),
|
2015-07-21 15:19:37 +00:00
|
|
|
index: 0,
|
|
|
|
colors: colors,
|
2016-01-12 18:07:42 +00:00
|
|
|
rank: buildEmptyRank(0)}
|
2015-07-21 15:19:37 +00:00
|
|
|
|
|
|
|
t.move(line, 2, true)
|
|
|
|
t.printHighlighted(item, false, C.ColHeader, 0, false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
2015-01-01 19:49:30 +00:00
|
|
|
for i := 0; i < maxy; i++ {
|
2015-07-21 15:19:37 +00:00
|
|
|
line := i + 2 + len(t.header)
|
2015-04-21 14:50:53 +00:00
|
|
|
if t.inlineInfo {
|
2015-08-02 04:06:15 +00:00
|
|
|
line--
|
2015-04-21 14:50:53 +00:00
|
|
|
}
|
|
|
|
t.move(line, 0, true)
|
2015-01-01 19:49:30 +00:00
|
|
|
if i < count {
|
2015-02-25 16:42:15 +00:00
|
|
|
t.printItem(t.merger.Get(i+t.offset), i == t.cy-t.offset)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printItem(item *Item, current bool) {
|
2015-04-17 13:23:52 +00:00
|
|
|
_, selected := t.selected[item.index]
|
2015-01-01 19:49:30 +00:00
|
|
|
if current {
|
2015-01-11 18:01:24 +00:00
|
|
|
C.CPrint(C.ColCursor, true, ">")
|
2015-01-01 19:49:30 +00:00
|
|
|
if selected {
|
2015-06-07 14:32:07 +00:00
|
|
|
C.CPrint(C.ColSelected, true, ">")
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2015-01-11 18:01:24 +00:00
|
|
|
C.CPrint(C.ColCurrent, true, " ")
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch, true)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2015-01-11 18:01:24 +00:00
|
|
|
C.CPrint(C.ColCursor, true, " ")
|
2015-01-01 19:49:30 +00:00
|
|
|
if selected {
|
2015-01-11 18:01:24 +00:00
|
|
|
C.CPrint(C.ColSelected, true, ">")
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
|
|
|
C.Print(" ")
|
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
t.printHighlighted(item, false, 0, C.ColMatch, false)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func 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 {
|
|
|
|
l += runeWidth(r, l)
|
|
|
|
if idx > 0 && l > width {
|
|
|
|
return runes[:idx], len(runes) - idx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return runes, 0
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2015-01-18 07:59:04 +00:00
|
|
|
func displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {
|
|
|
|
l := 0
|
|
|
|
for _, r := range runes {
|
|
|
|
l += runeWidth(r, l+prefixWidth)
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2015-01-08 17:37:08 +00:00
|
|
|
func trimLeft(runes []rune, width int) ([]rune, int32) {
|
2015-01-01 19:49:30 +00:00
|
|
|
currentWidth := 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++
|
2015-01-18 07:59:04 +00:00
|
|
|
currentWidth = displayWidthWithLimit(runes, 2, width)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
return runes, trimmed
|
|
|
|
}
|
|
|
|
|
2015-04-16 03:56:01 +00:00
|
|
|
func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, current bool) {
|
2015-01-11 18:01:24 +00:00
|
|
|
var maxe int32
|
2015-01-01 19:49:30 +00:00
|
|
|
for _, offset := range item.offsets {
|
|
|
|
if offset[1] > maxe {
|
|
|
|
maxe = offset[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Overflow
|
2015-08-02 15:14:34 +00:00
|
|
|
text := make([]rune, len(item.text))
|
|
|
|
copy(text, item.text)
|
2015-03-22 07:05:54 +00:00
|
|
|
offsets := item.colorOffsets(col2, bold, current)
|
2015-07-26 14:02:04 +00:00
|
|
|
maxWidth := C.MaxX() - 3 - t.marginInt[1] - t.marginInt[3]
|
2015-01-01 19:49:30 +00:00
|
|
|
fullWidth := displayWidth(text)
|
|
|
|
if fullWidth > maxWidth {
|
2015-04-16 03:56:01 +00:00
|
|
|
if t.hscroll {
|
2015-01-01 19:49:30 +00:00
|
|
|
// Stri..
|
2015-04-16 03:56:01 +00:00
|
|
|
matchEndWidth := displayWidth(text[:maxe])
|
|
|
|
if matchEndWidth <= maxWidth-2 {
|
|
|
|
text, _ = trimRight(text, maxWidth-2)
|
|
|
|
text = append(text, []rune("..")...)
|
|
|
|
} else {
|
|
|
|
// Stri..
|
|
|
|
if matchEndWidth < fullWidth-2 {
|
|
|
|
text = append(text[:maxe], []rune("..")...)
|
|
|
|
}
|
|
|
|
// ..ri..
|
|
|
|
var diff int32
|
|
|
|
text, diff = trimLeft(text, maxWidth-2)
|
|
|
|
|
|
|
|
// Transform offsets
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
text = append([]rune(".."), text...)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-04-16 03:56:01 +00:00
|
|
|
} else {
|
|
|
|
text, _ = trimRight(text, maxWidth-2)
|
|
|
|
text = append(text, []rune("..")...)
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
substr, prefixWidth = processTabs(text[index:b], prefixWidth)
|
|
|
|
C.CPrint(col1, bold, substr)
|
|
|
|
|
2015-03-19 03:14:26 +00:00
|
|
|
if b < e {
|
|
|
|
substr, prefixWidth = processTabs(text[b:e], prefixWidth)
|
2015-03-22 08:43:28 +00:00
|
|
|
C.CPrint(offset.color, offset.bold, 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 {
|
2015-01-18 07:59:04 +00:00
|
|
|
substr, _ = processTabs(text[index:], prefixWidth)
|
|
|
|
C.CPrint(col1, bold, substr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func processTabs(runes []rune, prefixWidth int) (string, int) {
|
|
|
|
var strbuf bytes.Buffer
|
|
|
|
l := prefixWidth
|
|
|
|
for _, r := range runes {
|
|
|
|
w := runeWidth(r, l)
|
|
|
|
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() {
|
2015-07-26 14:02:04 +00:00
|
|
|
t.calculateMargins()
|
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()
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) refresh() {
|
2015-01-07 03:46:45 +00:00
|
|
|
if !t.suppress {
|
|
|
|
C.Refresh()
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
return locs[len(locs)-1][0]
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
return loc[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
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...)
|
|
|
|
}
|
|
|
|
|
2015-03-31 13:05:02 +00:00
|
|
|
func keyMatch(key int, event C.Event) bool {
|
|
|
|
return event.Type == key || event.Type == C.Rune && int(event.Char) == key-C.AltZ
|
|
|
|
}
|
|
|
|
|
2015-11-08 16:42:01 +00:00
|
|
|
func quoteEntry(entry string) string {
|
|
|
|
return fmt.Sprintf("%q", entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
func executeCommand(template string, replacement string) {
|
|
|
|
command := strings.Replace(template, "{}", replacement, -1)
|
2015-06-14 03:25:08 +00:00
|
|
|
cmd := exec.Command("sh", "-c", command)
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
C.Endwin()
|
|
|
|
cmd.Run()
|
|
|
|
C.Refresh()
|
|
|
|
}
|
|
|
|
|
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() {
|
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)
|
|
|
|
signal.Notify(intChan, os.Interrupt, os.Kill)
|
|
|
|
go func() {
|
|
|
|
<-intChan
|
|
|
|
t.reqBox.Set(reqQuit, nil)
|
|
|
|
}()
|
|
|
|
|
2015-01-23 11:30:50 +00:00
|
|
|
resizeChan := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(resizeChan, syscall.SIGWINCH)
|
|
|
|
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()
|
|
|
|
t.calculateMargins()
|
|
|
|
t.printPrompt()
|
|
|
|
t.placeCursor()
|
|
|
|
C.Refresh()
|
|
|
|
t.printInfo()
|
|
|
|
t.printHeader()
|
|
|
|
t.mutex.Unlock()
|
|
|
|
go func() {
|
|
|
|
timer := time.NewTimer(initialDelay)
|
|
|
|
<-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()
|
|
|
|
if !reading {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(spinnerDuration)
|
|
|
|
t.reqBox.Set(reqInfo, nil)
|
|
|
|
}
|
|
|
|
}()
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-06-13 15:43:44 +00:00
|
|
|
exit := func(code int) {
|
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))
|
|
|
|
}
|
|
|
|
os.Exit(code)
|
|
|
|
}
|
|
|
|
|
2015-01-01 19:49:30 +00:00
|
|
|
go func() {
|
|
|
|
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()
|
|
|
|
for req := range *events {
|
|
|
|
switch req {
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqPrompt:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printPrompt()
|
2015-04-21 14:50:53 +00:00
|
|
|
if t.inlineInfo {
|
|
|
|
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()
|
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
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqRedraw:
|
2015-01-01 19:49:30 +00:00
|
|
|
C.Clear()
|
2015-01-23 11:30:50 +00:00
|
|
|
C.Endwin()
|
|
|
|
C.Refresh()
|
2015-01-01 19:49:30 +00:00
|
|
|
t.printAll()
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqClose:
|
2015-01-01 19:49:30 +00:00
|
|
|
C.Close()
|
2015-09-15 04:21:51 +00:00
|
|
|
if t.output() {
|
|
|
|
exit(exitOk)
|
|
|
|
}
|
|
|
|
exit(exitNoMatch)
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqQuit:
|
2015-01-01 19:49:30 +00:00
|
|
|
C.Close()
|
2015-09-18 01:25:07 +00:00
|
|
|
exit(exitInterrupt)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
2015-01-03 20:09:40 +00:00
|
|
|
t.placeCursor()
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
|
|
|
})
|
|
|
|
t.refresh()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
looping := true
|
|
|
|
for looping {
|
|
|
|
event := C.GetChar()
|
|
|
|
|
|
|
|
t.mutex.Lock()
|
|
|
|
previousInput := t.input
|
2015-01-12 03:56:17 +00:00
|
|
|
events := []util.EventType{reqPrompt}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-06-09 14:44:54 +00:00
|
|
|
selectItem := func(item *Item) bool {
|
|
|
|
if _, found := t.selected[item.index]; !found {
|
2015-08-28 12:23:10 +00:00
|
|
|
t.selected[item.index] = selectedItem{time.Now(), item.StringPtr(t.ansi)}
|
2015-06-09 14:44:54 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
toggleY := func(y int) {
|
|
|
|
item := t.merger.Get(y)
|
|
|
|
if !selectItem(item) {
|
|
|
|
delete(t.selected, item.index)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
toggle := func() {
|
|
|
|
if t.cy < t.merger.Length() {
|
|
|
|
toggleY(t.cy)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqInfo)
|
2015-01-10 03:21:17 +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
|
2015-03-28 17:59:32 +00:00
|
|
|
req(reqClose)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2015-05-20 12:25:15 +00:00
|
|
|
|
2015-10-12 17:36:11 +00:00
|
|
|
var doAction func(actionType, int) bool
|
|
|
|
doAction = func(action actionType, mapkey int) bool {
|
2015-10-12 17:24:38 +00:00
|
|
|
switch action {
|
|
|
|
case actIgnore:
|
|
|
|
case actExecute:
|
|
|
|
if t.cy >= 0 && t.cy < t.merger.Length() {
|
|
|
|
item := t.merger.Get(t.cy)
|
2015-11-08 16:42:01 +00:00
|
|
|
executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
|
|
|
|
}
|
|
|
|
case actExecuteMulti:
|
|
|
|
if len(t.selected) > 0 {
|
|
|
|
sels := make([]string, len(t.selected))
|
|
|
|
for i, sel := range t.sortSelected() {
|
|
|
|
sels[i] = quoteEntry(*sel.text)
|
|
|
|
}
|
|
|
|
executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
|
|
|
|
} else {
|
|
|
|
return doAction(actExecute, mapkey)
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
|
|
|
case actInvalid:
|
|
|
|
t.mutex.Unlock()
|
|
|
|
return false
|
|
|
|
case actToggleSort:
|
|
|
|
t.sort = !t.sort
|
|
|
|
t.eventBox.Set(EvtSearchNew, t.sort)
|
|
|
|
t.mutex.Unlock()
|
|
|
|
return false
|
|
|
|
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
|
|
|
}
|
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
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
case actForwardChar:
|
|
|
|
if t.cx < len(t.input) {
|
|
|
|
t.cx++
|
|
|
|
}
|
|
|
|
case actBackwardDeleteChar:
|
|
|
|
if t.cx > 0 {
|
|
|
|
t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
|
|
|
|
t.cx--
|
|
|
|
}
|
|
|
|
case actSelectAll:
|
|
|
|
if t.multi {
|
|
|
|
for i := 0; i < t.merger.Length(); i++ {
|
|
|
|
item := t.merger.Get(i)
|
|
|
|
selectItem(item)
|
|
|
|
}
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
|
|
|
case actDeselectAll:
|
|
|
|
if t.multi {
|
|
|
|
for i := 0; i < t.merger.Length(); i++ {
|
|
|
|
item := t.merger.Get(i)
|
|
|
|
delete(t.selected, item.index)
|
|
|
|
}
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
|
|
|
case actToggle:
|
|
|
|
if t.multi && t.merger.Length() > 0 {
|
|
|
|
toggle()
|
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
case actToggleAll:
|
|
|
|
if t.multi {
|
|
|
|
for i := 0; i < t.merger.Length(); i++ {
|
|
|
|
toggleY(i)
|
|
|
|
}
|
|
|
|
req(reqList, reqInfo)
|
|
|
|
}
|
|
|
|
case actToggleDown:
|
|
|
|
if t.multi && t.merger.Length() > 0 {
|
|
|
|
toggle()
|
|
|
|
t.vmove(-1)
|
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
case actToggleUp:
|
|
|
|
if t.multi && t.merger.Length() > 0 {
|
|
|
|
toggle()
|
|
|
|
t.vmove(1)
|
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
case actDown:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.vmove(-1)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actUp:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.vmove(1)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
2015-10-12 17:24:38 +00:00
|
|
|
case actAccept:
|
|
|
|
req(reqClose)
|
|
|
|
case actClearScreen:
|
|
|
|
req(reqRedraw)
|
|
|
|
case actUnixLineDiscard:
|
|
|
|
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:
|
|
|
|
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:
|
|
|
|
if t.cx > 0 {
|
|
|
|
t.rubout("[^[:alnum:]][[:alnum:]]")
|
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:
|
|
|
|
t.vmove(t.maxItems() - 1)
|
|
|
|
req(reqList)
|
|
|
|
case actPageDown:
|
|
|
|
t.vmove(-(t.maxItems() - 1))
|
|
|
|
req(reqList)
|
|
|
|
case actBackwardWord:
|
|
|
|
t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1
|
|
|
|
case actForwardWord:
|
|
|
|
t.cx += findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1
|
|
|
|
case actKillWord:
|
|
|
|
ncx := t.cx +
|
|
|
|
findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1
|
|
|
|
if ncx > t.cx {
|
|
|
|
t.yanked = copySlice(t.input[t.cx:ncx])
|
|
|
|
t.input = append(t.input[:t.cx], t.input[ncx:]...)
|
|
|
|
}
|
|
|
|
case actKillLine:
|
|
|
|
if t.cx < len(t.input) {
|
|
|
|
t.yanked = copySlice(t.input[t.cx:])
|
|
|
|
t.input = t.input[:t.cx]
|
|
|
|
}
|
|
|
|
case actRune:
|
|
|
|
prefix := copySlice(t.input[:t.cx])
|
|
|
|
t.input = append(append(prefix, event.Char), t.input[t.cx:]...)
|
|
|
|
t.cx++
|
|
|
|
case actPreviousHistory:
|
|
|
|
if t.history != nil {
|
|
|
|
t.history.override(string(t.input))
|
|
|
|
t.input = []rune(t.history.previous())
|
|
|
|
t.cx = len(t.input)
|
|
|
|
}
|
|
|
|
case actNextHistory:
|
|
|
|
if t.history != nil {
|
|
|
|
t.history.override(string(t.input))
|
|
|
|
t.input = []rune(t.history.next())
|
|
|
|
t.cx = len(t.input)
|
|
|
|
}
|
|
|
|
case actMouse:
|
|
|
|
me := event.MouseEvent
|
|
|
|
mx, my := me.X, me.Y
|
|
|
|
if me.S != 0 {
|
|
|
|
// Scroll
|
|
|
|
if t.merger.Length() > 0 {
|
|
|
|
if t.multi && me.Mod {
|
2015-07-26 14:02:04 +00:00
|
|
|
toggle()
|
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
t.vmove(me.S)
|
2015-07-26 14:02:04 +00:00
|
|
|
req(reqList)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-10-12 17:24:38 +00:00
|
|
|
} else if mx >= t.marginInt[3] && mx < C.MaxX()-t.marginInt[1] &&
|
|
|
|
my >= t.marginInt[0] && my < C.MaxY()-t.marginInt[2] {
|
|
|
|
mx -= t.marginInt[3]
|
|
|
|
my -= t.marginInt[0]
|
2015-11-09 13:01:40 +00:00
|
|
|
mx = util.Constrain(mx-displayWidth([]rune(t.prompt)), 0, len(t.input))
|
2015-10-12 17:24:38 +00:00
|
|
|
if !t.reverse {
|
|
|
|
my = t.maxHeight() - my - 1
|
|
|
|
}
|
|
|
|
min := 2 + len(t.header)
|
|
|
|
if t.inlineInfo {
|
|
|
|
min--
|
|
|
|
}
|
|
|
|
if me.Double {
|
|
|
|
// Double-click
|
|
|
|
if my >= min {
|
|
|
|
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
2015-10-12 17:36:11 +00:00
|
|
|
return doAction(t.keymap[C.DoubleClick], C.DoubleClick)
|
2015-10-12 17:24:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if me.Down {
|
|
|
|
if my == 0 && mx >= 0 {
|
|
|
|
// Prompt
|
|
|
|
t.cx = mx
|
|
|
|
} else if my >= min {
|
|
|
|
// List
|
|
|
|
if t.vset(t.offset+my-min) && t.multi && me.Mod {
|
|
|
|
toggle()
|
|
|
|
}
|
|
|
|
req(reqList)
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
action := t.keymap[event.Type]
|
|
|
|
mapkey := event.Type
|
|
|
|
if event.Type == C.Rune {
|
|
|
|
mapkey = int(event.Char) + int(C.AltZ)
|
|
|
|
if act, prs := t.keymap[mapkey]; prs {
|
|
|
|
action = act
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !doAction(action, mapkey) {
|
2015-10-12 17:24:38 +00:00
|
|
|
continue
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
changed := string(previousInput) != string(t.input)
|
|
|
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
|
|
|
|
|
|
|
if changed {
|
2015-04-16 13:13:31 +00:00
|
|
|
t.eventBox.Set(EvtSearchNew, t.sort)
|
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)
|
2015-01-01 19:49:30 +00:00
|
|
|
|
|
|
|
if t.cy > t.offset+(height-1) {
|
|
|
|
// Ceil
|
|
|
|
t.offset = t.cy - (height - 1)
|
|
|
|
} else if t.offset > t.cy {
|
|
|
|
// Floor
|
|
|
|
t.offset = t.cy
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) vmove(o int) {
|
|
|
|
if t.reverse {
|
2015-06-16 14:14:57 +00:00
|
|
|
o *= -1
|
|
|
|
}
|
|
|
|
dest := t.cy + o
|
|
|
|
if t.cycle {
|
|
|
|
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 {
|
2015-07-26 14:02:04 +00:00
|
|
|
max := t.maxHeight() - 2 - len(t.header)
|
2015-04-21 14:50:53 +00:00
|
|
|
if t.inlineInfo {
|
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
|
|
|
}
|