alt buffer

This commit is contained in:
Liam Galvin 2018-08-12 15:16:26 +01:00
parent 3ac0d62d72
commit 6d10ffd17c
11 changed files with 224 additions and 305 deletions

View File

@ -86,15 +86,15 @@ func (buffer *Buffer) CursorAttr() *CellAttributes {
return &buffer.cursorAttr 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) { if viewCol < 0 || rawLine < 0 || int(rawLine) >= len(buffer.lines) {
return nil return nil
} }
line := &buffer.lines[rawLine] line := &buffer.lines[rawLine]
if viewCol >= len(line.cells) { if int(viewCol) >= len(line.cells) {
return nil return nil
} }
return &line.cells[viewCol] 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() { func (buffer *Buffer) CarriageReturn() {
defer buffer.emitDisplayChange() defer buffer.emitDisplayChange()
buffer.cursorX = 0 buffer.cursorX = 0
@ -425,6 +442,7 @@ func (buffer *Buffer) EraseDisplayToCursor() {
} }
func (buffer *Buffer) ResizeView(width uint16, height uint16) { func (buffer *Buffer) ResizeView(width uint16, height uint16) {
defer buffer.emitDisplayChange() defer buffer.emitDisplayChange()
if buffer.viewHeight == 0 { if buffer.viewHeight == 0 {

View File

@ -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

View File

@ -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 { if gui.renderer == nil {
return 0, 0 return 0, 0
} }
@ -201,14 +201,14 @@ func (gui *GUI) Render() error {
lines := gui.terminal.GetVisibleLines() lines := gui.terminal.GetVisibleLines()
for y := 0; y < len(lines); y++ { for y := 0; y < len(lines); y++ {
for x, cell := range lines[y].Cells() { 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 { if gui.terminal.Modes().ShowCursor {
cx := int(gui.terminal.GetLogicalCursorX()) cx := uint(gui.terminal.GetLogicalCursorX())
cy := int(gui.terminal.GetLogicalCursorY()) cy := uint(gui.terminal.GetLogicalCursorY())
cy = int(cy) + int(gui.terminal.GetScrollOffset()) cy = cy + uint(gui.terminal.GetScrollOffset())
gui.renderer.DrawCursor(cx, cy, gui.config.ColourScheme.Cursor) gui.renderer.DrawCursor(cx, cy, gui.config.ColourScheme.Cursor)
} }

View File

@ -12,9 +12,9 @@ import (
type Renderer interface { type Renderer interface {
SetArea(areaX int, areaY int, areaWidth int, areaHeight int) SetArea(areaX int, areaY int, areaWidth int, areaHeight int)
DrawCell(cell buffer.Cell, col int, row int) DrawCell(cell buffer.Cell, col uint, row uint)
DrawCursor(col int, row int, colour config.Colour) DrawCursor(col uint, row uint, colour config.Colour)
GetTermSize() (int, int) GetTermSize() (uint, uint)
} }
type OpenGLRenderer struct { type OpenGLRenderer struct {
@ -27,10 +27,10 @@ type OpenGLRenderer struct {
cellWidth float32 cellWidth float32
cellHeight float32 cellHeight float32
verticalCellPadding float32 verticalCellPadding float32
termCols int termCols uint
termRows int termRows uint
cellPositions map[[2]int][2]float32 cellPositions map[[2]uint][2]float32
rectangles map[[2]int]*rectangle rectangles map[[2]uint]*rectangle
config config.Config config config.Config
colourAttr uint32 colourAttr uint32
program uint32 program uint32
@ -124,8 +124,8 @@ func NewOpenGLRenderer(config config.Config, font *glfont.Font, fontScale int32,
areaX: areaX, areaX: areaX,
areaY: areaY, areaY: areaY,
fontScale: fontScale, fontScale: fontScale,
cellPositions: map[[2]int][2]float32{}, cellPositions: map[[2]uint][2]float32{},
rectangles: map[[2]int]*rectangle{}, rectangles: map[[2]uint]*rectangle{},
config: config, config: config,
colourAttr: colourAttr, colourAttr: colourAttr,
program: program, program: program,
@ -134,7 +134,7 @@ func NewOpenGLRenderer(config config.Config, font *glfont.Font, fontScale int32,
return r return r
} }
func (r *OpenGLRenderer) GetTermSize() (int, int) { func (r *OpenGLRenderer) GetTermSize() (uint, uint) {
return r.termCols, r.termRows 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.verticalCellPadding = (0.25 * float32(r.fontScale))
r.cellWidth = font.Width(1, "X") r.cellWidth = font.Width(1, "X")
r.cellHeight = font.Height(1, "X") + (r.verticalCellPadding * 2) // vertical padding r.cellHeight = font.Height(1, "X") + (r.verticalCellPadding * 2) // vertical padding
r.termCols = int(math.Floor(float64(float32(r.areaWidth) / r.cellWidth))) r.termCols = uint(math.Floor(float64(float32(r.areaWidth) / r.cellWidth)))
r.termRows = int(math.Floor(float64(float32(r.areaHeight) / r.cellHeight))) r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
r.calculatePositions() r.calculatePositions()
r.generateRectangles() r.generateRectangles()
} }
func (r *OpenGLRenderer) calculatePositions() { func (r *OpenGLRenderer) calculatePositions() {
for line := 0; line < r.termRows; line++ { for line := uint(0); line < r.termRows; line++ {
for col := 0; col < r.termCols; col++ { for col := uint(0); col < r.termCols; col++ {
// rounding to whole pixels makes everything nice // rounding to whole pixels makes everything nice
x := float32(math.Round(float64((float32(col) * r.cellWidth)))) x := float32(math.Round(float64((float32(col) * r.cellWidth))))
y := float32(math.Round(float64( y := float32(math.Round(float64(
(float32(line) * r.cellHeight) + (r.cellHeight / 2) + r.verticalCellPadding, (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() { func (r *OpenGLRenderer) generateRectangles() {
gl.UseProgram(r.program) gl.UseProgram(r.program)
for line := 0; line < r.termRows; line++ { for line := uint(0); line < r.termRows; line++ {
for col := 0; col < r.termCols; col++ { 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 { if ok {
rect.Free() rect.Free()
} }
@ -188,14 +188,14 @@ func (r *OpenGLRenderer) generateRectangles() {
// rounding to whole pixels makes everything nice // rounding to whole pixels makes everything nice
x := float32(float64((float32(col) * r.cellWidth))) x := float32(float64((float32(col) * r.cellWidth)))
y := float32(float64((float32(line) * r.cellHeight) + (r.cellHeight))) 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? if !ok { // probably trying to draw during resize - perhaps add a mutex?
return return
} }
@ -218,7 +218,7 @@ func (r *OpenGLRenderer) DrawCursor(col int, row int, colour config.Colour) {
//gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL) //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) { if cell.Attr().Hidden || (cell.Rune() == 0x00) {
return return
@ -235,7 +235,7 @@ func (r *OpenGLRenderer) DrawCell(cell buffer.Cell, col int, row int) {
bg = cell.Bg() bg = cell.Bg()
} }
pos, ok := r.cellPositions[[2]int{col, row}] pos, ok := r.cellPositions[[2]uint{col, row}]
if !ok { if !ok {
panic(fmt.Sprintf("Missing position data for cell at %d,%d", col, row)) 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 // don't bother rendering rectangles that are the same colour as the background
if bg != r.config.ColourScheme.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 { if !ok {
panic(fmt.Sprintf("Missing rectangle data for cell at %d,%d", col, row)) panic(fmt.Sprintf("Missing rectangle data for cell at %d,%d", col, row))
} }

View File

@ -84,6 +84,8 @@ func main() {
// parse this // parse this
conf := getConfig() conf := getConfig()
os.Setenv("TERM", "xterm-256color")
logger, err := getLogger(conf) logger, err := getLogger(conf)
if err != nil { if err != nil {
fmt.Printf("Failed to create logger: %s\n", err) fmt.Printf("Failed to create logger: %s\n", err)

View File

@ -15,36 +15,36 @@ var ansiSequenceMap = map[rune]escapeSequenceHandler{
func indexHandler(buffer chan rune, terminal *Terminal) error { func indexHandler(buffer chan rune, terminal *Terminal) error {
// @todo is thus right? // @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." // "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 { if terminal.ActiveBuffer().CursorLine() == terminal.ActiveBuffer().ViewHeight()-1 {
terminal.buffer.NewLine() terminal.ActiveBuffer().NewLine()
return nil return nil
} }
terminal.buffer.MovePosition(0, 1) terminal.ActiveBuffer().MovePosition(0, 1)
return nil return nil
} }
func reverseIndexHandler(buffer chan rune, terminal *Terminal) error { func reverseIndexHandler(buffer chan rune, terminal *Terminal) error {
terminal.buffer.MovePosition(0, -1) terminal.ActiveBuffer().MovePosition(0, -1)
return nil return nil
} }
func saveCursorHandler(buffer chan rune, terminal *Terminal) error { func saveCursorHandler(buffer chan rune, terminal *Terminal) error {
terminal.buffer.SaveCursor() terminal.ActiveBuffer().SaveCursor()
return nil return nil
} }
func restoreCursorHandler(buffer chan rune, terminal *Terminal) error { func restoreCursorHandler(buffer chan rune, terminal *Terminal) error {
terminal.buffer.RestoreCursor() terminal.ActiveBuffer().RestoreCursor()
return nil 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 // if the byte is an escape character, read the next byte to determine which one
b := <-buffer b := <-pty
handler, ok := ansiSequenceMap[b] handler, ok := ansiSequenceMap[b]
if ok { if ok {
return handler(buffer, terminal) return handler(pty, terminal)
} }
switch b { switch b {
@ -52,7 +52,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error {
case 'c': case 'c':
terminal.logger.Errorf("RIS not yet supported") terminal.logger.Errorf("RIS not yet supported")
case '(': case '(':
b = <-buffer b = <-pty
switch b { switch b {
case 'A': //uk @todo handle these? case 'A': //uk @todo handle these?
//terminal.charSet = C0 //terminal.charSet = C0
@ -60,7 +60,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error {
//terminal.charSet = C0 //terminal.charSet = C0
} }
case ')': case ')':
b = <-buffer b = <-pty
switch b { switch b {
case 'A': //uk @todo handle these? case 'A': //uk @todo handle these?
//terminal.charSet = C1 //terminal.charSet = C1
@ -68,7 +68,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error {
//terminal.charSet = C1 //terminal.charSet = C1
} }
case '*': case '*':
b = <-buffer b = <-pty
switch b { switch b {
case 'A': //uk @todo handle these? case 'A': //uk @todo handle these?
//terminal.charSet = C2 //terminal.charSet = C2
@ -76,7 +76,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error {
//terminal.charSet = C2 //terminal.charSet = C2
} }
case '+': case '+':
b = <-buffer b = <-pty
switch b { switch b {
case 'A': //uk @todo handle these? case 'A': //uk @todo handle these?
//terminal.charSet = C3 //terminal.charSet = C3
@ -90,7 +90,7 @@ func ansiHandler(buffer chan rune, terminal *Terminal) error {
case '?': case '?':
pm := "" pm := ""
for { for {
b = <-buffer b = <-pty
switch b { switch b {
case 'h': case 'h':
switch pm { switch pm {

View File

@ -22,10 +22,28 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error {
switch modeStr { switch modeStr {
case "?1": case "?1":
terminal.modes.ApplicationCursorKeys = enabled terminal.modes.ApplicationCursorKeys = enabled
case "?12": case "?12", "?13":
terminal.modes.BlinkingCursor = enabled terminal.modes.BlinkingCursor = enabled
case "?25": case "?25":
terminal.modes.ShowCursor = enabled 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: default:
return fmt.Errorf("Unsupported CSI %sl code", modeStr) 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 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 return nil
} }
@ -79,7 +97,7 @@ func csiLinePositionAbsolute(params []string, intermediate string, terminal *Ter
type csiSequenceHandler func(params []string, intermediate string, terminal *Terminal) error type csiSequenceHandler func(params []string, intermediate string, terminal *Terminal) error
// CSI: Control Sequence Introducer [ // CSI: Control Sequence Introducer [
func csiHandler(buffer chan rune, terminal *Terminal) error { func csiHandler(pty chan rune, terminal *Terminal) error {
var final rune var final rune
var b rune var b rune
var err error var err error
@ -87,7 +105,7 @@ func csiHandler(buffer chan rune, terminal *Terminal) error {
intermediate := "" intermediate := ""
CSI: CSI:
for { for {
b = <-buffer b = <-pty
switch true { switch true {
case b >= 0x30 && b <= 0x3F: case b >= 0x30 && b <= 0x3F:
param = param + string(b) param = param + string(b)
@ -117,7 +135,7 @@ CSI:
distance = 1 distance = 1
} }
} }
terminal.buffer.MovePosition(0, -int16(distance)) terminal.ActiveBuffer().MovePosition(0, -int16(distance))
case 'B': case 'B':
distance := 1 distance := 1
if len(params) > 0 { if len(params) > 0 {
@ -128,7 +146,7 @@ CSI:
} }
} }
terminal.buffer.MovePosition(0, int16(distance)) terminal.ActiveBuffer().MovePosition(0, int16(distance))
case 'C': case 'C':
distance := 1 distance := 1
@ -140,7 +158,7 @@ CSI:
} }
} }
terminal.buffer.MovePosition(int16(distance), 0) terminal.ActiveBuffer().MovePosition(int16(distance), 0)
case 'D': case 'D':
@ -153,7 +171,7 @@ CSI:
} }
} }
terminal.buffer.MovePosition(-int16(distance), 0) terminal.ActiveBuffer().MovePosition(-int16(distance), 0)
case 'E': case 'E':
distance := 1 distance := 1
@ -165,8 +183,8 @@ CSI:
} }
} }
terminal.buffer.MovePosition(0, int16(distance)) terminal.ActiveBuffer().MovePosition(0, int16(distance))
terminal.buffer.SetPosition(0, terminal.buffer.CursorLine()) terminal.ActiveBuffer().SetPosition(0, terminal.ActiveBuffer().CursorLine())
case 'F': case 'F':
@ -178,8 +196,8 @@ CSI:
distance = 1 distance = 1
} }
} }
terminal.buffer.MovePosition(0, -int16(distance)) terminal.ActiveBuffer().MovePosition(0, -int16(distance))
terminal.buffer.SetPosition(0, terminal.buffer.CursorLine()) terminal.ActiveBuffer().SetPosition(0, terminal.ActiveBuffer().CursorLine())
case 'G': 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': 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: default:
err = fmt.Errorf("Unknown CSI control sequence: 0x%02X (ESC[%s%s%s)", final, param, intermediate, string(final)) 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 = 1
} }
} }
_ = n
terminal.ActiveBuffer().EraseCharacters(n)
return nil return nil
} }
@ -246,11 +266,11 @@ func csiEraseInDisplayHandler(params []string, intermediate string, terminal *Te
switch n { switch n {
case "0", "": case "0", "":
terminal.buffer.EraseDisplayFromCursor() terminal.ActiveBuffer().EraseDisplayFromCursor()
case "1": case "1":
terminal.buffer.EraseDisplayToCursor() terminal.ActiveBuffer().EraseDisplayToCursor()
case "2": case "2":
terminal.buffer.EraseDisplay() terminal.ActiveBuffer().EraseDisplay()
default: default:
return fmt.Errorf("Unsupported ED: CSI %s J", n) return fmt.Errorf("Unsupported ED: CSI %s J", n)
} }
@ -268,11 +288,11 @@ func csiEraseInLineHandler(params []string, intermediate string, terminal *Termi
switch n { switch n {
case "0", "": //erase adter cursor case "0", "": //erase adter cursor
terminal.buffer.EraseLineFromCursor() terminal.ActiveBuffer().EraseLineFromCursor()
case "1": // erase to cursor inclusive case "1": // erase to cursor inclusive
terminal.buffer.EraseLineToCursor() terminal.ActiveBuffer().EraseLineToCursor()
case "2": // erase entire case "2": // erase entire
terminal.buffer.EraseLine() terminal.ActiveBuffer().EraseLine()
default: default:
return fmt.Errorf("Unsupported EL: CSI %s K", n) return fmt.Errorf("Unsupported EL: CSI %s K", n)
} }

View File

@ -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())
}
}

View File

@ -8,7 +8,7 @@ import (
type TerminalCharSet int 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{ var escapeSequenceMap = map[rune]escapeSequenceHandler{
0x1b: ansiHandler, 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 // 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 //if terminal.config.slomo
//time.Sleep(time.Millisecond * 100) //time.Sleep(time.Millisecond * 100)
b := <-buffer b := <-pty
handler, ok := escapeSequenceMap[b] handler, ok := escapeSequenceMap[b]
if ok { 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) terminal.logger.Errorf("Error handling escape sequence 0x%X: %s", b, err)
} }
continue continue
@ -62,19 +62,19 @@ func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) {
switch b { switch b {
case 0x0a: case 0x0a:
terminal.buffer.NewLine() terminal.ActiveBuffer().NewLine()
case 0x0d: case 0x0d:
terminal.buffer.CarriageReturn() terminal.ActiveBuffer().CarriageReturn()
case 0x08: case 0x08:
// backspace // backspace
terminal.buffer.Backspace() terminal.ActiveBuffer().Backspace()
case 0x07: case 0x07:
// @todo ring bell - flash red or some shit? // @todo ring bell - flash red or some shit?
default: default:
// render character at current location // render character at current location
// fmt.Printf("%s\n", string([]byte{b})) // fmt.Printf("%s\n", string([]byte{b}))
if b >= 0x20 { if b >= 0x20 {
terminal.buffer.Write(b) terminal.ActiveBuffer().Write(b)
} else { } else {
terminal.logger.Error("Non-readable rune received: 0x%X", b) terminal.logger.Error("Non-readable rune received: 0x%X", b)
} }

View File

@ -12,103 +12,103 @@ func sgrSequenceHandler(params []string, intermediate string, terminal *Terminal
param := params[i] param := params[i]
switch param { switch param {
case "00", "0", "": case "00", "0", "":
attr := terminal.buffer.CursorAttr() attr := terminal.ActiveBuffer().CursorAttr()
*attr = buffer.CellAttributes{ *attr = buffer.CellAttributes{
FgColour: terminal.config.ColourScheme.Foreground, FgColour: terminal.config.ColourScheme.Foreground,
BgColour: terminal.config.ColourScheme.Background, BgColour: terminal.config.ColourScheme.Background,
} }
case "1", "01": case "1", "01":
terminal.buffer.CursorAttr().Bold = true terminal.ActiveBuffer().CursorAttr().Bold = true
case "2", "02": case "2", "02":
terminal.buffer.CursorAttr().Dim = true terminal.ActiveBuffer().CursorAttr().Dim = true
case "4", "04": case "4", "04":
terminal.buffer.CursorAttr().Underline = true terminal.ActiveBuffer().CursorAttr().Underline = true
case "5", "05": case "5", "05":
terminal.buffer.CursorAttr().Blink = true terminal.ActiveBuffer().CursorAttr().Blink = true
case "7", "07": case "7", "07":
terminal.buffer.CursorAttr().Reverse = true terminal.ActiveBuffer().CursorAttr().Reverse = true
case "8", "08": case "8", "08":
terminal.buffer.CursorAttr().Hidden = true terminal.ActiveBuffer().CursorAttr().Hidden = true
case "21": case "21":
terminal.buffer.CursorAttr().Bold = false terminal.ActiveBuffer().CursorAttr().Bold = false
case "22": case "22":
terminal.buffer.CursorAttr().Dim = false terminal.ActiveBuffer().CursorAttr().Dim = false
case "24": case "24":
terminal.buffer.CursorAttr().Underline = false terminal.ActiveBuffer().CursorAttr().Underline = false
case "25": case "25":
terminal.buffer.CursorAttr().Blink = false terminal.ActiveBuffer().CursorAttr().Blink = false
case "27": case "27":
terminal.buffer.CursorAttr().Reverse = false terminal.ActiveBuffer().CursorAttr().Reverse = false
case "28": case "28":
terminal.buffer.CursorAttr().Hidden = false terminal.ActiveBuffer().CursorAttr().Hidden = false
case "39": case "39":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Foreground terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Foreground
case "30": case "30":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Black terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Black
case "31": case "31":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Red terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Red
case "32": case "32":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Green terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Green
case "33": case "33":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Yellow terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Yellow
case "34": case "34":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Blue terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Blue
case "35": case "35":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Magenta terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Magenta
case "36": case "36":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.Cyan terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.Cyan
case "37": case "37":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.White terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.White
case "90": case "90":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.DarkGrey terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.DarkGrey
case "91": case "91":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightRed terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightRed
case "92": case "92":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightGreen terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightGreen
case "93": case "93":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightYellow terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightYellow
case "94": case "94":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightBlue terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightBlue
case "95": case "95":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightMagenta terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightMagenta
case "96": case "96":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.LightCyan terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.LightCyan
case "97": case "97":
terminal.buffer.CursorAttr().FgColour = terminal.config.ColourScheme.White terminal.ActiveBuffer().CursorAttr().FgColour = terminal.config.ColourScheme.White
case "49": case "49":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Background terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Background
case "40": case "40":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Black terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Black
case "41": case "41":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Red terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Red
case "42": case "42":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Green terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Green
case "43": case "43":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Yellow terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Yellow
case "44": case "44":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Blue terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Blue
case "45": case "45":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Magenta terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Magenta
case "46": case "46":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.Cyan terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.Cyan
case "47": case "47":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.White terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.White
case "100": case "100":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.DarkGrey terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.DarkGrey
case "101": case "101":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightRed terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightRed
case "102": case "102":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightGreen terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightGreen
case "103": case "103":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightYellow terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightYellow
case "104": case "104":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightBlue terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightBlue
case "105": case "105":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightMagenta terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightMagenta
case "106": case "106":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.LightCyan terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.LightCyan
case "107": case "107":
terminal.buffer.CursorAttr().BgColour = terminal.config.ColourScheme.White terminal.ActiveBuffer().CursorAttr().BgColour = terminal.config.ColourScheme.White
default: default:
return fmt.Errorf("Unknown SGR control sequence: (ESC[%s%sm)", param, intermediate) return fmt.Errorf("Unknown SGR control sequence: (ESC[%s%sm)", param, intermediate)
} }

View File

@ -15,18 +15,24 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
const (
MainBuffer uint8 = 0
AltBuffer uint8 = 1
)
type Terminal struct { type Terminal struct {
buffer *buffer.Buffer buffers []*buffer.Buffer
lock sync.Mutex activeBufferIndex uint8
pty *os.File lock sync.Mutex
logger *zap.SugaredLogger pty *os.File
title string logger *zap.SugaredLogger
size Winsize title string
config config.Config size Winsize
titleHandlers []chan bool config config.Config
pauseChan chan bool titleHandlers []chan bool
resumeChan chan bool pauseChan chan bool
modes Modes resumeChan chan bool
modes Modes
} }
type Modes struct { type Modes struct {
@ -50,10 +56,16 @@ type Position struct {
func New(pty *os.File, logger *zap.SugaredLogger, config config.Config) *Terminal { func New(pty *os.File, logger *zap.SugaredLogger, config config.Config) *Terminal {
return &Terminal{ return &Terminal{
buffer: buffer.NewBuffer(0, 0, buffer.CellAttributes{ buffers: []*buffer.Buffer{
FgColour: config.ColourScheme.Foreground, buffer.NewBuffer(1, 1, buffer.CellAttributes{
BgColour: config.ColourScheme.Background, FgColour: config.ColourScheme.Foreground,
}), BgColour: config.ColourScheme.Background,
}),
buffer.NewBuffer(1, 1, buffer.CellAttributes{
FgColour: config.ColourScheme.Foreground,
BgColour: config.ColourScheme.Background,
}),
},
pty: pty, pty: pty,
logger: logger, logger: logger,
config: config, 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 { func (terminal *Terminal) GetScrollOffset() uint {
return terminal.buffer.GetScrollOffset() return terminal.ActiveBuffer().GetScrollOffset()
} }
func (terminal *Terminal) ScrollDown(lines uint16) { func (terminal *Terminal) ScrollDown(lines uint16) {
terminal.buffer.ScrollDown(lines) terminal.ActiveBuffer().ScrollDown(lines)
} }
func (terminal *Terminal) ScrollUp(lines uint16) { func (terminal *Terminal) ScrollUp(lines uint16) {
terminal.buffer.ScrollUp(lines) terminal.ActiveBuffer().ScrollUp(lines)
} }
func (terminal *Terminal) ScrollPageDown() { func (terminal *Terminal) ScrollPageDown() {
terminal.buffer.ScrollPageDown() terminal.ActiveBuffer().ScrollPageDown()
} }
func (terminal *Terminal) ScrollPageUp() { func (terminal *Terminal) ScrollPageUp() {
terminal.buffer.ScrollPageUp() terminal.ActiveBuffer().ScrollPageUp()
} }
func (terminal *Terminal) ScrollToEnd() { func (terminal *Terminal) ScrollToEnd() {
terminal.buffer.ScrollToEnd() terminal.ActiveBuffer().ScrollToEnd()
} }
func (terminal *Terminal) GetVisibleLines() []buffer.Line { func (terminal *Terminal) GetVisibleLines() []buffer.Line {
return terminal.buffer.GetVisibleLines() return terminal.ActiveBuffer().GetVisibleLines()
} }
func (terminal *Terminal) GetCell(col int, row int) *buffer.Cell { func (terminal *Terminal) GetCell(col uint16, row uint16) *buffer.Cell {
return terminal.buffer.GetCell(col, row) return terminal.ActiveBuffer().GetCell(col, row)
} }
func (terminal *Terminal) AttachDisplayChangeHandler(handler chan bool) { 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) { func (terminal *Terminal) AttachTitleChangeHandler(handler chan bool) {
@ -117,19 +145,19 @@ func (terminal *Terminal) emitTitleChange() {
} }
func (terminal *Terminal) GetLogicalCursorX() uint16 { func (terminal *Terminal) GetLogicalCursorX() uint16 {
if terminal.buffer.CursorColumn() >= terminal.buffer.Width() { if terminal.ActiveBuffer().CursorColumn() >= terminal.ActiveBuffer().Width() {
return 0 return 0
} }
return terminal.buffer.CursorColumn() return terminal.ActiveBuffer().CursorColumn()
} }
func (terminal *Terminal) GetLogicalCursorY() uint16 { func (terminal *Terminal) GetLogicalCursorY() uint16 {
if terminal.buffer.CursorColumn() >= terminal.buffer.Width() { if terminal.ActiveBuffer().CursorColumn() >= terminal.ActiveBuffer().Width() {
return terminal.buffer.CursorLine() + 1 return terminal.ActiveBuffer().CursorLine() + 1
} }
return terminal.buffer.CursorLine() return terminal.ActiveBuffer().CursorLine()
} }
func (terminal *Terminal) GetTitle() string { func (terminal *Terminal) GetTitle() string {
@ -174,21 +202,21 @@ func (terminal *Terminal) Read() error {
} }
func (terminal *Terminal) Clear() { func (terminal *Terminal) Clear() {
terminal.buffer.Clear() terminal.ActiveBuffer().Clear()
} }
func (terminal *Terminal) GetSize() (int, int) { func (terminal *Terminal) GetSize() (int, int) {
return int(terminal.size.Width), int(terminal.size.Height) 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() terminal.lock.Lock()
defer terminal.lock.Unlock() defer terminal.lock.Unlock()
terminal.size.Width = uint16(newCols) terminal.size.Width = uint16(newCols)
terminal.size.Height = uint16(newLines) 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()), _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(terminal.pty.Fd()),
uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(&terminal.size))) uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(&terminal.size)))