mirror of https://github.com/liamg/aminal.git
Avoid polling in main GUI loop (#245)
Instead of waking up regularly WaitEventsTimeout, WaitEvents and PostEmptyEvent are used to make the main loop purely event driven. The rate of redraws due to terminal activity can be tweaked via the new wakePeriod const. This leads to some significant performance improvements: Aminal now consumes: - no CPU when idle (previously ~2.7% on my laptop) - ~8.5% CPU on my machine when running htop full screen on a large monitor (previously ~18.5% on my laptop) - scrolling large amounts of output is an order of magnitude faster This change also incidentally fixes data races around the terminal dirty flag (which is now gone).
This commit is contained in:
parent
a355d10656
commit
8f183ba440
|
@ -21,13 +21,17 @@ const (
|
|||
SelectionRegionRectangular
|
||||
)
|
||||
|
||||
type Notifier interface {
|
||||
Notify()
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
lines []Line
|
||||
displayChangeHandlers []chan bool
|
||||
savedX uint16
|
||||
savedY uint16
|
||||
savedCursorAttr *CellAttributes
|
||||
dirty bool
|
||||
dirty Notifier
|
||||
selectionStart *Position
|
||||
selectionEnd *Position
|
||||
selectionMode SelectionMode
|
||||
|
@ -54,7 +58,7 @@ func comparePositions(pos1 *Position, pos2 *Position) int {
|
|||
}
|
||||
|
||||
// NewBuffer creates a new terminal buffer
|
||||
func NewBuffer(terminalState *TerminalState) *Buffer {
|
||||
func NewBuffer(terminalState *TerminalState, dirty Notifier) *Buffer {
|
||||
b := &Buffer{
|
||||
lines: []Line{},
|
||||
selectionStart: nil,
|
||||
|
@ -62,6 +66,7 @@ func NewBuffer(terminalState *TerminalState) *Buffer {
|
|||
selectionMode: SelectionChar,
|
||||
isSelectionComplete: true,
|
||||
terminalState: terminalState,
|
||||
dirty: dirty,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
@ -251,17 +256,15 @@ func (buffer *Buffer) StartSelection(col uint16, viewRow uint16, mode SelectionM
|
|||
}
|
||||
|
||||
buffer.isSelectionComplete = false
|
||||
|
||||
buffer.emitDisplayChange()
|
||||
buffer.dirty.Notify()
|
||||
}
|
||||
|
||||
func (buffer *Buffer) ExtendSelection(col uint16, viewRow uint16, complete bool) {
|
||||
|
||||
if buffer.isSelectionComplete {
|
||||
return
|
||||
}
|
||||
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
if buffer.selectionStart == nil {
|
||||
buffer.selectionEnd = nil
|
||||
|
@ -285,7 +288,7 @@ func (buffer *Buffer) ClearSelection() {
|
|||
buffer.selectionEnd = nil
|
||||
buffer.isSelectionComplete = true
|
||||
|
||||
buffer.emitDisplayChange()
|
||||
buffer.dirty.Notify()
|
||||
}
|
||||
|
||||
func (buffer *Buffer) getActualSelection(selectionRegionMode SelectionRegionMode) (*Position, *Position) {
|
||||
|
@ -368,14 +371,6 @@ func (buffer *Buffer) InSelection(col uint16, row uint16, selectionRegionMode Se
|
|||
(rawY < end.Line || (rawY == end.Line && int(col) <= end.Col))
|
||||
}
|
||||
|
||||
func (buffer *Buffer) IsDirty() bool {
|
||||
if !buffer.dirty {
|
||||
return false
|
||||
}
|
||||
buffer.dirty = false
|
||||
return true
|
||||
}
|
||||
|
||||
func (buffer *Buffer) HasScrollableRegion() bool {
|
||||
return buffer.terminalState.topMargin > 0 || buffer.terminalState.bottomMargin < uint(buffer.ViewHeight())-1
|
||||
}
|
||||
|
@ -395,7 +390,7 @@ func (buffer *Buffer) getAreaScrollRange() (top uint64, bottom uint64) {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) AreaScrollDown(lines uint16) {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
// NOTE: bottom is exclusive
|
||||
top, bottom := buffer.getAreaScrollRange()
|
||||
|
@ -411,7 +406,7 @@ func (buffer *Buffer) AreaScrollDown(lines uint16) {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) AreaScrollUp(lines uint16) {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
// NOTE: bottom is exclusive
|
||||
top, bottom := buffer.getAreaScrollRange()
|
||||
|
@ -471,10 +466,6 @@ func (buffer *Buffer) GetRawCell(viewCol uint16, rawLine uint64) *Cell {
|
|||
return &line.cells[viewCol]
|
||||
}
|
||||
|
||||
func (buffer *Buffer) emitDisplayChange() {
|
||||
buffer.dirty = true
|
||||
}
|
||||
|
||||
// Column returns cursor column
|
||||
func (buffer *Buffer) CursorColumn() uint16 {
|
||||
// @todo originMode and left margin
|
||||
|
@ -550,8 +541,7 @@ func (buffer *Buffer) deleteLine() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) insertLine() {
|
||||
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
if !buffer.InScrollableRegion() {
|
||||
pos := buffer.RawLine()
|
||||
|
@ -636,7 +626,7 @@ func (buffer *Buffer) Index() {
|
|||
// This sequence causes the active position to move downward one line without changing the column position.
|
||||
// If the active position is at the bottom margin, a scroll up is performed."
|
||||
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
if buffer.InScrollableRegion() {
|
||||
|
||||
|
@ -662,8 +652,7 @@ func (buffer *Buffer) Index() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) ReverseIndex() {
|
||||
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
if uint(buffer.terminalState.cursorY) == buffer.terminalState.topMargin {
|
||||
buffer.AreaScrollDown(1)
|
||||
|
@ -674,7 +663,8 @@ func (buffer *Buffer) ReverseIndex() {
|
|||
|
||||
// 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) {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
// scroll to bottom on input
|
||||
buffer.terminalState.scrollLinesFromBottom = 0
|
||||
|
||||
|
@ -837,7 +827,7 @@ func (buffer *Buffer) MovePosition(x int16, y int16) {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) SetPosition(col uint16, line uint16) {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
useCol := col
|
||||
useLine := line
|
||||
|
@ -876,7 +866,7 @@ func (buffer *Buffer) GetVisibleLines() []Line {
|
|||
// tested to here
|
||||
|
||||
func (buffer *Buffer) Clear() {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
for i := 0; i < int(buffer.ViewHeight()); i++ {
|
||||
buffer.lines = append(buffer.lines, newLine())
|
||||
}
|
||||
|
@ -916,13 +906,13 @@ func (buffer *Buffer) getViewLine(index uint16) *Line {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) EraseLine() {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
line := buffer.getCurrentLine()
|
||||
line.cells = []Cell{}
|
||||
}
|
||||
|
||||
func (buffer *Buffer) EraseLineToCursor() {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
line := buffer.getCurrentLine()
|
||||
for i := 0; i <= int(buffer.terminalState.cursorX); i++ {
|
||||
if i < len(line.cells) {
|
||||
|
@ -932,7 +922,7 @@ func (buffer *Buffer) EraseLineToCursor() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) EraseLineFromCursor() {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
line := buffer.getCurrentLine()
|
||||
|
||||
if len(line.cells) > 0 {
|
||||
|
@ -950,7 +940,7 @@ func (buffer *Buffer) EraseLineFromCursor() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) EraseDisplay() {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
for i := uint16(0); i < (buffer.ViewHeight()); i++ {
|
||||
rawLine := buffer.convertViewLineToRawLine(i)
|
||||
if int(rawLine) < len(buffer.lines) {
|
||||
|
@ -960,7 +950,7 @@ func (buffer *Buffer) EraseDisplay() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) DeleteChars(n int) {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
line := buffer.getCurrentLine()
|
||||
if int(buffer.terminalState.cursorX) >= len(line.cells) {
|
||||
|
@ -975,7 +965,7 @@ func (buffer *Buffer) DeleteChars(n int) {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) EraseCharacters(n int) {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
line := buffer.getCurrentLine()
|
||||
|
||||
|
@ -990,7 +980,7 @@ func (buffer *Buffer) EraseCharacters(n int) {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) EraseDisplayFromCursor() {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
line := buffer.getCurrentLine()
|
||||
|
||||
max := int(buffer.terminalState.cursorX)
|
||||
|
@ -1006,7 +996,7 @@ func (buffer *Buffer) EraseDisplayFromCursor() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) EraseDisplayToCursor() {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
line := buffer.getCurrentLine()
|
||||
|
||||
for i := 0; i <= int(buffer.terminalState.cursorX); i++ {
|
||||
|
@ -1024,8 +1014,7 @@ func (buffer *Buffer) EraseDisplayToCursor() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) ResizeView(width uint16, height uint16) {
|
||||
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
if buffer.terminalState.viewHeight == 0 {
|
||||
buffer.terminalState.viewWidth = width
|
||||
|
@ -1162,7 +1151,7 @@ func (buffer *Buffer) CompareViewLines(path string) bool {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) ReverseVideo() {
|
||||
defer buffer.emitDisplayChange()
|
||||
defer buffer.dirty.Notify()
|
||||
|
||||
for _, line := range buffer.lines {
|
||||
line.ReverseVideo()
|
||||
|
|
|
@ -9,8 +9,24 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBufferCreation(t *testing.T) {
|
||||
b, n := makeBufferForTesting(10, 20)
|
||||
assert.Equal(t, uint16(10), b.Width())
|
||||
assert.Equal(t, uint16(20), b.ViewHeight())
|
||||
assert.Equal(t, uint16(0), b.CursorColumn())
|
||||
assert.Equal(t, uint16(0), b.CursorLine())
|
||||
assert.NotNil(t, b.lines)
|
||||
n.AssertNotNotified(t)
|
||||
}
|
||||
|
||||
func TestWriteNotify(t *testing.T) {
|
||||
b, n := makeBufferForTesting(30, 3)
|
||||
b.Write(rune('x'))
|
||||
n.AssertNotified(t)
|
||||
}
|
||||
|
||||
func TestTabbing(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(30, 3, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(30, 3)
|
||||
b.Write([]rune("hello")...)
|
||||
b.Tab()
|
||||
b.Write([]rune("x")...)
|
||||
|
@ -39,7 +55,7 @@ hell xxx good
|
|||
}
|
||||
|
||||
func TestOffsets(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(10, 3, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(10, 3)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -59,18 +75,8 @@ func TestOffsets(t *testing.T) {
|
|||
assert.Equal(t, 5, b.Height())
|
||||
}
|
||||
|
||||
func TestBufferCreation(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(10, 20, CellAttributes{}, 1000))
|
||||
assert.Equal(t, uint16(10), b.Width())
|
||||
assert.Equal(t, uint16(20), b.ViewHeight())
|
||||
assert.Equal(t, uint16(0), b.CursorColumn())
|
||||
assert.Equal(t, uint16(0), b.CursorLine())
|
||||
assert.NotNil(t, b.lines)
|
||||
}
|
||||
|
||||
func TestBufferWriteIncrementsCursorCorrectly(t *testing.T) {
|
||||
|
||||
b := NewBuffer(NewTerminalState(5, 4, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(5, 4)
|
||||
|
||||
/*01234
|
||||
|-----
|
||||
|
@ -117,7 +123,7 @@ func TestBufferWriteIncrementsCursorCorrectly(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(3, 20, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(3, 20)
|
||||
b.terminalState.LineFeedMode = false
|
||||
|
||||
b.Write('a', 'b', 'c')
|
||||
|
@ -142,7 +148,7 @@ func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(3, 20, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(3, 20)
|
||||
b.terminalState.LineFeedMode = false
|
||||
/*
|
||||
|abc
|
||||
|
@ -170,46 +176,59 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSetPosition(t *testing.T) {
|
||||
|
||||
b := NewBuffer(NewTerminalState(120, 80, CellAttributes{}, 1000))
|
||||
b, n := makeBufferForTesting(120, 80)
|
||||
assert.Equal(t, 0, int(b.CursorColumn()))
|
||||
assert.Equal(t, 0, int(b.CursorLine()))
|
||||
|
||||
b.SetPosition(60, 10)
|
||||
assert.Equal(t, 60, int(b.CursorColumn()))
|
||||
assert.Equal(t, 10, int(b.CursorLine()))
|
||||
n.AssertNotified(t)
|
||||
|
||||
b.SetPosition(0, 0)
|
||||
assert.Equal(t, 0, int(b.CursorColumn()))
|
||||
assert.Equal(t, 0, int(b.CursorLine()))
|
||||
n.AssertNotified(t)
|
||||
|
||||
b.SetPosition(120, 90)
|
||||
assert.Equal(t, 119, int(b.CursorColumn()))
|
||||
assert.Equal(t, 79, int(b.CursorLine()))
|
||||
|
||||
n.AssertNotified(t)
|
||||
}
|
||||
|
||||
func TestMovePosition(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(120, 80, CellAttributes{}, 1000))
|
||||
b, n := makeBufferForTesting(120, 80)
|
||||
assert.Equal(t, 0, int(b.CursorColumn()))
|
||||
assert.Equal(t, 0, int(b.CursorLine()))
|
||||
|
||||
b.MovePosition(-1, -1)
|
||||
assert.Equal(t, 0, int(b.CursorColumn()))
|
||||
assert.Equal(t, 0, int(b.CursorLine()))
|
||||
n.AssertNotified(t)
|
||||
|
||||
b.MovePosition(30, 20)
|
||||
assert.Equal(t, 30, int(b.CursorColumn()))
|
||||
assert.Equal(t, 20, int(b.CursorLine()))
|
||||
n.AssertNotified(t)
|
||||
|
||||
b.MovePosition(30, 20)
|
||||
assert.Equal(t, 60, int(b.CursorColumn()))
|
||||
assert.Equal(t, 40, int(b.CursorLine()))
|
||||
n.AssertNotified(t)
|
||||
|
||||
b.MovePosition(-1, -1)
|
||||
assert.Equal(t, 59, int(b.CursorColumn()))
|
||||
assert.Equal(t, 39, int(b.CursorLine()))
|
||||
n.AssertNotified(t)
|
||||
|
||||
b.MovePosition(100, 100)
|
||||
assert.Equal(t, 119, int(b.CursorColumn()))
|
||||
assert.Equal(t, 79, int(b.CursorLine()))
|
||||
n.AssertNotified(t)
|
||||
}
|
||||
|
||||
func TestVisibleLines(t *testing.T) {
|
||||
|
||||
b := NewBuffer(NewTerminalState(80, 10, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 10)
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -259,7 +278,7 @@ func TestVisibleLines(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClearWithoutFullView(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 10, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 10)
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -275,7 +294,7 @@ func TestClearWithoutFullView(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClearWithFullView(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 5)
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -306,7 +325,7 @@ func TestClearWithFullView(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCarriageReturn(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 20, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 20)
|
||||
b.Write([]rune("hello!")...)
|
||||
b.CarriageReturn()
|
||||
b.Write([]rune("secret")...)
|
||||
|
@ -315,7 +334,7 @@ func TestCarriageReturn(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCarriageReturnOnFullLine(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(20, 20, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(20, 20)
|
||||
b.Write([]rune("abcdeabcdeabcdeabcde")...)
|
||||
b.CarriageReturn()
|
||||
b.Write([]rune("xxxxxxxxxxxxxxxxxxxx")...)
|
||||
|
@ -324,7 +343,7 @@ func TestCarriageReturnOnFullLine(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCarriageReturnOnFullLastLine(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(20, 2, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(20, 2)
|
||||
b.NewLine()
|
||||
b.Write([]rune("abcdeabcdeabcdeabcde")...)
|
||||
b.CarriageReturn()
|
||||
|
@ -335,7 +354,7 @@ func TestCarriageReturnOnFullLastLine(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 6, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 6)
|
||||
b.Write([]rune("hello!")...)
|
||||
b.CarriageReturn()
|
||||
b.Write([]rune("secret")...)
|
||||
|
@ -345,7 +364,7 @@ func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(6, 10, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(6, 10)
|
||||
b.terminalState.cursorY = 3
|
||||
b.CarriageReturn()
|
||||
assert.Equal(t, uint16(0), b.terminalState.cursorX)
|
||||
|
@ -353,7 +372,7 @@ func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetCell(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 20, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 20)
|
||||
b.Write([]rune("Hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -369,7 +388,7 @@ func TestGetCell(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetCellWithHistory(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 2)
|
||||
|
||||
b.Write([]rune("Hello")...)
|
||||
b.CarriageReturn()
|
||||
|
@ -387,7 +406,7 @@ func TestGetCellWithHistory(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetCellWithBadCursor(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 2)
|
||||
b.Write([]rune("Hello\r\nthere\r\nsomething...")...)
|
||||
require.Nil(t, b.GetCell(8, 3))
|
||||
require.Nil(t, b.GetCell(90, 0))
|
||||
|
@ -395,12 +414,12 @@ func TestGetCellWithBadCursor(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCursorAttr(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 2)
|
||||
assert.Equal(t, &b.terminalState.CursorAttr, b.CursorAttr())
|
||||
}
|
||||
|
||||
func TestCursorPositionQuerying(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 20, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 20)
|
||||
b.terminalState.cursorX = 17
|
||||
b.terminalState.cursorY = 9
|
||||
assert.Equal(t, b.terminalState.cursorX, b.CursorColumn())
|
||||
|
@ -408,7 +427,7 @@ func TestCursorPositionQuerying(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRawPositionQuerying(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 5)
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -445,7 +464,7 @@ func TestRawPositionQuerying(t *testing.T) {
|
|||
|
||||
// CSI 2 K
|
||||
func TestEraseLine(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 5)
|
||||
b.Write([]rune("hello, this is a test")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -457,7 +476,7 @@ func TestEraseLine(t *testing.T) {
|
|||
|
||||
// CSI 1 K
|
||||
func TestEraseLineToCursor(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 5)
|
||||
b.Write([]rune("hello, this is a test")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -471,7 +490,7 @@ func TestEraseLineToCursor(t *testing.T) {
|
|||
|
||||
// CSI 0 K
|
||||
func TestEraseLineAfterCursor(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 5)
|
||||
b.Write([]rune("hello, this is a test")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -482,7 +501,7 @@ func TestEraseLineAfterCursor(t *testing.T) {
|
|||
assert.Equal(t, "dele", b.lines[1].String())
|
||||
}
|
||||
func TestEraseDisplay(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 5)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -498,7 +517,7 @@ func TestEraseDisplay(t *testing.T) {
|
|||
}
|
||||
}
|
||||
func TestEraseDisplayToCursor(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 5)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -516,7 +535,7 @@ func TestEraseDisplayToCursor(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEraseDisplayFromCursor(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 5)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
@ -532,7 +551,7 @@ func TestEraseDisplayFromCursor(t *testing.T) {
|
|||
assert.Equal(t, "", lines[2].String())
|
||||
}
|
||||
func TestBackspace(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 5)
|
||||
b.Write([]rune("hello")...)
|
||||
b.Backspace()
|
||||
b.Backspace()
|
||||
|
@ -542,7 +561,7 @@ func TestBackspace(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestHorizontalResizeView(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 10, CellAttributes{}, 1000))
|
||||
b, _ := makeBufferForTesting(80, 10)
|
||||
|
||||
// 60 characters
|
||||
b.Write([]rune(`hellohellohellohellohellohellohellohellohellohellohellohello`)...)
|
||||
|
@ -626,7 +645,7 @@ dbye
|
|||
*/
|
||||
|
||||
func TestBufferMaxLines(t *testing.T) {
|
||||
b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 2))
|
||||
b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 2), new(testNotifier))
|
||||
b.terminalState.LineFeedMode = false
|
||||
|
||||
b.Write([]rune("hello")...)
|
||||
|
@ -640,30 +659,20 @@ func TestBufferMaxLines(t *testing.T) {
|
|||
assert.Equal(t, "world", b.lines[1].String())
|
||||
}
|
||||
|
||||
func makeBufferForTestingSelection() *Buffer {
|
||||
b := NewBuffer(NewTerminalState(80, 10, CellAttributes{}, 10))
|
||||
b.terminalState.LineFeedMode = false
|
||||
|
||||
b.Write([]rune("The quick brown")...)
|
||||
b.NewLine()
|
||||
b.Write([]rune("fox jumps over")...)
|
||||
b.NewLine()
|
||||
b.Write([]rune("the lazy dog")...)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func TestSelectingChars(t *testing.T) {
|
||||
b := makeBufferForTestingSelection()
|
||||
b, n := makeBufferForTestingSelection()
|
||||
|
||||
b.StartSelection(2, 0, SelectionChar)
|
||||
n.AssertNotified(t)
|
||||
|
||||
b.ExtendSelection(4, 1, true)
|
||||
n.AssertNotified(t)
|
||||
|
||||
assert.Equal(t, "e quick brown\nfox j", b.GetSelectedText(SelectionRegionNormal))
|
||||
}
|
||||
|
||||
func TestSelectingWordsDown(t *testing.T) {
|
||||
b := makeBufferForTestingSelection()
|
||||
b, _ := makeBufferForTestingSelection()
|
||||
|
||||
b.StartSelection(6, 1, SelectionWord)
|
||||
b.ExtendSelection(5, 2, true)
|
||||
|
@ -672,7 +681,7 @@ func TestSelectingWordsDown(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSelectingWordsUp(t *testing.T) {
|
||||
b := makeBufferForTestingSelection()
|
||||
b, _ := makeBufferForTestingSelection()
|
||||
|
||||
b.StartSelection(5, 2, SelectionWord)
|
||||
b.ExtendSelection(6, 1, true)
|
||||
|
@ -681,7 +690,7 @@ func TestSelectingWordsUp(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSelectingLinesDown(t *testing.T) {
|
||||
b := makeBufferForTestingSelection()
|
||||
b, _ := makeBufferForTestingSelection()
|
||||
|
||||
b.StartSelection(6, 1, SelectionLine)
|
||||
b.ExtendSelection(4, 2, true)
|
||||
|
@ -690,7 +699,7 @@ func TestSelectingLinesDown(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSelectingLineUp(t *testing.T) {
|
||||
b := makeBufferForTestingSelection()
|
||||
b, _ := makeBufferForTestingSelection()
|
||||
|
||||
b.StartSelection(8, 2, SelectionLine)
|
||||
b.ExtendSelection(3, 1, true)
|
||||
|
@ -699,7 +708,7 @@ func TestSelectingLineUp(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSelectingAfterText(t *testing.T) {
|
||||
b := makeBufferForTestingSelection()
|
||||
b, _ := makeBufferForTestingSelection()
|
||||
|
||||
b.StartSelection(6, 3, SelectionChar)
|
||||
b.ExtendSelection(6, 3, true)
|
||||
|
@ -711,3 +720,41 @@ func TestSelectingAfterText(t *testing.T) {
|
|||
assert.Equal(t, end.Col, 79)
|
||||
assert.Equal(t, end.Line, 3)
|
||||
}
|
||||
|
||||
func makeBufferForTestingSelection() (*Buffer, *testNotifier) {
|
||||
b, n := makeBufferForTesting(80, 10)
|
||||
b.terminalState.LineFeedMode = false
|
||||
|
||||
b.Write([]rune("The quick brown")...)
|
||||
b.NewLine()
|
||||
b.Write([]rune("fox jumps over")...)
|
||||
b.NewLine()
|
||||
b.Write([]rune("the lazy dog")...)
|
||||
|
||||
return b, n
|
||||
}
|
||||
|
||||
func makeBufferForTesting(cols, rows uint16) (*Buffer, *testNotifier) {
|
||||
n := new(testNotifier)
|
||||
b := NewBuffer(NewTerminalState(cols, rows, CellAttributes{}, 100), n)
|
||||
return b, n
|
||||
}
|
||||
|
||||
// testNotifier implements the Notifier interface and provides helpers
|
||||
// for checking it Notify was called (or not).
|
||||
type testNotifier struct {
|
||||
notified bool
|
||||
}
|
||||
|
||||
func (n *testNotifier) Notify() {
|
||||
n.notified = true
|
||||
}
|
||||
|
||||
func (n *testNotifier) AssertNotified(t *testing.T) {
|
||||
assert.True(t, n.notified)
|
||||
n.notified = false
|
||||
}
|
||||
|
||||
func (n *testNotifier) AssertNotNotified(t *testing.T) {
|
||||
assert.False(t, n.notified)
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func actionPaste(gui *GUI) {
|
|||
|
||||
func actionToggleDebug(gui *GUI) {
|
||||
gui.showDebugInfo = !gui.showDebugInfo
|
||||
gui.terminal.SetDirty()
|
||||
gui.terminal.NotifyDirty()
|
||||
}
|
||||
|
||||
func actionSearchSelection(gui *GUI) {
|
||||
|
|
159
gui/gui.go
159
gui/gui.go
|
@ -24,6 +24,12 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// wakePeriod controls how often the main loop is woken up. This has
|
||||
// significant impact on how Aminal feels to use. Adjust with care and
|
||||
// test changes on all supported platforms.
|
||||
const wakePeriod = time.Second / 120
|
||||
const halfWakePeriod = wakePeriod / 2
|
||||
|
||||
type GUI struct {
|
||||
window *glfw.Window
|
||||
logger *zap.SugaredLogger
|
||||
|
@ -362,11 +368,11 @@ func (gui *GUI) Render() error {
|
|||
gui.window.SetMouseButtonCallback(gui.mouseButtonCallback)
|
||||
gui.window.SetCursorPosCallback(gui.mouseMoveCallback)
|
||||
gui.window.SetRefreshCallback(func(w *glfw.Window) {
|
||||
gui.terminal.SetDirtyLocked()
|
||||
gui.terminal.NotifyDirty()
|
||||
})
|
||||
gui.window.SetFocusCallback(func(w *glfw.Window, focused bool) {
|
||||
if focused {
|
||||
gui.terminal.SetDirtyLocked()
|
||||
gui.terminal.NotifyDirty()
|
||||
}
|
||||
})
|
||||
gui.window.SetPosCallback(gui.windowPosChangeCallback)
|
||||
|
@ -419,82 +425,119 @@ func (gui *GUI) Render() error {
|
|||
r, err := version.GetNewerRelease()
|
||||
if err == nil && r != nil {
|
||||
latestVersion = r.TagName
|
||||
gui.terminal.SetDirty()
|
||||
gui.terminal.NotifyDirty()
|
||||
}
|
||||
}()
|
||||
|
||||
startTime := time.Now()
|
||||
showMessage := true
|
||||
|
||||
stop := make(chan struct{})
|
||||
go gui.waker(stop)
|
||||
|
||||
for !gui.window.ShouldClose() {
|
||||
gui.redraw()
|
||||
|
||||
forceRedraw := false
|
||||
|
||||
select {
|
||||
case <-titleChan:
|
||||
gui.window.SetTitle(gui.terminal.GetTitle())
|
||||
case <-resizeChan:
|
||||
gui.resizeToTerminal()
|
||||
case reverse := <-reverseChan:
|
||||
gui.generateDefaultCell(reverse)
|
||||
forceRedraw = true
|
||||
default:
|
||||
// this is more efficient than glfw.PollEvents()
|
||||
glfw.WaitEventsTimeout(0.02) // up to 50fps on no input, otherwise higher
|
||||
}
|
||||
|
||||
if gui.terminal.CheckDirty() || forceRedraw {
|
||||
|
||||
gui.redraw(true)
|
||||
|
||||
if gui.showDebugInfo {
|
||||
gui.textbox(2, 2, fmt.Sprintf(`Cursor: %d,%d
|
||||
if gui.showDebugInfo {
|
||||
gui.textbox(2, 2, fmt.Sprintf(`Cursor: %d,%d
|
||||
View Size: %d,%d
|
||||
Buffer Size: %d lines
|
||||
`,
|
||||
gui.terminal.GetLogicalCursorX(),
|
||||
gui.terminal.GetLogicalCursorY(),
|
||||
gui.terminal.ActiveBuffer().ViewWidth(),
|
||||
gui.terminal.ActiveBuffer().ViewHeight(),
|
||||
gui.terminal.ActiveBuffer().Height(),
|
||||
),
|
||||
[3]float32{1, 1, 1},
|
||||
[3]float32{0.8, 0, 0},
|
||||
)
|
||||
}
|
||||
|
||||
if showMessage {
|
||||
if latestVersion != "" && time.Since(startTime) < time.Second*10 && gui.terminal.ActiveBuffer().RawLine() == 0 {
|
||||
time.AfterFunc(time.Second, gui.terminal.SetDirtyLocked)
|
||||
_, h := gui.terminal.GetSize()
|
||||
var msg string
|
||||
if version.Version == "" {
|
||||
msg = "You are using a development build of Aminal."
|
||||
} else {
|
||||
msg = fmt.Sprintf("Version %s of Aminal is now available.", strings.Replace(latestVersion, "v", "", -1))
|
||||
}
|
||||
gui.textbox(
|
||||
2,
|
||||
uint16(h-3),
|
||||
fmt.Sprintf("%s (%d)", msg, 10-int(time.Since(startTime).Seconds())),
|
||||
[3]float32{1, 1, 1},
|
||||
[3]float32{0, 0.5, 0},
|
||||
)
|
||||
} else {
|
||||
showMessage = false
|
||||
}
|
||||
}
|
||||
|
||||
gui.SwapBuffers()
|
||||
gui.terminal.GetLogicalCursorX(),
|
||||
gui.terminal.GetLogicalCursorY(),
|
||||
gui.terminal.ActiveBuffer().ViewWidth(),
|
||||
gui.terminal.ActiveBuffer().ViewHeight(),
|
||||
gui.terminal.ActiveBuffer().Height(),
|
||||
),
|
||||
[3]float32{1, 1, 1},
|
||||
[3]float32{0.8, 0, 0},
|
||||
)
|
||||
}
|
||||
|
||||
if showMessage {
|
||||
if latestVersion != "" && time.Since(startTime) < time.Second*10 && gui.terminal.ActiveBuffer().RawLine() == 0 {
|
||||
time.AfterFunc(time.Second, gui.terminal.NotifyDirty)
|
||||
_, h := gui.terminal.GetSize()
|
||||
var msg string
|
||||
if version.Version == "" {
|
||||
msg = "You are using a development build of Aminal."
|
||||
} else {
|
||||
msg = fmt.Sprintf("Version %s of Aminal is now available.", strings.Replace(latestVersion, "v", "", -1))
|
||||
}
|
||||
gui.textbox(
|
||||
2,
|
||||
uint16(h-3),
|
||||
fmt.Sprintf("%s (%d)", msg, 10-int(time.Since(startTime).Seconds())),
|
||||
[3]float32{1, 1, 1},
|
||||
[3]float32{0, 0.5, 0},
|
||||
)
|
||||
} else {
|
||||
showMessage = false
|
||||
}
|
||||
}
|
||||
|
||||
gui.SwapBuffers()
|
||||
glfw.WaitEvents() // Go to sleep until next event.
|
||||
|
||||
// Process any terminal events since the last wakeup.
|
||||
terminalEvents:
|
||||
for {
|
||||
select {
|
||||
case <-titleChan:
|
||||
gui.window.SetTitle(gui.terminal.GetTitle())
|
||||
case <-resizeChan:
|
||||
gui.resizeToTerminal()
|
||||
case reverse := <-reverseChan:
|
||||
gui.generateDefaultCell(reverse)
|
||||
default:
|
||||
break terminalEvents
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(stop) // Tell waker to end.
|
||||
|
||||
gui.logger.Debugf("Stopping render...")
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// waker is a goroutine which listens to the terminal's dirty channel,
|
||||
// waking up the main thread when the GUI needs to be
|
||||
// redrawn. Limiting is applied on wakeups to avoid excessive CPU
|
||||
// usage when the terminal is being updated rapidly.
|
||||
func (gui *GUI) waker(stop <-chan struct{}) {
|
||||
dirty := gui.terminal.Dirty()
|
||||
var nextWake <-chan time.Time
|
||||
var last time.Time
|
||||
for {
|
||||
select {
|
||||
case <-dirty:
|
||||
if nextWake == nil {
|
||||
if time.Since(last) > wakePeriod {
|
||||
// There hasn't been a wakeup recently so schedule
|
||||
// the next one sooner.
|
||||
nextWake = time.After(halfWakePeriod)
|
||||
} else {
|
||||
nextWake = time.After(wakePeriod)
|
||||
}
|
||||
}
|
||||
case last = <-nextWake:
|
||||
// TODO(mjs) - This is somewhat of a voodoo sleep but it
|
||||
// avoid various rendering issues on Windows in some
|
||||
// situations. Suspect that this will become unnecessary
|
||||
// once various goroutine synchronisation issues have been
|
||||
// resolved.
|
||||
time.Sleep(halfWakePeriod)
|
||||
|
||||
glfw.PostEmptyEvent()
|
||||
nextWake = nil
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *GUI) renderTerminalData(shouldLock bool) {
|
||||
if shouldLock {
|
||||
gui.terminal.Lock()
|
||||
|
|
|
@ -5,8 +5,8 @@ type overlay interface {
|
|||
}
|
||||
|
||||
func (gui *GUI) setOverlay(m overlay) {
|
||||
defer gui.terminal.SetDirtyLocked()
|
||||
gui.overlay = m
|
||||
gui.terminal.NotifyDirty()
|
||||
}
|
||||
|
||||
func (gui *GUI) renderOverlay() {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package terminal
|
||||
|
||||
func newNotifier() *notifier {
|
||||
return ¬ifier{
|
||||
C: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
type notifier struct {
|
||||
C chan struct{}
|
||||
}
|
||||
|
||||
// Notify is used to signal an event in a non-blocking way.
|
||||
func (n *notifier) Notify() {
|
||||
select {
|
||||
case n.C <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
|
@ -28,25 +28,25 @@ var runeMap = map[rune]runeHandler{
|
|||
|
||||
func newLineHandler(terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().NewLine()
|
||||
terminal.isDirty = true
|
||||
terminal.NotifyDirty()
|
||||
return nil
|
||||
}
|
||||
|
||||
func tabHandler(terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().Tab()
|
||||
terminal.isDirty = true
|
||||
terminal.NotifyDirty()
|
||||
return nil
|
||||
}
|
||||
|
||||
func carriageReturnHandler(terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().CarriageReturn()
|
||||
terminal.isDirty = true
|
||||
terminal.NotifyDirty()
|
||||
return nil
|
||||
}
|
||||
|
||||
func backspaceHandler(terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().Backspace()
|
||||
terminal.isDirty = true
|
||||
terminal.NotifyDirty()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -80,16 +80,16 @@ func (terminal *Terminal) processRuneLocked(b rune) {
|
|||
}
|
||||
|
||||
func (terminal *Terminal) processRune(b rune) {
|
||||
defer terminal.NotifyDirty()
|
||||
|
||||
if handler, ok := runeMap[b]; ok {
|
||||
if err := handler(terminal); err != nil {
|
||||
terminal.logger.Errorf("Error handling control code: %s", err)
|
||||
}
|
||||
terminal.isDirty = true
|
||||
return
|
||||
}
|
||||
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
|
||||
terminal.ActiveBuffer().Write(terminal.translateRune(b))
|
||||
terminal.isDirty = true
|
||||
}
|
||||
|
||||
func (terminal *Terminal) translateRune(b rune) rune {
|
||||
|
@ -129,7 +129,7 @@ func (terminal *Terminal) processInput(pty chan rune) {
|
|||
if err := ansiHandler(pty, terminal); err != nil {
|
||||
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
||||
}
|
||||
terminal.SetDirtyLocked()
|
||||
terminal.NotifyDirty()
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -50,12 +50,12 @@ type Terminal struct {
|
|||
mouseMode MouseMode
|
||||
mouseExtMode MouseExtMode
|
||||
bracketedPasteMode bool
|
||||
isDirty bool
|
||||
charWidth float32
|
||||
charHeight float32
|
||||
lastBuffer uint8
|
||||
terminalState *buffer.TerminalState
|
||||
platformDependentSettings platform.PlatformDependentSettings
|
||||
dirty *notifier
|
||||
}
|
||||
|
||||
type Modes struct {
|
||||
|
@ -85,15 +85,28 @@ func New(pty platform.Pty, logger *zap.SugaredLogger, config *config.Config) *Te
|
|||
ShowCursor: true,
|
||||
},
|
||||
platformDependentSettings: pty.GetPlatformDependentSettings(),
|
||||
dirty: newNotifier(),
|
||||
}
|
||||
t.buffers = []*buffer.Buffer{
|
||||
buffer.NewBuffer(t.terminalState),
|
||||
buffer.NewBuffer(t.terminalState),
|
||||
buffer.NewBuffer(t.terminalState),
|
||||
buffer.NewBuffer(t.terminalState, t.dirty),
|
||||
buffer.NewBuffer(t.terminalState, t.dirty),
|
||||
buffer.NewBuffer(t.terminalState, t.dirty),
|
||||
}
|
||||
t.activeBuffer = t.buffers[0]
|
||||
return t
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Dirty returns a channel that receives an empty struct whenever the
|
||||
// terminal becomes dirty.
|
||||
func (terminal *Terminal) Dirty() <-chan struct{} {
|
||||
return terminal.dirty.C
|
||||
}
|
||||
|
||||
// NotifyDirty is used to signal that the terminal is dirty and the
|
||||
// screen must be redrawn.
|
||||
func (terminal *Terminal) NotifyDirty() {
|
||||
terminal.dirty.Notify()
|
||||
}
|
||||
|
||||
func (terminal *Terminal) SetProgram(program uint32) {
|
||||
|
@ -104,19 +117,6 @@ func (terminal *Terminal) SetBracketedPasteMode(enabled bool) {
|
|||
terminal.bracketedPasteMode = enabled
|
||||
}
|
||||
|
||||
func (terminal *Terminal) CheckDirty() bool {
|
||||
terminal.Lock()
|
||||
defer terminal.Unlock()
|
||||
|
||||
d := terminal.isDirty
|
||||
terminal.isDirty = false
|
||||
return d || terminal.ActiveBuffer().IsDirty()
|
||||
}
|
||||
|
||||
func (terminal *Terminal) SetDirty() {
|
||||
terminal.isDirty = true
|
||||
}
|
||||
|
||||
func (terminal *Terminal) IsApplicationCursorKeysModeEnabled() bool {
|
||||
return terminal.modes.ApplicationCursorKeys
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ func (terminal *Terminal) GetScrollOffset() uint {
|
|||
}
|
||||
|
||||
func (terminal *Terminal) ScreenScrollDown(lines uint16) {
|
||||
defer terminal.SetDirty()
|
||||
defer terminal.NotifyDirty()
|
||||
buffer := terminal.ActiveBuffer()
|
||||
|
||||
if buffer.Height() < int(buffer.ViewHeight()) {
|
||||
|
@ -202,7 +202,7 @@ func (terminal *Terminal) AreaScrollDown(lines uint16) {
|
|||
}
|
||||
|
||||
func (terminal *Terminal) ScreenScrollUp(lines uint16) {
|
||||
defer terminal.SetDirty()
|
||||
defer terminal.NotifyDirty()
|
||||
buffer := terminal.ActiveBuffer()
|
||||
|
||||
if buffer.Height() < int(buffer.ViewHeight()) {
|
||||
|
@ -226,8 +226,8 @@ func (terminal *Terminal) ScrollPageUp() {
|
|||
}
|
||||
|
||||
func (terminal *Terminal) ScrollToEnd() {
|
||||
defer terminal.SetDirty()
|
||||
terminal.terminalState.SetScrollOffset(0)
|
||||
terminal.NotifyDirty()
|
||||
}
|
||||
|
||||
func (terminal *Terminal) GetVisibleLines() []buffer.Line {
|
||||
|
@ -301,6 +301,7 @@ func (terminal *Terminal) GetTitle() string {
|
|||
func (terminal *Terminal) SetTitle(title string) {
|
||||
terminal.title = title
|
||||
terminal.emitTitleChange()
|
||||
terminal.NotifyDirty()
|
||||
}
|
||||
|
||||
// Write sends data, i.e. locally typed keystrokes to the pty
|
||||
|
@ -378,6 +379,7 @@ func (terminal *Terminal) SetSize(newCols uint, newLines uint) error {
|
|||
terminal.ActiveBuffer().ResizeView(terminal.size.Width, terminal.size.Height)
|
||||
|
||||
terminal.emitResize()
|
||||
terminal.NotifyDirty()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -424,6 +426,7 @@ func (terminal *Terminal) SetScreenMode(enabled bool) {
|
|||
buffer.ReverseVideo()
|
||||
}
|
||||
terminal.emitReverse(enabled)
|
||||
terminal.NotifyDirty()
|
||||
}
|
||||
|
||||
func (terminal *Terminal) Lock() {
|
||||
|
|
Loading…
Reference in New Issue