package termimport ()// Cell is an indivisible unit on the screen. It is not necessarily 1 column// wide.typeCellstruct {TextstringStylestring}// Pos is a line/column position.typePosstruct {Line, Colint}// CellsWidth returns the total width of a Cell slice.func ( []Cell) int { := 0for , := range { += wcwidth.Of(.Text) }return}// CompareCells returns whether two Cell slices are equal, and when they are// not, the first index at which they differ.func (, []Cell) (bool, int) {for , := range {if >= len() || != [] {returnfalse, } }iflen() < len() {returnfalse, len() }returntrue, 0}// Buffer reflects a continuous range of lines on the terminal.//// The Unix terminal API provides only awkward ways of querying the terminal// Buffer, so we keep an internal reflection and do one-way synchronizations// (Buffer -> terminal, and not the other way around). This requires us to// exactly match the terminal's idea of the width of characters (wcwidth) and// where to insert soft carriage returns, so there could be bugs.typeBufferstruct {Widthint// Lines the content of the buffer.LinesLines// Dot is what the user perceives as the cursor.DotPos}// Lines stores multiple lines.typeLines [][]Cell// Line stores a single line.typeLine []Cell// NewBuffer builds a new buffer, with one empty line.func ( int) *Buffer {return &Buffer{Width: , Lines: [][]Cell{make([]Cell, 0, )}}}// Col returns the column the cursor is in.func ( *Buffer) () int {returnCellsWidth(.Lines[len(.Lines)-1])}// Cursor returns the current position of the cursor.func ( *Buffer) () Pos {returnPos{len(.Lines) - 1, .Col()}}// BuffersHeight computes the combined height of a number of buffers.func ( ...*Buffer) ( int) {for , := range {if != nil { += len(.Lines) } }return}// TrimToLines trims a buffer to the lines [low, high).func ( *Buffer) (, int) {if < 0 { = 0 }if > len(.Lines) { = len(.Lines) }for := 0; < ; ++ { .Lines[] = nil }for := ; < len(.Lines); ++ { .Lines[] = nil } .Lines = .Lines[:] .Dot.Line -= if .Dot.Line < 0 { .Dot.Line = 0 }}// Extend adds all lines from b2 to the bottom of this buffer. If moveDot is// true, the dot is updated to match the dot of b2.func ( *Buffer) ( *Buffer, bool) {if != nil && .Lines != nil {if { .Dot.Line = .Dot.Line + len(.Lines) .Dot.Col = .Dot.Col } .Lines = append(.Lines, .Lines...) }}// ExtendRight extends bb to the right. It pads each line in b to be b.Width and// appends the corresponding line in b2 to it, making new lines when b2 has more// lines than bb.func ( *Buffer) ( *Buffer) { := 0 := .Width .Width += .Widthfor ; < len(.Lines) && < len(.Lines); ++ {if := CellsWidth(.Lines[]); < { .Lines[] = append(.Lines[], makeSpacing(-)...) } .Lines[] = append(.Lines[], .Lines[]...) }for ; < len(.Lines); ++ { := append(makeSpacing(), .Lines[]...) .Lines = append(.Lines, ) }}// Buffer returns itself.func ( *Buffer) () *Buffer { return }// TTYString returns a string for representing the buffer on the terminal.func ( *Buffer) () string {if == nil {return"nil" } := new(strings.Builder)fmt.Fprintf(, "Width = %d, Dot = (%d, %d)\n", .Width, .Dot.Line, .Dot.Col)// Top border .WriteString("┌" + strings.Repeat("─", .Width) + "┐\n")for , := range .Lines {// Left border .WriteRune('│')// Content := "" := 0for , := range {if .Style != {switch {case == "": .WriteString("\033[" + .Style + "m")case .Style == "": .WriteString("\033[m")default: .WriteString("\033[;" + .Style + "m") } = .Style } .WriteString(.Text) += wcwidth.Of(.Text) }if != "" { .WriteString("\033[m") }if < .Width { .WriteString("$" + strings.Repeat(" ", .Width--1)) }// Right border and newline .WriteString("│\n") }// Bottom border .WriteString("└" + strings.Repeat("─", .Width) + "┘\n")return .String()}
The pages are generated with Goldsv0.2.8-preview. (GOOS=darwin GOARCH=arm64)