buffer is getting pretty solid

This commit is contained in:
Liam Galvin 2018-08-08 13:27:12 +01:00
parent 9ac2e9cba0
commit 11b4a2e487
5 changed files with 144 additions and 166 deletions

View File

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

View File

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

View File

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

View File

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

View File

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