lots more progress

This commit is contained in:
Liam Galvin 2018-08-07 17:58:55 +01:00
parent e54e0e2182
commit d5f5cf8592
12 changed files with 214 additions and 144 deletions

View File

@ -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()) {
line := newLine()
line.setWrapped(true)
buffer.lines = append(buffer.lines, line)
buffer.cursorY++ buffer.cursorY++
} else {
// @todo test this branch _, err := buffer.getCurrentLine()
line := &buffer.lines[buffer.RawLine()] if err != nil {
line := newLine()
line.setWrapped(true) line.setWrapped(true)
buffer.lines = append(buffer.lines, line)
} }
} }
} }
} }
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

View File

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

View File

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

View File

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

1
cover.out Normal file
View File

@ -0,0 +1 @@
mode: set

Binary file not shown.

View File

@ -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,15 +143,37 @@ 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()
@ -169,8 +183,21 @@ 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)
}
gui.font.Print(10, float32(gui.height-20), 1.5, fmt.Sprintf("%s", fpsData))
}
glfw.PollEvents() glfw.PollEvents()
if gui.config.Rendering.AlwaysRepaint || frames > 0 {
frameCount++
gui.window.SwapBuffers() gui.window.SwapBuffers()
frames--
}
} }
gui.logger.Debugf("Stopping render...") gui.logger.Debugf("Stopping render...")

View File

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

View File

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

View File

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

View File

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

View File

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