260109.1 · 3f94bcb5bf93b0c04a7fc4087dff8f99e84e4a9b
Cancel key reading when 'execute' triggered via a server request (#4653)
Fix #4524 Close #4648
reading declarations
@@ -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)+ 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+ }+ if nonblock || !cancellable {+ return getter()+ }++ rpipe, wpipe, err := os.Pipe() if err != nil {- return 0, false+ // 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 int(b[0]), true+ 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