mirror of https://github.com/liamg/aminal.git
lots more progress
This commit is contained in:
parent
e54e0e2182
commit
d5f5cf8592
108
buffer/buffer.go
108
buffer/buffer.go
|
@ -2,8 +2,6 @@ package buffer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Buffer struct {
|
type Buffer struct {
|
||||||
|
@ -46,7 +44,7 @@ func (buffer *Buffer) GetCell(viewCol int, viewRow int) *Cell {
|
||||||
return &line.cells[viewCol]
|
return &line.cells[viewCol]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) attachDisplayChangeHandler(handler chan bool) {
|
func (buffer *Buffer) AttachDisplayChangeHandler(handler chan bool) {
|
||||||
if buffer.displayChangeHandlers == nil {
|
if buffer.displayChangeHandlers == nil {
|
||||||
buffer.displayChangeHandlers = []chan bool{}
|
buffer.displayChangeHandlers = []chan bool{}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +55,10 @@ func (buffer *Buffer) attachDisplayChangeHandler(handler chan bool) {
|
||||||
func (buffer *Buffer) emitDisplayChange() {
|
func (buffer *Buffer) emitDisplayChange() {
|
||||||
for _, channel := range buffer.displayChangeHandlers {
|
for _, channel := range buffer.displayChangeHandlers {
|
||||||
go func(c chan bool) {
|
go func(c chan bool) {
|
||||||
c <- true
|
select {
|
||||||
|
case c <- true:
|
||||||
|
default:
|
||||||
|
}
|
||||||
}(channel)
|
}(channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,44 +133,64 @@ func (buffer *Buffer) Write(runes ...rune) {
|
||||||
|
|
||||||
func (buffer *Buffer) incrementCursorPosition() {
|
func (buffer *Buffer) incrementCursorPosition() {
|
||||||
|
|
||||||
if buffer.CursorColumn()+1 < buffer.Width() {
|
defer buffer.emitDisplayChange()
|
||||||
|
|
||||||
|
if buffer.CursorColumn()+1 < buffer.Width() { // if not at end of line
|
||||||
|
|
||||||
buffer.cursorX++
|
buffer.cursorX++
|
||||||
} else {
|
|
||||||
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
|
} 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 := newLine()
|
||||||
line.setWrapped(true)
|
line.setWrapped(true)
|
||||||
buffer.lines = append(buffer.lines, line)
|
buffer.lines = append(buffer.lines, line)
|
||||||
buffer.cursorX = 0
|
buffer.cursorX = 0
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// if we're not on the bottom line...
|
||||||
|
|
||||||
buffer.cursorX = 0
|
buffer.cursorX = 0
|
||||||
if buffer.Height() < int(buffer.ViewHeight()) {
|
buffer.cursorY++
|
||||||
|
|
||||||
|
_, err := buffer.getCurrentLine()
|
||||||
|
if err != nil {
|
||||||
line := newLine()
|
line := newLine()
|
||||||
line.setWrapped(true)
|
line.setWrapped(true)
|
||||||
buffer.lines = append(buffer.lines, line)
|
buffer.lines = append(buffer.lines, line)
|
||||||
buffer.cursorY++
|
|
||||||
} else {
|
|
||||||
// @todo test this branch
|
|
||||||
line := &buffer.lines[buffer.RawLine()]
|
|
||||||
line.setWrapped(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) CarriageReturn() {
|
func (buffer *Buffer) CarriageReturn() {
|
||||||
|
|
||||||
|
defer buffer.emitDisplayChange()
|
||||||
|
|
||||||
line, err := buffer.getCurrentLine()
|
line, err := buffer.getCurrentLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// @todo check this...
|
||||||
buffer.cursorX = 0
|
buffer.cursorX = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if buffer.cursorX == 0 && line.wrapped {
|
if buffer.cursorX == 0 && line.wrapped {
|
||||||
buffer.cursorY--
|
buffer.cursorY--
|
||||||
|
if len(line.cells) == 0 {
|
||||||
|
rawLine := int(buffer.RawLine())
|
||||||
|
buffer.lines = append(buffer.lines[:rawLine], buffer.lines[rawLine+1:]...)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
buffer.cursorX = 0
|
buffer.cursorX = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) NewLine() {
|
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 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 {
|
if buffer.cursorX == 0 {
|
||||||
line := &buffer.lines[buffer.RawLine()]
|
line := &buffer.lines[buffer.RawLine()]
|
||||||
|
@ -190,25 +211,45 @@ func (buffer *Buffer) NewLine() {
|
||||||
|
|
||||||
func (buffer *Buffer) MovePosition(x int16, y int16) {
|
func (buffer *Buffer) MovePosition(x int16, y int16) {
|
||||||
|
|
||||||
|
var toX uint16
|
||||||
|
var toY uint16
|
||||||
|
|
||||||
if int16(buffer.cursorX)+x < 0 {
|
if int16(buffer.cursorX)+x < 0 {
|
||||||
x = -int16(buffer.cursorX)
|
toX = 0
|
||||||
|
} else {
|
||||||
|
toX = uint16(int16(buffer.cursorX) + x)
|
||||||
}
|
}
|
||||||
|
|
||||||
if int16(buffer.cursorY)+y < 0 {
|
if int16(buffer.cursorY)+y < 0 {
|
||||||
y = -int16(buffer.cursorY)
|
toY = 0
|
||||||
|
} else {
|
||||||
|
toY = uint16(int16(buffer.cursorY) + y)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.SetPosition(uint16(int16(buffer.cursorX)+x), uint16(int16(buffer.cursorY)+y))
|
buffer.SetPosition(toX, toY)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buffer *Buffer) ShowCursor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buffer *Buffer) HideCursor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buffer *Buffer) SetCursorBlink(enabled bool) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) SetPosition(col uint16, line uint16) {
|
func (buffer *Buffer) SetPosition(col uint16, line uint16) {
|
||||||
|
defer buffer.emitDisplayChange()
|
||||||
if col >= buffer.ViewWidth() {
|
if col >= buffer.ViewWidth() {
|
||||||
col = buffer.ViewWidth() - 1
|
col = buffer.ViewWidth() - 1
|
||||||
logrus.Errorf("Cannot set cursor position: column %d is outside of the current view width (%d columns)", col, buffer.ViewWidth())
|
//logrus.Errorf("Cannot set cursor position: column %d is outside of the current view width (%d columns)", col, buffer.ViewWidth())
|
||||||
}
|
}
|
||||||
if line >= buffer.ViewHeight() {
|
if line >= buffer.ViewHeight() {
|
||||||
line = buffer.ViewHeight() - 1
|
line = buffer.ViewHeight() - 1
|
||||||
logrus.Errorf("Cannot set cursor position: line %d is outside of the current view height (%d lines)", line, buffer.ViewHeight())
|
//logrus.Errorf("Cannot set cursor position: line %d is outside of the current view height (%d lines)", line, buffer.ViewHeight())
|
||||||
}
|
}
|
||||||
buffer.cursorX = col
|
buffer.cursorX = col
|
||||||
buffer.cursorY = line
|
buffer.cursorY = line
|
||||||
|
@ -227,10 +268,11 @@ func (buffer *Buffer) GetVisibleLines() []Line {
|
||||||
// tested to here
|
// tested to here
|
||||||
|
|
||||||
func (buffer *Buffer) Clear() {
|
func (buffer *Buffer) Clear() {
|
||||||
|
defer buffer.emitDisplayChange()
|
||||||
for i := 0; i < int(buffer.ViewHeight()); i++ {
|
for i := 0; i < int(buffer.ViewHeight()); i++ {
|
||||||
buffer.lines = append(buffer.lines, newLine())
|
buffer.lines = append(buffer.lines, newLine())
|
||||||
}
|
}
|
||||||
buffer.SetPosition(0, 0)
|
buffer.SetPosition(0, 0) // do we need to set position?
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) getCurrentLine() (*Line, error) {
|
func (buffer *Buffer) getCurrentLine() (*Line, error) {
|
||||||
|
@ -243,6 +285,7 @@ func (buffer *Buffer) getCurrentLine() (*Line, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) EraseLine() {
|
func (buffer *Buffer) EraseLine() {
|
||||||
|
defer buffer.emitDisplayChange()
|
||||||
line, err := buffer.getCurrentLine()
|
line, err := buffer.getCurrentLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -251,6 +294,7 @@ func (buffer *Buffer) EraseLine() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) EraseLineToCursor() {
|
func (buffer *Buffer) EraseLineToCursor() {
|
||||||
|
defer buffer.emitDisplayChange()
|
||||||
line, err := buffer.getCurrentLine()
|
line, err := buffer.getCurrentLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -263,42 +307,52 @@ func (buffer *Buffer) EraseLineToCursor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) EraseLineAfterCursor() {
|
func (buffer *Buffer) EraseLineAfterCursor() {
|
||||||
|
defer buffer.emitDisplayChange()
|
||||||
line, err := buffer.getCurrentLine()
|
line, err := buffer.getCurrentLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := int(buffer.cursorX + 1); i < len(line.cells); i++ {
|
|
||||||
line.cells[i].erase()
|
max := int(buffer.cursorX + 1)
|
||||||
|
if max > len(line.cells) {
|
||||||
|
max = len(line.cells)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
line.cells = line.cells[:max]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) EraseDisplayAfterCursor() {
|
func (buffer *Buffer) EraseDisplayAfterCursor() {
|
||||||
|
defer buffer.emitDisplayChange()
|
||||||
line, err := buffer.getCurrentLine()
|
line, err := buffer.getCurrentLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
line.cells = line.cells[:buffer.cursorX]
|
line.cells = line.cells[:buffer.cursorX]
|
||||||
for i := int(buffer.RawLine() + 1); i < buffer.Height(); i++ {
|
for i := buffer.cursorY + 1; i < buffer.ViewHeight(); i++ {
|
||||||
if i < len(buffer.lines) {
|
rawLine := buffer.convertViewLineToRawLine(i)
|
||||||
buffer.lines[i].cells = []Cell{}
|
if int(rawLine) < len(buffer.lines) {
|
||||||
|
buffer.lines[int(rawLine)].cells = []Cell{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) EraseDisplayToCursor() {
|
func (buffer *Buffer) EraseDisplayToCursor() {
|
||||||
|
defer buffer.emitDisplayChange()
|
||||||
line, err := buffer.getCurrentLine()
|
line, err := buffer.getCurrentLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
line.cells = line.cells[buffer.cursorX+1:]
|
line.cells = line.cells[buffer.cursorX+1:]
|
||||||
for i := 0; i < int(buffer.RawLine()); i++ {
|
for i := uint16(0); i < buffer.cursorY; i++ {
|
||||||
if i < len(buffer.lines) {
|
rawLine := buffer.convertViewLineToRawLine(i)
|
||||||
buffer.lines[i].cells = []Cell{}
|
if int(rawLine) < len(buffer.lines) {
|
||||||
|
buffer.lines[int(rawLine)].cells = []Cell{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) ResizeView(width uint16, height uint16) {
|
func (buffer *Buffer) ResizeView(width uint16, height uint16) {
|
||||||
|
defer buffer.emitDisplayChange()
|
||||||
buffer.viewWidth = width
|
buffer.viewWidth = width
|
||||||
buffer.viewHeight = height
|
buffer.viewHeight = height
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOffsets(t *testing.T) {
|
func TestOffsets(t *testing.T) {
|
||||||
b := NewBuffer(10, 8)
|
b := NewBuffer(10, 8, CellAttributes{})
|
||||||
test := "hellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\n?"
|
test := "hellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\nhellothere\n?"
|
||||||
b.Write([]rune(test)...)
|
b.Write([]rune(test)...)
|
||||||
assert.Equal(t, uint16(10), b.ViewWidth())
|
assert.Equal(t, uint16(10), b.ViewWidth())
|
||||||
|
@ -19,7 +19,7 @@ func TestOffsets(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBufferCreation(t *testing.T) {
|
func TestBufferCreation(t *testing.T) {
|
||||||
b := NewBuffer(10, 20)
|
b := NewBuffer(10, 20, CellAttributes{})
|
||||||
assert.Equal(t, uint16(10), b.Width())
|
assert.Equal(t, uint16(10), b.Width())
|
||||||
assert.Equal(t, uint16(20), b.ViewHeight())
|
assert.Equal(t, uint16(20), b.ViewHeight())
|
||||||
assert.Equal(t, uint16(0), b.CursorColumn())
|
assert.Equal(t, uint16(0), b.CursorColumn())
|
||||||
|
@ -29,7 +29,7 @@ func TestBufferCreation(t *testing.T) {
|
||||||
|
|
||||||
func TestBufferCursorIncrement(t *testing.T) {
|
func TestBufferCursorIncrement(t *testing.T) {
|
||||||
|
|
||||||
b := NewBuffer(5, 4)
|
b := NewBuffer(5, 4, CellAttributes{})
|
||||||
b.incrementCursorPosition()
|
b.incrementCursorPosition()
|
||||||
require.Equal(t, uint16(1), b.CursorColumn())
|
require.Equal(t, uint16(1), b.CursorColumn())
|
||||||
require.Equal(t, uint16(0), b.CursorLine())
|
require.Equal(t, uint16(0), b.CursorLine())
|
||||||
|
@ -76,7 +76,7 @@ func TestBufferCursorIncrement(t *testing.T) {
|
||||||
}
|
}
|
||||||
func TestBufferWrite(t *testing.T) {
|
func TestBufferWrite(t *testing.T) {
|
||||||
|
|
||||||
b := NewBuffer(5, 20)
|
b := NewBuffer(5, 20, CellAttributes{})
|
||||||
|
|
||||||
assert.Equal(t, uint16(0), b.CursorColumn())
|
assert.Equal(t, uint16(0), b.CursorColumn())
|
||||||
assert.Equal(t, uint16(0), b.CursorLine())
|
assert.Equal(t, uint16(0), b.CursorLine())
|
||||||
|
@ -110,7 +110,7 @@ func TestBufferWrite(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
|
func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
|
||||||
b := NewBuffer(3, 20)
|
b := NewBuffer(3, 20, CellAttributes{})
|
||||||
b.Write('a', 'b', 'c')
|
b.Write('a', 'b', 'c')
|
||||||
assert.Equal(t, uint16(0), b.cursorX)
|
assert.Equal(t, uint16(0), b.cursorX)
|
||||||
b.Write(0x0a)
|
b.Write(0x0a)
|
||||||
|
@ -124,7 +124,7 @@ func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
|
func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
|
||||||
b := NewBuffer(3, 20)
|
b := NewBuffer(3, 20, CellAttributes{})
|
||||||
b.Write('a', 'b', 'c', 'd')
|
b.Write('a', 'b', 'c', 'd')
|
||||||
b.Write(0x0a)
|
b.Write(0x0a)
|
||||||
b.Write('e', 'f')
|
b.Write('e', 'f')
|
||||||
|
@ -143,7 +143,7 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
|
||||||
|
|
||||||
func TestSetPosition(t *testing.T) {
|
func TestSetPosition(t *testing.T) {
|
||||||
|
|
||||||
b := NewBuffer(120, 80)
|
b := NewBuffer(120, 80, CellAttributes{})
|
||||||
assert.Equal(t, 0, int(b.CursorColumn()))
|
assert.Equal(t, 0, int(b.CursorColumn()))
|
||||||
assert.Equal(t, 0, int(b.CursorLine()))
|
assert.Equal(t, 0, int(b.CursorLine()))
|
||||||
b.SetPosition(60, 10)
|
b.SetPosition(60, 10)
|
||||||
|
@ -159,7 +159,7 @@ func TestSetPosition(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMovePosition(t *testing.T) {
|
func TestMovePosition(t *testing.T) {
|
||||||
b := NewBuffer(120, 80)
|
b := NewBuffer(120, 80, CellAttributes{})
|
||||||
assert.Equal(t, 0, int(b.CursorColumn()))
|
assert.Equal(t, 0, int(b.CursorColumn()))
|
||||||
assert.Equal(t, 0, int(b.CursorLine()))
|
assert.Equal(t, 0, int(b.CursorLine()))
|
||||||
b.MovePosition(-1, -1)
|
b.MovePosition(-1, -1)
|
||||||
|
@ -181,7 +181,7 @@ func TestMovePosition(t *testing.T) {
|
||||||
|
|
||||||
func TestVisibleLines(t *testing.T) {
|
func TestVisibleLines(t *testing.T) {
|
||||||
|
|
||||||
b := NewBuffer(80, 10)
|
b := NewBuffer(80, 10, CellAttributes{})
|
||||||
b.Write([]rune("hello 1\n")...)
|
b.Write([]rune("hello 1\n")...)
|
||||||
b.Write([]rune("hello 2\n")...)
|
b.Write([]rune("hello 2\n")...)
|
||||||
b.Write([]rune("hello 3\n")...)
|
b.Write([]rune("hello 3\n")...)
|
||||||
|
@ -205,7 +205,7 @@ func TestVisibleLines(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClearWithoutFullView(t *testing.T) {
|
func TestClearWithoutFullView(t *testing.T) {
|
||||||
b := NewBuffer(80, 10)
|
b := NewBuffer(80, 10, CellAttributes{})
|
||||||
b.Write([]rune("hello 1\n")...)
|
b.Write([]rune("hello 1\n")...)
|
||||||
b.Write([]rune("hello 2\n")...)
|
b.Write([]rune("hello 2\n")...)
|
||||||
b.Write([]rune("hello 3")...)
|
b.Write([]rune("hello 3")...)
|
||||||
|
@ -217,7 +217,7 @@ func TestClearWithoutFullView(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClearWithFullView(t *testing.T) {
|
func TestClearWithFullView(t *testing.T) {
|
||||||
b := NewBuffer(80, 5)
|
b := NewBuffer(80, 5, CellAttributes{})
|
||||||
b.Write([]rune("hello 1\n")...)
|
b.Write([]rune("hello 1\n")...)
|
||||||
b.Write([]rune("hello 2\n")...)
|
b.Write([]rune("hello 2\n")...)
|
||||||
b.Write([]rune("hello 3\n")...)
|
b.Write([]rune("hello 3\n")...)
|
||||||
|
@ -233,7 +233,32 @@ func TestClearWithFullView(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCarriageReturn(t *testing.T) {
|
||||||
|
b := NewBuffer(80, 20, CellAttributes{})
|
||||||
|
b.Write([]rune("hello!\rsecret")...)
|
||||||
|
lines := b.GetVisibleLines()
|
||||||
|
assert.Equal(t, "secret", lines[0].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
||||||
|
b := NewBuffer(80, 6, CellAttributes{})
|
||||||
|
b.Write([]rune("hello!\rsecret")...)
|
||||||
|
lines := b.GetVisibleLines()
|
||||||
|
assert.Equal(t, "secret", lines[0].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCarriageReturnOnOverWrappedLine(t *testing.T) {
|
||||||
|
b := NewBuffer(6, 10, CellAttributes{})
|
||||||
|
b.Write([]rune("hello there!\rsecret sauce")...)
|
||||||
|
lines := b.GetVisibleLines()
|
||||||
|
require.Equal(t, 4, len(lines))
|
||||||
|
assert.Equal(t, "hello ", lines[0].String())
|
||||||
|
assert.Equal(t, "secret", lines[1].String())
|
||||||
|
assert.Equal(t, " sauce", lines[2].String())
|
||||||
|
assert.Equal(t, "", lines[3].String())
|
||||||
|
}
|
||||||
|
|
||||||
func TestResizeView(t *testing.T) {
|
func TestResizeView(t *testing.T) {
|
||||||
b := NewBuffer(80, 20)
|
b := NewBuffer(80, 20, CellAttributes{})
|
||||||
b.ResizeView(40, 10)
|
b.ResizeView(40, 10)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,18 @@ func newCell() Cell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) Rune() rune {
|
func (cell *Cell) Rune() rune {
|
||||||
|
if cell.r == 0 {
|
||||||
|
return '~'
|
||||||
|
}
|
||||||
return cell.r
|
return cell.r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) Fg() [3]float32 {
|
func (cell *Cell) Fg() [3]float32 {
|
||||||
return cell.attr.FgColour
|
if cell.r == 0 {
|
||||||
|
return [3]float32{0.5, 0.5, 0.5}
|
||||||
|
}
|
||||||
|
return [3]float32{0.9, 0.9, 0.9} // @todo fix this
|
||||||
|
//return cell.attr.FgColour
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) Bg() [3]float32 {
|
func (cell *Cell) Bg() [3]float32 {
|
||||||
|
|
|
@ -8,6 +8,11 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DebugMode bool `yaml:"debug"`
|
DebugMode bool `yaml:"debug"`
|
||||||
ColourScheme terminal.ColourScheme
|
ColourScheme terminal.ColourScheme
|
||||||
|
Rendering RenderingConfig `yaml:"rendering"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenderingConfig struct {
|
||||||
|
AlwaysRepaint bool `yaml:"always_repaint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultConfig = Config{
|
var DefaultConfig = Config{
|
||||||
|
|
Binary file not shown.
65
gui/gui.go
65
gui/gui.go
|
@ -116,11 +116,6 @@ func (gui *GUI) Render() error {
|
||||||
|
|
||||||
gui.logger.Debugf("Starting pty read handling...")
|
gui.logger.Debugf("Starting pty read handling...")
|
||||||
|
|
||||||
updateChan := make(chan bool, 1024)
|
|
||||||
|
|
||||||
gui.terminal.OnUpdate(func() {
|
|
||||||
updateChan <- true
|
|
||||||
})
|
|
||||||
go func() {
|
go func() {
|
||||||
err := gui.terminal.Read()
|
err := gui.terminal.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -129,9 +124,6 @@ func (gui *GUI) Render() error {
|
||||||
gui.Close()
|
gui.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Millisecond * 100)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
gui.logger.Debugf("Starting render...")
|
gui.logger.Debugf("Starting render...")
|
||||||
|
|
||||||
gl.UseProgram(program)
|
gl.UseProgram(program)
|
||||||
|
@ -142,7 +134,7 @@ func (gui *GUI) Render() error {
|
||||||
//gl.DepthFunc(gl.LESS)
|
//gl.DepthFunc(gl.LESS)
|
||||||
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||||
|
|
||||||
glfw.SwapInterval(1)
|
//glfw.SwapInterval(1)
|
||||||
|
|
||||||
gl.ClearColor(
|
gl.ClearColor(
|
||||||
gui.config.ColourScheme.DefaultBg[0],
|
gui.config.ColourScheme.DefaultBg[0],
|
||||||
|
@ -151,26 +143,61 @@ func (gui *GUI) Render() error {
|
||||||
1.0,
|
1.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
changeChan := make(chan bool, 1)
|
||||||
|
titleChan := make(chan bool, 1)
|
||||||
|
|
||||||
|
gui.terminal.AttachTitleChangeHandler(titleChan)
|
||||||
|
gui.terminal.AttachDisplayChangeHandler(changeChan)
|
||||||
|
|
||||||
|
frames := 0
|
||||||
|
frameCount := 0
|
||||||
|
fps := 0
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
for !gui.window.ShouldClose() {
|
for !gui.window.ShouldClose() {
|
||||||
|
|
||||||
|
select {
|
||||||
|
|
||||||
|
case <-changeChan:
|
||||||
|
frames = 2
|
||||||
|
gui.logger.Sync()
|
||||||
|
case <-titleChan:
|
||||||
|
gui.window.SetTitle(gui.terminal.GetTitle())
|
||||||
|
case <-ticker.C:
|
||||||
|
fps = frameCount
|
||||||
|
frameCount = 0
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
gl.UseProgram(program)
|
gl.UseProgram(program)
|
||||||
|
|
||||||
// Render the string.
|
if gui.config.Rendering.AlwaysRepaint || frames > 0 {
|
||||||
// @todo uncommentbut dont run all of the time... - perhaps use onTitleChange event from terminal?
|
|
||||||
//gui.window.SetTitle(gui.terminal.GetTitle())
|
|
||||||
|
|
||||||
//gl.ClearColor(0.5, 0.5, 0.5, 1.0)
|
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
cols, rows := gui.getTermSize()
|
||||||
cols, rows := gui.getTermSize()
|
|
||||||
|
|
||||||
for row := 0; row < rows; row++ {
|
for row := 0; row < rows; row++ {
|
||||||
for col := 0; col < cols; col++ {
|
for col := 0; col < cols; col++ {
|
||||||
gui.renderer.DrawCell(gui.terminal.GetCell(col, row), col, row)
|
gui.renderer.DrawCell(gui.terminal.GetCell(col, row), col, row)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui.font.SetColor(1, 0.5, 0.5, 0.5)
|
||||||
|
fpsData := ""
|
||||||
|
if gui.config.Rendering.AlwaysRepaint {
|
||||||
|
fpsData = fmt.Sprintf("%d FPS", fps)
|
||||||
|
}
|
||||||
|
gui.font.Print(10, float32(gui.height-20), 1.5, fmt.Sprintf("%s", fpsData))
|
||||||
}
|
}
|
||||||
|
|
||||||
glfw.PollEvents()
|
glfw.PollEvents()
|
||||||
gui.window.SwapBuffers()
|
|
||||||
|
if gui.config.Rendering.AlwaysRepaint || frames > 0 {
|
||||||
|
frameCount++
|
||||||
|
gui.window.SwapBuffers()
|
||||||
|
frames--
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.logger.Debugf("Stopping render...")
|
gui.logger.Debugf("Stopping render...")
|
||||||
|
|
3
main.go
3
main.go
|
@ -34,8 +34,7 @@ func loadConfigFile() config.Config {
|
||||||
|
|
||||||
places := []string{
|
places := []string{
|
||||||
fmt.Sprintf("%s/.raft.yml", home),
|
fmt.Sprintf("%s/.raft.yml", home),
|
||||||
fmt.Sprintf("%s/.raft/config.yml", home),
|
fmt.Sprintf("%s/.config/raft.yml", home),
|
||||||
fmt.Sprintf("%s/.config/raft/config.yml", home),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, place := range places {
|
for _, place := range places {
|
||||||
|
|
|
@ -53,11 +53,7 @@ CSI:
|
||||||
distance = 1
|
distance = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if terminal.position.Line-distance >= 0 {
|
terminal.buffer.MovePosition(0, -int16(distance))
|
||||||
terminal.position.Line -= distance
|
|
||||||
} else {
|
|
||||||
terminal.position.Line = 0
|
|
||||||
}
|
|
||||||
case 'B':
|
case 'B':
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
|
@ -68,12 +64,7 @@ CSI:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, h := terminal.GetSize()
|
terminal.buffer.MovePosition(0, int16(distance))
|
||||||
if terminal.position.Line+distance >= h {
|
|
||||||
terminal.position.Line = h - 1
|
|
||||||
} else {
|
|
||||||
terminal.position.Line += distance
|
|
||||||
}
|
|
||||||
case 'C':
|
case 'C':
|
||||||
|
|
||||||
distance := 1
|
distance := 1
|
||||||
|
@ -85,11 +76,7 @@ CSI:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.position.Col += distance
|
terminal.buffer.MovePosition(int16(distance), 0)
|
||||||
w, _ := terminal.GetSize()
|
|
||||||
if terminal.position.Col >= w {
|
|
||||||
terminal.position.Col = w - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'D':
|
case 'D':
|
||||||
|
|
||||||
|
@ -102,10 +89,7 @@ CSI:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.position.Col -= distance
|
terminal.buffer.MovePosition(-int16(distance), 0)
|
||||||
if terminal.position.Col < 0 {
|
|
||||||
terminal.position.Col = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'E':
|
case 'E':
|
||||||
distance := 1
|
distance := 1
|
||||||
|
@ -117,8 +101,8 @@ CSI:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.position.Line += distance
|
terminal.buffer.MovePosition(0, int16(distance))
|
||||||
terminal.position.Col = 0
|
terminal.buffer.SetPosition(0, terminal.buffer.CursorLine())
|
||||||
|
|
||||||
case 'F':
|
case 'F':
|
||||||
|
|
||||||
|
@ -130,12 +114,8 @@ CSI:
|
||||||
distance = 1
|
distance = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if terminal.position.Line-distance >= 0 {
|
terminal.buffer.MovePosition(0, -int16(distance))
|
||||||
terminal.position.Line -= distance
|
terminal.buffer.SetPosition(0, terminal.buffer.CursorLine())
|
||||||
} else {
|
|
||||||
terminal.position.Line = 0
|
|
||||||
}
|
|
||||||
terminal.position.Col = 0
|
|
||||||
|
|
||||||
case 'G':
|
case 'G':
|
||||||
|
|
||||||
|
@ -148,7 +128,7 @@ CSI:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.position.Col = distance - 1 // 1 based to 0 based
|
terminal.buffer.SetPosition(uint16(distance-1), terminal.buffer.CursorLine())
|
||||||
|
|
||||||
case 'H', 'f':
|
case 'H', 'f':
|
||||||
|
|
||||||
|
@ -168,19 +148,19 @@ CSI:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.position.Col = x - 1
|
|
||||||
terminal.position.Line = y - 1
|
terminal.buffer.SetPosition(uint16(x-1), uint16(y-1))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
switch param + intermediate + string(final) {
|
switch param + intermediate + string(final) {
|
||||||
case "?25h":
|
case "?25h":
|
||||||
terminal.showCursor()
|
terminal.buffer.ShowCursor()
|
||||||
case "?25l":
|
case "?25l":
|
||||||
terminal.hideCursor()
|
terminal.buffer.HideCursor()
|
||||||
case "?12h":
|
case "?12h":
|
||||||
// todo enable cursor blink
|
terminal.buffer.SetCursorBlink(true)
|
||||||
case "?12l":
|
case "?12l":
|
||||||
// todo disable cursor blink
|
terminal.buffer.SetCursorBlink(false)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unknown CSI control sequence: 0x%02X (ESC[%s%s%s)", final, param, intermediate, string(final))
|
return fmt.Errorf("Unknown CSI control sequence: 0x%02X (ESC[%s%s%s)", final, param, intermediate, string(final))
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,15 +38,11 @@ func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if b != 0x0d {
|
|
||||||
//lineOverflow = false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch b {
|
switch b {
|
||||||
case 0x0a:
|
case 0x0a:
|
||||||
terminal.buffer.NewLine()
|
terminal.buffer.NewLine()
|
||||||
case 0x0d:
|
case 0x0d:
|
||||||
terminal.buffer.SetPosition(0, terminal.buffer.CursorLine())
|
terminal.buffer.CarriageReturn()
|
||||||
case 0x08:
|
case 0x08:
|
||||||
// backspace
|
// backspace
|
||||||
terminal.buffer.MovePosition(-1, 0)
|
terminal.buffer.MovePosition(-1, 0)
|
||||||
|
@ -62,6 +58,5 @@ func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.triggerOnUpdate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func oscHandler(buffer chan rune, terminal *Terminal) error {
|
||||||
}
|
}
|
||||||
title = append(title, b)
|
title = append(title, b)
|
||||||
}
|
}
|
||||||
terminal.title = string(title)
|
terminal.SetTitle(string(title))
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Invalid OSC 0 control sequence: 0x%02X", b)
|
return fmt.Errorf("Invalid OSC 0 control sequence: 0x%02X", b)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,13 @@ import (
|
||||||
|
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
buffer *buffer.Buffer
|
buffer *buffer.Buffer
|
||||||
position Position // line and col
|
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
pty *os.File
|
pty *os.File
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
title string
|
title string
|
||||||
onUpdate []func()
|
|
||||||
size Winsize
|
size Winsize
|
||||||
colourScheme ColourScheme
|
colourScheme ColourScheme
|
||||||
cursorVisible bool
|
titleHandlers []chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Line struct {
|
type Line struct {
|
||||||
|
@ -53,9 +51,8 @@ func New(pty *os.File, logger *zap.SugaredLogger, colourScheme ColourScheme) *Te
|
||||||
}),
|
}),
|
||||||
pty: pty,
|
pty: pty,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
onUpdate: []func(){},
|
|
||||||
colourScheme: colourScheme,
|
colourScheme: colourScheme,
|
||||||
cursorVisible: true,
|
titleHandlers: []chan bool{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,51 +60,31 @@ func (terminal *Terminal) GetCell(col int, row int) *buffer.Cell {
|
||||||
return terminal.buffer.GetCell(col, row)
|
return terminal.buffer.GetCell(col, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) OnUpdate(handler func()) {
|
func (terminal *Terminal) AttachDisplayChangeHandler(handler chan bool) {
|
||||||
terminal.onUpdate = append(terminal.onUpdate, handler)
|
terminal.buffer.AttachDisplayChangeHandler(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) triggerOnUpdate() {
|
func (terminal *Terminal) AttachTitleChangeHandler(handler chan bool) {
|
||||||
for _, handler := range terminal.onUpdate {
|
terminal.titleHandlers = append(terminal.titleHandlers, handler)
|
||||||
go handler()
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) emitTitleChange() {
|
||||||
|
for _, h := range terminal.titleHandlers {
|
||||||
|
go func(c chan bool) {
|
||||||
|
c <- true
|
||||||
|
}(h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) getPosition() Position {
|
|
||||||
return terminal.position
|
|
||||||
}
|
|
||||||
|
|
||||||
func (terminal *Terminal) IsCursorVisible() bool {
|
|
||||||
return terminal.cursorVisible
|
|
||||||
}
|
|
||||||
|
|
||||||
func (terminal *Terminal) showCursor() {
|
|
||||||
terminal.cursorVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (terminal *Terminal) hideCursor() {
|
|
||||||
terminal.cursorVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (terminal *Terminal) incrementPosition() {
|
|
||||||
terminal.SetPosition(terminal.position.Col+1, terminal.position.Line)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (terminal *Terminal) SetPosition(col int, line int) {
|
|
||||||
terminal.position = Position{
|
|
||||||
Col: col,
|
|
||||||
Line: line,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (terminal *Terminal) GetPosition() Position {
|
|
||||||
return terminal.position
|
|
||||||
}
|
|
||||||
|
|
||||||
func (terminal *Terminal) GetTitle() string {
|
func (terminal *Terminal) GetTitle() string {
|
||||||
return terminal.title
|
return terminal.title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) SetTitle(title string) {
|
||||||
|
terminal.title = title
|
||||||
|
terminal.emitTitleChange()
|
||||||
|
}
|
||||||
|
|
||||||
// Write sends data, i.e. locally typed keystrokes to the pty
|
// Write sends data, i.e. locally typed keystrokes to the pty
|
||||||
func (terminal *Terminal) Write(data []byte) error {
|
func (terminal *Terminal) Write(data []byte) error {
|
||||||
_, err := terminal.pty.Write(data)
|
_, err := terminal.pty.Write(data)
|
||||||
|
|
Loading…
Reference in New Issue