package termimport ()varlogWriterDetail = false// Writer represents the output to a terminal.typeWriterinterface {// Buffer returns the current buffer.Buffer() *Buffer// ResetBuffer resets the current buffer.ResetBuffer()// UpdateBuffer updates the terminal display to reflect current buffer.UpdateBuffer(bufNoti, buf *Buffer, fullRefresh bool) error// ClearScreen clears the terminal screen and places the cursor at the top // left corner.ClearScreen()// ShowCursor shows the cursor.ShowCursor()// HideCursor hides the cursor.HideCursor()}// writer renders the editor UI.typewriterstruct {fileio.WritercurBuf *Buffer}// NewWriter returns a Writer that writes VT100 sequences to the given io.Writer.func ( io.Writer) Writer {return &writer{, &Buffer{}}}func ( *writer) () *Buffer {return .curBuf}func ( *writer) () { .curBuf = &Buffer{}}// deltaPos calculates the escape sequence needed to move the cursor from one// position to another. It use relative movements to move to the destination// line and absolute movement to move to the destination column.func (, Pos) []byte { := new(bytes.Buffer)if .Line < .Line {// move downfmt.Fprintf(, "\033[%dB", .Line-.Line) } elseif .Line > .Line {// move upfmt.Fprintf(, "\033[%dA", .Line-.Line) }fmt.Fprint(, "\r")if .Col > 0 {fmt.Fprintf(, "\033[%dC", .Col) }return .Bytes()}const (hideCursor = "\033[?25l"showCursor = "\033[?25h")// UpdateBuffer updates the terminal display to reflect current buffer.func ( *writer) (, *Buffer, bool) error {if .Width != .curBuf.Width && .curBuf.Lines != nil {// Width change, force full refresh .curBuf.Lines = nil = true } := new(bytes.Buffer) .WriteString(hideCursor)// Rewind cursorif := .curBuf.Dot.Line; > 0 {fmt.Fprintf(, "\033[%dA", ) } .WriteString("\r")if {// Erase from here. We may be in the top right corner of the screen; if // we simply do an erase here, tmux will save the current screen in the // scrollback buffer (presumably as a heuristics to detect full-screen // applications), but that is not something we want. So we write a space // first, and then erase, before rewinding back. // // Source code for tmux behavior: // https://github.com/tmux/tmux/blob/5f5f029e3b3a782dc616778739b2801b00b17c0e/screen-write.c#L1139 .WriteString(" \033[J\r") }// style of last written cell. := "" := func( string) {if != {fmt.Fprintf(, "\033[0;%sm", ) = } } := func( []Cell) {for , := range { (.Style) .WriteString(.Text) } }if != nil {iflogWriterDetail {logger.Printf("going to write %d lines of notifications", len(.Lines)) }// Write notificationsfor , := range .Lines { () ("") .WriteString("\033[K\n") }// TODO(xiaq): This is hacky; try to improve it.iflen(.curBuf.Lines) > 0 { .curBuf.Lines = .curBuf.Lines[1:] } }iflogWriterDetail {logger.Printf("going to write %d lines, oldBuf had %d", len(.Lines), len(.curBuf.Lines)) }for , := range .Lines {if > 0 { .WriteString("\n") }varint// First column where buf and oldBuf differ// No need to update current lineif ! && < len(.curBuf.Lines) {varboolif , = CompareCells(, .curBuf.Lines[]); {continue } }// Move to the first differing column if necessary. := CellsWidth([:])if != 0 {fmt.Fprintf(, "\033[%dC", ) }// Erase the rest of the line if necessary.if ! && < len(.curBuf.Lines) && < len(.curBuf.Lines[]) { ("") .WriteString("\033[K") } ([:]) }iflen(.curBuf.Lines) > len(.Lines) && ! {// 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. ("") .WriteString("\n\033[J\033[A") } ("") := .Cursor() .Write(deltaPos(, .Dot))// Show cursor. .WriteString(showCursor)iflogWriterDetail {logger.Printf("going to write %q", .String()) } , := .file.Write(.Bytes())if != nil {return } .curBuf = returnnil}func ( *writer) () {fmt.Fprint(.file, hideCursor)}func ( *writer) () {fmt.Fprint(.file, showCursor)}func ( *writer) () {fmt.Fprint(.file,"\033[H", // move cursor to the top left corner"\033[2J", // clear entire buffer )}
The pages are generated with Goldsv0.2.8-preview. (GOOS=darwin GOARCH=arm64)