|
|
|
@ -2,10 +2,12 @@ package fzf
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"math"
|
|
|
|
|
"net"
|
|
|
|
|
"os"
|
|
|
|
|
"os/signal"
|
|
|
|
|
"regexp"
|
|
|
|
@ -49,8 +51,8 @@ var whiteSuffix *regexp.Regexp
|
|
|
|
|
var offsetComponentRegex *regexp.Regexp
|
|
|
|
|
var offsetTrimCharsRegex *regexp.Regexp
|
|
|
|
|
var activeTempFiles []string
|
|
|
|
|
var activeTempFilesMutex sync.Mutex
|
|
|
|
|
var passThroughRegex *regexp.Regexp
|
|
|
|
|
var actionTypeRegex *regexp.Regexp
|
|
|
|
|
|
|
|
|
|
const clearCode string = "\x1b[2J"
|
|
|
|
|
|
|
|
|
@ -63,6 +65,7 @@ func init() {
|
|
|
|
|
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
|
|
|
|
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
|
|
|
|
activeTempFiles = []string{}
|
|
|
|
|
activeTempFilesMutex = sync.Mutex{}
|
|
|
|
|
|
|
|
|
|
// Parts of the preview output that should be passed through to the terminal
|
|
|
|
|
// * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it
|
|
|
|
@ -241,6 +244,7 @@ type Terminal struct {
|
|
|
|
|
unicode bool
|
|
|
|
|
listenAddr *listenAddress
|
|
|
|
|
listenPort *int
|
|
|
|
|
listener net.Listener
|
|
|
|
|
listenUnsafe bool
|
|
|
|
|
borderShape tui.BorderShape
|
|
|
|
|
cleanExit bool
|
|
|
|
@ -259,7 +263,7 @@ type Terminal struct {
|
|
|
|
|
hasResizeActions bool
|
|
|
|
|
triggerLoad bool
|
|
|
|
|
reading bool
|
|
|
|
|
running bool
|
|
|
|
|
running *util.AtomicBool
|
|
|
|
|
failed *string
|
|
|
|
|
jumping jumpMode
|
|
|
|
|
jumpLabels string
|
|
|
|
@ -278,12 +282,12 @@ type Terminal struct {
|
|
|
|
|
previewBox *util.EventBox
|
|
|
|
|
eventBox *util.EventBox
|
|
|
|
|
mutex sync.Mutex
|
|
|
|
|
initFunc func()
|
|
|
|
|
initFunc func() error
|
|
|
|
|
prevLines []itemLine
|
|
|
|
|
suppress bool
|
|
|
|
|
sigstop bool
|
|
|
|
|
startChan chan fitpad
|
|
|
|
|
killChan chan int
|
|
|
|
|
killChan chan bool
|
|
|
|
|
serverInputChan chan []*action
|
|
|
|
|
serverOutputChan chan string
|
|
|
|
|
eventChan chan tui.Event
|
|
|
|
@ -340,6 +344,7 @@ const (
|
|
|
|
|
reqPreviewRefresh
|
|
|
|
|
reqPreviewDelayed
|
|
|
|
|
reqQuit
|
|
|
|
|
reqFatal
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type action struct {
|
|
|
|
@ -380,6 +385,7 @@ const (
|
|
|
|
|
actDeleteChar
|
|
|
|
|
actDeleteCharEof
|
|
|
|
|
actEndOfLine
|
|
|
|
|
actFatal
|
|
|
|
|
actForwardChar
|
|
|
|
|
actForwardWord
|
|
|
|
|
actKillLine
|
|
|
|
@ -511,6 +517,7 @@ type previewRequest struct {
|
|
|
|
|
pwindowSize tui.TermSize
|
|
|
|
|
scrollOffset int
|
|
|
|
|
list []*Item
|
|
|
|
|
env []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type previewResult struct {
|
|
|
|
@ -537,6 +544,7 @@ func defaultKeymap() map[tui.Event][]*action {
|
|
|
|
|
keymap[e] = toActions(a)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add(tui.Fatal, actFatal)
|
|
|
|
|
add(tui.Invalid, actInvalid)
|
|
|
|
|
add(tui.CtrlA, actBeginningOfLine)
|
|
|
|
|
add(tui.CtrlB, actBackwardChar)
|
|
|
|
@ -642,7 +650,7 @@ func evaluateHeight(opts *Options, termHeight int) int {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewTerminal returns new Terminal object
|
|
|
|
|
func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor) *Terminal {
|
|
|
|
|
func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor) (*Terminal, error) {
|
|
|
|
|
input := trimQuery(opts.Query)
|
|
|
|
|
var delay time.Duration
|
|
|
|
|
if opts.Tac {
|
|
|
|
@ -660,11 +668,12 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|
|
|
|
}
|
|
|
|
|
var renderer tui.Renderer
|
|
|
|
|
fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)
|
|
|
|
|
var err error
|
|
|
|
|
if fullscreen {
|
|
|
|
|
if tui.HasFullscreenRenderer() {
|
|
|
|
|
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
|
|
|
|
} else {
|
|
|
|
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
|
|
|
|
renderer, err = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
|
|
|
|
true, func(h int) int { return h })
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
@ -680,7 +689,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|
|
|
|
effectiveMinHeight += borderLines(opts.BorderShape)
|
|
|
|
|
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
|
|
|
|
}
|
|
|
|
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
|
|
|
|
renderer, err = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
wordRubout := "[^\\pL\\pN][\\pL\\pN]"
|
|
|
|
|
wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
|
|
|
|
@ -693,6 +705,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|
|
|
|
for key, action := range opts.Keymap {
|
|
|
|
|
keymapCopy[key] = action
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t := Terminal{
|
|
|
|
|
initDelay: delay,
|
|
|
|
|
infoStyle: opts.InfoStyle,
|
|
|
|
@ -754,7 +767,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|
|
|
|
hasLoadActions: false,
|
|
|
|
|
triggerLoad: false,
|
|
|
|
|
reading: true,
|
|
|
|
|
running: true,
|
|
|
|
|
running: util.NewAtomicBool(true),
|
|
|
|
|
failed: nil,
|
|
|
|
|
jumping: jumpDisabled,
|
|
|
|
|
jumpLabels: opts.JumpLabels,
|
|
|
|
@ -775,12 +788,12 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|
|
|
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
|
|
|
|
theme: opts.Theme,
|
|
|
|
|
startChan: make(chan fitpad, 1),
|
|
|
|
|
killChan: make(chan int),
|
|
|
|
|
killChan: make(chan bool),
|
|
|
|
|
serverInputChan: make(chan []*action, 100),
|
|
|
|
|
serverOutputChan: make(chan string),
|
|
|
|
|
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
|
|
|
|
|
tui: renderer,
|
|
|
|
|
initFunc: func() { renderer.Init() },
|
|
|
|
|
initFunc: func() error { return renderer.Init() },
|
|
|
|
|
executing: util.NewAtomicBool(false),
|
|
|
|
|
lastAction: actStart,
|
|
|
|
|
lastFocus: minItem.Index()}
|
|
|
|
@ -832,14 +845,15 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|
|
|
|
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
|
|
|
|
|
|
|
|
|
if t.listenAddr != nil {
|
|
|
|
|
port, err := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
|
|
|
|
|
listener, port, err := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errorExit(err.Error())
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
t.listener = listener
|
|
|
|
|
t.listenPort = &port
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &t
|
|
|
|
|
return &t, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Terminal) environ() []string {
|
|
|
|
@ -2103,12 +2117,11 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
height := t.pwindow.Height()
|
|
|
|
|
header := []string{}
|
|
|
|
|
body := t.previewer.lines
|
|
|
|
|
headerLines := t.previewOpts.headerLines
|
|
|
|
|
// Do not enable preview header lines if it's value is too large
|
|
|
|
|
if headerLines > 0 && headerLines < util.Min(len(body), height) {
|
|
|
|
|
header = t.previewer.lines[0:headerLines]
|
|
|
|
|
header := t.previewer.lines[0:headerLines]
|
|
|
|
|
body = t.previewer.lines[headerLines:]
|
|
|
|
|
// Always redraw header
|
|
|
|
|
t.renderPreviewText(height, header, 0, false)
|
|
|
|
@ -2232,9 +2245,8 @@ Loop:
|
|
|
|
|
t.pwindow.Move(height-1, maxWidth-1)
|
|
|
|
|
t.previewed.filled = true
|
|
|
|
|
break Loop
|
|
|
|
|
} else {
|
|
|
|
|
t.pwindow.MoveAndClear(y+requiredLines, 0)
|
|
|
|
|
}
|
|
|
|
|
t.pwindow.MoveAndClear(y+requiredLines, 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -2485,8 +2497,6 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
|
|
|
|
case 'q':
|
|
|
|
|
flags.forceUpdate = true
|
|
|
|
|
// query flag is not skipped
|
|
|
|
|
default:
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -2512,21 +2522,27 @@ func hasPreviewFlags(template string) (slot bool, plus bool, forceUpdate bool) {
|
|
|
|
|
func writeTemporaryFile(data []string, printSep string) string {
|
|
|
|
|
f, err := os.CreateTemp("", "fzf-preview-*")
|
|
|
|
|
if err != nil {
|
|
|
|
|
errorExit("Unable to create temporary file")
|
|
|
|
|
// Unable to create temporary file
|
|
|
|
|
// FIXME: Should we terminate the program?
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
|
|
f.WriteString(strings.Join(data, printSep))
|
|
|
|
|
f.WriteString(printSep)
|
|
|
|
|
activeTempFilesMutex.Lock()
|
|
|
|
|
activeTempFiles = append(activeTempFiles, f.Name())
|
|
|
|
|
activeTempFilesMutex.Unlock()
|
|
|
|
|
return f.Name()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cleanTemporaryFiles() {
|
|
|
|
|
activeTempFilesMutex.Lock()
|
|
|
|
|
for _, filename := range activeTempFiles {
|
|
|
|
|
os.Remove(filename)
|
|
|
|
|
}
|
|
|
|
|
activeTempFiles = []string{}
|
|
|
|
|
activeTempFilesMutex.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type replacePlaceholderParams struct {
|
|
|
|
@ -2836,18 +2852,18 @@ func (t *Terminal) toggleItem(item *Item) bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Terminal) killPreview(code int) {
|
|
|
|
|
func (t *Terminal) killPreview() {
|
|
|
|
|
select {
|
|
|
|
|
case t.killChan <- code:
|
|
|
|
|
case t.killChan <- true:
|
|
|
|
|
default:
|
|
|
|
|
if code != exitCancel {
|
|
|
|
|
t.eventBox.Set(EvtQuit, code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Terminal) cancelPreview() {
|
|
|
|
|
t.killPreview(exitCancel)
|
|
|
|
|
select {
|
|
|
|
|
case t.killChan <- false:
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Terminal) pwindowSize() tui.TermSize {
|
|
|
|
@ -2871,7 +2887,7 @@ func (t *Terminal) currentIndex() int32 {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loop is called to start Terminal I/O
|
|
|
|
|
func (t *Terminal) Loop() {
|
|
|
|
|
func (t *Terminal) Loop() error {
|
|
|
|
|
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
|
|
|
|
fitpad := <-t.startChan
|
|
|
|
|
fit := fitpad.fit
|
|
|
|
@ -2895,14 +2911,23 @@ func (t *Terminal) Loop() {
|
|
|
|
|
return util.Min(termHeight, contentHeight+pad)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Context
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
|
|
{ // Late initialization
|
|
|
|
|
intChan := make(chan os.Signal, 1)
|
|
|
|
|
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
|
|
|
|
go func() {
|
|
|
|
|
for s := range intChan {
|
|
|
|
|
// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
|
|
|
|
|
if !(s == os.Interrupt && t.executing.Get()) {
|
|
|
|
|
t.reqBox.Set(reqQuit, nil)
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
case s := <-intChan:
|
|
|
|
|
// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
|
|
|
|
|
if !(s == os.Interrupt && t.executing.Get()) {
|
|
|
|
|
t.reqBox.Set(reqQuit, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
@ -2911,8 +2936,12 @@ func (t *Terminal) Loop() {
|
|
|
|
|
notifyOnCont(contChan)
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
<-contChan
|
|
|
|
|
t.reqBox.Set(reqReinit, nil)
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
case <-contChan:
|
|
|
|
|
t.reqBox.Set(reqReinit, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
@ -2921,14 +2950,23 @@ func (t *Terminal) Loop() {
|
|
|
|
|
notifyOnResize(resizeChan) // Non-portable
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
<-resizeChan
|
|
|
|
|
t.reqBox.Set(reqResize, nil)
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
case <-resizeChan:
|
|
|
|
|
t.reqBox.Set(reqResize, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.mutex.Lock()
|
|
|
|
|
t.initFunc()
|
|
|
|
|
if err := t.initFunc(); err != nil {
|
|
|
|
|
t.mutex.Unlock()
|
|
|
|
|
cancel()
|
|
|
|
|
t.eventBox.Set(EvtQuit, quitSignal{ExitError, err})
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
t.termSize = t.tui.Size()
|
|
|
|
|
t.resizeWindows(false)
|
|
|
|
|
t.window.Erase()
|
|
|
|
@ -2945,7 +2983,7 @@ func (t *Terminal) Loop() {
|
|
|
|
|
|
|
|
|
|
// Keep the spinner spinning
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
for t.running.Get() {
|
|
|
|
|
t.mutex.Lock()
|
|
|
|
|
reading := t.reading
|
|
|
|
|
t.mutex.Unlock()
|
|
|
|
@ -2960,15 +2998,20 @@ func (t *Terminal) Loop() {
|
|
|
|
|
if t.hasPreviewer() {
|
|
|
|
|
go func() {
|
|
|
|
|
var version int64
|
|
|
|
|
stop := false
|
|
|
|
|
for {
|
|
|
|
|
var items []*Item
|
|
|
|
|
var commandTemplate string
|
|
|
|
|
var pwindow tui.Window
|
|
|
|
|
var pwindowSize tui.TermSize
|
|
|
|
|
var env []string
|
|
|
|
|
initialOffset := 0
|
|
|
|
|
t.previewBox.Wait(func(events *util.Events) {
|
|
|
|
|
for req, value := range *events {
|
|
|
|
|
switch req {
|
|
|
|
|
case reqQuit:
|
|
|
|
|
stop = true
|
|
|
|
|
return
|
|
|
|
|
case reqPreviewEnqueue:
|
|
|
|
|
request := value.(previewRequest)
|
|
|
|
|
commandTemplate = request.template
|
|
|
|
@ -2976,17 +3019,20 @@ func (t *Terminal) Loop() {
|
|
|
|
|
items = request.list
|
|
|
|
|
pwindow = request.pwindow
|
|
|
|
|
pwindowSize = request.pwindowSize
|
|
|
|
|
env = request.env
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
events.Clear()
|
|
|
|
|
})
|
|
|
|
|
if stop {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
version++
|
|
|
|
|
// We don't display preview window if no match
|
|
|
|
|
if items[0] != nil {
|
|
|
|
|
_, query := t.Input()
|
|
|
|
|
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
|
|
|
|
cmd := t.executor.ExecCommand(command, true)
|
|
|
|
|
env := t.environ()
|
|
|
|
|
if pwindowSize.Lines > 0 {
|
|
|
|
|
lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines)
|
|
|
|
|
columns := fmt.Sprintf("COLUMNS=%d", pwindowSize.Columns)
|
|
|
|
@ -3071,12 +3117,13 @@ func (t *Terminal) Loop() {
|
|
|
|
|
Loop:
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
break Loop
|
|
|
|
|
case <-timer.C:
|
|
|
|
|
t.reqBox.Set(reqPreviewDelayed, version)
|
|
|
|
|
case code := <-t.killChan:
|
|
|
|
|
if code != exitCancel {
|
|
|
|
|
case immediately := <-t.killChan:
|
|
|
|
|
if immediately {
|
|
|
|
|
util.KillCommand(cmd)
|
|
|
|
|
t.eventBox.Set(EvtQuit, code)
|
|
|
|
|
} else {
|
|
|
|
|
// We can immediately kill a long-running preview program
|
|
|
|
|
// once we started rendering its partial output
|
|
|
|
@ -3123,19 +3170,25 @@ func (t *Terminal) Loop() {
|
|
|
|
|
if len(command) > 0 && t.canPreview() {
|
|
|
|
|
_, list := t.buildPlusList(command, false)
|
|
|
|
|
t.cancelPreview()
|
|
|
|
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list})
|
|
|
|
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list, t.environ()})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
var focusedIndex int32 = minItem.Index()
|
|
|
|
|
var focusedIndex = minItem.Index()
|
|
|
|
|
var version int64 = -1
|
|
|
|
|
running := true
|
|
|
|
|
code := exitError
|
|
|
|
|
code := ExitError
|
|
|
|
|
exit := func(getCode func() int) {
|
|
|
|
|
if t.hasPreviewer() {
|
|
|
|
|
t.previewBox.Set(reqQuit, nil)
|
|
|
|
|
}
|
|
|
|
|
if t.listener != nil {
|
|
|
|
|
t.listener.Close()
|
|
|
|
|
}
|
|
|
|
|
t.tui.Close()
|
|
|
|
|
code = getCode()
|
|
|
|
|
if code <= exitNoMatch && t.history != nil {
|
|
|
|
|
if code <= ExitNoMatch && t.history != nil {
|
|
|
|
|
t.history.append(string(t.input))
|
|
|
|
|
}
|
|
|
|
|
running = false
|
|
|
|
@ -3203,9 +3256,9 @@ func (t *Terminal) Loop() {
|
|
|
|
|
case reqClose:
|
|
|
|
|
exit(func() int {
|
|
|
|
|
if t.output() {
|
|
|
|
|
return exitOk
|
|
|
|
|
return ExitOk
|
|
|
|
|
}
|
|
|
|
|
return exitNoMatch
|
|
|
|
|
return ExitNoMatch
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
case reqPreviewDisplay:
|
|
|
|
@ -3233,11 +3286,14 @@ func (t *Terminal) Loop() {
|
|
|
|
|
case reqPrintQuery:
|
|
|
|
|
exit(func() int {
|
|
|
|
|
t.printer(string(t.input))
|
|
|
|
|
return exitOk
|
|
|
|
|
return ExitOk
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
case reqQuit:
|
|
|
|
|
exit(func() int { return exitInterrupt })
|
|
|
|
|
exit(func() int { return ExitInterrupt })
|
|
|
|
|
return
|
|
|
|
|
case reqFatal:
|
|
|
|
|
exit(func() int { return ExitError })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -3245,8 +3301,11 @@ func (t *Terminal) Loop() {
|
|
|
|
|
t.mutex.Unlock()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
// prof.Stop()
|
|
|
|
|
t.killPreview(code)
|
|
|
|
|
|
|
|
|
|
t.eventBox.Set(EvtQuit, quitSignal{code, nil})
|
|
|
|
|
t.running.Set(false)
|
|
|
|
|
t.killPreview()
|
|
|
|
|
cancel()
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
looping := true
|
|
|
|
@ -3256,8 +3315,16 @@ func (t *Terminal) Loop() {
|
|
|
|
|
barrier := make(chan bool)
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
<-barrier
|
|
|
|
|
t.eventChan <- t.tui.GetChar()
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
case <-barrier:
|
|
|
|
|
}
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
case t.eventChan <- t.tui.GetChar():
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
previewDraggingPos := -1
|
|
|
|
@ -3353,7 +3420,7 @@ func (t *Terminal) Loop() {
|
|
|
|
|
t.pressed = ret
|
|
|
|
|
t.reqBox.Set(reqClose, nil)
|
|
|
|
|
t.mutex.Unlock()
|
|
|
|
|
return
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -3362,8 +3429,7 @@ func (t *Terminal) Loop() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var doAction func(*action) bool
|
|
|
|
|
var doActions func(actions []*action) bool
|
|
|
|
|
doActions = func(actions []*action) bool {
|
|
|
|
|
doActions := func(actions []*action) bool {
|
|
|
|
|
for iter := 0; iter <= maxFocusEvents; iter++ {
|
|
|
|
|
currentIndex := t.currentIndex()
|
|
|
|
|
for _, action := range actions {
|
|
|
|
@ -3433,7 +3499,7 @@ func (t *Terminal) Loop() {
|
|
|
|
|
if valid {
|
|
|
|
|
t.cancelPreview()
|
|
|
|
|
t.previewBox.Set(reqPreviewEnqueue,
|
|
|
|
|
previewRequest{t.previewOpts.command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list})
|
|
|
|
|
previewRequest{t.previewOpts.command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list, t.environ()})
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Discard the preview content so that it won't accidentally appear
|
|
|
|
@ -3547,8 +3613,9 @@ func (t *Terminal) Loop() {
|
|
|
|
|
}
|
|
|
|
|
case actTransform:
|
|
|
|
|
body := t.executeCommand(a.a, false, true, true, false)
|
|
|
|
|
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {})
|
|
|
|
|
return doActions(actions)
|
|
|
|
|
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
|
|
|
|
return doActions(actions)
|
|
|
|
|
}
|
|
|
|
|
case actTransformBorderLabel:
|
|
|
|
|
label := t.executeCommand(a.a, false, true, true, true)
|
|
|
|
|
t.borderLabelOpts.label = label
|
|
|
|
@ -3580,6 +3647,8 @@ func (t *Terminal) Loop() {
|
|
|
|
|
t.input = current.text.ToRunes()
|
|
|
|
|
t.cx = len(t.input)
|
|
|
|
|
}
|
|
|
|
|
case actFatal:
|
|
|
|
|
req(reqFatal)
|
|
|
|
|
case actAbort:
|
|
|
|
|
req(reqQuit)
|
|
|
|
|
case actDeleteChar:
|
|
|
|
@ -4005,10 +4074,10 @@ func (t *Terminal) Loop() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if me.Down {
|
|
|
|
|
mx_cons := util.Constrain(mx-t.promptLen, 0, len(t.input))
|
|
|
|
|
if my == t.promptLine() && mx_cons >= 0 {
|
|
|
|
|
mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))
|
|
|
|
|
if my == t.promptLine() && mxCons >= 0 {
|
|
|
|
|
// Prompt
|
|
|
|
|
t.cx = mx_cons + t.xoffset
|
|
|
|
|
t.cx = mxCons + t.xoffset
|
|
|
|
|
} else if my >= min {
|
|
|
|
|
t.vset(t.offset + my - min)
|
|
|
|
|
req(reqList)
|
|
|
|
@ -4066,15 +4135,17 @@ func (t *Terminal) Loop() {
|
|
|
|
|
t.reading = true
|
|
|
|
|
}
|
|
|
|
|
case actUnbind:
|
|
|
|
|
keys := parseKeyChords(a.a, "PANIC")
|
|
|
|
|
for key := range keys {
|
|
|
|
|
delete(t.keymap, key)
|
|
|
|
|
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
|
|
|
|
|
for key := range keys {
|
|
|
|
|
delete(t.keymap, key)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case actRebind:
|
|
|
|
|
keys := parseKeyChords(a.a, "PANIC")
|
|
|
|
|
for key := range keys {
|
|
|
|
|
if originalAction, found := t.keymapOrg[key]; found {
|
|
|
|
|
t.keymap[key] = originalAction
|
|
|
|
|
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
|
|
|
|
|
for key := range keys {
|
|
|
|
|
if originalAction, found := t.keymapOrg[key]; found {
|
|
|
|
|
t.keymap[key] = originalAction
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case actChangePreview:
|
|
|
|
@ -4221,6 +4292,7 @@ func (t *Terminal) Loop() {
|
|
|
|
|
t.reqBox.Set(event, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Terminal) constrain() {
|
|
|
|
|