2015-01-01 19:49:30 +00:00
|
|
|
package fzf
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"regexp"
|
|
|
|
"sort"
|
|
|
|
"sync"
|
|
|
|
"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 {
|
|
|
|
prompt string
|
|
|
|
reverse bool
|
|
|
|
tac bool
|
|
|
|
cx int
|
|
|
|
cy int
|
|
|
|
offset int
|
|
|
|
yanked []rune
|
|
|
|
input []rune
|
|
|
|
multi bool
|
|
|
|
printQuery bool
|
|
|
|
count int
|
|
|
|
progress int
|
|
|
|
reading bool
|
2015-01-09 16:06:08 +00:00
|
|
|
merger *Merger
|
2015-01-01 19:49:30 +00:00
|
|
|
selected map[*string]*string
|
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-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
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
|
|
|
|
reqList
|
|
|
|
reqRefresh
|
|
|
|
reqRedraw
|
|
|
|
reqClose
|
|
|
|
reqQuit
|
2015-01-01 19:49:30 +00:00
|
|
|
)
|
|
|
|
|
2015-01-07 03:46:45 +00:00
|
|
|
const (
|
2015-01-11 18:01:24 +00:00
|
|
|
initialDelay = 100 * time.Millisecond
|
|
|
|
spinnerDuration = 200 * time.Millisecond
|
2015-01-07 03:46:45 +00:00
|
|
|
)
|
|
|
|
|
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)
|
|
|
|
return &Terminal{
|
|
|
|
prompt: opts.Prompt,
|
|
|
|
tac: opts.Sort == 0,
|
|
|
|
reverse: opts.Reverse,
|
|
|
|
cx: displayWidth(input),
|
|
|
|
cy: 0,
|
|
|
|
offset: 0,
|
|
|
|
yanked: []rune{},
|
|
|
|
input: input,
|
|
|
|
multi: opts.Multi,
|
|
|
|
printQuery: opts.PrintQuery,
|
2015-01-09 16:06:08 +00:00
|
|
|
merger: EmptyMerger,
|
2015-01-01 19:49:30 +00:00
|
|
|
selected: make(map[*string]*string),
|
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-01-01 19:49:30 +00:00
|
|
|
initFunc: func() {
|
|
|
|
C.Init(opts.Color, opts.Color256, opts.Black, opts.Mouse)
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
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-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
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) listIndex(y int) int {
|
|
|
|
if t.tac {
|
2015-01-09 16:06:08 +00:00
|
|
|
return t.merger.Length() - y - 1
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
return y
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) output() {
|
|
|
|
if t.printQuery {
|
|
|
|
fmt.Println(string(t.input))
|
|
|
|
}
|
|
|
|
if len(t.selected) == 0 {
|
2015-01-14 21:06:22 +00:00
|
|
|
cnt := t.merger.Length()
|
|
|
|
if cnt > 0 && cnt > t.cy {
|
2015-01-11 18:01:24 +00:00
|
|
|
fmt.Println(t.merger.Get(t.listIndex(t.cy)).AsString())
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for ptr, orig := range t.selected {
|
|
|
|
if orig != nil {
|
|
|
|
fmt.Println(*orig)
|
|
|
|
} else {
|
|
|
|
fmt.Println(*ptr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func displayWidth(runes []rune) int {
|
|
|
|
l := 0
|
|
|
|
for _, r := range runes {
|
|
|
|
l += runewidth.RuneWidth(r)
|
|
|
|
}
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) move(y int, x int, clear bool) {
|
|
|
|
maxy := C.MaxY()
|
|
|
|
if !t.reverse {
|
|
|
|
y = maxy - y - 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if clear {
|
|
|
|
C.MoveAndClear(y, x)
|
|
|
|
} else {
|
|
|
|
C.Move(y, x)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) placeCursor() {
|
|
|
|
t.move(0, len(t.prompt)+displayWidth(t.input[:t.cx]), false)
|
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
|
|
|
t.move(1, 0, true)
|
|
|
|
if t.reading {
|
2015-01-11 18:01:24 +00:00
|
|
|
duration := int64(spinnerDuration)
|
2015-01-01 19:49:30 +00:00
|
|
|
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
2015-01-11 18:01:24 +00:00
|
|
|
C.CPrint(C.ColSpinner, true, _spinner[idx])
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
t.move(1, 2, false)
|
2015-01-09 16:06:08 +00:00
|
|
|
output := fmt.Sprintf("%d/%d", t.merger.Length(), t.count)
|
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
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printList() {
|
|
|
|
t.constrain()
|
|
|
|
|
|
|
|
maxy := 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++ {
|
|
|
|
t.move(i+2, 0, true)
|
|
|
|
if i < count {
|
2015-01-09 16:06:08 +00:00
|
|
|
t.printItem(t.merger.Get(t.listIndex(i+t.offset)), i == t.cy-t.offset)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printItem(item *Item, current bool) {
|
|
|
|
_, selected := t.selected[item.text]
|
|
|
|
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-01-11 18:01:24 +00:00
|
|
|
C.CPrint(C.ColCurrent, 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-01-11 18:01:24 +00:00
|
|
|
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch)
|
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-01-11 18:01:24 +00:00
|
|
|
t.printHighlighted(item, false, 0, C.ColMatch)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func trimRight(runes []rune, width int) ([]rune, int) {
|
|
|
|
currentWidth := displayWidth(runes)
|
|
|
|
trimmed := 0
|
|
|
|
|
|
|
|
for currentWidth > width && len(runes) > 0 {
|
|
|
|
sz := len(runes)
|
|
|
|
currentWidth -= runewidth.RuneWidth(runes[sz-1])
|
|
|
|
runes = runes[:sz-1]
|
2015-01-11 18:01:24 +00:00
|
|
|
trimmed++
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
return runes, trimmed
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
currentWidth -= runewidth.RuneWidth(runes[0])
|
|
|
|
runes = runes[1:]
|
2015-01-11 18:01:24 +00:00
|
|
|
trimmed++
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
return runes, trimmed
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) {
|
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
|
|
|
|
text := []rune(*item.text)
|
|
|
|
offsets := item.offsets
|
|
|
|
maxWidth := C.MaxX() - 3
|
|
|
|
fullWidth := displayWidth(text)
|
|
|
|
if fullWidth > maxWidth {
|
|
|
|
// Stri..
|
|
|
|
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..
|
2015-01-08 17:37:08 +00:00
|
|
|
var diff int32
|
2015-01-01 19:49:30 +00:00
|
|
|
text, diff = trimLeft(text, maxWidth-2)
|
|
|
|
|
|
|
|
// Transform offsets
|
|
|
|
offsets = make([]Offset, len(item.offsets))
|
|
|
|
for idx, offset := range item.offsets {
|
|
|
|
b, e := offset[0], offset[1]
|
|
|
|
b += 2 - diff
|
|
|
|
e += 2 - diff
|
2015-01-12 03:56:17 +00:00
|
|
|
b = util.Max32(b, 2)
|
2015-01-01 19:49:30 +00:00
|
|
|
if b < e {
|
|
|
|
offsets[idx] = Offset{b, e}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
text = append([]rune(".."), text...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Sort(ByOrder(offsets))
|
2015-01-11 18:01:24 +00:00
|
|
|
var index int32
|
2015-01-01 19:49:30 +00:00
|
|
|
for _, offset := range offsets {
|
2015-01-12 03:56:17 +00:00
|
|
|
b := util.Max32(index, offset[0])
|
|
|
|
e := util.Max32(index, offset[1])
|
2015-01-01 19:49:30 +00:00
|
|
|
C.CPrint(col1, bold, string(text[index:b]))
|
|
|
|
C.CPrint(col2, bold, string(text[b:e]))
|
|
|
|
index = e
|
|
|
|
}
|
2015-01-08 17:37:08 +00:00
|
|
|
if index < int32(len(text)) {
|
2015-01-01 19:49:30 +00:00
|
|
|
C.CPrint(col1, bold, string(text[index:]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) printAll() {
|
|
|
|
t.printList()
|
|
|
|
t.printInfo()
|
|
|
|
t.printPrompt()
|
|
|
|
}
|
|
|
|
|
|
|
|
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-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() {
|
|
|
|
{ // Late initialization
|
|
|
|
t.mutex.Lock()
|
|
|
|
t.initFunc()
|
|
|
|
t.printPrompt()
|
2015-01-03 20:09:40 +00:00
|
|
|
t.placeCursor()
|
2015-01-07 03:46:45 +00:00
|
|
|
C.Refresh()
|
|
|
|
t.printInfo()
|
2015-01-01 19:49:30 +00:00
|
|
|
t.mutex.Unlock()
|
2015-01-07 03:46:45 +00:00
|
|
|
go func() {
|
2015-01-11 18:01:24 +00:00
|
|
|
timer := time.NewTimer(initialDelay)
|
2015-01-07 03:46:45 +00:00
|
|
|
<-timer.C
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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-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-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()
|
|
|
|
t.printAll()
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqClose:
|
2015-01-01 19:49:30 +00:00
|
|
|
C.Close()
|
|
|
|
t.output()
|
|
|
|
os.Exit(0)
|
2015-01-11 18:01:24 +00:00
|
|
|
case reqQuit:
|
2015-01-01 19:49:30 +00:00
|
|
|
C.Close()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
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-01-10 03:21:17 +00:00
|
|
|
toggle := func() {
|
|
|
|
idx := t.listIndex(t.cy)
|
|
|
|
if idx < t.merger.Length() {
|
|
|
|
item := t.merger.Get(idx)
|
|
|
|
if _, found := t.selected[item.text]; !found {
|
|
|
|
t.selected[item.text] = item.origText
|
|
|
|
} else {
|
|
|
|
delete(t.selected, item.text)
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqInfo)
|
2015-01-10 03:21:17 +00:00
|
|
|
}
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
switch event.Type {
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.Invalid:
|
2015-01-08 13:04:12 +00:00
|
|
|
t.mutex.Unlock()
|
2015-01-01 19:49:30 +00:00
|
|
|
continue
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.CtrlA:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.cx = 0
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.CtrlB:
|
2015-01-01 19:49:30 +00:00
|
|
|
if t.cx > 0 {
|
2015-01-11 18:01:24 +00:00
|
|
|
t.cx--
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.CtrlC, C.CtrlG, C.CtrlQ, C.ESC:
|
|
|
|
req(reqQuit)
|
|
|
|
case C.CtrlD:
|
2015-01-01 19:49:30 +00:00
|
|
|
if !t.delChar() && t.cx == 0 {
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqQuit)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.CtrlE:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.cx = len(t.input)
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.CtrlF:
|
2015-01-01 19:49:30 +00:00
|
|
|
if t.cx < len(t.input) {
|
2015-01-11 18:01:24 +00:00
|
|
|
t.cx++
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.CtrlH:
|
2015-01-01 19:49:30 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
|
2015-01-11 18:01:24 +00:00
|
|
|
t.cx--
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.Tab:
|
2015-01-09 16:06:08 +00:00
|
|
|
if t.multi && t.merger.Length() > 0 {
|
2015-01-01 19:49:30 +00:00
|
|
|
toggle()
|
|
|
|
t.vmove(-1)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.BTab:
|
2015-01-09 16:06:08 +00:00
|
|
|
if t.multi && t.merger.Length() > 0 {
|
2015-01-01 19:49:30 +00:00
|
|
|
toggle()
|
|
|
|
t.vmove(1)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.CtrlJ, C.CtrlN:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.vmove(-1)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
|
|
|
case C.CtrlK, C.CtrlP:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.vmove(1)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
|
|
|
case C.CtrlM:
|
|
|
|
req(reqClose)
|
|
|
|
case C.CtrlL:
|
|
|
|
req(reqRedraw)
|
|
|
|
case C.CtrlU:
|
2015-01-01 19:49:30 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.yanked = copySlice(t.input[:t.cx])
|
|
|
|
t.input = t.input[t.cx:]
|
|
|
|
t.cx = 0
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.CtrlW:
|
2015-01-01 19:49:30 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.rubout("\\s\\S")
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.AltBS:
|
2015-01-01 19:49:30 +00:00
|
|
|
if t.cx > 0 {
|
|
|
|
t.rubout("[^[:alnum:]][[:alnum:]]")
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.CtrlY:
|
2015-01-16 19:55:29 +00:00
|
|
|
suffix := copySlice(t.input[t.cx:])
|
|
|
|
t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
|
2015-01-01 19:49:30 +00:00
|
|
|
t.cx += len(t.yanked)
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.Del:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.delChar()
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.PgUp:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.vmove(maxItems() - 1)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
|
|
|
case C.PgDn:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.vmove(-(maxItems() - 1))
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
|
|
|
case C.AltB:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.AltF:
|
2015-01-01 19:49:30 +00:00
|
|
|
t.cx += findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.AltD:
|
2015-01-01 19:49:30 +00:00
|
|
|
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:]...)
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
case C.Rune:
|
2015-01-01 19:49:30 +00:00
|
|
|
prefix := copySlice(t.input[:t.cx])
|
|
|
|
t.input = append(append(prefix, event.Char), t.input[t.cx:]...)
|
2015-01-11 18:01:24 +00:00
|
|
|
t.cx++
|
|
|
|
case C.Mouse:
|
2015-01-01 19:49:30 +00:00
|
|
|
me := event.MouseEvent
|
2015-01-12 03:56:17 +00:00
|
|
|
mx, my := util.Constrain(me.X-len(t.prompt), 0, len(t.input)), me.Y
|
2015-01-01 19:49:30 +00:00
|
|
|
if !t.reverse {
|
|
|
|
my = C.MaxY() - my - 1
|
|
|
|
}
|
|
|
|
if me.S != 0 {
|
|
|
|
// Scroll
|
2015-01-10 03:21:17 +00:00
|
|
|
if t.merger.Length() > 0 {
|
|
|
|
if t.multi && me.Mod {
|
|
|
|
toggle()
|
|
|
|
}
|
|
|
|
t.vmove(me.S)
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
} else if me.Double {
|
|
|
|
// Double-click
|
|
|
|
if my >= 2 {
|
2015-01-10 05:50:24 +00:00
|
|
|
if t.vset(my-2) && t.listIndex(t.cy) < t.merger.Length() {
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqClose)
|
2015-01-10 03:26:11 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
} else if me.Down {
|
|
|
|
if my == 0 && mx >= 0 {
|
|
|
|
// Prompt
|
|
|
|
t.cx = mx
|
|
|
|
} else if my >= 2 {
|
|
|
|
// List
|
2015-01-10 05:50:24 +00:00
|
|
|
if t.vset(t.offset+my-2) && t.multi && me.Mod {
|
2015-01-01 19:49:30 +00:00
|
|
|
toggle()
|
|
|
|
}
|
2015-01-11 18:01:24 +00:00
|
|
|
req(reqList)
|
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-01-11 18:01:24 +00:00
|
|
|
t.eventBox.Set(EvtSearchNew, nil)
|
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-01-01 19:49:30 +00:00
|
|
|
height := C.MaxY() - 2
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) vmove(o int) {
|
|
|
|
if t.reverse {
|
2015-01-10 05:50:24 +00:00
|
|
|
t.vset(t.cy - o)
|
2015-01-01 19:49:30 +00:00
|
|
|
} else {
|
2015-01-10 05:50:24 +00:00
|
|
|
t.vset(t.cy + o)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
func maxItems() int {
|
|
|
|
return C.MaxY() - 2
|
|
|
|
}
|