From 6d10ffd17c770d7391962bf7c0e8f248d46764e3 Mon Sep 17 00:00:00 2001 From: Liam Galvin Date: Sun, 12 Aug 2018 15:16:26 +0100 Subject: [PATCH] alt buffer --- buffer/buffer.go | 24 +++++++++-- buffer/coverage.out | 58 ------------------------- gui/gui.go | 10 ++--- gui/renderer.go | 48 ++++++++++----------- main.go | 2 + terminal/ansi.go | 28 ++++++------ terminal/csi.go | 64 ++++++++++++++++++---------- terminal/delete_test.go | 91 --------------------------------------- terminal/output.go | 16 +++---- terminal/sgr.go | 94 ++++++++++++++++++++--------------------- terminal/terminal.go | 94 ++++++++++++++++++++++++++--------------- 11 files changed, 224 insertions(+), 305 deletions(-) delete mode 100644 buffer/coverage.out delete mode 100644 terminal/delete_test.go diff --git a/buffer/buffer.go b/buffer/buffer.go index 32820c9..d0baa14 100644 --- a/buffer/buffer.go +++ b/buffer/buffer.go @@ -86,15 +86,15 @@ func (buffer *Buffer) CursorAttr() *CellAttributes { return &buffer.cursorAttr } -func (buffer *Buffer) GetCell(viewCol int, viewRow int) *Cell { +func (buffer *Buffer) GetCell(viewCol uint16, viewRow uint16) *Cell { - rawLine := buffer.convertViewLineToRawLine(uint16(viewRow)) + rawLine := buffer.convertViewLineToRawLine(viewRow) if viewCol < 0 || rawLine < 0 || int(rawLine) >= len(buffer.lines) { return nil } line := &buffer.lines[rawLine] - if viewCol >= len(line.cells) { + if int(viewCol) >= len(line.cells) { return nil } return &line.cells[viewCol] @@ -245,6 +245,23 @@ func (buffer *Buffer) Backspace() { } } +func (buffer *Buffer) BackspaceDelete() { + + if buffer.cursorX == 0 { + line := buffer.getCurrentLine() + if line.wrapped { + buffer.MovePosition(int16(buffer.Width()-1), -1) + buffer.GetCell(buffer.cursorX, buffer.cursorY).erase() + } else { + //@todo ring bell or whatever + fmt.Println("BELL?") + } + } else { + buffer.MovePosition(-1, 0) + buffer.GetCell(buffer.cursorX, buffer.cursorY).erase() + } +} + func (buffer *Buffer) CarriageReturn() { defer buffer.emitDisplayChange() buffer.cursorX = 0 @@ -425,6 +442,7 @@ func (buffer *Buffer) EraseDisplayToCursor() { } func (buffer *Buffer) ResizeView(width uint16, height uint16) { + defer buffer.emitDisplayChange() if buffer.viewHeight == 0 { diff --git a/buffer/coverage.out b/buffer/coverage.out deleted file mode 100644 index 6f380af..0000000 --- a/buffer/coverage.out +++ /dev/null @@ -1,58 +0,0 @@ -mode: set -gitlab.com/liamg/raft/buffer/buffer.go:17.59,25.2 3 1 -gitlab.com/liamg/raft/buffer/buffer.go:28.45,30.2 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:33.43,35.2 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:38.40,40.40 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:43.2,43.75 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:40.40,42.3 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:47.38,49.2 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:51.42,53.2 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:55.36,57.2 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:59.43,61.2 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:63.53,64.49 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:64.49,66.3 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:70.44,71.26 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:71.26,73.16 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:77.3,78.53 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:81.3,84.35 4 1 -gitlab.com/liamg/raft/buffer/buffer.go:73.16,75.12 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:78.53,80.4 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:88.49,90.46 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:90.46,92.3 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:92.8,93.44 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:93.44,98.4 4 1 -gitlab.com/liamg/raft/buffer/buffer.go:98.9,100.50 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:100.50,105.5 4 1 -gitlab.com/liamg/raft/buffer/buffer.go:105.10,106.58 1 0 -gitlab.com/liamg/raft/buffer/buffer.go:107.5,108.26 2 0 -gitlab.com/liamg/raft/buffer/buffer.go:114.33,116.25 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:124.2,124.43 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:116.25,118.19 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:118.19,121.4 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:124.43,127.3 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:127.8,130.3 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:133.54,135.33 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:139.2,139.33 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:143.2,143.86 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:135.33,137.3 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:139.33,141.3 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:146.60,147.31 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:151.2,151.33 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:155.2,156.23 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:147.31,150.3 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:151.33,154.3 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:159.48,161.80 2 1 -gitlab.com/liamg/raft/buffer/buffer.go:166.2,166.14 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:161.80,162.38 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:162.38,164.4 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:171.31,172.48 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:175.2,175.26 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:172.48,174.3 1 1 -gitlab.com/liamg/raft/buffer/buffer.go:178.63,183.2 2 1 -gitlab.com/liamg/raft/buffer/cell.go:20.21,22.2 1 1 -gitlab.com/liamg/raft/buffer/cell.go:24.35,27.2 2 1 -gitlab.com/liamg/raft/buffer/line.go:8.21,13.2 1 1 -gitlab.com/liamg/raft/buffer/line.go:15.44,17.2 1 1 -gitlab.com/liamg/raft/buffer/line.go:19.35,21.34 2 1 -gitlab.com/liamg/raft/buffer/line.go:24.2,24.22 1 1 -gitlab.com/liamg/raft/buffer/line.go:21.34,23.3 1 1 diff --git a/gui/gui.go b/gui/gui.go index 6f1a53e..abb9433 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -75,7 +75,7 @@ func (gui *GUI) glfwScrollCallback(w *glfw.Window, xoff float64, yoff float64) { } } -func (gui *GUI) getTermSize() (int, int) { +func (gui *GUI) getTermSize() (uint, uint) { if gui.renderer == nil { return 0, 0 } @@ -201,14 +201,14 @@ func (gui *GUI) Render() error { lines := gui.terminal.GetVisibleLines() for y := 0; y < len(lines); y++ { for x, cell := range lines[y].Cells() { - gui.renderer.DrawCell(cell, x, y) + gui.renderer.DrawCell(cell, uint(x), uint(y)) } } if gui.terminal.Modes().ShowCursor { - cx := int(gui.terminal.GetLogicalCursorX()) - cy := int(gui.terminal.GetLogicalCursorY()) - cy = int(cy) + int(gui.terminal.GetScrollOffset()) + cx := uint(gui.terminal.GetLogicalCursorX()) + cy := uint(gui.terminal.GetLogicalCursorY()) + cy = cy + uint(gui.terminal.GetScrollOffset()) gui.renderer.DrawCursor(cx, cy, gui.config.ColourScheme.Cursor) } diff --git a/gui/renderer.go b/gui/renderer.go index 65a1304..034e92d 100644 --- a/gui/renderer.go +++ b/gui/renderer.go @@ -12,9 +12,9 @@ import ( type Renderer interface { SetArea(areaX int, areaY int, areaWidth int, areaHeight int) - DrawCell(cell buffer.Cell, col int, row int) - DrawCursor(col int, row int, colour config.Colour) - GetTermSize() (int, int) + DrawCell(cell buffer.Cell, col uint, row uint) + DrawCursor(col uint, row uint, colour config.Colour) + GetTermSize() (uint, uint) } type OpenGLRenderer struct { @@ -27,10 +27,10 @@ type OpenGLRenderer struct { cellWidth float32 cellHeight float32 verticalCellPadding float32 - termCols int - termRows int - cellPositions map[[2]int][2]float32 - rectangles map[[2]int]*rectangle + termCols uint + termRows uint + cellPositions map[[2]uint][2]float32 + rectangles map[[2]uint]*rectangle config config.Config colourAttr uint32 program uint32 @@ -124,8 +124,8 @@ func NewOpenGLRenderer(config config.Config, font *glfont.Font, fontScale int32, areaX: areaX, areaY: areaY, fontScale: fontScale, - cellPositions: map[[2]int][2]float32{}, - rectangles: map[[2]int]*rectangle{}, + cellPositions: map[[2]uint][2]float32{}, + rectangles: map[[2]uint]*rectangle{}, config: config, colourAttr: colourAttr, program: program, @@ -134,7 +134,7 @@ func NewOpenGLRenderer(config config.Config, font *glfont.Font, fontScale int32, return r } -func (r *OpenGLRenderer) GetTermSize() (int, int) { +func (r *OpenGLRenderer) GetTermSize() (uint, uint) { return r.termCols, r.termRows } @@ -156,31 +156,31 @@ func (r *OpenGLRenderer) SetFont(font *glfont.Font) { // @todo check for monospa r.verticalCellPadding = (0.25 * float32(r.fontScale)) r.cellWidth = font.Width(1, "X") r.cellHeight = font.Height(1, "X") + (r.verticalCellPadding * 2) // vertical padding - r.termCols = int(math.Floor(float64(float32(r.areaWidth) / r.cellWidth))) - r.termRows = int(math.Floor(float64(float32(r.areaHeight) / r.cellHeight))) + r.termCols = uint(math.Floor(float64(float32(r.areaWidth) / r.cellWidth))) + r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight))) r.calculatePositions() r.generateRectangles() } func (r *OpenGLRenderer) calculatePositions() { - for line := 0; line < r.termRows; line++ { - for col := 0; col < r.termCols; col++ { + for line := uint(0); line < r.termRows; line++ { + for col := uint(0); col < r.termCols; col++ { // rounding to whole pixels makes everything nice x := float32(math.Round(float64((float32(col) * r.cellWidth)))) y := float32(math.Round(float64( (float32(line) * r.cellHeight) + (r.cellHeight / 2) + r.verticalCellPadding, ))) - r.cellPositions[[2]int{col, line}] = [2]float32{x, y} + r.cellPositions[[2]uint{col, line}] = [2]float32{x, y} } } } func (r *OpenGLRenderer) generateRectangles() { gl.UseProgram(r.program) - for line := 0; line < r.termRows; line++ { - for col := 0; col < r.termCols; col++ { + for line := uint(0); line < r.termRows; line++ { + for col := uint(0); col < r.termCols; col++ { - rect, ok := r.rectangles[[2]int{col, line}] + rect, ok := r.rectangles[[2]uint{col, line}] if ok { rect.Free() } @@ -188,14 +188,14 @@ func (r *OpenGLRenderer) generateRectangles() { // rounding to whole pixels makes everything nice x := float32(float64((float32(col) * r.cellWidth))) y := float32(float64((float32(line) * r.cellHeight) + (r.cellHeight))) - r.rectangles[[2]int{col, line}] = r.newRectangle(x, y, r.colourAttr) + r.rectangles[[2]uint{col, line}] = r.newRectangle(x, y, r.colourAttr) } } } -func (r *OpenGLRenderer) DrawCursor(col int, row int, colour config.Colour) { +func (r *OpenGLRenderer) DrawCursor(col uint, row uint, colour config.Colour) { - rect, ok := r.rectangles[[2]int{col, row}] + rect, ok := r.rectangles[[2]uint{col, row}] if !ok { // probably trying to draw during resize - perhaps add a mutex? return } @@ -218,7 +218,7 @@ func (r *OpenGLRenderer) DrawCursor(col int, row int, colour config.Colour) { //gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL) } -func (r *OpenGLRenderer) DrawCell(cell buffer.Cell, col int, row int) { +func (r *OpenGLRenderer) DrawCell(cell buffer.Cell, col uint, row uint) { if cell.Attr().Hidden || (cell.Rune() == 0x00) { return @@ -235,7 +235,7 @@ func (r *OpenGLRenderer) DrawCell(cell buffer.Cell, col int, row int) { bg = cell.Bg() } - pos, ok := r.cellPositions[[2]int{col, row}] + pos, ok := r.cellPositions[[2]uint{col, row}] if !ok { panic(fmt.Sprintf("Missing position data for cell at %d,%d", col, row)) } @@ -245,7 +245,7 @@ func (r *OpenGLRenderer) DrawCell(cell buffer.Cell, col int, row int) { // don't bother rendering rectangles that are the same colour as the background if bg != r.config.ColourScheme.Background { - rect, ok := r.rectangles[[2]int{col, row}] + rect, ok := r.rectangles[[2]uint{col, row}] if !ok { panic(fmt.Sprintf("Missing rectangle data for cell at %d,%d", col, row)) } diff --git a/main.go b/main.go index 28303ab..0ffe0db 100644 --- a/main.go +++ b/main.go @@ -84,6 +84,8 @@ func main() { // parse this conf := getConfig() + os.Setenv("TERM", "xterm-256color") + logger, err := getLogger(conf) if err != nil { fmt.Printf("Failed to create logger: %s\n", err) diff --git a/terminal/ansi.go b/terminal/ansi.go index cee7b45..e043fe0 100644 --- a/terminal/ansi.go +++ b/terminal/ansi.go @@ -15,36 +15,36 @@ var ansiSequenceMap = map[rune]escapeSequenceHandler{ func indexHandler(buffer chan rune, terminal *Terminal) error { // @todo is thus right? // "This sequence causes the active position to move downward one line without changing the column position. If the active position is at the bottom margin, a scroll up is performed." - if terminal.buffer.CursorLine() == terminal.buffer.ViewHeight()-1 { - terminal.buffer.NewLine() + if terminal.ActiveBuffer().CursorLine() == terminal.ActiveBuffer().ViewHeight()-1 { + terminal.ActiveBuffer().NewLine() return nil } - terminal.buffer.MovePosition(0, 1) + terminal.ActiveBuffer().MovePosition(0, 1) return nil } func reverseIndexHandler(buffer chan rune, terminal *Terminal) error { - terminal.buffer.MovePosition(0, -1) + terminal.ActiveBuffer().MovePosition(0, -1) return nil } func saveCursorHandler(buffer chan rune, terminal *Terminal) error { - terminal.buffer.SaveCursor() + terminal.ActiveBuffer().SaveCursor() return nil } func restoreCursorHandler(buffer chan rune, terminal *Terminal) error { - terminal.buffer.RestoreCursor() + terminal.ActiveBuffer().RestoreCursor() return nil } -func ansiHandler(buffer chan rune, terminal *Terminal) error { +func ansiHandler(pty chan rune, terminal *Terminal) error { // if the byte is an escape character, read the next byte to determine which one - b := <-buffer + b := <-pty handler, ok := ansiSequenceMap[b] if ok { - return handler(buffer, terminal) + return handler(pty, terminal) } switch b { @@ -52,7 +52,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error { case 'c': terminal.logger.Errorf("RIS not yet supported") case '(': - b = <-buffer + b = <-pty switch b { case 'A': //uk @todo handle these? //terminal.charSet = C0 @@ -60,7 +60,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error { //terminal.charSet = C0 } case ')': - b = <-buffer + b = <-pty switch b { case 'A': //uk @todo handle these? //terminal.charSet = C1 @@ -68,7 +68,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error { //terminal.charSet = C1 } case '*': - b = <-buffer + b = <-pty switch b { case 'A': //uk @todo handle these? //terminal.charSet = C2 @@ -76,7 +76,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error { //terminal.charSet = C2 } case '+': - b = <-buffer + b = <-pty switch b { case 'A': //uk @todo handle these? //terminal.charSet = C3 @@ -90,7 +90,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error { case '?': pm := "" for { - b = <-buffer + b = <-pty switch b { case 'h': switch pm { diff --git a/terminal/csi.go b/terminal/csi.go index 2e2b3c0..711ecc5 100644 --- a/terminal/csi.go +++ b/terminal/csi.go @@ -22,10 +22,28 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error { switch modeStr { case "?1": terminal.modes.ApplicationCursorKeys = enabled - case "?12": + case "?12", "?13": terminal.modes.BlinkingCursor = enabled case "?25": terminal.modes.ShowCursor = enabled + case "?47", "?1047": + if enabled { + terminal.UseAltBuffer() + } else { + terminal.UseMainBuffer() + } + case "?1048": + if enabled { + terminal.ActiveBuffer().SaveCursor() + } else { + terminal.ActiveBuffer().RestoreCursor() + } + case "?1049": + if enabled { + terminal.UseAltBuffer() + } else { + terminal.UseMainBuffer() + } default: return fmt.Errorf("Unsupported CSI %sl code", modeStr) } @@ -43,7 +61,7 @@ func csiEraseCharactersHandler(params []string, intermediate string, terminal *T } } - terminal.buffer.EraseCharacters(count) + terminal.ActiveBuffer().EraseCharacters(count) return nil } @@ -71,7 +89,7 @@ func csiLinePositionAbsolute(params []string, intermediate string, terminal *Ter } } - terminal.buffer.SetPosition(uint16(col), terminal.buffer.CursorLine()) + terminal.ActiveBuffer().SetPosition(uint16(col), terminal.ActiveBuffer().CursorLine()) return nil } @@ -79,7 +97,7 @@ func csiLinePositionAbsolute(params []string, intermediate string, terminal *Ter type csiSequenceHandler func(params []string, intermediate string, terminal *Terminal) error // CSI: Control Sequence Introducer [ -func csiHandler(buffer chan rune, terminal *Terminal) error { +func csiHandler(pty chan rune, terminal *Terminal) error { var final rune var b rune var err error @@ -87,7 +105,7 @@ func csiHandler(buffer chan rune, terminal *Terminal) error { intermediate := "" CSI: for { - b = <-buffer + b = <-pty switch true { case b >= 0x30 && b <= 0x3F: param = param + string(b) @@ -117,7 +135,7 @@ CSI: distance = 1 } } - terminal.buffer.MovePosition(0, -int16(distance)) + terminal.ActiveBuffer().MovePosition(0, -int16(distance)) case 'B': distance := 1 if len(params) > 0 { @@ -128,7 +146,7 @@ CSI: } } - terminal.buffer.MovePosition(0, int16(distance)) + terminal.ActiveBuffer().MovePosition(0, int16(distance)) case 'C': distance := 1 @@ -140,7 +158,7 @@ CSI: } } - terminal.buffer.MovePosition(int16(distance), 0) + terminal.ActiveBuffer().MovePosition(int16(distance), 0) case 'D': @@ -153,7 +171,7 @@ CSI: } } - terminal.buffer.MovePosition(-int16(distance), 0) + terminal.ActiveBuffer().MovePosition(-int16(distance), 0) case 'E': distance := 1 @@ -165,8 +183,8 @@ CSI: } } - terminal.buffer.MovePosition(0, int16(distance)) - terminal.buffer.SetPosition(0, terminal.buffer.CursorLine()) + terminal.ActiveBuffer().MovePosition(0, int16(distance)) + terminal.ActiveBuffer().SetPosition(0, terminal.ActiveBuffer().CursorLine()) case 'F': @@ -178,8 +196,8 @@ CSI: distance = 1 } } - terminal.buffer.MovePosition(0, -int16(distance)) - terminal.buffer.SetPosition(0, terminal.buffer.CursorLine()) + terminal.ActiveBuffer().MovePosition(0, -int16(distance)) + terminal.ActiveBuffer().SetPosition(0, terminal.ActiveBuffer().CursorLine()) case 'G': @@ -192,7 +210,7 @@ CSI: } } - terminal.buffer.SetPosition(uint16(distance-1), terminal.buffer.CursorLine()) + terminal.ActiveBuffer().SetPosition(uint16(distance-1), terminal.ActiveBuffer().CursorLine()) case 'H', 'f': @@ -213,7 +231,7 @@ CSI: } } - terminal.buffer.SetPosition(uint16(x-1), uint16(y-1)) + terminal.ActiveBuffer().SetPosition(uint16(x-1), uint16(y-1)) default: err = fmt.Errorf("Unknown CSI control sequence: 0x%02X (ESC[%s%s%s)", final, param, intermediate, string(final)) @@ -232,7 +250,9 @@ func csiDeleteHandler(params []string, intermediate string, terminal *Terminal) n = 1 } } - _ = n + + terminal.ActiveBuffer().EraseCharacters(n) + return nil } @@ -246,11 +266,11 @@ func csiEraseInDisplayHandler(params []string, intermediate string, terminal *Te switch n { case "0", "": - terminal.buffer.EraseDisplayFromCursor() + terminal.ActiveBuffer().EraseDisplayFromCursor() case "1": - terminal.buffer.EraseDisplayToCursor() + terminal.ActiveBuffer().EraseDisplayToCursor() case "2": - terminal.buffer.EraseDisplay() + terminal.ActiveBuffer().EraseDisplay() default: return fmt.Errorf("Unsupported ED: CSI %s J", n) } @@ -268,11 +288,11 @@ func csiEraseInLineHandler(params []string, intermediate string, terminal *Termi switch n { case "0", "": //erase adter cursor - terminal.buffer.EraseLineFromCursor() + terminal.ActiveBuffer().EraseLineFromCursor() case "1": // erase to cursor inclusive - terminal.buffer.EraseLineToCursor() + terminal.ActiveBuffer().EraseLineToCursor() case "2": // erase entire - terminal.buffer.EraseLine() + terminal.ActiveBuffer().EraseLine() default: return fmt.Errorf("Unsupported EL: CSI %s K", n) } diff --git a/terminal/delete_test.go b/terminal/delete_test.go deleted file mode 100644 index 250490b..0000000 --- a/terminal/delete_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package terminal - -import "testing" - -func TestDelete(t *testing.T) { - terminal := &Terminal{ - lines: []Line{ - { - Cells: []Cell{ - { - r: 'a', - }, - { - r: 'b', - }, - { - r: 'c', - }, - { - r: 'd', - }, - { - r: 'e', - }, - }, - }, - { - Cells: []Cell{ - { - r: 'f', - }, - { - r: 'g', - }, - { - r: 'h', - }, - { - r: 'i', - }, - { - r: 'j', - }, - }, - }, - { - Cells: []Cell{ - { - r: 'k', - }, - { - r: 'l', - }, - { - r: 'm', - }, - { - r: 'n', - }, - { - r: 'o', - }, - }, - }, - }, - } - - terminal.position = Position{ - Col: 3, - Line: 1, - } - - if err := terminal.delete(2); err != nil { - t.Errorf("Delete failed: %s", err) - } - - if len(terminal.lines) != 3 { - t.Errorf("No. of lines has changed by deleting characters") - } - - if "fgh" != terminal.lines[1].String() { - t.Errorf("Unexpected string after deletion: %s", terminal.lines[1].String()) - } - if "abcde" != terminal.lines[0].String() { - t.Errorf("Unexpected string after deletion: %s", terminal.lines[0].String()) - } - - if "klmno" != terminal.lines[2].String() { - t.Errorf("Unexpected string after deletion: %s", terminal.lines[2].String()) - } -} diff --git a/terminal/output.go b/terminal/output.go index c7470e5..0adba03 100644 --- a/terminal/output.go +++ b/terminal/output.go @@ -8,7 +8,7 @@ import ( type TerminalCharSet int -type escapeSequenceHandler func(buffer chan rune, terminal *Terminal) error +type escapeSequenceHandler func(pty chan rune, terminal *Terminal) error var escapeSequenceMap = map[rune]escapeSequenceHandler{ 0x1b: ansiHandler, @@ -28,7 +28,7 @@ func (terminal *Terminal) Resume() { } } -func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) { +func (terminal *Terminal) processInput(ctx context.Context, pty chan rune) { // https://en.wikipedia.org/wiki/ANSI_escape_code @@ -47,12 +47,12 @@ func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) { //if terminal.config.slomo //time.Sleep(time.Millisecond * 100) - b := <-buffer + b := <-pty handler, ok := escapeSequenceMap[b] if ok { - if err := handler(buffer, terminal); err != nil { + if err := handler(pty, terminal); err != nil { terminal.logger.Errorf("Error handling escape sequence 0x%X: %s", b, err) } continue @@ -62,19 +62,19 @@ func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) { switch b { case 0x0a: - terminal.buffer.NewLine() + terminal.ActiveBuffer().NewLine() case 0x0d: - terminal.buffer.CarriageReturn() + terminal.ActiveBuffer().CarriageReturn() case 0x08: // backspace - terminal.buffer.Backspace() + terminal.ActiveBuffer().Backspace() case 0x07: // @todo ring bell - flash red or some shit? default: // render character at current location // fmt.Printf("%s\n", string([]byte{b})) if b >= 0x20 { - terminal.buffer.Write(b) + terminal.ActiveBuffer().Write(b) } else { terminal.logger.Error("Non-readable rune received: 0x%X", b) } diff --git a/terminal/sgr.go b/terminal/sgr.go index 04a3ea8..2d3499e 100644 --- a/terminal/sgr.go +++ b/terminal/sgr.go @@ -12,103 +12,103 @@ func sgrSequenceHandler(params []string, intermediate string, terminal *Terminal param := params[i] switch param { case "00", "0", "": - attr := terminal.buffer.CursorAttr() + attr := terminal.ActiveBuffer().CursorAttr() *attr = buffer.CellAttributes{ FgColour: terminal.config.ColourScheme.Foreground, BgColour: terminal.config.ColourScheme.Background, } case "1", "01": - terminal.buffer.CursorAttr().Bold = true + terminal.ActiveBuffer().CursorAttr().Bold = true case "2", "02": - terminal.buffer.CursorAttr().Dim = true + terminal.ActiveBuffer().CursorAttr().Dim = true case "4", "04": - terminal.buffer.CursorAttr().Underline = true + terminal.ActiveBuffer().CursorAttr().Underline = true case "5", "05": - terminal.buffer.CursorAttr().Blink = true + terminal.ActiveBuffer().CursorAttr().Blink = true case "7", "07": - terminal.buffer.CursorAttr().Reverse = true + terminal.ActiveBuffer().CursorAttr().Reverse = true case "8", "08": - terminal.buffer.CursorAttr().Hidden = true + terminal.ActiveBuffer().CursorAttr().Hidden = true case "21": - terminal.buffer.CursorAttr().Bold = false + terminal.ActiveBuffer().CursorAttr().Bold = false case "22": - terminal.buffer.CursorAttr().Dim = false + terminal.ActiveBuffer().CursorAttr().Dim = false case "24": - terminal.buffer.CursorAttr().Underline = false + terminal.ActiveBuffer().CursorAttr().Underline = false case "25": - terminal.buffer.CursorAttr().Blink = false + terminal.ActiveBuffer().CursorAttr().Blink = false case "27": - terminal.buffer.CursorAttr().Reverse = false + terminal.ActiveBuffer().CursorAttr().Reverse = false case "28": - terminal.buffer.CursorAttr().Hidden = false + terminal.ActiveBuffer().CursorAttr().Hidden = false case "39": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Foreground + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Foreground case "30": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Black + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Black case "31": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Red + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Red case "32": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Green + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Green case "33": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Yellow + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Yellow case "34": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Blue + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Blue case "35": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Magenta + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Magenta case "36": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Cyan + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Cyan case "37": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.White + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.White case "90": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.DarkGrey + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.DarkGrey case "91": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightRed + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightRed case "92": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightGreen + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightGreen case "93": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightYellow + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightYellow case "94": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightBlue + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightBlue case "95": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightMagenta + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightMagenta case "96": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightCyan + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightCyan case "97": - terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.White + terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.White case "49": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Background + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Background case "40": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Black + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Black case "41": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Red + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Red case "42": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Green + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Green case "43": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Yellow + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Yellow case "44": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Blue + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Blue case "45": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Magenta + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Magenta case "46": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Cyan + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Cyan case "47": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.White + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.White case "100": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.DarkGrey + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.DarkGrey case "101": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightRed + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightRed case "102": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightGreen + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightGreen case "103": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightYellow + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightYellow case "104": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightBlue + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightBlue case "105": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightMagenta + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightMagenta case "106": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightCyan + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightCyan case "107": - terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.White + terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.White default: return fmt.Errorf("Unknown SGR control sequence: (ESC[%s%sm)", param, intermediate) } diff --git a/terminal/terminal.go b/terminal/terminal.go index 6532b88..0c4badb 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -15,18 +15,24 @@ import ( "go.uber.org/zap" ) +const ( + MainBuffer uint8 = 0 + AltBuffer uint8 = 1 +) + type Terminal struct { - buffer *buffer.Buffer - lock sync.Mutex - pty *os.File - logger *zap.SugaredLogger - title string - size Winsize - config config.Config - titleHandlers []chan bool - pauseChan chan bool - resumeChan chan bool - modes Modes + buffers []*buffer.Buffer + activeBufferIndex uint8 + lock sync.Mutex + pty *os.File + logger *zap.SugaredLogger + title string + size Winsize + config config.Config + titleHandlers []chan bool + pauseChan chan bool + resumeChan chan bool + modes Modes } type Modes struct { @@ -50,10 +56,16 @@ type Position struct { func New(pty *os.File, logger *zap.SugaredLogger, config config.Config) *Terminal { return &Terminal{ - buffer: buffer.NewBuffer(0, 0, buffer.CellAttributes{ - FgColour: config.ColourScheme.Foreground, - BgColour: config.ColourScheme.Background, - }), + buffers: []*buffer.Buffer{ + buffer.NewBuffer(1, 1, buffer.CellAttributes{ + FgColour: config.ColourScheme.Foreground, + BgColour: config.ColourScheme.Background, + }), + buffer.NewBuffer(1, 1, buffer.CellAttributes{ + FgColour: config.ColourScheme.Foreground, + BgColour: config.ColourScheme.Background, + }), + }, pty: pty, logger: logger, config: config, @@ -66,38 +78,54 @@ func New(pty *os.File, logger *zap.SugaredLogger, config config.Config) *Termina } } +func (terminal *Terminal) UseMainBuffer() { + terminal.activeBufferIndex = MainBuffer + terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height)) +} + +func (terminal *Terminal) UseAltBuffer() { + terminal.activeBufferIndex = AltBuffer + terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height)) +} + +func (terminal *Terminal) ActiveBuffer() *buffer.Buffer { + return terminal.buffers[terminal.activeBufferIndex] +} + func (terminal *Terminal) GetScrollOffset() uint { - return terminal.buffer.GetScrollOffset() + return terminal.ActiveBuffer().GetScrollOffset() } func (terminal *Terminal) ScrollDown(lines uint16) { - terminal.buffer.ScrollDown(lines) + terminal.ActiveBuffer().ScrollDown(lines) } func (terminal *Terminal) ScrollUp(lines uint16) { - terminal.buffer.ScrollUp(lines) + terminal.ActiveBuffer().ScrollUp(lines) } func (terminal *Terminal) ScrollPageDown() { - terminal.buffer.ScrollPageDown() + terminal.ActiveBuffer().ScrollPageDown() } func (terminal *Terminal) ScrollPageUp() { - terminal.buffer.ScrollPageUp() + terminal.ActiveBuffer().ScrollPageUp() } func (terminal *Terminal) ScrollToEnd() { - terminal.buffer.ScrollToEnd() + terminal.ActiveBuffer().ScrollToEnd() } func (terminal *Terminal) GetVisibleLines() []buffer.Line { - return terminal.buffer.GetVisibleLines() + return terminal.ActiveBuffer().GetVisibleLines() } -func (terminal *Terminal) GetCell(col int, row int) *buffer.Cell { - return terminal.buffer.GetCell(col, row) +func (terminal *Terminal) GetCell(col uint16, row uint16) *buffer.Cell { + return terminal.ActiveBuffer().GetCell(col, row) } func (terminal *Terminal) AttachDisplayChangeHandler(handler chan bool) { - terminal.buffer.AttachDisplayChangeHandler(handler) + for i := range terminal.buffers { + terminal.buffers[i].AttachDisplayChangeHandler(handler) + } } func (terminal *Terminal) AttachTitleChangeHandler(handler chan bool) { @@ -117,19 +145,19 @@ func (terminal *Terminal) emitTitleChange() { } func (terminal *Terminal) GetLogicalCursorX() uint16 { - if terminal.buffer.CursorColumn() >= terminal.buffer.Width() { + if terminal.ActiveBuffer().CursorColumn() >= terminal.ActiveBuffer().Width() { return 0 } - return terminal.buffer.CursorColumn() + return terminal.ActiveBuffer().CursorColumn() } func (terminal *Terminal) GetLogicalCursorY() uint16 { - if terminal.buffer.CursorColumn() >= terminal.buffer.Width() { - return terminal.buffer.CursorLine() + 1 + if terminal.ActiveBuffer().CursorColumn() >= terminal.ActiveBuffer().Width() { + return terminal.ActiveBuffer().CursorLine() + 1 } - return terminal.buffer.CursorLine() + return terminal.ActiveBuffer().CursorLine() } func (terminal *Terminal) GetTitle() string { @@ -174,21 +202,21 @@ func (terminal *Terminal) Read() error { } func (terminal *Terminal) Clear() { - terminal.buffer.Clear() + terminal.ActiveBuffer().Clear() } func (terminal *Terminal) GetSize() (int, int) { return int(terminal.size.Width), int(terminal.size.Height) } -func (terminal *Terminal) SetSize(newCols int, newLines int) error { +func (terminal *Terminal) SetSize(newCols uint, newLines uint) error { terminal.lock.Lock() defer terminal.lock.Unlock() terminal.size.Width = uint16(newCols) terminal.size.Height = uint16(newLines) - terminal.buffer.ResizeView(terminal.size.Width, terminal.size.Height) + terminal.ActiveBuffer().ResizeView(terminal.size.Width, terminal.size.Height) _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(terminal.pty.Fd()), uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(&terminal.size)))