mirror of
https://github.com/junegunn/fzf.git
synced 2026-02-21 00:58:42 +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 {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
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:
|
case callback := <-t.callbackChan:
|
||||||
event = tui.Invalid.AsEvent()
|
event = tui.Invalid.AsEvent()
|
||||||
actions = append(actions, &action{t: actAsync})
|
actions = append(actions, &action{t: actAsync})
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ func (r *FullscreenRenderer) ShowCursor() {}
|
|||||||
func (r *FullscreenRenderer) Refresh() {}
|
func (r *FullscreenRenderer) Refresh() {}
|
||||||
func (r *FullscreenRenderer) Close() {}
|
func (r *FullscreenRenderer) Close() {}
|
||||||
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
|
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
|
||||||
|
func (r *FullscreenRenderer) Top() int { return 0 }
|
||||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||||
func (r *FullscreenRenderer) Top() int { return 0 }
|
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
func (r *FullscreenRenderer) GetChar(bool) Event { return Event{} }
|
||||||
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
func (r *FullscreenRenderer) CancelGetChar() {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const (
|
|||||||
escPollInterval = 5
|
escPollInterval = 5
|
||||||
offsetPollTries = 10
|
offsetPollTries = 10
|
||||||
maxInputBuffer = 1024 * 1024
|
maxInputBuffer = 1024 * 1024
|
||||||
|
maxSelectTries = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultTtyDevice string = "/dev/tty"
|
const DefaultTtyDevice string = "/dev/tty"
|
||||||
@@ -49,6 +50,18 @@ const DIM string = "\x1b[2m"
|
|||||||
const CR string = DIM + "␍"
|
const CR string = DIM + "␍"
|
||||||
const LF 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) {
|
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) {
|
||||||
bytes := []byte(str)
|
bytes := []byte(str)
|
||||||
runes := []rune{}
|
runes := []rune{}
|
||||||
@@ -104,6 +117,7 @@ type LightRenderer struct {
|
|||||||
clicks [][2]int
|
clicks [][2]int
|
||||||
ttyin *os.File
|
ttyin *os.File
|
||||||
ttyout *os.File
|
ttyout *os.File
|
||||||
|
cancel func()
|
||||||
buffer []byte
|
buffer []byte
|
||||||
origState *term.State
|
origState *term.State
|
||||||
width int
|
width int
|
||||||
@@ -118,9 +132,9 @@ type LightRenderer struct {
|
|||||||
x int
|
x int
|
||||||
maxHeightFunc func(int) int
|
maxHeightFunc func(int) int
|
||||||
showCursor bool
|
showCursor bool
|
||||||
|
mutex sync.Mutex
|
||||||
|
|
||||||
// Windows only
|
// Windows only
|
||||||
mutex sync.Mutex
|
|
||||||
ttyinChannel chan byte
|
ttyinChannel chan byte
|
||||||
inHandle uintptr
|
inHandle uintptr
|
||||||
outHandle uintptr
|
outHandle uintptr
|
||||||
@@ -262,16 +276,18 @@ func getEnv(name string, defaultValue int) int {
|
|||||||
return atoi(env, defaultValue)
|
return atoi(env, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getBytes() ([]byte, error) {
|
func (r *LightRenderer) getBytes(cancellable bool) ([]byte, getCharResult, error) {
|
||||||
bytes, err := r.getBytesInternal(r.buffer, false)
|
return r.getBytesInternal(cancellable, r.buffer, false)
|
||||||
return bytes, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte, error) {
|
func (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblock bool) ([]byte, getCharResult, error) {
|
||||||
c, ok := r.getch(nonblock)
|
c, result := r.getch(cancellable, nonblock)
|
||||||
if !nonblock && !ok {
|
if result == getCharCancelled {
|
||||||
|
return buffer, getCharCancelled, nil
|
||||||
|
}
|
||||||
|
if !nonblock && !result.ok() {
|
||||||
r.Close()
|
r.Close()
|
||||||
return nil, errors.New("failed to read " + DefaultTtyDevice)
|
return nil, getCharError, errors.New("failed to read " + DefaultTtyDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
@@ -282,8 +298,8 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte,
|
|||||||
|
|
||||||
pc := c
|
pc := c
|
||||||
for {
|
for {
|
||||||
c, ok = r.getch(true)
|
c, result = r.getch(false, true)
|
||||||
if !ok {
|
if !result.ok() {
|
||||||
if retries > 0 {
|
if retries > 0 {
|
||||||
retries--
|
retries--
|
||||||
time.Sleep(escPollInterval * time.Millisecond)
|
time.Sleep(escPollInterval * time.Millisecond)
|
||||||
@@ -302,20 +318,24 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte,
|
|||||||
// so terminate fzf immediately.
|
// so terminate fzf immediately.
|
||||||
if len(buffer) > maxInputBuffer {
|
if len(buffer) > maxInputBuffer {
|
||||||
r.Close()
|
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 err error
|
||||||
|
var result getCharResult
|
||||||
if len(r.buffer) == 0 {
|
if len(r.buffer) == 0 {
|
||||||
r.buffer, err = r.getBytes()
|
r.buffer, result, err = r.getBytes(cancellable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Event{Fatal, 0, nil}
|
return Event{Fatal, 0, nil}
|
||||||
}
|
}
|
||||||
|
if result == getCharCancelled {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(r.buffer) == 0 {
|
if len(r.buffer) == 0 {
|
||||||
return Event{Fatal, 0, nil}
|
return Event{Fatal, 0, nil}
|
||||||
@@ -351,9 +371,14 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
if ev.Type == Invalid {
|
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}
|
return Event{Fatal, 0, nil}
|
||||||
}
|
}
|
||||||
|
if result == getCharCancelled {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
|
||||||
ev = r.escSequence(&sz)
|
ev = r.escSequence(&sz)
|
||||||
}
|
}
|
||||||
return ev
|
return ev
|
||||||
@@ -371,6 +396,21 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
return Event{Rune, char, nil}
|
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 {
|
func (r *LightRenderer) escSequence(sz *int) Event {
|
||||||
if len(r.buffer) < 2 {
|
if len(r.buffer) < 2 {
|
||||||
return Event{Esc, 0, nil}
|
return Event{Esc, 0, nil}
|
||||||
|
|||||||
@@ -15,10 +15,27 @@ func TestLightRenderer(t *testing.T) {
|
|||||||
|
|
||||||
light_renderer := renderer.(*LightRenderer)
|
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) {
|
assertCharSequence := func(sequence string, name string) {
|
||||||
bytes := []byte(sequence)
|
bytes := []byte(sequence)
|
||||||
light_renderer.buffer = bytes
|
light_renderer.buffer = bytes
|
||||||
event := light_renderer.GetChar()
|
event := light_renderer.GetChar(true)
|
||||||
if event.KeyName() != name {
|
if event.KeyName() != name {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"sequence: %q | %v | '%s' (%s) != %s",
|
"sequence: %q | %v | '%s' (%s) != %s",
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
|||||||
var err error
|
var err error
|
||||||
bytes := []byte{}
|
bytes := []byte{}
|
||||||
for tries := range offsetPollTries {
|
for tries := range offsetPollTries {
|
||||||
bytes, err = r.getBytesInternal(bytes, tries > 0)
|
bytes, _, err = r.getBytesInternal(false, bytes, tries > 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
@@ -114,15 +114,62 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
|||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResult) {
|
||||||
b := make([]byte, 1)
|
|
||||||
fd := r.fd()
|
fd := r.fd()
|
||||||
util.SetNonblock(r.ttyin, nonblock)
|
getter := func() (int, getCharResult) {
|
||||||
_, err := util.Read(fd, b)
|
b := make([]byte, 1)
|
||||||
if err != nil {
|
util.SetNonblock(r.ttyin, nonblock)
|
||||||
return 0, false
|
_, 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 {
|
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)
|
return int(bufferInfo.CursorPosition.Y), int(bufferInfo.CursorPosition.X)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResult) {
|
||||||
if nonblock {
|
if !nonblock && !cancellable {
|
||||||
select {
|
|
||||||
case bc := <-r.ttyinChannel:
|
|
||||||
return int(bc), true
|
|
||||||
case <-time.After(timeoutInterval * time.Millisecond):
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bc := <-r.ttyinChannel
|
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}
|
return TermSize{lines, cols, 0, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event {
|
func (r *FullscreenRenderer) GetChar(cancellable bool) Event {
|
||||||
ev := _screen.PollEvent()
|
ev := _screen.PollEvent()
|
||||||
switch ev := ev.(type) {
|
switch ev := ev.(type) {
|
||||||
case *tcell.EventPaste:
|
case *tcell.EventPaste:
|
||||||
@@ -703,6 +703,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) CancelGetChar() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Pause(clear bool) {
|
func (r *FullscreenRenderer) Pause(clear bool) {
|
||||||
if clear {
|
if clear {
|
||||||
_screen.Suspend()
|
_screen.Suspend()
|
||||||
|
|||||||
@@ -749,7 +749,8 @@ type Renderer interface {
|
|||||||
HideCursor()
|
HideCursor()
|
||||||
ShowCursor()
|
ShowCursor()
|
||||||
|
|
||||||
GetChar() Event
|
GetChar(cancellable bool) Event
|
||||||
|
CancelGetChar()
|
||||||
|
|
||||||
Top() int
|
Top() int
|
||||||
MaxX() int
|
MaxX() int
|
||||||
|
|||||||
Reference in New Issue
Block a user