package fzf
import (
"bytes"
"fmt"
"os"
"os/signal"
"regexp"
"sort"
"strings"
"sync"
"syscall"
"time"
C "github.com/junegunn/fzf/src/curses"
"github.com/junegunn/fzf/src/util"
"github.com/junegunn/go-runewidth"
)
// Terminal represents terminal input/output
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
merger *Merger
selected map[*string]selectedItem
reqBox *util.EventBox
eventBox *util.EventBox
mutex sync.Mutex
initFunc func()
suppress bool
startChan chan bool
}
type selectedItem struct {
at time.Time
text *string
}
type ByTimeOrder []selectedItem
func (a ByTimeOrder) Len() int {
return len(a)
}
func (a ByTimeOrder) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a ByTimeOrder) Less(i, j int) bool {
return a[i].at.Before(a[j].at)
}
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
var _runeWidths = make(map[rune]int)
const (
reqPrompt util.EventType = iota
reqInfo
reqList
reqRefresh
reqRedraw
reqClose
reqQuit
)
const (
initialDelay = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond
)
// NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := []rune(opts.Query)
return &Terminal{
prompt: opts.Prompt,
tac: opts.Sort == 0,
reverse: opts.Reverse,
cx: len(input),
cy: 0,
offset: 0,
yanked: []rune{},
input: input,
multi: opts.Multi,
printQuery: opts.PrintQuery,
merger: EmptyMerger,
selected: make(map[*string]selectedItem),
reqBox: util.NewEventBox(),
eventBox: eventBox,
mutex: sync.Mutex{},
suppress: true,
startChan: make(chan bool, 1),
initFunc: func() {
C.Init(opts.Color, opts.Color256, opts.Black, opts.Mouse)
}}
}
// Input returns current query string
func (t *Terminal) Input() []rune {
t.mutex.Lock()
defer t.mutex.Unlock()
return copySlice(t.input)
}
// UpdateCount updates the count information
func (t *Terminal) UpdateCount(cnt int, final bool) {
t.mutex.Lock()
t.count = cnt
t.reading = !final
t.mutex.Unlock()
t.reqBox.Set(reqInfo, nil)
if final {
t.reqBox.Set(reqRefresh, nil)
}
}
// UpdateProgress updates the search progress
func (t *Terminal) UpdateProgress(progress float32) {
t.mutex.Lock()
newProgress := int(progress * 100)
changed := t.progress != newProgress
t.progress = newProgress
t.mutex.Unlock()
if changed {
t.reqBox.Set(reqInfo, nil)
}
}
// UpdateList updates Merger to display the list
func (t *Terminal) UpdateList(merger *Merger) {
t.mutex.Lock()
t.progress = 100
t.merger = merger
t.mutex.Unlock()
t.reqBox.Set(reqInfo, nil)
t.reqBox.Set(reqList, nil)
}
func (t *Terminal) listIndex(y int) int {
if t.tac {
return t.merger.Length() - y - 1
}
return y
}
func (t *Terminal) output() {
if t.printQuery {
fmt.Println(string(t.input))
}
if len(t.selected) == 0 {
cnt := t.merger.Length()
if cnt > 0 && cnt > t.cy {
fmt.Println(t.merger.Get(t.listIndex(t.cy)).AsString())
}
} else {
sels := make([]selectedItem, 0, len(t.selected))
for _, sel := range t.selected {
sels = append(sels, sel)
}
sort.Sort(ByTimeOrder(sels))
for _, sel := range sels {
fmt.Println(*sel.text)
}
}
}
func runeWidth(r rune, prefixWidth int) int {
if r == '\t' {
return 8 - prefixWidth%8
} else if w, found := _runeWidths[r]; found {
return w
} else {
w := runewidth.RuneWidth(r)