From d5f5cf8592bbb562518401b40ca458880235190f Mon Sep 17 00:00:00 2001 From: Liam Galvin Date: Tue, 7 Aug 2018 17:58:55 +0100 Subject: [PATCH] lots more progress --- buffer/buffer.go | 108 +++++++++++++++++++++++++--------- buffer/buffer_test.go | 49 +++++++++++---- buffer/cell.go | 9 ++- config/config.go | 5 ++ cover.out | 1 + fonts/CamingoCode-Regular.ttf | Bin 93500 -> 0 bytes gui/gui.go | 65 ++++++++++++++------ main.go | 3 +- terminal/csi.go | 50 +++++----------- terminal/escapes.go | 7 +-- terminal/osc.go | 2 +- terminal/terminal.go | 59 ++++++------------- 12 files changed, 214 insertions(+), 144 deletions(-) create mode 100644 cover.out delete mode 100644 fonts/CamingoCode-Regular.ttf diff --git a/buffer/buffer.go b/buffer/buffer.go index 0f427d6..0ce00d1 100644 --- a/buffer/buffer.go +++ b/buffer/buffer.go @@ -2,8 +2,6 @@ package buffer import ( "fmt" - - "github.com/sirupsen/logrus" ) type Buffer struct { @@ -46,7 +44,7 @@ func (buffer *Buffer) GetCell(viewCol int, viewRow int) *Cell { return &line.cells[viewCol] } -func (buffer *Buffer) attachDisplayChangeHandler(handler chan bool) { +func (buffer *Buffer) AttachDisplayChangeHandler(handler chan bool) { if buffer.displayChangeHandlers == nil { buffer.displayChangeHandlers = []chan bool{} } @@ -57,7 +55,10 @@ func (buffer *Buffer) attachDisplayChangeHandler(handler chan bool) { func (buffer *Buffer) emitDisplayChange() { for _, channel := range buffer.displayChangeHandlers { go func(c chan bool) { - c <- true + select { + case c <- true: + default: + } }(channel) } } @@ -132,44 +133,64 @@ func (buffer *Buffer) Write(runes ...rune) { 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++ - } 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.setWrapped(true) buffer.lines = append(buffer.lines, line) buffer.cursorX = 0 + } else { + // if we're not on the bottom line... + buffer.cursorX = 0 - if buffer.Height() < int(buffer.ViewHeight()) { + buffer.cursorY++ + + _, err := buffer.getCurrentLine() + if err != nil { line := newLine() line.setWrapped(true) 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() { + + defer buffer.emitDisplayChange() + line, err := buffer.getCurrentLine() if err != nil { + // @todo check this... buffer.cursorX = 0 return } if buffer.cursorX == 0 && line.wrapped { buffer.cursorY-- + if len(line.cells) == 0 { + rawLine := int(buffer.RawLine()) + buffer.lines = append(buffer.lines[:rawLine], buffer.lines[rawLine+1:]...) + } } 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 := &buffer.lines[buffer.RawLine()] @@ -190,25 +211,45 @@ func (buffer *Buffer) NewLine() { func (buffer *Buffer) MovePosition(x int16, y int16) { + var toX uint16 + var toY uint16 + 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 { - 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) { + defer buffer.emitDisplayChange() if col >= buffer.ViewWidth() { 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() { 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.cursorY = line @@ -227,10 +268,11 @@ func (buffer *Buffer) GetVisibleLines() []Line { // tested to here func (buffer *Buffer) Clear() { + defer buffer.emitDisplayChange() for i := 0; i < int(buffer.ViewHeight()); i++ { 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) { @@ -243,6 +285,7 @@ func (buffer *Buffer) getCurrentLine() (*Line, error) { } func (buffer *Buffer) EraseLine() { + defer buffer.emitDisplayChange() line, err := buffer.getCurrentLine() if err != nil { return @@ -251,6 +294,7 @@ func (buffer *Buffer) EraseLine() { } func (buffer *Buffer) EraseLineToCursor() { + defer buffer.emitDisplayChange() line, err := buffer.getCurrentLine() if err != nil { return @@ -263,42 +307,52 @@ func (buffer *Buffer) EraseLineToCursor() { } func (buffer *Buffer) EraseLineAfterCursor() { + defer buffer.emitDisplayChange() line, err := buffer.getCurrentLine() if err != nil { 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() { + defer buffer.emitDisplayChange() line, err := buffer.getCurrentLine() if err != nil { return } line.cells = line.cells[:buffer.cursorX] - for i := int(buffer.RawLine() + 1); i < buffer.Height(); i++ { - if i < len(buffer.lines) { - buffer.lines[i].cells = []Cell{} + for i := buffer.cursorY + 1; i < buffer.ViewHeight(); i++ { + rawLine := buffer.convertViewLineToRawLine(i) + if int(rawLine) < len(buffer.lines) { + buffer.lines[int(rawLine)].cells = []Cell{} } } } func (buffer *Buffer) EraseDisplayToCursor() { + defer buffer.emitDisplayChange() line, err := buffer.getCurrentLine() if err != nil { return } line.cells = line.cells[buffer.cursorX+1:] - for i := 0; i < int(buffer.RawLine()); i++ { - if i < len(buffer.lines) { - buffer.lines[i].cells = []Cell{} + for i := uint16(0); i < buffer.cursorY; i++ { + rawLine := buffer.convertViewLineToRawLine(i) + if int(rawLine) < len(buffer.lines) { + buffer.lines[int(rawLine)].cells = []Cell{} } } } func (buffer *Buffer) ResizeView(width uint16, height uint16) { + defer buffer.emitDisplayChange() buffer.viewWidth = width buffer.viewHeight = height diff --git a/buffer/buffer_test.go b/buffer/buffer_test.go index 722858c..24d1823 100644 --- a/buffer/buffer_test.go +++ b/buffer/buffer_test.go @@ -9,7 +9,7 @@ import ( ) 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?" b.Write([]rune(test)...) assert.Equal(t, uint16(10), b.ViewWidth()) @@ -19,7 +19,7 @@ func TestOffsets(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(20), b.ViewHeight()) assert.Equal(t, uint16(0), b.CursorColumn()) @@ -29,7 +29,7 @@ func TestBufferCreation(t *testing.T) { func TestBufferCursorIncrement(t *testing.T) { - b := NewBuffer(5, 4) + b := NewBuffer(5, 4, CellAttributes{}) b.incrementCursorPosition() require.Equal(t, uint16(1), b.CursorColumn()) require.Equal(t, uint16(0), b.CursorLine()) @@ -76,7 +76,7 @@ func TestBufferCursorIncrement(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.CursorLine()) @@ -110,7 +110,7 @@ func TestBufferWrite(t *testing.T) { } func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) { - b := NewBuffer(3, 20) + b := NewBuffer(3, 20, CellAttributes{}) b.Write('a', 'b', 'c') assert.Equal(t, uint16(0), b.cursorX) b.Write(0x0a) @@ -124,7 +124,7 @@ func TestWritingNewLineAsFirstRuneOnWrappedLine(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(0x0a) b.Write('e', 'f') @@ -143,7 +143,7 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(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.CursorLine())) b.SetPosition(60, 10) @@ -159,7 +159,7 @@ func TestSetPosition(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.CursorLine())) b.MovePosition(-1, -1) @@ -181,7 +181,7 @@ func TestMovePosition(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 2\n")...) b.Write([]rune("hello 3\n")...) @@ -205,7 +205,7 @@ func TestVisibleLines(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 2\n")...) b.Write([]rune("hello 3")...) @@ -217,7 +217,7 @@ func TestClearWithoutFullView(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 2\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) { - b := NewBuffer(80, 20) + b := NewBuffer(80, 20, CellAttributes{}) b.ResizeView(40, 10) } diff --git a/buffer/cell.go b/buffer/cell.go index 03e511e..28966b0 100644 --- a/buffer/cell.go +++ b/buffer/cell.go @@ -22,11 +22,18 @@ func newCell() Cell { } func (cell *Cell) Rune() rune { + if cell.r == 0 { + return '~' + } return cell.r } 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 { diff --git a/config/config.go b/config/config.go index 0e93c73..87e5b41 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,11 @@ import ( type Config struct { DebugMode bool `yaml:"debug"` ColourScheme terminal.ColourScheme + Rendering RenderingConfig `yaml:"rendering"` +} + +type RenderingConfig struct { + AlwaysRepaint bool `yaml:"always_repaint"` } var DefaultConfig = Config{ diff --git a/cover.out b/cover.out new file mode 100644 index 0000000..5f02b11 --- /dev/null +++ b/cover.out @@ -0,0 +1 @@ +mode: set diff --git a/fonts/CamingoCode-Regular.ttf b/fonts/CamingoCode-Regular.ttf deleted file mode 100644 index 04b88608e2aef7ee1fe7651e32b0147c116f640f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93500 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 RH2ps`{XaDQ{~nqy{tt~3IX(aY diff --git a/gui/gui.go b/gui/gui.go index 9625a42..da57119 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -116,11 +116,6 @@ func (gui *GUI) Render() error { gui.logger.Debugf("Starting pty read handling...") - updateChan := make(chan bool, 1024) - - gui.terminal.OnUpdate(func() { - updateChan <- true - }) go func() { err := gui.terminal.Read() if err != nil { @@ -129,9 +124,6 @@ func (gui *GUI) Render() error { gui.Close() }() - ticker := time.NewTicker(time.Millisecond * 100) - defer ticker.Stop() - gui.logger.Debugf("Starting render...") gl.UseProgram(program) @@ -142,7 +134,7 @@ func (gui *GUI) Render() error { //gl.DepthFunc(gl.LESS) gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) - glfw.SwapInterval(1) + //glfw.SwapInterval(1) gl.ClearColor( gui.config.ColourScheme.DefaultBg[0], @@ -151,26 +143,61 @@ func (gui *GUI) Render() error { 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() { + 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) - // Render the string. - // @todo uncommentbut dont run all of the time... - perhaps use onTitleChange event from terminal? - //gui.window.SetTitle(gui.terminal.GetTitle()) + if gui.config.Rendering.AlwaysRepaint || frames > 0 { - //gl.ClearColor(0.5, 0.5, 0.5, 1.0) - gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) - cols, rows := gui.getTermSize() + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + cols, rows := gui.getTermSize() - for row := 0; row < rows; row++ { - for col := 0; col < cols; col++ { - gui.renderer.DrawCell(gui.terminal.GetCell(col, row), col, row) + for row := 0; row < rows; row++ { + for col := 0; col < cols; col++ { + 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() - gui.window.SwapBuffers() + + if gui.config.Rendering.AlwaysRepaint || frames > 0 { + frameCount++ + gui.window.SwapBuffers() + frames-- + } } gui.logger.Debugf("Stopping render...") diff --git a/main.go b/main.go index 3dfbdfd..fb0b97e 100644 --- a/main.go +++ b/main.go @@ -34,8 +34,7 @@ func loadConfigFile() config.Config { places := []string{ fmt.Sprintf("%s/.raft.yml", home), - fmt.Sprintf("%s/.raft/config.yml", home), - fmt.Sprintf("%s/.config/raft/config.yml", home), + fmt.Sprintf("%s/.config/raft.yml", home), } for _, place := range places { diff --git a/terminal/csi.go b/terminal/csi.go index 61b63f4..cb4d927 100644 --- a/terminal/csi.go +++ b/terminal/csi.go @@ -53,11 +53,7 @@ CSI: distance = 1 } } - if terminal.position.Line-distance >= 0 { - terminal.position.Line -= distance - } else { - terminal.position.Line = 0 - } + terminal.buffer.MovePosition(0, -int16(distance)) case 'B': distance := 1 if len(params) > 0 { @@ -68,12 +64,7 @@ CSI: } } - _, h := terminal.GetSize() - if terminal.position.Line+distance >= h { - terminal.position.Line = h - 1 - } else { - terminal.position.Line += distance - } + terminal.buffer.MovePosition(0, int16(distance)) case 'C': distance := 1 @@ -85,11 +76,7 @@ CSI: } } - terminal.position.Col += distance - w, _ := terminal.GetSize() - if terminal.position.Col >= w { - terminal.position.Col = w - 1 - } + terminal.buffer.MovePosition(int16(distance), 0) case 'D': @@ -102,10 +89,7 @@ CSI: } } - terminal.position.Col -= distance - if terminal.position.Col < 0 { - terminal.position.Col = 0 - } + terminal.buffer.MovePosition(-int16(distance), 0) case 'E': distance := 1 @@ -117,8 +101,8 @@ CSI: } } - terminal.position.Line += distance - terminal.position.Col = 0 + terminal.buffer.MovePosition(0, int16(distance)) + terminal.buffer.SetPosition(0, terminal.buffer.CursorLine()) case 'F': @@ -130,12 +114,8 @@ CSI: distance = 1 } } - if terminal.position.Line-distance >= 0 { - terminal.position.Line -= distance - } else { - terminal.position.Line = 0 - } - terminal.position.Col = 0 + terminal.buffer.MovePosition(0, -int16(distance)) + terminal.buffer.SetPosition(0, terminal.buffer.CursorLine()) 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': @@ -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: switch param + intermediate + string(final) { case "?25h": - terminal.showCursor() + terminal.buffer.ShowCursor() case "?25l": - terminal.hideCursor() + terminal.buffer.HideCursor() case "?12h": - // todo enable cursor blink + terminal.buffer.SetCursorBlink(true) case "?12l": - // todo disable cursor blink + terminal.buffer.SetCursorBlink(false) default: return fmt.Errorf("Unknown CSI control sequence: 0x%02X (ESC[%s%s%s)", final, param, intermediate, string(final)) } diff --git a/terminal/escapes.go b/terminal/escapes.go index a62976d..84abfa3 100644 --- a/terminal/escapes.go +++ b/terminal/escapes.go @@ -38,15 +38,11 @@ func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) { continue } - if b != 0x0d { - //lineOverflow = false - } - switch b { case 0x0a: terminal.buffer.NewLine() case 0x0d: - terminal.buffer.SetPosition(0, terminal.buffer.CursorLine()) + terminal.buffer.CarriageReturn() case 0x08: // backspace terminal.buffer.MovePosition(-1, 0) @@ -62,6 +58,5 @@ func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) { } } - terminal.triggerOnUpdate() } } diff --git a/terminal/osc.go b/terminal/osc.go index ca75847..77b2547 100644 --- a/terminal/osc.go +++ b/terminal/osc.go @@ -16,7 +16,7 @@ func oscHandler(buffer chan rune, terminal *Terminal) error { } title = append(title, b) } - terminal.title = string(title) + terminal.SetTitle(string(title)) } else { return fmt.Errorf("Invalid OSC 0 control sequence: 0x%02X", b) } diff --git a/terminal/terminal.go b/terminal/terminal.go index 0674015..bccd68d 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -16,15 +16,13 @@ import ( type Terminal struct { buffer *buffer.Buffer - position Position // line and col lock sync.Mutex pty *os.File logger *zap.SugaredLogger title string - onUpdate []func() size Winsize colourScheme ColourScheme - cursorVisible bool + titleHandlers []chan bool } type Line struct { @@ -53,9 +51,8 @@ func New(pty *os.File, logger *zap.SugaredLogger, colourScheme ColourScheme) *Te }), pty: pty, logger: logger, - onUpdate: []func(){}, 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) } -func (terminal *Terminal) OnUpdate(handler func()) { - terminal.onUpdate = append(terminal.onUpdate, handler) +func (terminal *Terminal) AttachDisplayChangeHandler(handler chan bool) { + terminal.buffer.AttachDisplayChangeHandler(handler) } -func (terminal *Terminal) triggerOnUpdate() { - for _, handler := range terminal.onUpdate { - go handler() +func (terminal *Terminal) AttachTitleChangeHandler(handler chan bool) { + terminal.titleHandlers = append(terminal.titleHandlers, 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 { 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 func (terminal *Terminal) Write(data []byte) error { _, err := terminal.pty.Write(data)