github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/cmds/core/elvish/edit/tty/writer.go (about) 1 package tty 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 8 "github.com/u-root/u-root/cmds/core/elvish/edit/ui" 9 ) 10 11 var logWriterDetail = false 12 13 type Writer interface { 14 // CurrentBuffer returns the current buffer. 15 CurrentBuffer() *ui.Buffer 16 // ResetCurrentBuffer resets the current buffer. 17 ResetCurrentBuffer() 18 // CommitBuffer updates the terminal display to reflect current buffer. 19 CommitBuffer(bufNoti, buf *ui.Buffer, fullRefresh bool) error 20 } 21 22 // writer renders the editor UI. 23 type writer struct { 24 file *os.File 25 curBuf *ui.Buffer 26 } 27 28 func NewWriter(f *os.File) Writer { 29 return &writer{f, &ui.Buffer{}} 30 } 31 32 // CurrentBuffer returns the current buffer. 33 func (w *writer) CurrentBuffer() *ui.Buffer { 34 return w.curBuf 35 } 36 37 // ResetCurrentBuffer resets the current buffer. 38 func (w *writer) ResetCurrentBuffer() { 39 w.curBuf = &ui.Buffer{} 40 } 41 42 // deltaPos calculates the escape sequence needed to move the cursor from one 43 // position to another. It use relative movements to move to the destination 44 // line and absolute movement to move to the destination column. 45 func deltaPos(from, to ui.Pos) []byte { 46 buf := new(bytes.Buffer) 47 if from.Line < to.Line { 48 // move down 49 fmt.Fprintf(buf, "\033[%dB", to.Line-from.Line) 50 } else if from.Line > to.Line { 51 // move up 52 fmt.Fprintf(buf, "\033[%dA", from.Line-to.Line) 53 } 54 fmt.Fprint(buf, "\r") 55 if to.Col > 0 { 56 fmt.Fprintf(buf, "\033[%dC", to.Col) 57 } 58 return buf.Bytes() 59 } 60 61 // CommitBuffer updates the terminal display to reflect current buffer. 62 func (w *writer) CommitBuffer(bufNoti, buf *ui.Buffer, fullRefresh bool) error { 63 if buf.Width != w.curBuf.Width && w.curBuf.Lines != nil { 64 // Width change, force full refresh 65 w.curBuf.Lines = nil 66 fullRefresh = true 67 } 68 69 bytesBuf := new(bytes.Buffer) 70 71 // Hide cursor. 72 bytesBuf.WriteString("\033[?25l") 73 74 // Rewind cursor 75 if pLine := w.curBuf.Dot.Line; pLine > 0 { 76 fmt.Fprintf(bytesBuf, "\033[%dA", pLine) 77 } 78 bytesBuf.WriteString("\r") 79 80 if fullRefresh { 81 // Do an erase. 82 bytesBuf.WriteString("\033[J") 83 } 84 85 // style of last written cell. 86 style := "" 87 88 switchStyle := func(newstyle string) { 89 if newstyle != style { 90 fmt.Fprintf(bytesBuf, "\033[0;%sm", newstyle) 91 style = newstyle 92 } 93 } 94 95 writeCells := func(cs []ui.Cell) { 96 for _, c := range cs { 97 if c.Width > 0 { 98 switchStyle(c.Style) 99 } 100 bytesBuf.WriteString(c.Text) 101 } 102 } 103 104 if bufNoti != nil { 105 if logWriterDetail { 106 logger.Printf("going to write %d lines of notifications", len(bufNoti.Lines)) 107 } 108 109 // Write notifications 110 for _, line := range bufNoti.Lines { 111 writeCells(line) 112 switchStyle("") 113 bytesBuf.WriteString("\033[K\n") 114 } 115 // XXX Hacky. 116 if len(w.curBuf.Lines) > 0 { 117 w.curBuf.Lines = w.curBuf.Lines[1:] 118 } 119 } 120 121 if logWriterDetail { 122 logger.Printf("going to write %d lines, oldBuf had %d", len(buf.Lines), len(w.curBuf.Lines)) 123 } 124 125 for i, line := range buf.Lines { 126 if i > 0 { 127 bytesBuf.WriteString("\n") 128 } 129 var j int // First column where buf and oldBuf differ 130 // No need to update current line 131 if !fullRefresh && i < len(w.curBuf.Lines) { 132 var eq bool 133 if eq, j = ui.CompareCells(line, w.curBuf.Lines[i]); eq { 134 continue 135 } 136 } 137 // Move to the first differing column if necessary. 138 firstCol := ui.CellsWidth(line[:j]) 139 if firstCol != 0 { 140 fmt.Fprintf(bytesBuf, "\033[%dC", firstCol) 141 } 142 // Erase the rest of the line if necessary. 143 if !fullRefresh && i < len(w.curBuf.Lines) && j < len(w.curBuf.Lines[i]) { 144 switchStyle("") 145 bytesBuf.WriteString("\033[K") 146 } 147 writeCells(line[j:]) 148 } 149 if len(w.curBuf.Lines) > len(buf.Lines) && !fullRefresh { 150 // If the old buffer is higher, erase old content. 151 // Note that we cannot simply write \033[J, because if the cursor is 152 // just over the last column -- which is precisely the case if we have a 153 // rprompt, \033[J will also erase the last column. 154 switchStyle("") 155 bytesBuf.WriteString("\n\033[J\033[A") 156 } 157 switchStyle("") 158 cursor := buf.Cursor() 159 bytesBuf.Write(deltaPos(cursor, buf.Dot)) 160 161 // Show cursor. 162 bytesBuf.WriteString("\033[?25h") 163 164 if logWriterDetail { 165 logger.Printf("going to write %q", bytesBuf.String()) 166 } 167 168 _, err := w.file.Write(bytesBuf.Bytes()) 169 if err != nil { 170 return err 171 } 172 173 w.curBuf = buf 174 return nil 175 }