* Correct handling of DeviceAttributes request

* added DECCOLM support

* added DECALN control sequence support

* added NEL support

* bug fix: Erase To Cursor should be inclusive

* added support for 'Origin Mode' (DECOM) -- top/bottom margins only

* vttest test 1 screen 3: margin tests fixes

* added support for intermediate controls inside CSI sequence

* added support for LNM (Line Feed/New Line Mode)

* removed obsolete 'intermediate' parameter

* window resize on programmatic CSI resize

* DECCOLM should clear screen on both set and reset

* bug fix in autowrap mode

* TestCursorMovement runs all test cases; screen template images updated

Signed-off-by: Max Risuhin <risuhin.max@gmail.com>

* bug fix: line mode messing with autowrap

* added ResetVerticalMargins() method

* IsAutoWrap(), IsNewLineMode()

* corrected DECALN

* fixed NEL to work in Line Feed mode

* tyding up: removed map of 1 element

* Terminal state moved out from buffer

* fix
This commit is contained in:
rrrooommmaaa 2019-01-26 01:43:56 +03:00 committed by Liam Galvin
parent 3ea8a70874
commit 4e7b8b40e7
8 changed files with 285 additions and 267 deletions

View File

@ -11,29 +11,16 @@ import (
type Buffer struct { type Buffer struct {
lines []Line lines []Line
cursorX uint16
cursorY uint16
viewHeight uint16
viewWidth uint16
cursorAttr CellAttributes
displayChangeHandlers []chan bool displayChangeHandlers []chan bool
savedX uint16 savedX uint16
savedY uint16 savedY uint16
scrollLinesFromBottom uint
topMargin uint // see DECSTBM docs - this is for scrollable regions
bottomMargin uint // see DECSTBM docs - this is for scrollable regions
replaceMode bool // overwrite character at cursor or insert new
originMode bool // see DECOM docs - whether cursor is positioned within the margins or not
lineFeedMode bool
autoWrap bool
dirty bool dirty bool
selectionStart *Position selectionStart *Position
selectionEnd *Position selectionEnd *Position
selectionComplete bool // whether the selected text can update or whether it is final selectionComplete bool // whether the selected text can update or whether it is final
selectionExpanded bool // whether the selection to word expansion has already run on this point selectionExpanded bool // whether the selection to word expansion has already run on this point
selectionClickTime time.Time selectionClickTime time.Time
defaultCell Cell terminalState *TerminalState
maxLines uint64
} }
type Position struct { type Position struct {
@ -42,24 +29,17 @@ type Position struct {
} }
// NewBuffer creates a new terminal buffer // NewBuffer creates a new terminal buffer
func NewBuffer(viewCols uint16, viewLines uint16, attr CellAttributes, maxLines uint64) *Buffer { func NewBuffer(terminalState *TerminalState) *Buffer {
b := &Buffer{ b := &Buffer{
cursorX: 0,
cursorY: 0,
lines: []Line{}, lines: []Line{},
cursorAttr: attr, terminalState: terminalState,
autoWrap: true,
defaultCell: Cell{attr: attr},
maxLines: maxLines,
} }
b.SetVerticalMargins(0, uint(viewLines-1))
b.ResizeView(viewCols, viewLines)
return b return b
} }
func (buffer *Buffer) GetURLAtPosition(col uint16, viewRow uint16) string { func (buffer *Buffer) GetURLAtPosition(col uint16, viewRow uint16) string {
row := buffer.convertViewLineToRawLine((viewRow)) - uint64(buffer.scrollLinesFromBottom) row := buffer.convertViewLineToRawLine((viewRow)) - uint64(buffer.terminalState.scrollLinesFromBottom)
cell := buffer.GetRawCell(col, row) cell := buffer.GetRawCell(col, row)
if cell == nil || cell.Rune() == 0x00 { if cell == nil || cell.Rune() == 0x00 {
@ -79,7 +59,7 @@ func (buffer *Buffer) GetURLAtPosition(col uint16, viewRow uint16) string {
candidate = fmt.Sprintf("%c%s", cell.Rune(), candidate) candidate = fmt.Sprintf("%c%s", cell.Rune(), candidate)
} }
for i := col + 1; i < buffer.viewWidth; i++ { for i := col + 1; i < buffer.terminalState.viewWidth; i++ {
cell := buffer.GetRawCell(i, row) cell := buffer.GetRawCell(i, row)
if cell == nil { if cell == nil {
break break
@ -104,7 +84,7 @@ func (buffer *Buffer) GetURLAtPosition(col uint16, viewRow uint16) string {
func (buffer *Buffer) SelectWordAtPosition(col uint16, viewRow uint16) { func (buffer *Buffer) SelectWordAtPosition(col uint16, viewRow uint16) {
row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.scrollLinesFromBottom) row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.terminalState.scrollLinesFromBottom)
cell := buffer.GetRawCell(col, row) cell := buffer.GetRawCell(col, row)
if cell == nil || cell.Rune() == 0x00 { if cell == nil || cell.Rune() == 0x00 {
@ -125,7 +105,7 @@ func (buffer *Buffer) SelectWordAtPosition(col uint16, viewRow uint16) {
start = i start = i
} }
for i := col; i < buffer.viewWidth; i++ { for i := col; i < buffer.terminalState.viewWidth; i++ {
cell := buffer.GetRawCell(i, row) cell := buffer.GetRawCell(i, row)
if cell == nil { if cell == nil {
break break
@ -197,7 +177,7 @@ func (buffer *Buffer) GetSelectedText() string {
line := buffer.lines[row] line := buffer.lines[row]
minX := 0 minX := 0
maxX := int(buffer.viewWidth) - 1 maxX := int(buffer.terminalState.viewWidth) - 1
if row == y1 { if row == y1 {
minX = x1 minX = x1
} else if !line.wrapped { } else if !line.wrapped {
@ -221,7 +201,7 @@ func (buffer *Buffer) GetSelectedText() string {
} }
func (buffer *Buffer) StartSelection(col uint16, viewRow uint16) { func (buffer *Buffer) StartSelection(col uint16, viewRow uint16) {
row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.scrollLinesFromBottom) row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.terminalState.scrollLinesFromBottom)
if buffer.selectionComplete { if buffer.selectionComplete {
buffer.selectionEnd = nil buffer.selectionEnd = nil
@ -270,7 +250,7 @@ func (buffer *Buffer) EndSelection(col uint16, viewRow uint16, complete bool) {
return return
} }
row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.scrollLinesFromBottom) row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.terminalState.scrollLinesFromBottom)
if int(col) == buffer.selectionStart.Col && int(row) == int(buffer.selectionStart.Line) && complete { if int(col) == buffer.selectionStart.Col && int(row) == int(buffer.selectionStart.Line) && complete {
return return
@ -311,7 +291,7 @@ func (buffer *Buffer) InSelection(col uint16, row uint16) bool {
x2 = buffer.selectionEnd.Col x2 = buffer.selectionEnd.Col
} }
rawY := int(buffer.convertViewLineToRawLine(row) - uint64(buffer.scrollLinesFromBottom)) rawY := int(buffer.convertViewLineToRawLine(row) - uint64(buffer.terminalState.scrollLinesFromBottom))
return (rawY > y1 || (rawY == y1 && int(col) >= x1)) && (rawY < y2 || (rawY == y2 && int(col) <= x2)) return (rawY > y1 || (rawY == y1 && int(col) >= x1)) && (rawY < y2 || (rawY == y2 && int(col) <= x2))
} }
@ -323,47 +303,16 @@ func (buffer *Buffer) IsDirty() bool {
return true return true
} }
func (buffer *Buffer) SetAutoWrap(enabled bool) {
buffer.autoWrap = enabled
}
func (buffer *Buffer) IsAutoWrap() bool {
return buffer.autoWrap
}
func (buffer *Buffer) SetOriginMode(enabled bool) {
buffer.originMode = enabled
buffer.SetPosition(0, 0)
}
func (buffer *Buffer) SetInsertMode() {
buffer.replaceMode = false
}
func (buffer *Buffer) SetReplaceMode() {
buffer.replaceMode = true
}
func (buffer *Buffer) SetVerticalMargins(top uint, bottom uint) {
buffer.topMargin = top
buffer.bottomMargin = bottom
}
// ResetVerticalMargins resets margins to extreme positions
func (buffer *Buffer) ResetVerticalMargins() {
buffer.SetVerticalMargins(0, uint(buffer.viewHeight-1))
}
func (buffer *Buffer) GetScrollOffset() uint { func (buffer *Buffer) GetScrollOffset() uint {
return buffer.scrollLinesFromBottom return buffer.terminalState.scrollLinesFromBottom
} }
func (buffer *Buffer) HasScrollableRegion() bool { func (buffer *Buffer) HasScrollableRegion() bool {
return buffer.topMargin > 0 || buffer.bottomMargin < uint(buffer.ViewHeight())-1 return buffer.terminalState.topMargin > 0 || buffer.terminalState.bottomMargin < uint(buffer.ViewHeight())-1
} }
func (buffer *Buffer) InScrollableRegion() bool { func (buffer *Buffer) InScrollableRegion() bool {
return buffer.HasScrollableRegion() && uint(buffer.cursorY) >= buffer.topMargin && uint(buffer.cursorY) <= buffer.bottomMargin return buffer.HasScrollableRegion() && uint(buffer.terminalState.cursorY) >= buffer.terminalState.topMargin && uint(buffer.terminalState.cursorY) <= buffer.terminalState.bottomMargin
} }
func (buffer *Buffer) ScrollDown(lines uint16) { func (buffer *Buffer) ScrollDown(lines uint16) {
@ -374,10 +323,10 @@ func (buffer *Buffer) ScrollDown(lines uint16) {
return return
} }
if uint(lines) > buffer.scrollLinesFromBottom { if uint(lines) > buffer.terminalState.scrollLinesFromBottom {
lines = uint16(buffer.scrollLinesFromBottom) lines = uint16(buffer.terminalState.scrollLinesFromBottom)
} }
buffer.scrollLinesFromBottom -= uint(lines) buffer.terminalState.scrollLinesFromBottom -= uint(lines)
} }
func (buffer *Buffer) ScrollUp(lines uint16) { func (buffer *Buffer) ScrollUp(lines uint16) {
@ -388,36 +337,36 @@ func (buffer *Buffer) ScrollUp(lines uint16) {
return return
} }
if uint(lines)+buffer.scrollLinesFromBottom >= (uint(buffer.Height()) - uint(buffer.ViewHeight())) { if uint(lines)+buffer.terminalState.scrollLinesFromBottom >= (uint(buffer.Height()) - uint(buffer.ViewHeight())) {
buffer.scrollLinesFromBottom = uint(buffer.Height()) - uint(buffer.ViewHeight()) buffer.terminalState.scrollLinesFromBottom = uint(buffer.Height()) - uint(buffer.ViewHeight())
} else { } else {
buffer.scrollLinesFromBottom += uint(lines) buffer.terminalState.scrollLinesFromBottom += uint(lines)
} }
} }
func (buffer *Buffer) ScrollPageDown() { func (buffer *Buffer) ScrollPageDown() {
buffer.ScrollDown(buffer.viewHeight) buffer.ScrollDown(buffer.terminalState.viewHeight)
} }
func (buffer *Buffer) ScrollPageUp() { func (buffer *Buffer) ScrollPageUp() {
buffer.ScrollUp(buffer.viewHeight) buffer.ScrollUp(buffer.terminalState.viewHeight)
} }
func (buffer *Buffer) ScrollToEnd() { func (buffer *Buffer) ScrollToEnd() {
defer buffer.emitDisplayChange() defer buffer.emitDisplayChange()
buffer.scrollLinesFromBottom = 0 buffer.terminalState.scrollLinesFromBottom = 0
} }
func (buffer *Buffer) SaveCursor() { func (buffer *Buffer) SaveCursor() {
buffer.savedX = buffer.cursorX buffer.savedX = buffer.terminalState.cursorX
buffer.savedY = buffer.cursorY buffer.savedY = buffer.terminalState.cursorY
} }
func (buffer *Buffer) RestoreCursor() { func (buffer *Buffer) RestoreCursor() {
buffer.cursorX = buffer.savedX buffer.terminalState.cursorX = buffer.savedX
buffer.cursorY = buffer.savedY buffer.terminalState.cursorY = buffer.savedY
} }
func (buffer *Buffer) CursorAttr() *CellAttributes { func (buffer *Buffer) CursorAttr() *CellAttributes {
return &buffer.cursorAttr return &buffer.terminalState.cursorAttr
} }
func (buffer *Buffer) GetCell(viewCol uint16, viewRow uint16) *Cell { func (buffer *Buffer) GetCell(viewCol uint16, viewRow uint16) *Cell {
@ -444,57 +393,57 @@ func (buffer *Buffer) emitDisplayChange() {
// Column returns cursor column // Column returns cursor column
func (buffer *Buffer) CursorColumn() uint16 { func (buffer *Buffer) CursorColumn() uint16 {
// @todo originMode and left margin // @todo originMode and left margin
return buffer.cursorX return buffer.terminalState.cursorX
} }
// Line returns cursor line // Line returns cursor line
func (buffer *Buffer) CursorLine() uint16 { func (buffer *Buffer) CursorLine() uint16 {
if buffer.originMode { if buffer.terminalState.OriginMode {
result := buffer.cursorY - uint16(buffer.topMargin) result := buffer.terminalState.cursorY - uint16(buffer.terminalState.topMargin)
if result < 0 { if result < 0 {
result = 0 result = 0
} }
return result return result
} }
return buffer.cursorY return buffer.terminalState.cursorY
} }
func (buffer *Buffer) TopMargin() uint { func (buffer *Buffer) TopMargin() uint {
return buffer.topMargin return buffer.terminalState.topMargin
} }
func (buffer *Buffer) BottomMargin() uint { func (buffer *Buffer) BottomMargin() uint {
return buffer.bottomMargin return buffer.terminalState.bottomMargin
} }
// translates the cursor line to the raw buffer line // translates the cursor line to the raw buffer line
func (buffer *Buffer) RawLine() uint64 { func (buffer *Buffer) RawLine() uint64 {
return buffer.convertViewLineToRawLine(buffer.cursorY) return buffer.convertViewLineToRawLine(buffer.terminalState.cursorY)
} }
func (buffer *Buffer) convertViewLineToRawLine(viewLine uint16) uint64 { func (buffer *Buffer) convertViewLineToRawLine(viewLine uint16) uint64 {
rawHeight := buffer.Height() rawHeight := buffer.Height()
if int(buffer.viewHeight) > rawHeight { if int(buffer.terminalState.viewHeight) > rawHeight {
return uint64(viewLine) return uint64(viewLine)
} }
return uint64(int(viewLine) + (rawHeight - int(buffer.viewHeight))) return uint64(int(viewLine) + (rawHeight - int(buffer.terminalState.viewHeight)))
} }
func (buffer *Buffer) convertRawLineToViewLine(rawLine uint64) uint16 { func (buffer *Buffer) convertRawLineToViewLine(rawLine uint64) uint16 {
rawHeight := buffer.Height() rawHeight := buffer.Height()
if int(buffer.viewHeight) > rawHeight { if int(buffer.terminalState.viewHeight) > rawHeight {
return uint16(rawLine) return uint16(rawLine)
} }
return uint16(int(rawLine) - (rawHeight - int(buffer.viewHeight))) return uint16(int(rawLine) - (rawHeight - int(buffer.terminalState.viewHeight)))
} }
// Width returns the width of the buffer in columns // Width returns the width of the buffer in columns
func (buffer *Buffer) Width() uint16 { func (buffer *Buffer) Width() uint16 {
return buffer.viewWidth return buffer.terminalState.viewWidth
} }
func (buffer *Buffer) ViewWidth() uint16 { func (buffer *Buffer) ViewWidth() uint16 {
return buffer.viewWidth return buffer.terminalState.viewWidth
} }
func (buffer *Buffer) Height() int { func (buffer *Buffer) Height() int {
@ -502,7 +451,7 @@ func (buffer *Buffer) Height() int {
} }
func (buffer *Buffer) ViewHeight() uint16 { func (buffer *Buffer) ViewHeight() uint16 {
return buffer.viewHeight return buffer.terminalState.viewHeight
} }
func (buffer *Buffer) deleteLine() { func (buffer *Buffer) deleteLine() {
@ -530,8 +479,8 @@ func (buffer *Buffer) insertLine() {
copy(out[pos+1:], buffer.lines[pos:]) copy(out[pos+1:], buffer.lines[pos:])
buffer.lines = out buffer.lines = out
} else { } else {
topIndex := buffer.convertViewLineToRawLine(uint16(buffer.topMargin)) topIndex := buffer.convertViewLineToRawLine(uint16(buffer.terminalState.topMargin))
bottomIndex := buffer.convertViewLineToRawLine(uint16(buffer.bottomMargin)) bottomIndex := buffer.convertViewLineToRawLine(uint16(buffer.terminalState.bottomMargin))
before := buffer.lines[:topIndex] before := buffer.lines[:topIndex]
after := buffer.lines[bottomIndex+1:] after := buffer.lines[bottomIndex+1:]
out := make([]Line, len(buffer.lines)) out := make([]Line, len(buffer.lines))
@ -558,7 +507,7 @@ func (buffer *Buffer) InsertBlankCharacters(count int) {
index := int(buffer.RawLine()) index := int(buffer.RawLine())
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
cells := buffer.lines[index].cells cells := buffer.lines[index].cells
buffer.lines[index].cells = append(cells[:buffer.cursorX], append([]Cell{buffer.defaultCell}, cells[buffer.cursorX:]...)...) buffer.lines[index].cells = append(cells[:buffer.terminalState.cursorX], append([]Cell{buffer.terminalState.defaultCell}, cells[buffer.terminalState.cursorX:]...)...)
} }
} }
@ -569,7 +518,7 @@ func (buffer *Buffer) InsertLines(count int) {
return return
} }
buffer.cursorX = 0 buffer.terminalState.cursorX = 0
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
buffer.insertLine() buffer.insertLine()
@ -584,7 +533,7 @@ func (buffer *Buffer) DeleteLines(count int) {
return return
} }
buffer.cursorX = 0 buffer.terminalState.cursorX = 0
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
buffer.deleteLine() buffer.deleteLine()
@ -601,12 +550,12 @@ func (buffer *Buffer) Index() {
if buffer.InScrollableRegion() { if buffer.InScrollableRegion() {
if uint(buffer.cursorY) < buffer.bottomMargin { if uint(buffer.terminalState.cursorY) < buffer.terminalState.bottomMargin {
buffer.cursorY++ buffer.terminalState.cursorY++
} else { } else {
topIndex := buffer.convertViewLineToRawLine(uint16(buffer.topMargin)) topIndex := buffer.convertViewLineToRawLine(uint16(buffer.terminalState.topMargin))
bottomIndex := buffer.convertViewLineToRawLine(uint16(buffer.bottomMargin)) bottomIndex := buffer.convertViewLineToRawLine(uint16(buffer.terminalState.bottomMargin))
for i := topIndex; i < bottomIndex; i++ { for i := topIndex; i < bottomIndex; i++ {
buffer.lines[i] = buffer.lines[i+1] buffer.lines[i] = buffer.lines[i+1]
@ -618,7 +567,7 @@ func (buffer *Buffer) Index() {
return return
} }
if buffer.cursorY >= buffer.ViewHeight()-1 { if buffer.terminalState.cursorY >= buffer.ViewHeight()-1 {
buffer.lines = append(buffer.lines, newLine()) buffer.lines = append(buffer.lines, newLine())
maxLines := buffer.getMaxLines() maxLines := buffer.getMaxLines()
if uint64(len(buffer.lines)) > maxLines { if uint64(len(buffer.lines)) > maxLines {
@ -626,7 +575,7 @@ func (buffer *Buffer) Index() {
buffer.lines = buffer.lines[:maxLines] buffer.lines = buffer.lines[:maxLines]
} }
} else { } else {
buffer.cursorY++ buffer.terminalState.cursorY++
} }
} }
@ -636,12 +585,12 @@ func (buffer *Buffer) ReverseIndex() {
if buffer.InScrollableRegion() { if buffer.InScrollableRegion() {
if uint(buffer.cursorY) > buffer.topMargin { if uint(buffer.terminalState.cursorY) > buffer.terminalState.topMargin {
buffer.cursorY-- buffer.terminalState.cursorY--
} else { } else {
topIndex := buffer.convertViewLineToRawLine(uint16(buffer.topMargin)) topIndex := buffer.convertViewLineToRawLine(uint16(buffer.terminalState.topMargin))
bottomIndex := buffer.convertViewLineToRawLine(uint16(buffer.bottomMargin)) bottomIndex := buffer.convertViewLineToRawLine(uint16(buffer.terminalState.bottomMargin))
for i := bottomIndex; i > topIndex; i-- { for i := bottomIndex; i > topIndex; i-- {
buffer.lines[i] = buffer.lines[i-1] buffer.lines[i] = buffer.lines[i-1]
@ -652,8 +601,8 @@ func (buffer *Buffer) ReverseIndex() {
return return
} }
if buffer.cursorY > 0 { if buffer.terminalState.cursorY > 0 {
buffer.cursorY-- buffer.terminalState.cursorY--
} }
} }
@ -661,13 +610,13 @@ func (buffer *Buffer) ReverseIndex() {
func (buffer *Buffer) Write(runes ...rune) { func (buffer *Buffer) Write(runes ...rune) {
// scroll to bottom on input // scroll to bottom on input
buffer.scrollLinesFromBottom = 0 buffer.terminalState.scrollLinesFromBottom = 0
for _, r := range runes { for _, r := range runes {
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
if buffer.replaceMode { if buffer.terminalState.ReplaceMode {
if buffer.CursorColumn() >= buffer.Width() { if buffer.CursorColumn() >= buffer.Width() {
// @todo replace rune at position 0 on next line down // @todo replace rune at position 0 on next line down
@ -675,27 +624,27 @@ func (buffer *Buffer) Write(runes ...rune) {
} }
for int(buffer.CursorColumn()) >= len(line.cells) { for int(buffer.CursorColumn()) >= len(line.cells) {
line.cells = append(line.cells, buffer.defaultCell) line.cells = append(line.cells, buffer.terminalState.defaultCell)
} }
line.cells[buffer.cursorX].attr = buffer.cursorAttr line.cells[buffer.terminalState.cursorX].attr = buffer.terminalState.cursorAttr
line.cells[buffer.cursorX].setRune(r) line.cells[buffer.terminalState.cursorX].setRune(r)
buffer.incrementCursorPosition() buffer.incrementCursorPosition()
continue continue
} }
if buffer.CursorColumn() >= buffer.Width() { // if we're after the line, move to next if buffer.CursorColumn() >= buffer.Width() { // if we're after the line, move to next
if buffer.autoWrap { if buffer.terminalState.AutoWrap {
buffer.NewLineEx(true) buffer.NewLineEx(true)
newLine := buffer.getCurrentLine() newLine := buffer.getCurrentLine()
if len(newLine.cells) == 0 { if len(newLine.cells) == 0 {
newLine.cells = append(newLine.cells, buffer.defaultCell) newLine.cells = append(newLine.cells, buffer.terminalState.defaultCell)
} }
cell := &newLine.cells[0] cell := &newLine.cells[0]
cell.setRune(r) cell.setRune(r)
cell.attr = buffer.cursorAttr cell.attr = buffer.terminalState.cursorAttr
} else { } else {
// no more room on line and wrapping is disabled // no more room on line and wrapping is disabled
@ -706,12 +655,12 @@ func (buffer *Buffer) Write(runes ...rune) {
} else { } else {
for int(buffer.CursorColumn()) >= len(line.cells) { for int(buffer.CursorColumn()) >= len(line.cells) {
line.cells = append(line.cells, buffer.defaultCell) line.cells = append(line.cells, buffer.terminalState.defaultCell)
} }
cell := &line.cells[buffer.CursorColumn()] cell := &line.cells[buffer.CursorColumn()]
cell.setRune(r) cell.setRune(r)
cell.attr = buffer.cursorAttr cell.attr = buffer.terminalState.cursorAttr
} }
buffer.incrementCursorPosition() buffer.incrementCursorPosition()
@ -722,7 +671,7 @@ func (buffer *Buffer) incrementCursorPosition() {
// we can increment one column past the end of the line. // we can increment one column past the end of the line.
// this is effectively the beginning of the next line, except when we \r etc. // this is effectively the beginning of the next line, except when we \r etc.
if buffer.CursorColumn() < buffer.Width() { if buffer.CursorColumn() < buffer.Width() {
buffer.cursorX++ buffer.terminalState.cursorX++
} }
} }
@ -730,12 +679,12 @@ func (buffer *Buffer) inDoWrap() bool {
// xterm uses 'do_wrap' flag for this special terminal state // xterm uses 'do_wrap' flag for this special terminal state
// we use the cursor position right after the boundary // we use the cursor position right after the boundary
// let's see how it works out // let's see how it works out
return buffer.cursorX == buffer.viewWidth // @todo rightMargin return buffer.terminalState.cursorX == buffer.terminalState.viewWidth // @todo rightMargin
} }
func (buffer *Buffer) Backspace() { func (buffer *Buffer) Backspace() {
if buffer.cursorX == 0 { if buffer.terminalState.cursorX == 0 {
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
if line.wrapped { if line.wrapped {
buffer.MovePosition(int16(buffer.Width()-1), -1) buffer.MovePosition(int16(buffer.Width()-1), -1)
@ -757,14 +706,14 @@ func (buffer *Buffer) CarriageReturn() {
if line == nil { if line == nil {
break break
} }
if line.wrapped && buffer.cursorY > 0 { if line.wrapped && buffer.terminalState.cursorY > 0 {
buffer.cursorY-- buffer.terminalState.cursorY--
} else { } else {
break break
} }
} }
buffer.cursorX = 0 buffer.terminalState.cursorX = 0
} }
func (buffer *Buffer) Tab() { func (buffer *Buffer) Tab() {
@ -772,11 +721,11 @@ func (buffer *Buffer) Tab() {
max := tabSize max := tabSize
// @todo rightMargin // @todo rightMargin
if buffer.cursorX < buffer.viewWidth { if buffer.terminalState.cursorX < buffer.terminalState.viewWidth {
max = int(buffer.viewWidth - buffer.cursorX - 1) max = int(buffer.terminalState.viewWidth - buffer.terminalState.cursorX - 1)
} }
shift := tabSize - (int(buffer.cursorX+1) % tabSize) shift := tabSize - (int(buffer.terminalState.cursorX+1) % tabSize)
if shift > max { if shift > max {
shift = max shift = max
@ -793,8 +742,8 @@ func (buffer *Buffer) NewLine() {
func (buffer *Buffer) NewLineEx(forceCursorToMargin bool) { func (buffer *Buffer) NewLineEx(forceCursorToMargin bool) {
if buffer.IsNewLineMode() || forceCursorToMargin { if buffer.terminalState.IsNewLineMode() || forceCursorToMargin {
buffer.cursorX = 0 buffer.terminalState.cursorX = 0
} }
buffer.Index() buffer.Index()
@ -807,16 +756,8 @@ func (buffer *Buffer) NewLineEx(forceCursorToMargin bool) {
} }
} }
func (buffer *Buffer) SetNewLineMode() {
buffer.lineFeedMode = false
}
func (buffer *Buffer) SetLineFeedMode() {
buffer.lineFeedMode = true
}
func (buffer *Buffer) IsNewLineMode() bool { func (buffer *Buffer) IsNewLineMode() bool {
return buffer.lineFeedMode == false return buffer.terminalState.LineFeedMode == false
} }
func (buffer *Buffer) MovePosition(x int16, y int16) { func (buffer *Buffer) MovePosition(x int16, y int16) {
@ -847,9 +788,9 @@ func (buffer *Buffer) SetPosition(col uint16, line uint16) {
useLine := line useLine := line
maxLine := buffer.ViewHeight() - 1 maxLine := buffer.ViewHeight() - 1
if buffer.originMode { if buffer.terminalState.OriginMode {
useLine += uint16(buffer.topMargin) useLine += uint16(buffer.terminalState.topMargin)
maxLine = uint16(buffer.bottomMargin) maxLine = uint16(buffer.terminalState.bottomMargin)
// @todo left and right margins // @todo left and right margins
} }
if useLine > maxLine { if useLine > maxLine {
@ -861,15 +802,15 @@ func (buffer *Buffer) SetPosition(col uint16, line uint16) {
//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())
} }
buffer.cursorX = useCol buffer.terminalState.cursorX = useCol
buffer.cursorY = useLine buffer.terminalState.cursorY = useLine
} }
func (buffer *Buffer) GetVisibleLines() []Line { func (buffer *Buffer) GetVisibleLines() []Line {
lines := []Line{} lines := []Line{}
for i := buffer.Height() - int(buffer.ViewHeight()); i < buffer.Height(); i++ { for i := buffer.Height() - int(buffer.ViewHeight()); i < buffer.Height(); i++ {
y := i - int(buffer.scrollLinesFromBottom) y := i - int(buffer.terminalState.scrollLinesFromBottom)
if y >= 0 && y < len(buffer.lines) { if y >= 0 && y < len(buffer.lines) {
lines = append(lines, buffer.lines[y]) lines = append(lines, buffer.lines[y])
} }
@ -889,7 +830,7 @@ func (buffer *Buffer) Clear() {
// creates if necessary // creates if necessary
func (buffer *Buffer) getCurrentLine() *Line { func (buffer *Buffer) getCurrentLine() *Line {
return buffer.getViewLine(buffer.cursorY) return buffer.getViewLine(buffer.terminalState.cursorY)
} }
func (buffer *Buffer) getViewLine(index uint16) *Line { func (buffer *Buffer) getViewLine(index uint16) *Line {
@ -921,9 +862,9 @@ func (buffer *Buffer) EraseLine() {
func (buffer *Buffer) EraseLineToCursor() { func (buffer *Buffer) EraseLineToCursor() {
defer buffer.emitDisplayChange() defer buffer.emitDisplayChange()
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
for i := 0; i <= int(buffer.cursorX); i++ { for i := 0; i <= int(buffer.terminalState.cursorX); i++ {
if i < len(line.cells) { if i < len(line.cells) {
line.cells[i].erase(buffer.defaultCell.attr.BgColour) line.cells[i].erase(buffer.terminalState.defaultCell.attr.BgColour)
} }
} }
} }
@ -933,9 +874,9 @@ func (buffer *Buffer) EraseLineFromCursor() {
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
if len(line.cells) > 0 { if len(line.cells) > 0 {
cx := buffer.cursorX cx := buffer.terminalState.cursorX
if int(cx) < len(line.cells) { if int(cx) < len(line.cells) {
line.cells = line.cells[:buffer.cursorX] line.cells = line.cells[:buffer.terminalState.cursorX]
} }
} }
@ -962,14 +903,14 @@ func (buffer *Buffer) DeleteChars(n int) {
defer buffer.emitDisplayChange() defer buffer.emitDisplayChange()
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
if int(buffer.cursorX) >= len(line.cells) { if int(buffer.terminalState.cursorX) >= len(line.cells) {
return return
} }
before := line.cells[:buffer.cursorX] before := line.cells[:buffer.terminalState.cursorX]
if int(buffer.cursorX)+n >= len(line.cells) { if int(buffer.terminalState.cursorX)+n >= len(line.cells) {
n = len(line.cells) - int(buffer.cursorX) n = len(line.cells) - int(buffer.terminalState.cursorX)
} }
after := line.cells[int(buffer.cursorX)+n:] after := line.cells[int(buffer.terminalState.cursorX)+n:]
line.cells = append(before, after...) line.cells = append(before, after...)
} }
@ -978,13 +919,13 @@ func (buffer *Buffer) EraseCharacters(n int) {
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
max := int(buffer.cursorX) + n max := int(buffer.terminalState.cursorX) + n
if max > len(line.cells) { if max > len(line.cells) {
max = len(line.cells) max = len(line.cells)
} }
for i := int(buffer.cursorX); i < max; i++ { for i := int(buffer.terminalState.cursorX); i < max; i++ {
line.cells[i].erase(buffer.defaultCell.attr.BgColour) line.cells[i].erase(buffer.terminalState.defaultCell.attr.BgColour)
} }
} }
@ -992,13 +933,13 @@ func (buffer *Buffer) EraseDisplayFromCursor() {
defer buffer.emitDisplayChange() defer buffer.emitDisplayChange()
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
max := int(buffer.cursorX) max := int(buffer.terminalState.cursorX)
if max > len(line.cells) { if max > len(line.cells) {
max = len(line.cells) max = len(line.cells)
} }
line.cells = line.cells[:max] line.cells = line.cells[:max]
for i := buffer.cursorY + 1; i < buffer.ViewHeight(); i++ { for i := buffer.terminalState.cursorY + 1; i < buffer.ViewHeight(); i++ {
rawLine := buffer.convertViewLineToRawLine(i) rawLine := buffer.convertViewLineToRawLine(i)
if int(rawLine) < len(buffer.lines) { if int(rawLine) < len(buffer.lines) {
buffer.lines[int(rawLine)].cells = []Cell{} buffer.lines[int(rawLine)].cells = []Cell{}
@ -1010,13 +951,13 @@ func (buffer *Buffer) EraseDisplayToCursor() {
defer buffer.emitDisplayChange() defer buffer.emitDisplayChange()
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
for i := 0; i <= int(buffer.cursorX); i++ { for i := 0; i <= int(buffer.terminalState.cursorX); i++ {
if i >= len(line.cells) { if i >= len(line.cells) {
break break
} }
line.cells[i].erase(buffer.defaultCell.attr.BgColour) line.cells[i].erase(buffer.terminalState.defaultCell.attr.BgColour)
} }
for i := uint16(0); i < buffer.cursorY; i++ { for i := uint16(0); i < buffer.terminalState.cursorY; i++ {
rawLine := buffer.convertViewLineToRawLine(i) rawLine := buffer.convertViewLineToRawLine(i)
if int(rawLine) < len(buffer.lines) { if int(rawLine) < len(buffer.lines) {
buffer.lines[int(rawLine)].cells = []Cell{} buffer.lines[int(rawLine)].cells = []Cell{}
@ -1028,19 +969,19 @@ func (buffer *Buffer) ResizeView(width uint16, height uint16) {
defer buffer.emitDisplayChange() defer buffer.emitDisplayChange()
if buffer.viewHeight == 0 { if buffer.terminalState.viewHeight == 0 {
buffer.viewWidth = width buffer.terminalState.viewWidth = width
buffer.viewHeight = height buffer.terminalState.viewHeight = height
return return
} }
// @todo scroll to bottom on resize // @todo scroll to bottom on resize
line := buffer.getCurrentLine() line := buffer.getCurrentLine()
cXFromEndOfLine := len(line.cells) - int(buffer.cursorX+1) cXFromEndOfLine := len(line.cells) - int(buffer.terminalState.cursorX+1)
cursorYMovement := 0 cursorYMovement := 0
if width < buffer.viewWidth { // wrap lines if we're shrinking if width < buffer.terminalState.viewWidth { // wrap lines if we're shrinking
for i := 0; i < len(buffer.lines); i++ { for i := 0; i < len(buffer.lines); i++ {
line := &buffer.lines[i] line := &buffer.lines[i]
//line.Cleanse() //line.Cleanse()
@ -1060,7 +1001,7 @@ func (buffer *Buffer) ResizeView(width uint16, height uint16) {
} }
} }
if i+1 <= int(buffer.cursorY) { if i+1 <= int(buffer.terminalState.cursorY) {
cursorYMovement++ cursorYMovement++
} }
@ -1072,7 +1013,7 @@ func (buffer *Buffer) ResizeView(width uint16, height uint16) {
} }
} }
} else if width > buffer.viewWidth { // unwrap lines if we're growing } else if width > buffer.terminalState.viewWidth { // unwrap lines if we're growing
for i := 0; i < len(buffer.lines)-1; i++ { for i := 0; i < len(buffer.lines)-1; i++ {
line := &buffer.lines[i] line := &buffer.lines[i]
//line.Cleanse() //line.Cleanse()
@ -1093,7 +1034,7 @@ func (buffer *Buffer) ResizeView(width uint16, height uint16) {
line.cells = append(line.cells, nextLine.cells[:moveCount]...) line.cells = append(line.cells, nextLine.cells[:moveCount]...)
if moveCount == len(nextLine.cells) { if moveCount == len(nextLine.cells) {
if i+offset <= int(buffer.cursorY) { if i+offset <= int(buffer.terminalState.cursorY) {
cursorYMovement-- cursorYMovement--
} }
@ -1111,26 +1052,26 @@ func (buffer *Buffer) ResizeView(width uint16, height uint16) {
} }
} }
buffer.viewWidth = width buffer.terminalState.viewWidth = width
buffer.viewHeight = height buffer.terminalState.viewHeight = height
cY := uint16(len(buffer.lines) - 1) cY := uint16(len(buffer.lines) - 1)
if cY >= buffer.viewHeight { if cY >= buffer.terminalState.viewHeight {
cY = buffer.viewHeight - 1 cY = buffer.terminalState.viewHeight - 1
} }
buffer.cursorY = cY buffer.terminalState.cursorY = cY
// position cursorX // position cursorX
line = buffer.getCurrentLine() line = buffer.getCurrentLine()
buffer.cursorX = uint16((len(line.cells) - cXFromEndOfLine) - 1) buffer.terminalState.cursorX = uint16((len(line.cells) - cXFromEndOfLine) - 1)
buffer.ResetVerticalMargins() buffer.terminalState.ResetVerticalMargins()
} }
func (buffer *Buffer) getMaxLines() uint64 { func (buffer *Buffer) getMaxLines() uint64 {
result := buffer.maxLines result := buffer.terminalState.maxLines
if result < uint64(buffer.viewHeight) { if result < uint64(buffer.terminalState.viewHeight) {
result = uint64(buffer.viewHeight) result = uint64(buffer.terminalState.viewHeight)
} }
return result return result
@ -1161,4 +1102,3 @@ func (buffer *Buffer) Compare(path string) bool {
} }
return bytes.Equal(f, bufferContent) return bytes.Equal(f, bufferContent)
} }

View File

@ -10,7 +10,7 @@ import (
) )
func TestTabbing(t *testing.T) { func TestTabbing(t *testing.T) {
b := NewBuffer(30, 3, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(30, 3, CellAttributes{}, 1000))
b.Write([]rune("hello")...) b.Write([]rune("hello")...)
b.Tab() b.Tab()
b.Write([]rune("x")...) b.Write([]rune("x")...)
@ -39,7 +39,7 @@ hell xxx good
} }
func TestOffsets(t *testing.T) { func TestOffsets(t *testing.T) {
b := NewBuffer(10, 3, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(10, 3, CellAttributes{}, 1000))
b.Write([]rune("hello")...) b.Write([]rune("hello")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -60,7 +60,7 @@ func TestOffsets(t *testing.T) {
} }
func TestBufferCreation(t *testing.T) { func TestBufferCreation(t *testing.T) {
b := NewBuffer(10, 20, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(10, 20, CellAttributes{}, 1000))
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())
@ -70,7 +70,7 @@ func TestBufferCreation(t *testing.T) {
func TestBufferWriteIncrementsCursorCorrectly(t *testing.T) { func TestBufferWriteIncrementsCursorCorrectly(t *testing.T) {
b := NewBuffer(5, 4, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(5, 4, CellAttributes{}, 1000))
/*01234 /*01234
|----- |-----
@ -117,21 +117,21 @@ func TestBufferWriteIncrementsCursorCorrectly(t *testing.T) {
} }
func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) { func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
b := NewBuffer(3, 20, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(3, 20, CellAttributes{}, 1000))
b.Write('a', 'b', 'c') b.Write('a', 'b', 'c')
assert.Equal(t, uint16(3), b.cursorX) assert.Equal(t, uint16(3), b.terminalState.cursorX)
assert.Equal(t, uint16(0), b.cursorY) assert.Equal(t, uint16(0), b.terminalState.cursorY)
b.NewLine() b.NewLine()
assert.Equal(t, uint16(0), b.cursorX) assert.Equal(t, uint16(0), b.terminalState.cursorX)
assert.Equal(t, uint16(1), b.cursorY) assert.Equal(t, uint16(1), b.terminalState.cursorY)
b.Write('d', 'e', 'f') b.Write('d', 'e', 'f')
assert.Equal(t, uint16(3), b.cursorX) assert.Equal(t, uint16(3), b.terminalState.cursorX)
assert.Equal(t, uint16(1), b.cursorY) assert.Equal(t, uint16(1), b.terminalState.cursorY)
b.NewLine() b.NewLine()
assert.Equal(t, uint16(0), b.cursorX) assert.Equal(t, uint16(0), b.terminalState.cursorX)
assert.Equal(t, uint16(2), b.cursorY) assert.Equal(t, uint16(2), b.terminalState.cursorY)
require.Equal(t, 3, len(b.lines)) require.Equal(t, 3, len(b.lines))
assert.Equal(t, "abc", b.lines[0].String()) assert.Equal(t, "abc", b.lines[0].String())
@ -140,7 +140,7 @@ func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
} }
func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) { func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
b := NewBuffer(3, 20, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(3, 20, CellAttributes{}, 1000))
/* /*
|abc |abc
|d |d
@ -168,7 +168,7 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
func TestSetPosition(t *testing.T) { func TestSetPosition(t *testing.T) {
b := NewBuffer(120, 80, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(120, 80, CellAttributes{}, 1000))
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)
@ -184,7 +184,7 @@ func TestSetPosition(t *testing.T) {
} }
func TestMovePosition(t *testing.T) { func TestMovePosition(t *testing.T) {
b := NewBuffer(120, 80, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(120, 80, CellAttributes{}, 1000))
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)
@ -206,7 +206,7 @@ func TestMovePosition(t *testing.T) {
func TestVisibleLines(t *testing.T) { func TestVisibleLines(t *testing.T) {
b := NewBuffer(80, 10, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 10, CellAttributes{}, 1000))
b.Write([]rune("hello 1")...) b.Write([]rune("hello 1")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -256,7 +256,7 @@ func TestVisibleLines(t *testing.T) {
} }
func TestClearWithoutFullView(t *testing.T) { func TestClearWithoutFullView(t *testing.T) {
b := NewBuffer(80, 10, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 10, CellAttributes{}, 1000))
b.Write([]rune("hello 1")...) b.Write([]rune("hello 1")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -272,7 +272,7 @@ func TestClearWithoutFullView(t *testing.T) {
} }
func TestClearWithFullView(t *testing.T) { func TestClearWithFullView(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
b.Write([]rune("hello 1")...) b.Write([]rune("hello 1")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -303,7 +303,7 @@ func TestClearWithFullView(t *testing.T) {
} }
func TestCarriageReturn(t *testing.T) { func TestCarriageReturn(t *testing.T) {
b := NewBuffer(80, 20, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 20, CellAttributes{}, 1000))
b.Write([]rune("hello!")...) b.Write([]rune("hello!")...)
b.CarriageReturn() b.CarriageReturn()
b.Write([]rune("secret")...) b.Write([]rune("secret")...)
@ -312,7 +312,7 @@ func TestCarriageReturn(t *testing.T) {
} }
func TestCarriageReturnOnFullLine(t *testing.T) { func TestCarriageReturnOnFullLine(t *testing.T) {
b := NewBuffer(20, 20, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(20, 20, CellAttributes{}, 1000))
b.Write([]rune("abcdeabcdeabcdeabcde")...) b.Write([]rune("abcdeabcdeabcdeabcde")...)
b.CarriageReturn() b.CarriageReturn()
b.Write([]rune("xxxxxxxxxxxxxxxxxxxx")...) b.Write([]rune("xxxxxxxxxxxxxxxxxxxx")...)
@ -321,7 +321,7 @@ func TestCarriageReturnOnFullLine(t *testing.T) {
} }
func TestCarriageReturnOnFullLastLine(t *testing.T) { func TestCarriageReturnOnFullLastLine(t *testing.T) {
b := NewBuffer(20, 2, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(20, 2, CellAttributes{}, 1000))
b.NewLine() b.NewLine()
b.Write([]rune("abcdeabcdeabcdeabcde")...) b.Write([]rune("abcdeabcdeabcdeabcde")...)
b.CarriageReturn() b.CarriageReturn()
@ -332,7 +332,7 @@ func TestCarriageReturnOnFullLastLine(t *testing.T) {
} }
func TestCarriageReturnOnWrappedLine(t *testing.T) { func TestCarriageReturnOnWrappedLine(t *testing.T) {
b := NewBuffer(80, 6, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 6, CellAttributes{}, 1000))
b.Write([]rune("hello!")...) b.Write([]rune("hello!")...)
b.CarriageReturn() b.CarriageReturn()
b.Write([]rune("secret")...) b.Write([]rune("secret")...)
@ -342,15 +342,15 @@ func TestCarriageReturnOnWrappedLine(t *testing.T) {
} }
func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) { func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) {
b := NewBuffer(6, 10, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(6, 10, CellAttributes{}, 1000))
b.cursorY = 3 b.terminalState.cursorY = 3
b.CarriageReturn() b.CarriageReturn()
assert.Equal(t, uint16(0), b.cursorX) assert.Equal(t, uint16(0), b.terminalState.cursorX)
assert.Equal(t, uint16(3), b.cursorY) assert.Equal(t, uint16(3), b.terminalState.cursorY)
} }
func TestGetCell(t *testing.T) { func TestGetCell(t *testing.T) {
b := NewBuffer(80, 20, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 20, CellAttributes{}, 1000))
b.Write([]rune("Hello")...) b.Write([]rune("Hello")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -366,7 +366,7 @@ func TestGetCell(t *testing.T) {
} }
func TestGetCellWithHistory(t *testing.T) { func TestGetCellWithHistory(t *testing.T) {
b := NewBuffer(80, 2, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 1000))
b.Write([]rune("Hello")...) b.Write([]rune("Hello")...)
b.CarriageReturn() b.CarriageReturn()
@ -384,7 +384,7 @@ func TestGetCellWithHistory(t *testing.T) {
} }
func TestGetCellWithBadCursor(t *testing.T) { func TestGetCellWithBadCursor(t *testing.T) {
b := NewBuffer(80, 2, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 1000))
b.Write([]rune("Hello\r\nthere\r\nsomething...")...) b.Write([]rune("Hello\r\nthere\r\nsomething...")...)
require.Nil(t, b.GetCell(8, 3)) require.Nil(t, b.GetCell(8, 3))
require.Nil(t, b.GetCell(90, 0)) require.Nil(t, b.GetCell(90, 0))
@ -392,20 +392,20 @@ func TestGetCellWithBadCursor(t *testing.T) {
} }
func TestCursorAttr(t *testing.T) { func TestCursorAttr(t *testing.T) {
b := NewBuffer(80, 2, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 1000))
assert.Equal(t, &b.cursorAttr, b.CursorAttr()) assert.Equal(t, &b.terminalState.cursorAttr, b.CursorAttr())
} }
func TestCursorPositionQuerying(t *testing.T) { func TestCursorPositionQuerying(t *testing.T) {
b := NewBuffer(80, 20, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 20, CellAttributes{}, 1000))
b.cursorX = 17 b.terminalState.cursorX = 17
b.cursorY = 9 b.terminalState.cursorY = 9
assert.Equal(t, b.cursorX, b.CursorColumn()) assert.Equal(t, b.terminalState.cursorX, b.CursorColumn())
assert.Equal(t, b.cursorY, b.CursorLine()) assert.Equal(t, b.terminalState.cursorY, b.CursorLine())
} }
func TestRawPositionQuerying(t *testing.T) { func TestRawPositionQuerying(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
b.Write([]rune("a")...) b.Write([]rune("a")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -435,14 +435,14 @@ func TestRawPositionQuerying(t *testing.T) {
b.NewLine() b.NewLine()
b.Write([]rune("a")...) b.Write([]rune("a")...)
b.cursorX = 3 b.terminalState.cursorX = 3
b.cursorY = 4 b.terminalState.cursorY = 4
assert.Equal(t, uint64(9), b.RawLine()) assert.Equal(t, uint64(9), b.RawLine())
} }
// CSI 2 K // CSI 2 K
func TestEraseLine(t *testing.T) { func TestEraseLine(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
b.Write([]rune("hello, this is a test")...) b.Write([]rune("hello, this is a test")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -454,7 +454,7 @@ func TestEraseLine(t *testing.T) {
// CSI 1 K // CSI 1 K
func TestEraseLineToCursor(t *testing.T) { func TestEraseLineToCursor(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
b.Write([]rune("hello, this is a test")...) b.Write([]rune("hello, this is a test")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -468,7 +468,7 @@ func TestEraseLineToCursor(t *testing.T) {
// CSI 0 K // CSI 0 K
func TestEraseLineAfterCursor(t *testing.T) { func TestEraseLineAfterCursor(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
b.Write([]rune("hello, this is a test")...) b.Write([]rune("hello, this is a test")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -479,7 +479,7 @@ func TestEraseLineAfterCursor(t *testing.T) {
assert.Equal(t, "dele", b.lines[1].String()) assert.Equal(t, "dele", b.lines[1].String())
} }
func TestEraseDisplay(t *testing.T) { func TestEraseDisplay(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
b.Write([]rune("hello")...) b.Write([]rune("hello")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -495,7 +495,7 @@ func TestEraseDisplay(t *testing.T) {
} }
} }
func TestEraseDisplayToCursor(t *testing.T) { func TestEraseDisplayToCursor(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
b.Write([]rune("hello")...) b.Write([]rune("hello")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -513,7 +513,7 @@ func TestEraseDisplayToCursor(t *testing.T) {
} }
func TestEraseDisplayFromCursor(t *testing.T) { func TestEraseDisplayFromCursor(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
b.Write([]rune("hello")...) b.Write([]rune("hello")...)
b.CarriageReturn() b.CarriageReturn()
b.NewLine() b.NewLine()
@ -529,7 +529,7 @@ func TestEraseDisplayFromCursor(t *testing.T) {
assert.Equal(t, "", lines[2].String()) assert.Equal(t, "", lines[2].String())
} }
func TestBackspace(t *testing.T) { func TestBackspace(t *testing.T) {
b := NewBuffer(80, 5, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 5, CellAttributes{}, 1000))
b.Write([]rune("hello")...) b.Write([]rune("hello")...)
b.Backspace() b.Backspace()
b.Backspace() b.Backspace()
@ -539,7 +539,7 @@ func TestBackspace(t *testing.T) {
} }
func TestHorizontalResizeView(t *testing.T) { func TestHorizontalResizeView(t *testing.T) {
b := NewBuffer(80, 10, CellAttributes{}, 1000) b := NewBuffer(NewTerminalState(80, 10, CellAttributes{}, 1000))
// 60 characters // 60 characters
b.Write([]rune(`hellohellohellohellohellohellohellohellohellohellohellohello`)...) b.Write([]rune(`hellohellohellohellohellohellohellohellohellohellohellohello`)...)
@ -549,8 +549,8 @@ func TestHorizontalResizeView(t *testing.T) {
b.Write([]rune(`goodbyegoodbye`)...) b.Write([]rune(`goodbyegoodbye`)...)
require.Equal(t, uint16(14), b.cursorX) require.Equal(t, uint16(14), b.terminalState.cursorX)
require.Equal(t, uint16(1), b.cursorY) require.Equal(t, uint16(1), b.terminalState.cursorY)
b.ResizeView(40, 10) b.ResizeView(40, 10)
@ -558,8 +558,8 @@ func TestHorizontalResizeView(t *testing.T) {
hellohellohellohello hellohellohellohello
goodbyegoodbye` goodbyegoodbye`
require.Equal(t, uint16(14), b.cursorX) require.Equal(t, uint16(14), b.terminalState.cursorX)
require.Equal(t, uint16(2), b.cursorY) require.Equal(t, uint16(2), b.terminalState.cursorY)
lines := b.GetVisibleLines() lines := b.GetVisibleLines()
strs := []string{} strs := []string{}
@ -612,8 +612,8 @@ goodbyegoodbye`
} }
require.Equal(t, expected, strings.Join(strs, "\n")) require.Equal(t, expected, strings.Join(strs, "\n"))
require.Equal(t, uint16(1), b.cursorY) require.Equal(t, uint16(1), b.terminalState.cursorY)
require.Equal(t, uint16(14), b.cursorX) require.Equal(t, uint16(14), b.terminalState.cursorX)
} }
/* /*
@ -623,7 +623,7 @@ dbye
*/ */
func TestBufferMaxLines(t *testing.T) { func TestBufferMaxLines(t *testing.T) {
b := NewBuffer(80, 2, CellAttributes{}, 2) b := NewBuffer(NewTerminalState(80, 2, CellAttributes{}, 2))
b.Write([]rune("hello")...) b.Write([]rune("hello")...)
b.NewLine() b.NewLine()

View File

@ -9,7 +9,7 @@ import (
func (buffer *Buffer) GetHintAtPosition(col uint16, viewRow uint16) *hints.Hint { func (buffer *Buffer) GetHintAtPosition(col uint16, viewRow uint16) *hints.Hint {
row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.scrollLinesFromBottom) row := buffer.convertViewLineToRawLine(viewRow) - uint64(buffer.terminalState.scrollLinesFromBottom)
cell := buffer.GetRawCell(col, row) cell := buffer.GetRawCell(col, row)
if cell == nil || cell.Rune() == 0x00 { if cell == nil || cell.Rune() == 0x00 {
@ -32,7 +32,7 @@ func (buffer *Buffer) GetHintAtPosition(col uint16, viewRow uint16) *hints.Hint
trimmed := strings.TrimLeft(candidate, " ") trimmed := strings.TrimLeft(candidate, " ")
sx := col - uint16(len(trimmed)-1) sx := col - uint16(len(trimmed)-1)
for i := col + 1; i < buffer.viewWidth; i++ { for i := col + 1; i < buffer.terminalState.viewWidth; i++ {
cell := buffer.GetRawCell(i, row) cell := buffer.GetRawCell(i, row)
if cell == nil { if cell == nil {
break break

49
buffer/terminal_state.go Normal file
View File

@ -0,0 +1,49 @@
package buffer
type TerminalState struct {
scrollLinesFromBottom uint
cursorX uint16
cursorY uint16
cursorAttr CellAttributes
defaultCell Cell
viewHeight uint16
viewWidth uint16
topMargin uint // see DECSTBM docs - this is for scrollable regions
bottomMargin uint // see DECSTBM docs - this is for scrollable regions
ReplaceMode bool // overwrite character at cursor or insert new
OriginMode bool // see DECOM docs - whether cursor is positioned within the margins or not
LineFeedMode bool
AutoWrap bool
maxLines uint64
}
// NewTerminalMode creates a new terminal state
func NewTerminalState(viewCols uint16, viewLines uint16, attr CellAttributes, maxLines uint64) *TerminalState {
b := &TerminalState{
cursorX: 0,
cursorY: 0,
cursorAttr: attr,
AutoWrap: true,
defaultCell: Cell{attr: attr},
maxLines: maxLines,
viewWidth: viewCols,
viewHeight: viewLines,
topMargin: 0,
bottomMargin: uint(viewLines - 1),
}
return b
}
func (terminalState *TerminalState) SetVerticalMargins(top uint, bottom uint) {
terminalState.topMargin = top
terminalState.bottomMargin = bottom
}
// ResetVerticalMargins resets margins to extreme positions
func (terminalState *TerminalState) ResetVerticalMargins() {
terminalState.SetVerticalMargins(0, uint(terminalState.viewHeight-1))
}
func (terminalState *TerminalState) IsNewLineMode() bool {
return terminalState.LineFeedMode == false
}

View File

@ -400,7 +400,7 @@ func csiSetMarginsHandler(params []string, terminal *Terminal) error {
top-- top--
bottom-- bottom--
terminal.ActiveBuffer().SetVerticalMargins(uint(top), uint(bottom)) terminal.terminalState.SetVerticalMargins(uint(top), uint(bottom))
terminal.ActiveBuffer().SetPosition(0, 0) terminal.ActiveBuffer().SetPosition(0, 0)
return nil return nil

View File

@ -25,15 +25,15 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error {
switch modeStr { switch modeStr {
case "4": case "4":
if enabled { // @todo support replace mode if enabled { // @todo support replace mode
terminal.ActiveBuffer().SetInsertMode() terminal.SetInsertMode()
} else { } else {
terminal.ActiveBuffer().SetReplaceMode() terminal.SetReplaceMode()
} }
case "20": case "20":
if enabled { if enabled {
terminal.ActiveBuffer().SetNewLineMode() terminal.SetNewLineMode()
} else { } else {
terminal.ActiveBuffer().SetLineFeedMode() terminal.SetLineFeedMode()
} }
case "?1": case "?1":
terminal.modes.ApplicationCursorKeys = enabled terminal.modes.ApplicationCursorKeys = enabled
@ -61,11 +61,11 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error {
*/ */
case "?6": case "?6":
// DECOM // DECOM
terminal.ActiveBuffer().SetOriginMode(enabled) terminal.SetOriginMode(enabled)
case "?7": case "?7":
// auto-wrap mode // auto-wrap mode
//DECAWM //DECAWM
terminal.ActiveBuffer().SetAutoWrap(enabled) terminal.SetAutoWrap(enabled)
case "?9": case "?9":
if enabled { if enabled {
terminal.logger.Infof("Turning on X10 mouse mode") terminal.logger.Infof("Turning on X10 mouse mode")

View File

@ -8,7 +8,7 @@ func screenStateHandler(pty chan rune, terminal *Terminal) error {
case '8': // DECALN -- Screen Alignment Pattern case '8': // DECALN -- Screen Alignment Pattern
// hide cursor? // hide cursor?
buffer := terminal.ActiveBuffer() buffer := terminal.ActiveBuffer()
buffer.ResetVerticalMargins() terminal.ResetVerticalMargins()
buffer.ScrollToEnd() buffer.ScrollToEnd()
// Fill the whole screen with E's // Fill the whole screen with E's
@ -16,7 +16,7 @@ func screenStateHandler(pty chan rune, terminal *Terminal) error {
for count > 0 { for count > 0 {
buffer.Write('E') buffer.Write('E')
count-- count--
if count > 0 && !buffer.IsAutoWrap() && count%buffer.ViewWidth() == 0 { if count > 0 && !terminal.IsAutoWrap() && count%buffer.ViewWidth() == 0 {
buffer.Index() buffer.Index()
buffer.CarriageReturn() buffer.CarriageReturn()
} }

View File

@ -48,6 +48,7 @@ type Terminal struct {
charWidth float32 charWidth float32
charHeight float32 charHeight float32
lastBuffer uint8 lastBuffer uint8
terminalState *buffer.TerminalState
platformDependentSettings platform.PlatformDependentSettings platformDependentSettings platform.PlatformDependentSettings
} }
@ -66,20 +67,10 @@ type Winsize struct {
func New(pty platform.Pty, logger *zap.SugaredLogger, config *config.Config) *Terminal { func New(pty platform.Pty, logger *zap.SugaredLogger, config *config.Config) *Terminal {
t := &Terminal{ t := &Terminal{
buffers: []*buffer.Buffer{ terminalState: buffer.NewTerminalState(1, 1, buffer.CellAttributes{
buffer.NewBuffer(1, 1, buffer.CellAttributes{
FgColour: config.ColourScheme.Foreground, FgColour: config.ColourScheme.Foreground,
BgColour: config.ColourScheme.Background, BgColour: config.ColourScheme.Background,
}, config.MaxLines), }, config.MaxLines),
buffer.NewBuffer(1, 1, buffer.CellAttributes{
FgColour: config.ColourScheme.Foreground,
BgColour: config.ColourScheme.Background,
}, config.MaxLines),
buffer.NewBuffer(1, 1, buffer.CellAttributes{
FgColour: config.ColourScheme.Foreground,
BgColour: config.ColourScheme.Background,
}, config.MaxLines),
},
pty: pty, pty: pty,
logger: logger, logger: logger,
config: config, config: config,
@ -89,7 +80,12 @@ func New(pty platform.Pty, logger *zap.SugaredLogger, config *config.Config) *Te
}, },
platformDependentSettings: pty.GetPlatformDependentSettings(), platformDependentSettings: pty.GetPlatformDependentSettings(),
} }
t.activeBuffer = t.buffers[MainBuffer] t.buffers = []*buffer.Buffer{
buffer.NewBuffer(t.terminalState),
buffer.NewBuffer(t.terminalState),
buffer.NewBuffer(t.terminalState),
}
t.activeBuffer = t.buffers[0]
return t return t
} }
@ -252,7 +248,7 @@ func (terminal *Terminal) Write(data []byte) error {
} }
func (terminal *Terminal) WriteReturn() error { func (terminal *Terminal) WriteReturn() error {
if terminal.ActiveBuffer().IsNewLineMode() { if terminal.terminalState.IsNewLineMode() {
return terminal.Write([]byte{0x0d, 0x0a}) return terminal.Write([]byte{0x0d, 0x0a})
} else { } else {
return terminal.Write([]byte{0x0d}) return terminal.Write([]byte{0x0d})
@ -320,3 +316,36 @@ func (terminal *Terminal) SetSize(newCols uint, newLines uint) error {
terminal.emitResize() terminal.emitResize()
return nil return nil
} }
func (terminal *Terminal) SetAutoWrap(enabled bool) {
terminal.terminalState.AutoWrap = enabled
}
func (terminal *Terminal) IsAutoWrap() bool {
return terminal.terminalState.AutoWrap
}
func (terminal *Terminal) SetOriginMode(enabled bool) {
terminal.terminalState.OriginMode = enabled
terminal.ActiveBuffer().SetPosition(0, 0)
}
func (terminal *Terminal) SetInsertMode() {
terminal.terminalState.ReplaceMode = false
}
func (terminal *Terminal) SetReplaceMode() {
terminal.terminalState.ReplaceMode = true
}
func (terminal *Terminal) SetNewLineMode() {
terminal.terminalState.LineFeedMode = false
}
func (terminal *Terminal) SetLineFeedMode() {
terminal.terminalState.LineFeedMode = true
}
func (terminal *Terminal) ResetVerticalMargins() {
terminal.terminalState.ResetVerticalMargins()
}