github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/edit/tty/async_reader.go (about) 1 package tty 2 3 import ( 4 "bufio" 5 "io" 6 "os" 7 "syscall" 8 9 "github.com/elves/elvish/sys" 10 ) 11 12 const ( 13 asyncReaderChanSize int = 128 14 ) 15 16 // AsyncReader delivers a Unix fd stream to a channel of runes. 17 type AsyncReader struct { 18 rd *os.File 19 bufrd *bufio.Reader 20 rCtrl, wCtrl *os.File 21 ctrlCh chan struct{} 22 ch chan rune 23 errCh chan error 24 } 25 26 // NewAsyncReader creates a new AsyncReader from a file. 27 func NewAsyncReader(rd *os.File) *AsyncReader { 28 ar := &AsyncReader{ 29 rd: rd, 30 bufrd: bufio.NewReaderSize(rd, 0), 31 ctrlCh: make(chan struct{}), 32 ch: make(chan rune, asyncReaderChanSize), 33 } 34 35 r, w, err := os.Pipe() 36 if err != nil { 37 panic(err) 38 } 39 ar.rCtrl, ar.wCtrl = r, w 40 return ar 41 } 42 43 // Chan returns a channel onto which the AsyncReader writes the runes it reads. 44 func (ar *AsyncReader) Chan() <-chan rune { 45 return ar.ch 46 } 47 48 // ErrorChan returns a channel onto which the AsyncReader writes the errors it 49 // encounters. 50 func (ar *AsyncReader) ErrorChan() <-chan error { 51 return ar.errCh 52 } 53 54 // Run runs the AsyncReader. It blocks until Quit is called and should be 55 // called in a separate goroutine. 56 func (ar *AsyncReader) Run() { 57 fd := int(ar.rd.Fd()) 58 cfd := int(ar.rCtrl.Fd()) 59 maxfd := max(fd, cfd) 60 fs := sys.NewFdSet() 61 var cBuf [1]byte 62 63 if nonblock, _ := sys.GetNonblock(fd); !nonblock { 64 sys.SetNonblock(fd, true) 65 defer sys.SetNonblock(fd, false) 66 } 67 68 for { 69 fs.Set(fd, cfd) 70 err := sys.Select(maxfd+1, fs, nil, nil, nil) 71 if err != nil { 72 switch err { 73 case syscall.EINTR: 74 continue 75 default: 76 ar.errCh <- err 77 return 78 } 79 } 80 if fs.IsSet(cfd) { 81 // Consume the written byte 82 ar.rCtrl.Read(cBuf[:]) 83 <-ar.ctrlCh 84 return 85 } 86 ReadRune: 87 for { 88 r, _, err := ar.bufrd.ReadRune() 89 switch err { 90 case nil: 91 // Logger.Printf("read rune: %q", r) 92 select { 93 case ar.ch <- r: 94 case <-ar.ctrlCh: 95 ar.rCtrl.Read(cBuf[:]) 96 return 97 } 98 case io.EOF: 99 return 100 default: 101 // BUG(xiaq): AsyncReader relies on the undocumented fact 102 // that (*os.File).Read returns an *os.File.PathError 103 patherr, ok := err.(*os.PathError) //.Err 104 if ok && patherr.Err == syscall.EWOULDBLOCK || patherr.Err == syscall.EAGAIN { 105 break ReadRune 106 } else { 107 select { 108 case ar.errCh <- err: 109 case <-ar.ctrlCh: 110 ar.rCtrl.Read(cBuf[:]) 111 return 112 } 113 } 114 } 115 } 116 } 117 } 118 119 // Quit terminates the loop of Run. 120 func (ar *AsyncReader) Quit() { 121 _, err := ar.wCtrl.Write([]byte{'q'}) 122 if err != nil { 123 panic(err) 124 } 125 ar.ctrlCh <- struct{}{} 126 } 127 128 // Close releases files and channels associated with the AsyncReader. It does 129 // not close the file used to create it. 130 func (ar *AsyncReader) Close() { 131 ar.rCtrl.Close() 132 ar.wCtrl.Close() 133 close(ar.ctrlCh) 134 close(ar.ch) 135 } 136 137 func max(a, b int) int { 138 if a >= b { 139 return a 140 } 141 return b 142 }