mirror of
https://github.com/junegunn/fzf.git
synced 2026-02-22 09:40:01 +08:00
Cancel key reading when 'execute' triggered via a server request (#4653)
Fix #4524 Close #4648
This commit is contained in:
@@ -5615,7 +5615,7 @@ func (t *Terminal) Loop() error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case t.keyChan <- t.tui.GetChar():
|
||||
case t.keyChan <- t.tui.GetChar(t.listenAddr != nil):
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -5702,6 +5702,13 @@ func (t *Terminal) Loop() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, action := range actions {
|
||||
if action.t == actExecute {
|
||||
t.tui.CancelGetChar()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case callback := <-t.callbackChan:
|
||||
event = tui.Invalid.AsEvent()
|
||||
actions = append(actions, &action{t: actAsync})
|
||||
|
||||
@@ -34,11 +34,11 @@ func (r *FullscreenRenderer) ShowCursor() {}
|
||||
func (r *FullscreenRenderer) Refresh() {}
|
||||
func (r *FullscreenRenderer) Close() {}
|
||||
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
|
||||
|
||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
||||
func (r *FullscreenRenderer) Top() int { return 0 }
|
||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||
func (r *FullscreenRenderer) Top() int { return 0 }
|
||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||
func (r *FullscreenRenderer) GetChar(bool) Event { return Event{} }
|
||||
func (r *FullscreenRenderer) CancelGetChar() {}
|
||||
|
||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
escPollInterval = 5
|
||||
offsetPollTries = 10
|
||||
maxInputBuffer = 1024 * 1024
|
||||
maxSelectTries = 100
|
||||
)
|
||||
|
||||
const DefaultTtyDevice string = "/dev/tty"
|
||||
@@ -49,6 +50,18 @@ const DIM string = "\x1b[2m"
|
||||
const CR string = DIM + "␍"
|
||||
const LF string = DIM + "␊"
|
||||
|
||||
type getCharResult int
|
||||
|
||||
const (
|
||||
getCharSuccess getCharResult = iota
|
||||
getCharError
|
||||
getCharCancelled
|
||||
)
|
||||
|
||||
func (r getCharResult) ok() bool {
|
||||
return r == getCharSuccess
|
||||
}
|
||||
|
||||
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) {
|
||||
bytes := []byte(str)
|
||||
runes := []rune{}
|
||||
@@ -104,6 +117,7 @@ type LightRenderer struct {
|
||||
clicks [][2]int
|
||||
ttyin *os.File
|
||||
ttyout *os.File
|
||||
cancel func()
|
||||
buffer []byte
|
||||
origState *term.State
|
||||
width int
|
||||
@@ -118,9 +132,9 @@ type LightRenderer struct {
|
||||
x int
|
||||
maxHeightFunc func(int) int
|
||||
showCursor bool
|
||||
mutex sync.Mutex
|
||||
|
||||
// Windows only
|
||||
mutex sync.Mutex
|
||||
ttyinChannel chan byte
|
||||
inHandle uintptr
|
||||
outHandle uintptr
|
||||
@@ -262,16 +276,18 @@ func getEnv(name string, defaultValue int) int {
|
||||
return atoi(env, defaultValue)
|
||||
}
|
||||
|
||||
func (r *LightRenderer) getBytes() ([]byte, error) {
|
||||
bytes, err := r.getBytesInternal(r.buffer, false)
|
||||
return bytes, err
|
||||
func (r *LightRenderer) getBytes(cancellable bool) ([]byte, getCharResult, error) {
|
||||
return r.getBytesInternal(cancellable, r.buffer, false)
|
||||
}
|
||||
|
||||
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte, error) {
|
||||
c, ok := r.getch(nonblock)
|
||||
if !nonblock && !ok {
|
||||
func (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblock bool) ([]byte, getCharResult, error) {
|
||||
c, result := r.getch(cancellable, nonblock)
|
||||
if result == getCharCancelled {
|
||||
return buffer, getCharCancelled, nil
|
||||
}
|
||||
if !nonblock && !result.ok() {
|
||||
r.Close()
|
||||
return nil, errors.New("failed to read " + DefaultTtyDevice)
|
||||
return nil, getCharError, errors.New("failed to read " + DefaultTtyDevice)
|
||||
}
|
||||
|
||||
retries := 0
|
||||
@@ -282,8 +298,8 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte,
|
||||
|
||||
pc := c
|
||||
for {
|
||||
c, ok = r.getch(true)
|
||||
if !ok {
|
||||
c, result = r.getch(false, true)
|
||||
if !result.ok() {
|
||||
if retries > 0 {
|
||||
retries--
|
||||
time.Sleep(escPollInterval * time.Millisecond)
|
||||
@@ -302,20 +318,24 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte,
|
||||
// so terminate fzf immediately.
|
||||
if len(buffer) > maxInputBuffer {
|
||||
r.Close()
|
||||
return nil, fmt.Errorf("input buffer overflow (%d): %v", len(buffer), buffer)
|
||||
return nil, getCharError, fmt.Errorf("input buffer overflow (%d): %v", len(buffer), buffer)
|
||||
}
|
||||
}
|
||||
|
||||
return buffer, nil
|
||||
return buffer, getCharSuccess, nil
|
||||
}
|
||||
|
||||
func (r *LightRenderer) GetChar() Event {
|
||||
func (r *LightRenderer) GetChar(cancellable bool) Event {
|
||||
var err error
|
||||
var result getCharResult
|
||||
if len(r.buffer) == 0 {
|
||||
r.buffer, err = r.getBytes()
|
||||
r.buffer, result, err = r.getBytes(cancellable)
|
||||
if err != nil {
|
||||
return Event{Fatal, 0, nil}
|
||||
}
|
||||
if result == getCharCancelled {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
}
|
||||
if len(r.buffer) == 0 {
|
||||
return Event{Fatal, 0, nil}
|
||||
@@ -351,9 +371,14 @@ func (r *LightRenderer) GetChar() Event {
|
||||
ev := r.escSequence(&sz)
|
||||
// Second chance
|
||||
if ev.Type == Invalid {
|
||||
if r.buffer, err = r.getBytes(); err != nil {
|
||||
r.buffer, result, err = r.getBytes(true)
|
||||
if err != nil {
|
||||
return Event{Fatal, 0, nil}
|
||||
}
|
||||
if result == getCharCancelled {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
||||
ev = r.escSequence(&sz)
|
||||
}
|
||||
return ev
|
||||
@@ -371,6 +396,21 @@ func (r *LightRenderer) GetChar() Event {
|
||||
return Event{Rune, char, nil}
|
||||
}
|
||||
|
||||
func (r *LightRenderer) CancelGetChar() {
|
||||
r.mutex.Lock()
|
||||
if r.cancel != nil {
|
||||
r.cancel()
|
||||
r.cancel = nil
|
||||
}
|
||||
r.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (r *LightRenderer) setCancel(f func()) {
|
||||
r.mutex.Lock()
|
||||
r.cancel = f
|
||||
r.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
if len(r.buffer) < 2 {
|
||||
return Event{Esc, 0, nil}
|
||||
|
||||
@@ -15,10 +15,27 @@ func TestLightRenderer(t *testing.T) {
|
||||
|
||||
light_renderer := renderer.(*LightRenderer)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
light_renderer.mutex.Lock()
|
||||
ready := light_renderer.cancel != nil
|
||||
light_renderer.mutex.Unlock()
|
||||
|
||||
if ready {
|
||||
light_renderer.CancelGetChar()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
event := light_renderer.GetChar(true)
|
||||
if event.Type != Invalid {
|
||||
t.Error("Not cancelled")
|
||||
}
|
||||
|
||||
assertCharSequence := func(sequence string, name string) {
|
||||
bytes := []byte(sequence)
|
||||
light_renderer.buffer = bytes
|
||||
event := light_renderer.GetChar()
|
||||
event := light_renderer.GetChar(true)
|
||||
if event.KeyName() != name {
|
||||
t.Errorf(
|
||||
"sequence: %q | %v | '%s' (%s) != %s",
|
||||
|
||||
@@ -99,7 +99,7 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
||||
var err error
|
||||
bytes := []byte{}
|
||||
for tries := range offsetPollTries {
|
||||
bytes, err = r.getBytesInternal(bytes, tries > 0)
|
||||
bytes, _, err = r.getBytesInternal(false, bytes, tries > 0)
|
||||
if err != nil {
|
||||
return -1, -1
|
||||
}
|
||||
@@ -114,15 +114,62 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||
b := make([]byte, 1)
|
||||
func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResult) {
|
||||
fd := r.fd()
|
||||
util.SetNonblock(r.ttyin, nonblock)
|
||||
_, err := util.Read(fd, b)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
getter := func() (int, getCharResult) {
|
||||
b := make([]byte, 1)
|
||||
util.SetNonblock(r.ttyin, nonblock)
|
||||
_, err := util.Read(fd, b)
|
||||
if err != nil {
|
||||
return 0, getCharError
|
||||
}
|
||||
return int(b[0]), getCharSuccess
|
||||
}
|
||||
return int(b[0]), true
|
||||
if nonblock || !cancellable {
|
||||
return getter()
|
||||
}
|
||||
|
||||
rpipe, wpipe, err := os.Pipe()
|
||||
if err != nil {
|
||||
// Fallback to blocking read without cancellation
|
||||
return getter()
|
||||
}
|
||||
r.setCancel(func() {
|
||||
wpipe.Write([]byte{0})
|
||||
})
|
||||
defer func() {
|
||||
r.setCancel(nil)
|
||||
rpipe.Close()
|
||||
wpipe.Close()
|
||||
}()
|
||||
|
||||
cancelFd := int(rpipe.Fd())
|
||||
for range maxSelectTries {
|
||||
var rfds unix.FdSet
|
||||
limit := len(rfds.Bits) * unix.NFDBITS
|
||||
if fd >= limit || cancelFd >= limit {
|
||||
return getter()
|
||||
}
|
||||
|
||||
rfds.Set(fd)
|
||||
rfds.Set(cancelFd)
|
||||
_, err := unix.Select(max(fd, cancelFd)+1, &rfds, nil, nil, nil)
|
||||
if err != nil {
|
||||
if err == syscall.EINTR {
|
||||
continue
|
||||
}
|
||||
return 0, getCharError
|
||||
}
|
||||
|
||||
if rfds.IsSet(cancelFd) {
|
||||
return 0, getCharCancelled
|
||||
}
|
||||
|
||||
if rfds.IsSet(fd) {
|
||||
return getter()
|
||||
}
|
||||
}
|
||||
return 0, getCharError
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Size() TermSize {
|
||||
|
||||
@@ -151,16 +151,33 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
||||
return int(bufferInfo.CursorPosition.Y), int(bufferInfo.CursorPosition.X)
|
||||
}
|
||||
|
||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||
if nonblock {
|
||||
select {
|
||||
case bc := <-r.ttyinChannel:
|
||||
return int(bc), true
|
||||
case <-time.After(timeoutInterval * time.Millisecond):
|
||||
return 0, false
|
||||
}
|
||||
} else {
|
||||
func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResult) {
|
||||
if !nonblock && !cancellable {
|
||||
bc := <-r.ttyinChannel
|
||||
return int(bc), true
|
||||
return int(bc), getCharSuccess
|
||||
}
|
||||
|
||||
var timeout <-chan time.Time
|
||||
if nonblock {
|
||||
timeout = time.After(timeoutInterval * time.Millisecond)
|
||||
}
|
||||
|
||||
var cancel chan struct{}
|
||||
if cancellable {
|
||||
cancel = make(chan struct{})
|
||||
r.setCancel(func() {
|
||||
close(cancel)
|
||||
})
|
||||
defer r.setCancel(nil)
|
||||
}
|
||||
|
||||
select {
|
||||
case bc := <-r.ttyinChannel:
|
||||
return int(bc), getCharSuccess
|
||||
case <-cancel:
|
||||
return 0, getCharCancelled
|
||||
case <-timeout:
|
||||
// NOTE: not really an error
|
||||
return 0, getCharError
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ func (r *FullscreenRenderer) Size() TermSize {
|
||||
return TermSize{lines, cols, 0, 0}
|
||||
}
|
||||
|
||||
func (r *FullscreenRenderer) GetChar() Event {
|
||||
func (r *FullscreenRenderer) GetChar(cancellable bool) Event {
|
||||
ev := _screen.PollEvent()
|
||||
switch ev := ev.(type) {
|
||||
case *tcell.EventPaste:
|
||||
@@ -703,6 +703,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
||||
func (r *FullscreenRenderer) CancelGetChar() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (r *FullscreenRenderer) Pause(clear bool) {
|
||||
if clear {
|
||||
_screen.Suspend()
|
||||
|
||||
@@ -749,7 +749,8 @@ type Renderer interface {
|
||||
HideCursor()
|
||||
ShowCursor()
|
||||
|
||||
GetChar() Event
|
||||
GetChar(cancellable bool) Event
|
||||
CancelGetChar()
|
||||
|
||||
Top() int
|
||||
MaxX() int
|
||||
|
||||
Reference in New Issue
Block a user