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  }