func (ar *AsyncReader) Run() { fd := int(ar.rd.Fd()) cfd := int(ar.rCtrl.Fd()) maxfd := max(fd, cfd) fs := sys.NewFdSet() var cBuf [1]byte if nonblock, _ := sys.GetNonblock(fd); !nonblock { sys.SetNonblock(fd, true) defer sys.SetNonblock(fd, false) } for { fs.Set(fd, cfd) err := sys.Select(maxfd+1, fs, nil, nil, nil) if err != nil { switch err { case syscall.EINTR: continue default: panic(err) } } if fs.IsSet(cfd) { // Consume the written byte ar.rCtrl.Read(cBuf[:]) ar.ackCtrl <- true return } else { r, _, err := ar.bufrd.ReadRune() switch err { case nil: ar.ch <- r case io.EOF: return default: // BUG(xiaq): AsyncReader relies on the undocumented fact // that (*os.File).Read returns an *os.File.PathError e := err.(*os.PathError).Err if e != syscall.EWOULDBLOCK && e != syscall.EAGAIN { panic(err) } } } } }
// Run runs the AsyncReader. It blocks until Quit is called and should be // called in a separate goroutine. func (ar *AsyncReader) Run() { fd := int(ar.rd.Fd()) cfd := int(ar.rCtrl.Fd()) maxfd := max(fd, cfd) fs := sys.NewFdSet() var cBuf [1]byte if nonblock, _ := sys.GetNonblock(fd); !nonblock { sys.SetNonblock(fd, true) defer sys.SetNonblock(fd, false) } for { fs.Set(fd, cfd) err := sys.Select(maxfd+1, fs, nil, nil, nil) if err != nil { switch err { case syscall.EINTR: continue default: ar.errCh <- err return } } if fs.IsSet(cfd) { // Consume the written byte ar.rCtrl.Read(cBuf[:]) <-ar.ctrlCh return } ReadRune: for { r, _, err := ar.bufrd.ReadRune() switch err { case nil: // Logger.Printf("read rune: %q", r) select { case ar.ch <- r: case <-ar.ctrlCh: ar.rCtrl.Read(cBuf[:]) return } case io.EOF: return default: // BUG(xiaq): AsyncReader relies on the undocumented fact // that (*os.File).Read returns an *os.File.PathError patherr, ok := err.(*os.PathError) //.Err if !ok { ar.errCh <- err return } e := patherr.Err if e == syscall.EWOULDBLOCK || e == syscall.EAGAIN { break ReadRune } else { ar.errCh <- err return } } } } }
// commitBuffer updates the terminal display to reflect current buffer. // TODO Instead of erasing w.oldBuf entirely and then draw buf, compute a // delta between w.oldBuf and buf func (w *writer) commitBuffer(bufNoti, buf *buffer, fullRefresh bool) error { if buf.width != w.oldBuf.width && w.oldBuf.cells != nil { // Width change, force full refresh w.oldBuf.cells = nil fullRefresh = true } bytesBuf := new(bytes.Buffer) // Hide cursor. bytesBuf.WriteString("\033[?25l") // Rewind cursor if pLine := w.oldBuf.dot.line; pLine > 0 { fmt.Fprintf(bytesBuf, "\033[%dA", pLine) } bytesBuf.WriteString("\r") if fullRefresh { // Do an erase. bytesBuf.WriteString("\033[J") } // style of last written cell. style := "" switchStyle := func(newstyle string) { if newstyle != style { fmt.Fprintf(bytesBuf, "\033[0;%sm", newstyle) style = newstyle } } writeCells := func(cs []cell) { for _, c := range cs { if c.width > 0 { switchStyle(c.style) } bytesBuf.WriteString(string(c.rune)) } } if bufNoti != nil { if logWriterDetail { Logger.Printf("going to write %d lines of notifications", len(bufNoti.cells)) } // Write notifications for _, line := range bufNoti.cells { writeCells(line) switchStyle("") bytesBuf.WriteString("\033[K\n") } // XXX Hacky. if len(w.oldBuf.cells) > 0 { w.oldBuf.cells = w.oldBuf.cells[1:] } } if logWriterDetail { Logger.Printf("going to write %d lines, oldBuf had %d", len(buf.cells), len(w.oldBuf.cells)) } for i, line := range buf.cells { if i > 0 { bytesBuf.WriteString("\n") } var j int // First column where buf and oldBuf differ // No need to update current line if !fullRefresh && i < len(w.oldBuf.cells) { var eq bool if eq, j = compareRows(line, w.oldBuf.cells[i]); eq { continue } } // Move to the first differing column if necessary. firstCol := widthOfCells(line[:j]) if firstCol != 0 { fmt.Fprintf(bytesBuf, "\033[%dG", firstCol+1) } // Erase the rest of the line if necessary. if !fullRefresh && i < len(w.oldBuf.cells) && j < len(w.oldBuf.cells[i]) { switchStyle("") bytesBuf.WriteString("\033[K") } writeCells(line[j:]) } if len(w.oldBuf.cells) > len(buf.cells) && !fullRefresh { // If the old buffer is higher, erase old content. // Note that we cannot simply write \033[J, because if the cursor is // just over the last column -- which is precisely the case if we have a // rprompt, \033[J will also erase the last column. switchStyle("") bytesBuf.WriteString("\n\033[J\033[A") } switchStyle("") cursor := buf.cursor() bytesBuf.Write(deltaPos(cursor, buf.dot)) // Show cursor. bytesBuf.WriteString("\033[?25h") if logWriterDetail { Logger.Printf("going to write %q", bytesBuf.String()) } fd := int(w.file.Fd()) if nonblock, _ := sys.GetNonblock(fd); nonblock { sys.SetNonblock(fd, false) defer sys.SetNonblock(fd, true) } _, err := w.file.Write(bytesBuf.Bytes()) if err != nil { return err } w.oldBuf = buf return nil }
func (ar *AsyncReader) run() { fd := int(ar.rd.Fd()) cfd := int(ar.rCtrl.Fd()) maxfd := max(fd, cfd) fs := sys.NewFdSet() var cBuf [1]byte defer close(ar.ch) sys.SetNonblock(fd, true) for { fs.Set(fd, cfd) err := sys.Select(maxfd+1, fs, nil, nil, nil) if err != nil { switch err { case syscall.EINTR: continue default: panic(err) } } if fs.IsSet(cfd) { // Consume the written byte ar.rCtrl.Read(cBuf[:]) switch cBuf[0] { case asyncReaderQuit: sys.SetNonblock(fd, false) ar.ackCtrl <- true return case asyncReaderContinue: ar.ackCtrl <- true case asyncReaderStop: sys.SetNonblock(fd, false) ar.ackCtrl <- true Stop: for { ar.rCtrl.Read(cBuf[:]) switch cBuf[0] { case asyncReaderQuit: ar.ackCtrl <- true return case asyncReaderContinue: sys.SetNonblock(fd, true) ar.ackCtrl <- true break Stop case asyncReaderStop: ar.ackCtrl <- true } } } } else { ReadRune: for { r, _, err := ar.bufrd.ReadRune() switch err { case nil: ar.ch <- r case io.EOF: return default: // BUG(xiaq): AsyncReader relies on the undocumented fact // that (*os.File).Read returns an *os.File.PathError e := err.(*os.PathError).Err if e == syscall.EWOULDBLOCK || e == syscall.EAGAIN { break ReadRune } else { panic(err) } } } } } }