From 11b4a2e48729c2f0d3eebe6e59e89436db54cbd1 Mon Sep 17 00:00:00 2001 From: Liam Galvin Date: Wed, 8 Aug 2018 13:27:12 +0100 Subject: [PATCH] buffer is getting pretty solid --- buffer/buffer.go | 163 ++++++++++++++++++------------------------ buffer/buffer_test.go | 127 +++++++++++++++----------------- gui/gui.go | 2 +- terminal/escapes.go | 2 +- terminal/terminal.go | 16 +++++ 5 files changed, 144 insertions(+), 166 deletions(-) diff --git a/buffer/buffer.go b/buffer/buffer.go index 0f1f8d5..57a09c0 100644 --- a/buffer/buffer.go +++ b/buffer/buffer.go @@ -115,12 +115,6 @@ func (buffer *Buffer) ViewHeight() uint16 { return buffer.viewHeight } -func (buffer *Buffer) ensureLinesExistToRawHeight() { - for int(buffer.RawLine()) >= len(buffer.lines) { - buffer.lines = append(buffer.lines, newLine()) - } -} - // Write will write a rune to the terminal at the position of the cursor, and increment the cursor position func (buffer *Buffer) Write(runes ...rune) { for _, r := range runes { @@ -131,14 +125,44 @@ func (buffer *Buffer) Write(runes ...rune) { buffer.CarriageReturn() continue } - buffer.ensureLinesExistToRawHeight() - line := &buffer.lines[buffer.RawLine()] - for int(buffer.CursorColumn()) >= len(line.cells) { - line.cells = append(line.cells, newCell()) + line := buffer.getCurrentLine() + + if buffer.CursorColumn() >= buffer.Width() { // if we're after the line, move to next + buffer.cursorX = 0 + + if buffer.cursorY >= buffer.ViewHeight()-1 { + buffer.lines = append(buffer.lines, newLine()) + } else { + buffer.cursorY++ + } + + //if int(buffer.RawLine()) >= len(buffer.lines) { // if we need to create a new line, set it to wrapped and write to it + + newLine := buffer.getCurrentLine() + newLine.setWrapped(true) + if len(newLine.cells) == 0 { + newLine.cells = []Cell{Cell{}} + } + cell := &newLine.cells[buffer.CursorColumn()] + cell.setRune(r) + cell.attr = buffer.cursorAttr + //} else { + //newLine := &buffer.lines[buffer.RawLine()] + //} + + // @todo if next line is wrapped then prepend to it and shuffle characters along line, wrapping to next if necessary + } else { + + for int(buffer.CursorColumn()) >= len(line.cells) { + line.cells = append(line.cells, newCell()) + } + + cell := &line.cells[buffer.CursorColumn()] + cell.setRune(r) + cell.attr = buffer.cursorAttr + } - cell := &line.cells[buffer.CursorColumn()] - cell.setRune(r) - cell.attr = buffer.cursorAttr + buffer.incrementCursorPosition() } } @@ -147,78 +171,30 @@ func (buffer *Buffer) incrementCursorPosition() { defer buffer.emitDisplayChange() - if buffer.CursorColumn()+1 < buffer.Width() { // if not at end of line + // we can increment one column past the end of the line. + // this is effectively the beginning of the next line, except when we \r etc. + if buffer.CursorColumn() < buffer.Width() { // if not at end of line buffer.cursorX++ - } else { // we're at the end of the current line - - if buffer.cursorY == buffer.viewHeight-1 { - // if we're on the last line, we can't move the cursor down, we have to move the buffer up, i.e. add a new line - - line := newLine() - line.setWrapped(true) - buffer.lines = append(buffer.lines, line) - buffer.cursorX = 0 - - } else { - // if we're not on the bottom line... - - buffer.cursorX = 0 - buffer.cursorY++ - - rawLine := int(buffer.RawLine()) - - line := newLine() - line.setWrapped(true) - buffer.lines = append(append(buffer.lines[:rawLine], line), buffer.lines[rawLine:]...) - } + } else { + panic("bad") } } func (buffer *Buffer) CarriageReturn() { - defer buffer.emitDisplayChange() - - line, err := buffer.getCurrentLine() - if err != nil { - - fmt.Println("Failed to get new line during carriage return") - - buffer.cursorX = 0 - return - } - - if buffer.cursorX == 0 && line.wrapped { - if len(line.cells) == 0 { - rawLine := int(buffer.RawLine()) - buffer.lines = append(buffer.lines[:rawLine], buffer.lines[rawLine+1:]...) - } - buffer.cursorY-- - } else { - buffer.cursorX = 0 - } + buffer.cursorX = 0 } func (buffer *Buffer) NewLine() { - defer buffer.emitDisplayChange() - - // if we're at the beginning of a line which wrapped from the previous one, and we need a new line, we can effectively not add a new line, and set the current one to non-wrapped - if buffer.cursorX == 0 { - line, err := buffer.getCurrentLine() - if err == nil && line != nil && line.wrapped { - line.setWrapped(false) - return - } - } - - if buffer.cursorY == buffer.viewHeight-1 { - buffer.ensureLinesExistToRawHeight() + if buffer.cursorY >= buffer.ViewHeight()-1 { buffer.lines = append(buffer.lines, newLine()) } else { buffer.cursorY++ } + buffer.cursorX = 0 } func (buffer *Buffer) MovePosition(x int16, y int16) { @@ -287,30 +263,36 @@ func (buffer *Buffer) Clear() { buffer.SetPosition(0, 0) // do we need to set position? } -func (buffer *Buffer) getCurrentLine() (*Line, error) { +// creates if necessary +func (buffer *Buffer) getCurrentLine() *Line { - if int(buffer.RawLine()) < len(buffer.lines) { - return &buffer.lines[buffer.RawLine()], nil + if buffer.cursorY >= buffer.ViewHeight() { + panic(fmt.Sprintf("cursor is outside of view: %d %d", buffer.cursorX, buffer.cursorY)) } - return nil, fmt.Errorf("Line %d does not exist", buffer.cursorY) + if len(buffer.lines) < int(buffer.ViewHeight()) { + for int(buffer.cursorY) >= len(buffer.lines) { + buffer.lines = append(buffer.lines, newLine()) + } + return &buffer.lines[int(buffer.cursorY)] + } + + if int(buffer.RawLine()) < len(buffer.lines) { + return &buffer.lines[buffer.RawLine()] + } + + panic(fmt.Sprintf("Failed to retrieve line for %d %d", buffer.cursorX, buffer.cursorY)) } func (buffer *Buffer) EraseLine() { defer buffer.emitDisplayChange() - line, err := buffer.getCurrentLine() - if err != nil { - return - } + line := buffer.getCurrentLine() line.cells = []Cell{} } func (buffer *Buffer) EraseLineToCursor() { defer buffer.emitDisplayChange() - line, err := buffer.getCurrentLine() - if err != nil { - return - } + line := buffer.getCurrentLine() for i := 0; i <= int(buffer.cursorX); i++ { if i < len(line.cells) { line.cells[i].erase() @@ -320,10 +302,7 @@ func (buffer *Buffer) EraseLineToCursor() { func (buffer *Buffer) EraseLineFromCursor() { defer buffer.emitDisplayChange() - line, err := buffer.getCurrentLine() - if err != nil { - return - } + line := buffer.getCurrentLine() if line.wrapped && buffer.cursorX == 0 { //panic("wtf") @@ -335,8 +314,6 @@ func (buffer *Buffer) EraseLineFromCursor() { max = len(line.cells) } - fmt.Printf("Erase line from cursor, cursor is at %d\n", buffer.cursorX) - for c := int(buffer.cursorX); c < len(line.cells); c++ { line.cells[c].erase() } @@ -354,10 +331,8 @@ func (buffer *Buffer) EraseDisplay() { func (buffer *Buffer) EraseDisplayFromCursor() { defer buffer.emitDisplayChange() - line, err := buffer.getCurrentLine() - if err != nil { - return - } + line := buffer.getCurrentLine() + line.cells = line.cells[:buffer.cursorX] for i := buffer.cursorY + 1; i < buffer.ViewHeight(); i++ { rawLine := buffer.convertViewLineToRawLine(i) @@ -369,10 +344,8 @@ func (buffer *Buffer) EraseDisplayFromCursor() { func (buffer *Buffer) EraseDisplayToCursor() { defer buffer.emitDisplayChange() - line, err := buffer.getCurrentLine() - if err != nil { - return - } + line := buffer.getCurrentLine() + for i := 0; i < int(buffer.cursorX); i++ { line.cells[i].erase() } diff --git a/buffer/buffer_test.go b/buffer/buffer_test.go index 58a4848..fcfa219 100644 --- a/buffer/buffer_test.go +++ b/buffer/buffer_test.go @@ -31,99 +31,74 @@ func TestBufferCreation(t *testing.T) { assert.NotNil(t, b.lines) } -func TestBufferCursorIncrement(t *testing.T) { +func TestBufferWriteIncrementsCursorCorrectly(t *testing.T) { b := NewBuffer(5, 4, CellAttributes{}) - b.incrementCursorPosition() + + /*01234 + |----- + 0|xxxxx + 1| + 2| + 3| + |----- + */ + + b.Write('x') require.Equal(t, uint16(1), b.CursorColumn()) require.Equal(t, uint16(0), b.CursorLine()) - b.incrementCursorPosition() + b.Write('x') require.Equal(t, uint16(2), b.CursorColumn()) require.Equal(t, uint16(0), b.CursorLine()) - b.incrementCursorPosition() + b.Write('x') require.Equal(t, uint16(3), b.CursorColumn()) require.Equal(t, uint16(0), b.CursorLine()) - b.incrementCursorPosition() + b.Write('x') require.Equal(t, uint16(4), b.CursorColumn()) require.Equal(t, uint16(0), b.CursorLine()) - b.incrementCursorPosition() - require.Equal(t, uint16(0), b.CursorColumn()) + b.Write('x') + require.Equal(t, uint16(5), b.CursorColumn()) + require.Equal(t, uint16(0), b.CursorLine()) + + b.Write('x') + require.Equal(t, uint16(1), b.CursorColumn()) require.Equal(t, uint16(1), b.CursorLine()) - b.incrementCursorPosition() - b.incrementCursorPosition() - b.incrementCursorPosition() - b.incrementCursorPosition() - b.incrementCursorPosition() - b.incrementCursorPosition() - b.incrementCursorPosition() - b.incrementCursorPosition() - b.incrementCursorPosition() - b.incrementCursorPosition() + b.Write('x') + require.Equal(t, uint16(2), b.CursorColumn()) + require.Equal(t, uint16(1), b.CursorLine()) - require.Equal(t, uint16(0), b.CursorColumn()) - require.Equal(t, uint16(3), b.CursorLine()) - - b.Write([]rune("hello\r\n")...) - b.Write([]rune("hello\r\n")...) - b.Write([]rune("hello\r\n")...) - b.Write([]rune("hello\r\n")...) - b.Write([]rune("hello\r\n")...) - b.Write([]rune("hello")...) - b.SetPosition(0, 2) - b.incrementCursorPosition() - -} -func TestBufferWrite(t *testing.T) { - - b := NewBuffer(5, 20, CellAttributes{}) - - assert.Equal(t, uint16(0), b.CursorColumn()) - assert.Equal(t, uint16(0), b.CursorLine()) - - b.Write('a') - assert.Equal(t, uint16(1), b.CursorColumn()) - assert.Equal(t, uint16(0), b.CursorLine()) - - b.Write('b') - assert.Equal(t, uint16(2), b.CursorColumn()) - assert.Equal(t, uint16(0), b.CursorLine()) - - b.Write('c') - assert.Equal(t, uint16(3), b.CursorColumn()) - assert.Equal(t, uint16(0), b.CursorLine()) - - b.Write('d') - assert.Equal(t, uint16(4), b.CursorColumn()) - assert.Equal(t, uint16(0), b.CursorLine()) - - b.Write('e') - assert.Equal(t, uint16(0), b.CursorColumn()) - assert.Equal(t, uint16(1), b.CursorLine()) - - b.Write('f') - assert.Equal(t, uint16(1), b.CursorColumn()) - assert.Equal(t, uint16(1), b.CursorLine()) - - //b.lines[0].cells[] + lines := b.GetVisibleLines() + require.Equal(t, 2, len(lines)) + assert.Equal(t, "xxxxx", lines[0].String()) + assert.Equal(t, "xx", lines[1].String()) } func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) { b := NewBuffer(3, 20, CellAttributes{}) b.Write('a', 'b', 'c') - assert.Equal(t, uint16(0), b.cursorX) + assert.Equal(t, uint16(3), b.cursorX) + assert.Equal(t, uint16(0), b.cursorY) b.Write(0x0a) + assert.Equal(t, uint16(0), b.cursorX) + assert.Equal(t, uint16(1), b.cursorY) + b.Write('d', 'e', 'f') + assert.Equal(t, uint16(3), b.cursorX) + assert.Equal(t, uint16(1), b.cursorY) b.Write(0x0a) + assert.Equal(t, uint16(0), b.cursorX) + assert.Equal(t, uint16(2), b.cursorY) + + require.Equal(t, 2, len(b.lines)) assert.Equal(t, "abc", b.lines[0].String()) assert.Equal(t, "def", b.lines[1].String()) - assert.Equal(t, "", b.lines[2].String()) } @@ -132,7 +107,7 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) { /* |abc |d - |_ef + |ef | | |z @@ -148,7 +123,7 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) { assert.Equal(t, "abc", b.lines[0].String()) assert.Equal(t, "d", b.lines[1].String()) - assert.Equal(t, "\x00ef", b.lines[2].String()) + assert.Equal(t, "ef", b.lines[2].String()) assert.Equal(t, "", b.lines[3].String()) assert.Equal(t, "", b.lines[4].String()) assert.Equal(t, "z", b.lines[5].String()) @@ -261,14 +236,28 @@ func TestCarriageReturnOnWrappedLine(t *testing.T) { } func TestCarriageReturnOnOverWrappedLine(t *testing.T) { + + /* + |hello + |secret + | sauce + | + | + | + | + | + | + | + */ + b := NewBuffer(6, 10, CellAttributes{}) b.Write([]rune("hello there!\rsecret sauce")...) lines := b.GetVisibleLines() - require.Equal(t, 4, len(lines)) + require.Equal(t, 3, len(lines)) assert.Equal(t, "hello ", lines[0].String()) assert.Equal(t, "secret", lines[1].String()) + assert.True(t, b.lines[1].wrapped) assert.Equal(t, " sauce", lines[2].String()) - assert.Equal(t, "", lines[3].String()) } func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) { @@ -380,7 +369,7 @@ func TestEraseLineAfterCursor(t *testing.T) { b.MovePosition(-3, 0) b.EraseLineFromCursor() assert.Equal(t, "hello, this is a test", b.lines[0].String()) - assert.Equal(t, "dele", b.lines[1].String()) + assert.Equal(t, "dele\x00\x00\x00", b.lines[1].String()) } func TestEraseDisplay(t *testing.T) { b := NewBuffer(80, 5, CellAttributes{}) diff --git a/gui/gui.go b/gui/gui.go index da57119..cb3f4b3 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -186,7 +186,7 @@ func (gui *GUI) Render() error { gui.font.SetColor(1, 0.5, 0.5, 0.5) fpsData := "" if gui.config.Rendering.AlwaysRepaint { - fpsData = fmt.Sprintf("%d FPS", fps) + fpsData = fmt.Sprintf("%d FPS | %d,%d", fps, gui.terminal.GetLogicalCursorX(), gui.terminal.GetLogicalCursorY()) } gui.font.Print(10, float32(gui.height-20), 1.5, fmt.Sprintf("%s", fpsData)) } diff --git a/terminal/escapes.go b/terminal/escapes.go index 225feb8..1f1a4e0 100644 --- a/terminal/escapes.go +++ b/terminal/escapes.go @@ -49,7 +49,7 @@ func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) { // backspace terminal.buffer.MovePosition(-1, 0) case 0x07: - // @todo ring bell + // @todo ring bell - flash red or some shit? default: // render character at current location // fmt.Printf("%s\n", string([]byte{b})) diff --git a/terminal/terminal.go b/terminal/terminal.go index bccd68d..baace09 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -76,6 +76,22 @@ func (terminal *Terminal) emitTitleChange() { } } +func (terminal *Terminal) GetLogicalCursorX() uint16 { + if terminal.buffer.CursorColumn() >= terminal.buffer.Width() { + return 0 + } + + return terminal.buffer.CursorColumn() +} + +func (terminal *Terminal) GetLogicalCursorY() uint16 { + if terminal.buffer.CursorColumn() >= terminal.buffer.Width() { + return terminal.buffer.CursorLine() + 1 + } + + return terminal.buffer.CursorLine() +} + func (terminal *Terminal) GetTitle() string { return terminal.title }