Vttest (#162)
* 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
114
buffer/buffer.go
|
@ -23,6 +23,8 @@ type Buffer struct {
|
||||||
topMargin uint // see DECSTBM docs - this is for scrollable regions
|
topMargin uint // see DECSTBM docs - this is for scrollable regions
|
||||||
bottomMargin 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
|
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
|
autoWrap bool
|
||||||
dirty bool
|
dirty bool
|
||||||
selectionStart *Position
|
selectionStart *Position
|
||||||
|
@ -317,6 +319,15 @@ func (buffer *Buffer) SetAutoWrap(enabled bool) {
|
||||||
buffer.autoWrap = enabled
|
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() {
|
func (buffer *Buffer) SetInsertMode() {
|
||||||
buffer.replaceMode = false
|
buffer.replaceMode = false
|
||||||
}
|
}
|
||||||
|
@ -330,6 +341,11 @@ func (buffer *Buffer) SetVerticalMargins(top uint, bottom uint) {
|
||||||
buffer.bottomMargin = bottom
|
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.scrollLinesFromBottom
|
||||||
}
|
}
|
||||||
|
@ -419,11 +435,19 @@ 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
|
||||||
return buffer.cursorX
|
return buffer.cursorX
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line returns cursor line
|
// Line returns cursor line
|
||||||
func (buffer *Buffer) CursorLine() uint16 {
|
func (buffer *Buffer) CursorLine() uint16 {
|
||||||
|
if buffer.originMode {
|
||||||
|
result := buffer.cursorY - uint16(buffer.topMargin)
|
||||||
|
if result < 0 {
|
||||||
|
result = 0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
return buffer.cursorY
|
return buffer.cursorY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,8 +516,8 @@ func (buffer *Buffer) insertLine() {
|
||||||
|
|
||||||
out := make([]Line, newLineCount)
|
out := make([]Line, newLineCount)
|
||||||
copy(
|
copy(
|
||||||
out[ : pos - ( uint64(len(buffer.lines)) + 1 - newLineCount )],
|
out[:pos-(uint64(len(buffer.lines))+1-newLineCount)],
|
||||||
buffer.lines[ uint64(len(buffer.lines)) + 1 - newLineCount : pos] )
|
buffer.lines[uint64(len(buffer.lines))+1-newLineCount:pos])
|
||||||
out[pos] = newLine()
|
out[pos] = newLine()
|
||||||
copy(out[pos+1:], buffer.lines[pos:])
|
copy(out[pos+1:], buffer.lines[pos:])
|
||||||
buffer.lines = out
|
buffer.lines = out
|
||||||
|
@ -655,13 +679,13 @@ func (buffer *Buffer) Write(runes ...rune) {
|
||||||
|
|
||||||
if buffer.autoWrap {
|
if buffer.autoWrap {
|
||||||
|
|
||||||
buffer.NewLine()
|
buffer.NewLineEx(true)
|
||||||
|
|
||||||
newLine := buffer.getCurrentLine()
|
newLine := buffer.getCurrentLine()
|
||||||
if len(newLine.cells) == 0 {
|
if len(newLine.cells) == 0 {
|
||||||
newLine.cells = []Cell{{}}
|
newLine.cells = append(newLine.cells, buffer.defaultCell)
|
||||||
}
|
}
|
||||||
cell := &newLine.cells[buffer.CursorColumn()]
|
cell := &newLine.cells[0]
|
||||||
cell.setRune(r)
|
cell.setRune(r)
|
||||||
cell.attr = buffer.cursorAttr
|
cell.attr = buffer.cursorAttr
|
||||||
|
|
||||||
|
@ -694,6 +718,13 @@ func (buffer *Buffer) incrementCursorPosition() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (buffer *Buffer) inDoWrap() bool {
|
||||||
|
// xterm uses 'do_wrap' flag for this special terminal state
|
||||||
|
// we use the cursor position right after the boundary
|
||||||
|
// let's see how it works out
|
||||||
|
return buffer.cursorX == buffer.viewWidth // @todo rightMargin
|
||||||
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) Backspace() {
|
func (buffer *Buffer) Backspace() {
|
||||||
|
|
||||||
if buffer.cursorX == 0 {
|
if buffer.cursorX == 0 {
|
||||||
|
@ -703,6 +734,9 @@ func (buffer *Buffer) Backspace() {
|
||||||
} else {
|
} else {
|
||||||
//@todo ring bell or whatever - actually i think the pty will trigger this
|
//@todo ring bell or whatever - actually i think the pty will trigger this
|
||||||
}
|
}
|
||||||
|
} else if buffer.inDoWrap() {
|
||||||
|
// the "do_wrap" implementation
|
||||||
|
buffer.MovePosition(-2, 0)
|
||||||
} else {
|
} else {
|
||||||
buffer.MovePosition(-1, 0)
|
buffer.MovePosition(-1, 0)
|
||||||
}
|
}
|
||||||
|
@ -727,15 +761,33 @@ func (buffer *Buffer) CarriageReturn() {
|
||||||
|
|
||||||
func (buffer *Buffer) Tab() {
|
func (buffer *Buffer) Tab() {
|
||||||
tabSize := 4
|
tabSize := 4
|
||||||
|
max := tabSize
|
||||||
|
|
||||||
|
// @todo rightMargin
|
||||||
|
if buffer.cursorX < buffer.viewWidth {
|
||||||
|
max = int(buffer.viewWidth - buffer.cursorX - 1)
|
||||||
|
}
|
||||||
|
|
||||||
shift := tabSize - (int(buffer.cursorX+1) % tabSize)
|
shift := tabSize - (int(buffer.cursorX+1) % tabSize)
|
||||||
|
|
||||||
|
if shift > max {
|
||||||
|
shift = max
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < shift; i++ {
|
for i := 0; i < shift; i++ {
|
||||||
buffer.Write(' ')
|
buffer.Write(' ')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) NewLine() {
|
func (buffer *Buffer) NewLine() {
|
||||||
|
buffer.NewLineEx(false)
|
||||||
|
}
|
||||||
|
|
||||||
buffer.cursorX = 0
|
func (buffer *Buffer) NewLineEx(forceCursorToMargin bool) {
|
||||||
|
|
||||||
|
if buffer.IsNewLineMode() || forceCursorToMargin {
|
||||||
|
buffer.cursorX = 0
|
||||||
|
}
|
||||||
buffer.Index()
|
buffer.Index()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -747,21 +799,34 @@ func (buffer *Buffer) NewLine() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (buffer *Buffer) SetNewLineMode() {
|
||||||
|
buffer.lineFeedMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buffer *Buffer) SetLineFeedMode() {
|
||||||
|
buffer.lineFeedMode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buffer *Buffer) IsNewLineMode() bool {
|
||||||
|
return buffer.lineFeedMode == false
|
||||||
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) MovePosition(x int16, y int16) {
|
func (buffer *Buffer) MovePosition(x int16, y int16) {
|
||||||
|
|
||||||
var toX uint16
|
var toX uint16
|
||||||
var toY uint16
|
var toY uint16
|
||||||
|
|
||||||
if int16(buffer.cursorX)+x < 0 {
|
if int16(buffer.CursorColumn())+x < 0 {
|
||||||
toX = 0
|
toX = 0
|
||||||
} else {
|
} else {
|
||||||
toX = uint16(int16(buffer.cursorX) + x)
|
toX = uint16(int16(buffer.CursorColumn()) + x)
|
||||||
}
|
}
|
||||||
|
|
||||||
if int16(buffer.cursorY)+y < 0 {
|
// should either use CursorLine() and SetPosition() or use absolutes, mind Origin Mode (DECOM)
|
||||||
|
if int16(buffer.CursorLine())+y < 0 {
|
||||||
toY = 0
|
toY = 0
|
||||||
} else {
|
} else {
|
||||||
toY = uint16(int16(buffer.cursorY) + y)
|
toY = uint16(int16(buffer.CursorLine()) + y)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.SetPosition(toX, toY)
|
buffer.SetPosition(toX, toY)
|
||||||
|
@ -770,17 +835,26 @@ func (buffer *Buffer) MovePosition(x int16, y int16) {
|
||||||
func (buffer *Buffer) SetPosition(col uint16, line uint16) {
|
func (buffer *Buffer) SetPosition(col uint16, line uint16) {
|
||||||
defer buffer.emitDisplayChange()
|
defer buffer.emitDisplayChange()
|
||||||
|
|
||||||
if col >= buffer.ViewWidth() {
|
useCol := col
|
||||||
col = buffer.ViewWidth() - 1
|
useLine := line
|
||||||
//logrus.Errorf("Cannot set cursor position: column %d is outside of the current view width (%d columns)", col, buffer.ViewWidth())
|
maxLine := buffer.ViewHeight() - 1
|
||||||
|
|
||||||
|
if buffer.originMode {
|
||||||
|
useLine += uint16(buffer.topMargin)
|
||||||
|
maxLine = uint16(buffer.bottomMargin)
|
||||||
|
// @todo left and right margins
|
||||||
}
|
}
|
||||||
if line >= buffer.ViewHeight() {
|
if useLine > maxLine {
|
||||||
line = buffer.ViewHeight() - 1
|
useLine = maxLine
|
||||||
//logrus.Errorf("Cannot set cursor position: line %d is outside of the current view height (%d lines)", line, buffer.ViewHeight())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.cursorX = col
|
if useCol >= buffer.ViewWidth() {
|
||||||
buffer.cursorY = line
|
useCol = buffer.ViewWidth() - 1
|
||||||
|
//logrus.Errorf("Cannot set cursor position: column %d is outside of the current view width (%d columns)", col, buffer.ViewWidth())
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.cursorX = useCol
|
||||||
|
buffer.cursorY = useLine
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) GetVisibleLines() []Line {
|
func (buffer *Buffer) GetVisibleLines() []Line {
|
||||||
|
@ -928,7 +1002,7 @@ 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.cursorX); i++ {
|
||||||
if i >= len(line.cells) {
|
if i >= len(line.cells) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -1042,7 +1116,7 @@ func (buffer *Buffer) ResizeView(width uint16, height uint16) {
|
||||||
line = buffer.getCurrentLine()
|
line = buffer.getCurrentLine()
|
||||||
buffer.cursorX = uint16((len(line.cells) - cXFromEndOfLine) - 1)
|
buffer.cursorX = uint16((len(line.cells) - cXFromEndOfLine) - 1)
|
||||||
|
|
||||||
buffer.SetVerticalMargins(0, uint(buffer.viewHeight-1))
|
buffer.ResetVerticalMargins()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) getMaxLines() uint64 {
|
func (buffer *Buffer) getMaxLines() uint64 {
|
||||||
|
|
|
@ -508,7 +508,7 @@ func TestEraseDisplayToCursor(t *testing.T) {
|
||||||
lines := b.GetVisibleLines()
|
lines := b.GetVisibleLines()
|
||||||
assert.Equal(t, "", lines[0].String())
|
assert.Equal(t, "", lines[0].String())
|
||||||
assert.Equal(t, "", lines[1].String())
|
assert.Equal(t, "", lines[1].String())
|
||||||
assert.Equal(t, "\x00\x00\x00ng", lines[2].String())
|
assert.Equal(t, "\x00\x00\x00\x00g", lines[2].String())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
70
gui/gui.go
|
@ -2,6 +2,7 @@ package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"image"
|
"image"
|
||||||
"image/png"
|
"image/png"
|
||||||
"os"
|
"os"
|
||||||
|
@ -28,8 +29,9 @@ type GUI struct {
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
config *config.Config
|
config *config.Config
|
||||||
terminal *terminal.Terminal
|
terminal *terminal.Terminal
|
||||||
width int //window width in pixels
|
width int //window width in pixels
|
||||||
height int //window height in pixels
|
height int //window height in pixels
|
||||||
|
resizeCache *ResizeCache // resize cache formed by resizeToTerminal()
|
||||||
dpiScale float32
|
dpiScale float32
|
||||||
fontMap *FontMap
|
fontMap *FontMap
|
||||||
fontScale float32
|
fontScale float32
|
||||||
|
@ -59,6 +61,13 @@ func Max(x, y int) int {
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResizeCache struct {
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
Cols uint
|
||||||
|
Rows uint
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GUI) GetMonitor() *glfw.Monitor {
|
func (g *GUI) GetMonitor() *glfw.Monitor {
|
||||||
|
|
||||||
if g.window == nil {
|
if g.window == nil {
|
||||||
|
@ -78,8 +87,8 @@ func (g *GUI) GetMonitor() *glfw.Monitor {
|
||||||
for _, monitor := range monitors {
|
for _, monitor := range monitors {
|
||||||
mode := monitor.GetVideoMode()
|
mode := monitor.GetVideoMode()
|
||||||
mx, my := monitor.GetPos()
|
mx, my := monitor.GetPos()
|
||||||
overlap := Max(0, Min(x + w, mx + mode.Width) - Max(x, mx)) *
|
overlap := Max(0, Min(x+w, mx+mode.Width)-Max(x, mx)) *
|
||||||
Max(0, Min(y + h, my + mode.Height) - Max(y, my))
|
Max(0, Min(y+h, my+mode.Height)-Max(y, my))
|
||||||
if bestMatch < overlap {
|
if bestMatch < overlap {
|
||||||
bestMatch = overlap
|
bestMatch = overlap
|
||||||
currentMonitor = monitor
|
currentMonitor = monitor
|
||||||
|
@ -153,6 +162,35 @@ func (gui *GUI) scale() float32 {
|
||||||
return float32(ww) / float32(pw)
|
return float32(ww) / float32(pw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// can only be called on OS thread
|
||||||
|
func (gui *GUI) resizeToTerminal(newCols uint, newRows uint) {
|
||||||
|
|
||||||
|
if gui.window.GetAttrib(glfw.Iconified) != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.resizeLock.Lock()
|
||||||
|
defer gui.resizeLock.Unlock()
|
||||||
|
|
||||||
|
cols, rows := gui.renderer.GetTermSize()
|
||||||
|
if cols == newCols && rows == newRows {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.logger.Debugf("Initiating GUI resize to columns=%d rows=%d", newCols, newRows)
|
||||||
|
|
||||||
|
gui.logger.Debugf("Calculating size...")
|
||||||
|
width, height := gui.renderer.GetRectangleSize(newCols, newRows)
|
||||||
|
|
||||||
|
roundedWidth := int(math.Ceil(float64(width)))
|
||||||
|
roundedHeight := int(math.Ceil(float64(height)))
|
||||||
|
|
||||||
|
gui.resizeCache = &ResizeCache{roundedWidth, roundedHeight, newCols, newRows}
|
||||||
|
|
||||||
|
gui.logger.Debugf("Resizing window to %dx%d", roundedWidth, roundedHeight)
|
||||||
|
gui.window.SetSize(roundedWidth, roundedHeight) // will trigger resize()
|
||||||
|
}
|
||||||
|
|
||||||
// can only be called on OS thread
|
// can only be called on OS thread
|
||||||
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
||||||
|
|
||||||
|
@ -174,14 +212,19 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
||||||
gui.logger.Debugf("Setting renderer area...")
|
gui.logger.Debugf("Setting renderer area...")
|
||||||
gui.renderer.SetArea(0, 0, gui.Width(), gui.Height())
|
gui.renderer.SetArea(0, 0, gui.Width(), gui.Height())
|
||||||
|
|
||||||
gui.logger.Debugf("Calculating size in cols/rows...")
|
if gui.resizeCache != nil && gui.resizeCache.Width == width && gui.resizeCache.Height == height {
|
||||||
cols, rows := gui.renderer.GetTermSize()
|
gui.logger.Debugf("No need to resize internal terminal!")
|
||||||
|
} else {
|
||||||
gui.logger.Debugf("Resizing internal terminal...")
|
gui.logger.Debugf("Calculating size in cols/rows...")
|
||||||
if err := gui.terminal.SetSize(cols, rows); err != nil {
|
cols, rows := gui.renderer.GetTermSize()
|
||||||
gui.logger.Errorf("Failed to resize terminal to %d cols, %d rows: %s", cols, rows, err)
|
gui.logger.Debugf("Resizing internal terminal...")
|
||||||
|
if err := gui.terminal.SetSize(cols, rows); err != nil {
|
||||||
|
gui.logger.Errorf("Failed to resize terminal to %d cols, %d rows: %s", cols, rows, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui.resizeCache = nil
|
||||||
|
|
||||||
gui.logger.Debugf("Setting viewport size...")
|
gui.logger.Debugf("Setting viewport size...")
|
||||||
gl.Viewport(0, 0, int32(gui.Width()), int32(gui.Height()))
|
gl.Viewport(0, 0, int32(gui.Width()), int32(gui.Height()))
|
||||||
|
|
||||||
|
@ -234,6 +277,7 @@ func (gui *GUI) Render() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
titleChan := make(chan bool, 1)
|
titleChan := make(chan bool, 1)
|
||||||
|
resizeChan := make(chan bool, 1)
|
||||||
|
|
||||||
gui.renderer = NewOpenGLRenderer(gui.config, gui.fontMap, 0, 0, gui.Width(), gui.Height(), gui.colourAttr, program)
|
gui.renderer = NewOpenGLRenderer(gui.config, gui.fontMap, 0, 0, gui.Width(), gui.Height(), gui.colourAttr, program)
|
||||||
|
|
||||||
|
@ -283,6 +327,7 @@ func (gui *GUI) Render() error {
|
||||||
)
|
)
|
||||||
|
|
||||||
gui.terminal.AttachTitleChangeHandler(titleChan)
|
gui.terminal.AttachTitleChangeHandler(titleChan)
|
||||||
|
gui.terminal.AttachResizeHandler(resizeChan)
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Second)
|
ticker := time.NewTicker(time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
@ -316,6 +361,9 @@ func (gui *GUI) Render() error {
|
||||||
select {
|
select {
|
||||||
case <-titleChan:
|
case <-titleChan:
|
||||||
gui.window.SetTitle(gui.terminal.GetTitle())
|
gui.window.SetTitle(gui.terminal.GetTitle())
|
||||||
|
case <-resizeChan:
|
||||||
|
cols, rows := gui.terminal.GetSize()
|
||||||
|
gui.resizeToTerminal(uint(cols), uint(rows))
|
||||||
default:
|
default:
|
||||||
// this is more efficient than glfw.PollEvents()
|
// this is more efficient than glfw.PollEvents()
|
||||||
glfw.WaitEventsTimeout(0.02) // up to 50fps on no input, otherwise higher
|
glfw.WaitEventsTimeout(0.02) // up to 50fps on no input, otherwise higher
|
||||||
|
@ -497,7 +545,7 @@ func (gui *GUI) createWindow() (*glfw.Window, error) {
|
||||||
return nil, fmt.Errorf("failed to create window, please update your graphics drivers and try again")
|
return nil, fmt.Errorf("failed to create window, please update your graphics drivers and try again")
|
||||||
}
|
}
|
||||||
|
|
||||||
window.SetSizeLimits(int(300 * gui.dpiScale), int(150 * gui.dpiScale), 10000, 10000)
|
window.SetSizeLimits(int(300*gui.dpiScale), int(150*gui.dpiScale), 10000, 10000)
|
||||||
window.MakeContextCurrent()
|
window.MakeContextCurrent()
|
||||||
window.Show()
|
window.Show()
|
||||||
window.Focus()
|
window.Focus()
|
||||||
|
|
|
@ -212,9 +212,7 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
||||||
0x09,
|
0x09,
|
||||||
})
|
})
|
||||||
case glfw.KeyEnter:
|
case glfw.KeyEnter:
|
||||||
gui.terminal.Write([]byte{
|
gui.terminal.WriteReturn()
|
||||||
0x0d,
|
|
||||||
})
|
|
||||||
case glfw.KeyKPEnter:
|
case glfw.KeyKPEnter:
|
||||||
if gui.terminal.IsApplicationCursorKeysModeEnabled() {
|
if gui.terminal.IsApplicationCursorKeysModeEnabled() {
|
||||||
gui.terminal.Write([]byte{
|
gui.terminal.Write([]byte{
|
||||||
|
@ -223,9 +221,7 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
||||||
'M',
|
'M',
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
gui.terminal.Write([]byte{
|
gui.terminal.WriteReturn()
|
||||||
0x0d,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
case glfw.KeyBackspace:
|
case glfw.KeyBackspace:
|
||||||
if modsPressed(mods, glfw.ModAlt) {
|
if modsPressed(mods, glfw.ModAlt) {
|
||||||
|
|
|
@ -176,6 +176,13 @@ func (r *OpenGLRenderer) SetArea(areaX int, areaY int, areaWidth int, areaHeight
|
||||||
r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
|
r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *OpenGLRenderer) GetRectangleSize(col uint, row uint) (float32, float32) {
|
||||||
|
x := float32(float32(col) * r.cellWidth)
|
||||||
|
y := float32(float32(row) * r.cellHeight)
|
||||||
|
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) getRectangle(col uint, row uint) *rectangle {
|
func (r *OpenGLRenderer) getRectangle(col uint, row uint) *rectangle {
|
||||||
x := float32(float32(col) * r.cellWidth)
|
x := float32(float32(col) * r.cellWidth)
|
||||||
y := float32(float32(row) * r.cellHeight) + r.cellHeight
|
y := float32(float32(row) * r.cellHeight) + r.cellHeight
|
||||||
|
|
22
main_test.go
|
@ -65,11 +65,33 @@ func TestCursorMovement(t *testing.T) {
|
||||||
if term.ActiveBuffer().Compare("vttest/test-cursor-movement-1") == false {
|
if term.ActiveBuffer().Compare("vttest/test-cursor-movement-1") == false {
|
||||||
t.Error(fmt.Sprint("ActiveBuffer doesn't match vttest template"))
|
t.Error(fmt.Sprint("ActiveBuffer doesn't match vttest template"))
|
||||||
}
|
}
|
||||||
|
g.Screenshot ("test-cursor-movement-1.png")
|
||||||
|
compareImages("vttest/test-cursor-movement-1.png", "test-cursor-movement-1.png")
|
||||||
|
|
||||||
enter(term)
|
enter(term)
|
||||||
sleep()
|
sleep()
|
||||||
g.Screenshot ("test-cursor-movement-2.png")
|
g.Screenshot ("test-cursor-movement-2.png")
|
||||||
compareImages("vttest/test-cursor-movement-2.png", "test-cursor-movement-2.png")
|
compareImages("vttest/test-cursor-movement-2.png", "test-cursor-movement-2.png")
|
||||||
|
|
||||||
|
enter(term)
|
||||||
|
sleep()
|
||||||
|
g.Screenshot ("test-cursor-movement-3.png")
|
||||||
|
compareImages("vttest/test-cursor-movement-3.png", "test-cursor-movement-3.png")
|
||||||
|
|
||||||
|
enter(term)
|
||||||
|
sleep()
|
||||||
|
g.Screenshot ("test-cursor-movement-4.png")
|
||||||
|
compareImages("vttest/test-cursor-movement-4.png", "test-cursor-movement-4.png")
|
||||||
|
|
||||||
|
enter(term)
|
||||||
|
sleep()
|
||||||
|
g.Screenshot ("test-cursor-movement-5.png")
|
||||||
|
compareImages("vttest/test-cursor-movement-5.png", "test-cursor-movement-5.png")
|
||||||
|
|
||||||
|
enter(term)
|
||||||
|
sleep()
|
||||||
|
g.Screenshot ("test-cursor-movement-6.png")
|
||||||
|
compareImages("vttest/test-cursor-movement-6.png", "test-cursor-movement-6.png")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,11 @@ var ansiSequenceMap = map[rune]escapeSequenceHandler{
|
||||||
'7': saveCursorHandler,
|
'7': saveCursorHandler,
|
||||||
'8': restoreCursorHandler,
|
'8': restoreCursorHandler,
|
||||||
'D': indexHandler,
|
'D': indexHandler,
|
||||||
|
'E': nextLineHandler, // NEL
|
||||||
'M': reverseIndexHandler,
|
'M': reverseIndexHandler,
|
||||||
'P': sixelHandler,
|
'P': sixelHandler,
|
||||||
'c': risHandler, //RIS
|
'c': risHandler, //RIS
|
||||||
|
'#': screenStateHandler,
|
||||||
'(': swallowHandler(1), // character set bullshit
|
'(': swallowHandler(1), // character set bullshit
|
||||||
')': swallowHandler(1), // character set bullshit
|
')': swallowHandler(1), // character set bullshit
|
||||||
'*': swallowHandler(1), // character set bullshit
|
'*': swallowHandler(1), // character set bullshit
|
||||||
|
@ -68,3 +70,8 @@ func ansiHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
|
||||||
return fmt.Errorf("Unknown ANSI control sequence byte: 0x%02X [%v]", b, string(b))
|
return fmt.Errorf("Unknown ANSI control sequence byte: 0x%02X [%v]", b, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nextLineHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
terminal.ActiveBuffer().NewLineEx(true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type csiSequenceHandler func(params []string, intermediate string, terminal *Terminal) error
|
type csiSequenceHandler func(params []string, terminal *Terminal) error
|
||||||
|
|
||||||
type csiMapping struct {
|
type csiMapping struct {
|
||||||
id rune
|
id rune
|
||||||
|
@ -56,19 +56,18 @@ type runeRange struct {
|
||||||
|
|
||||||
var csiTerminators = runeRange{0x40, 0x7e}
|
var csiTerminators = runeRange{0x40, 0x7e}
|
||||||
|
|
||||||
func loadCSI(pty chan rune) (final rune, param string, intermediate string) {
|
func loadCSI(pty chan rune) (final rune, param string, intermediate []rune) {
|
||||||
var b rune
|
var b rune
|
||||||
param = ""
|
param = ""
|
||||||
intermediate = ""
|
intermediate = []rune{}
|
||||||
CSI:
|
CSI:
|
||||||
for {
|
for {
|
||||||
b = <-pty
|
b = <-pty
|
||||||
switch true {
|
switch true {
|
||||||
case b >= 0x30 && b <= 0x3F:
|
case b >= 0x30 && b <= 0x3F:
|
||||||
param = param + string(b)
|
param = param + string(b)
|
||||||
case b >= 0x20 && b <= 0x2F:
|
case b > 0 && b <= 0x2F:
|
||||||
//intermediate? useful?
|
intermediate = append(intermediate, b)
|
||||||
intermediate += string(b)
|
|
||||||
case b >= csiTerminators.min && b <= csiTerminators.max:
|
case b >= csiTerminators.min && b <= csiTerminators.max:
|
||||||
final = b
|
final = b
|
||||||
break CSI
|
break CSI
|
||||||
|
@ -89,6 +88,11 @@ func splitParams(paramString string) []string {
|
||||||
func csiHandler(pty chan rune, terminal *Terminal) error {
|
func csiHandler(pty chan rune, terminal *Terminal) error {
|
||||||
final, param, intermediate := loadCSI(pty)
|
final, param, intermediate := loadCSI(pty)
|
||||||
|
|
||||||
|
// process intermediate control codes before the CSI
|
||||||
|
for _, b := range intermediate {
|
||||||
|
terminal.processRune(b)
|
||||||
|
}
|
||||||
|
|
||||||
params := splitParams(param)
|
params := splitParams(param)
|
||||||
|
|
||||||
for _, sequence := range csiSequences {
|
for _, sequence := range csiSequences {
|
||||||
|
@ -97,16 +101,16 @@ func csiHandler(pty chan rune, terminal *Terminal) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
x, y := terminal.ActiveBuffer().CursorColumn(), terminal.ActiveBuffer().CursorLine()
|
x, y := terminal.ActiveBuffer().CursorColumn(), terminal.ActiveBuffer().CursorLine()
|
||||||
err := sequence.handler(params, intermediate, terminal)
|
err := sequence.handler(params, terminal)
|
||||||
terminal.logger.Debugf("CSI 0x%02X (ESC[%s%s%s) %s - %d,%d -> %d,%d", final, param, intermediate, string(final), sequence.description, x, y, terminal.ActiveBuffer().CursorColumn(), terminal.ActiveBuffer().CursorLine())
|
terminal.logger.Debugf("CSI 0x%02X (ESC[%s%s) %s - %d,%d -> %d,%d", final, param, string(final), sequence.description, x, y, terminal.ActiveBuffer().CursorColumn(), terminal.ActiveBuffer().CursorLine())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("Unknown CSI control sequence: 0x%02X (ESC[%s%s%s)", final, param, intermediate, string(final))
|
return fmt.Errorf("Unknown CSI control sequence: 0x%02X (ESC[%s%s)", final, param, string(final))
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiSendDeviceAttributesHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiSendDeviceAttributesHandler(params []string, terminal *Terminal) error {
|
||||||
|
|
||||||
// we are VT100
|
// we are VT100
|
||||||
// for DA1 we'll respond 1;2
|
// for DA1 we'll respond 1;2
|
||||||
|
@ -132,7 +136,7 @@ func csiSendDeviceAttributesHandler(params []string, intermediate string, termin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiDeviceStatusReportHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiDeviceStatusReportHandler(params []string, terminal *Terminal) error {
|
||||||
|
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
return fmt.Errorf("Missing Device Status Report identifier")
|
return fmt.Errorf("Missing Device Status Report identifier")
|
||||||
|
@ -154,7 +158,7 @@ func csiDeviceStatusReportHandler(params []string, intermediate string, terminal
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiCursorUpHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiCursorUpHandler(params []string, terminal *Terminal) error {
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
|
@ -167,7 +171,7 @@ func csiCursorUpHandler(params []string, intermediate string, terminal *Terminal
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiCursorDownHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiCursorDownHandler(params []string, terminal *Terminal) error {
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
|
@ -181,7 +185,7 @@ func csiCursorDownHandler(params []string, intermediate string, terminal *Termin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiCursorForwardHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiCursorForwardHandler(params []string, terminal *Terminal) error {
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
|
@ -195,7 +199,7 @@ func csiCursorForwardHandler(params []string, intermediate string, terminal *Ter
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiCursorBackwardHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiCursorBackwardHandler(params []string, terminal *Terminal) error {
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
|
@ -209,7 +213,7 @@ func csiCursorBackwardHandler(params []string, intermediate string, terminal *Te
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiCursorNextLineHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiCursorNextLineHandler(params []string, terminal *Terminal) error {
|
||||||
|
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
|
@ -225,7 +229,7 @@ func csiCursorNextLineHandler(params []string, intermediate string, terminal *Te
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiCursorPrecedingLineHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiCursorPrecedingLineHandler(params []string, terminal *Terminal) error {
|
||||||
|
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
|
@ -240,7 +244,7 @@ func csiCursorPrecedingLineHandler(params []string, intermediate string, termina
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiCursorCharacterAbsoluteHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiCursorCharacterAbsoluteHandler(params []string, terminal *Terminal) error {
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
|
@ -274,14 +278,14 @@ func parseCursorPosition(params []string) (x, y int) {
|
||||||
return x, y
|
return x, y
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiCursorPositionHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiCursorPositionHandler(params []string, terminal *Terminal) error {
|
||||||
x, y := parseCursorPosition(params)
|
x, y := parseCursorPosition(params)
|
||||||
|
|
||||||
terminal.ActiveBuffer().SetPosition(uint16(x-1), uint16(y-1))
|
terminal.ActiveBuffer().SetPosition(uint16(x-1), uint16(y-1))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiScrollUpHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiScrollUpHandler(params []string, terminal *Terminal) error {
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 1 {
|
if len(params) > 1 {
|
||||||
return fmt.Errorf("Not supported")
|
return fmt.Errorf("Not supported")
|
||||||
|
@ -298,7 +302,7 @@ func csiScrollUpHandler(params []string, intermediate string, terminal *Terminal
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiInsertBlankCharactersHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiInsertBlankCharactersHandler(params []string, terminal *Terminal) error {
|
||||||
count := 1
|
count := 1
|
||||||
if len(params) > 1 {
|
if len(params) > 1 {
|
||||||
return fmt.Errorf("Not supported")
|
return fmt.Errorf("Not supported")
|
||||||
|
@ -316,7 +320,7 @@ func csiInsertBlankCharactersHandler(params []string, intermediate string, termi
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiInsertLinesHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiInsertLinesHandler(params []string, terminal *Terminal) error {
|
||||||
count := 1
|
count := 1
|
||||||
if len(params) > 1 {
|
if len(params) > 1 {
|
||||||
return fmt.Errorf("Not supported")
|
return fmt.Errorf("Not supported")
|
||||||
|
@ -334,7 +338,7 @@ func csiInsertLinesHandler(params []string, intermediate string, terminal *Termi
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiDeleteLinesHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiDeleteLinesHandler(params []string, terminal *Terminal) error {
|
||||||
count := 1
|
count := 1
|
||||||
if len(params) > 1 {
|
if len(params) > 1 {
|
||||||
return fmt.Errorf("Not supported")
|
return fmt.Errorf("Not supported")
|
||||||
|
@ -352,7 +356,7 @@ func csiDeleteLinesHandler(params []string, intermediate string, terminal *Termi
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiScrollDownHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiScrollDownHandler(params []string, terminal *Terminal) error {
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 1 {
|
if len(params) > 1 {
|
||||||
return fmt.Errorf("Not supported")
|
return fmt.Errorf("Not supported")
|
||||||
|
@ -370,7 +374,7 @@ func csiScrollDownHandler(params []string, intermediate string, terminal *Termin
|
||||||
}
|
}
|
||||||
|
|
||||||
// DECSTBM
|
// DECSTBM
|
||||||
func csiSetMarginsHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiSetMarginsHandler(params []string, terminal *Terminal) error {
|
||||||
top := 1
|
top := 1
|
||||||
bottom := int(terminal.ActiveBuffer().ViewHeight())
|
bottom := int(terminal.ActiveBuffer().ViewHeight())
|
||||||
|
|
||||||
|
@ -402,7 +406,7 @@ func csiSetMarginsHandler(params []string, intermediate string, terminal *Termin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiEraseCharactersHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiEraseCharactersHandler(params []string, terminal *Terminal) error {
|
||||||
count := 1
|
count := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
|
@ -417,19 +421,19 @@ func csiEraseCharactersHandler(params []string, intermediate string, terminal *T
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiResetModeHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiResetModeHandler(params []string, terminal *Terminal) error {
|
||||||
return csiSetMode(strings.Join(params, ""), false, terminal)
|
return csiSetMode(strings.Join(params, ""), false, terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiSetModeHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiSetModeHandler(params []string, terminal *Terminal) error {
|
||||||
return csiSetMode(strings.Join(params, ""), true, terminal)
|
return csiSetMode(strings.Join(params, ""), true, terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiWindowManipulation(params []string, intermediate string, terminal *Terminal) error {
|
func csiWindowManipulation(params []string, terminal *Terminal) error {
|
||||||
return fmt.Errorf("Window manipulation is not yet supported")
|
return fmt.Errorf("Window manipulation is not yet supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiLinePositionAbsolute(params []string, intermediate string, terminal *Terminal) error {
|
func csiLinePositionAbsolute(params []string, terminal *Terminal) error {
|
||||||
row := 1
|
row := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
|
@ -444,7 +448,7 @@ func csiLinePositionAbsolute(params []string, intermediate string, terminal *Ter
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiDeleteHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiDeleteHandler(params []string, terminal *Terminal) error {
|
||||||
n := 1
|
n := 1
|
||||||
if len(params) >= 1 {
|
if len(params) >= 1 {
|
||||||
var err error
|
var err error
|
||||||
|
@ -459,7 +463,7 @@ func csiDeleteHandler(params []string, intermediate string, terminal *Terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSI Ps J
|
// CSI Ps J
|
||||||
func csiEraseInDisplayHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiEraseInDisplayHandler(params []string, terminal *Terminal) error {
|
||||||
n := "0"
|
n := "0"
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
n = params[0]
|
n = params[0]
|
||||||
|
@ -481,7 +485,7 @@ func csiEraseInDisplayHandler(params []string, intermediate string, terminal *Te
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSI Ps K
|
// CSI Ps K
|
||||||
func csiEraseInLineHandler(params []string, intermediate string, terminal *Terminal) error {
|
func csiEraseInLineHandler(params []string, terminal *Terminal) error {
|
||||||
|
|
||||||
n := "0"
|
n := "0"
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
|
|
|
@ -29,8 +29,39 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error {
|
||||||
} else {
|
} else {
|
||||||
terminal.ActiveBuffer().SetReplaceMode()
|
terminal.ActiveBuffer().SetReplaceMode()
|
||||||
}
|
}
|
||||||
|
case "20":
|
||||||
|
if enabled {
|
||||||
|
terminal.ActiveBuffer().SetNewLineMode()
|
||||||
|
} else {
|
||||||
|
terminal.ActiveBuffer().SetLineFeedMode()
|
||||||
|
}
|
||||||
case "?1":
|
case "?1":
|
||||||
terminal.modes.ApplicationCursorKeys = enabled
|
terminal.modes.ApplicationCursorKeys = enabled
|
||||||
|
case "?3":
|
||||||
|
_, lines := terminal.GetSize()
|
||||||
|
if enabled {
|
||||||
|
// DECCOLM - COLumn mode, 132 characters per line
|
||||||
|
terminal.SetSize(132, uint(lines))
|
||||||
|
} else {
|
||||||
|
// DECCOLM - 80 characters per line (erases screen)
|
||||||
|
terminal.SetSize(80, uint(lines))
|
||||||
|
}
|
||||||
|
terminal.Clear()
|
||||||
|
/*
|
||||||
|
case "?4":
|
||||||
|
// DECSCLM
|
||||||
|
// @todo smooth scrolling / jump scrolling
|
||||||
|
case "?5":
|
||||||
|
// DECSCNM
|
||||||
|
if enabled {
|
||||||
|
// @todo SCreeN Mode, black on white background
|
||||||
|
} else {
|
||||||
|
// @todo Normal screen (white on black background)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
case "?6":
|
||||||
|
// DECOM
|
||||||
|
terminal.ActiveBuffer().SetOriginMode(enabled)
|
||||||
case "?7":
|
case "?7":
|
||||||
// auto-wrap mode
|
// auto-wrap mode
|
||||||
//DECAWM
|
//DECAWM
|
||||||
|
@ -78,7 +109,13 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error {
|
||||||
case "?2004":
|
case "?2004":
|
||||||
terminal.SetBracketedPasteMode(enabled)
|
terminal.SetBracketedPasteMode(enabled)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unsupported CSI %sl code", modeStr)
|
code := ""
|
||||||
|
if enabled {
|
||||||
|
code = "h"
|
||||||
|
} else {
|
||||||
|
code = "l"
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Unsupported CSI %s%s code", modeStr, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -8,66 +8,81 @@ import (
|
||||||
|
|
||||||
type TerminalCharSet int
|
type TerminalCharSet int
|
||||||
|
|
||||||
|
// single rune handler
|
||||||
|
type runeHandler func(terminal *Terminal) error
|
||||||
|
|
||||||
type escapeSequenceHandler func(pty chan rune, terminal *Terminal) error
|
type escapeSequenceHandler func(pty chan rune, terminal *Terminal) error
|
||||||
|
|
||||||
var escapeSequenceMap = map[rune]escapeSequenceHandler{
|
var runeMap = map[rune]runeHandler{
|
||||||
0x05: enqSequenceHandler,
|
0x05: enqHandler,
|
||||||
0x07: bellSequenceHandler,
|
0x07: bellHandler,
|
||||||
0x08: backspaceSequenceHandler,
|
0x08: backspaceHandler,
|
||||||
0x09: tabSequenceHandler,
|
0x09: tabHandler,
|
||||||
0x0a: newLineSequenceHandler,
|
0x0a: newLineHandler,
|
||||||
0x0b: newLineSequenceHandler,
|
0x0b: newLineHandler,
|
||||||
0x0c: newLineSequenceHandler,
|
0x0c: newLineHandler,
|
||||||
0x0d: carriageReturnSequenceHandler,
|
0x0d: carriageReturnHandler,
|
||||||
0x0e: shiftOutSequenceHandler,
|
0x0e: shiftOutHandler,
|
||||||
0x0f: shiftInSequenceHandler,
|
0x0f: shiftInHandler,
|
||||||
0x1b: ansiHandler,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLineSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func newLineHandler(terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().NewLine()
|
terminal.ActiveBuffer().NewLine()
|
||||||
terminal.isDirty = true
|
terminal.isDirty = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tabSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func tabHandler(terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().Tab()
|
terminal.ActiveBuffer().Tab()
|
||||||
terminal.isDirty = true
|
terminal.isDirty = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func carriageReturnSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func carriageReturnHandler(terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().CarriageReturn()
|
terminal.ActiveBuffer().CarriageReturn()
|
||||||
terminal.isDirty = true
|
terminal.isDirty = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func backspaceSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func backspaceHandler(terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().Backspace()
|
terminal.ActiveBuffer().Backspace()
|
||||||
terminal.isDirty = true
|
terminal.isDirty = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bellSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func bellHandler(terminal *Terminal) error {
|
||||||
// @todo ring bell - flash red or some shit?
|
// @todo ring bell - flash red or some shit?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func enqSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func enqHandler(terminal *Terminal) error {
|
||||||
terminal.logger.Errorf("Received ENQ!")
|
terminal.logger.Errorf("Received ENQ!")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shiftOutSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func shiftOutHandler(terminal *Terminal) error {
|
||||||
terminal.logger.Errorf("Received shift out")
|
terminal.logger.Errorf("Received shift out")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shiftInSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func shiftInHandler(terminal *Terminal) error {
|
||||||
terminal.logger.Errorf("Received shift in")
|
terminal.logger.Errorf("Received shift in")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) processRune(b rune) {
|
||||||
|
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(b)
|
||||||
|
terminal.isDirty = true
|
||||||
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) processInput(pty chan rune) {
|
func (terminal *Terminal) processInput(pty chan rune) {
|
||||||
|
|
||||||
// https://en.wikipedia.org/wiki/ANSI_escape_code
|
// https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
@ -82,19 +97,15 @@ func (terminal *Terminal) processInput(pty chan rune) {
|
||||||
|
|
||||||
b = <-pty
|
b = <-pty
|
||||||
|
|
||||||
if b < 0x20 {
|
if b == 0x1b {
|
||||||
if handler, ok := escapeSequenceMap[b]; ok {
|
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
|
||||||
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
|
if err := ansiHandler(pty, terminal); err != nil {
|
||||||
if err := handler(pty, terminal); err != nil {
|
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
||||||
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
|
||||||
}
|
|
||||||
terminal.isDirty = true
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
terminal.isDirty = true
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
|
terminal.processRune(b)
|
||||||
terminal.ActiveBuffer().Write(b)
|
|
||||||
terminal.isDirty = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func screenStateHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
b := <-pty
|
||||||
|
switch b {
|
||||||
|
case '8': // DECALN -- Screen Alignment Pattern
|
||||||
|
// hide cursor?
|
||||||
|
buffer := terminal.ActiveBuffer()
|
||||||
|
buffer.ResetVerticalMargins()
|
||||||
|
buffer.ScrollToEnd()
|
||||||
|
|
||||||
|
// Fill the whole screen with E's
|
||||||
|
count := buffer.ViewHeight() * buffer.ViewWidth()
|
||||||
|
for count > 0 {
|
||||||
|
buffer.Write('E')
|
||||||
|
count--
|
||||||
|
if count > 0 && !buffer.IsAutoWrap() && count%buffer.ViewWidth() == 0 {
|
||||||
|
buffer.Index()
|
||||||
|
buffer.CarriageReturn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// restore cursor
|
||||||
|
buffer.SetPosition(0, 0)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Screen State code not supported: 0x%02X [%v]", b, string(b))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/liamg/aminal/config"
|
"github.com/liamg/aminal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sgrSequenceHandler(params []string, intermediate string, terminal *Terminal) error {
|
func sgrSequenceHandler(params []string, terminal *Terminal) error {
|
||||||
|
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
params = []string{"0"}
|
params = []string{"0"}
|
||||||
|
@ -137,7 +137,7 @@ func sgrSequenceHandler(params []string, intermediate string, terminal *Terminal
|
||||||
terminal.ActiveBuffer().CursorAttr().BgColour = c
|
terminal.ActiveBuffer().CursorAttr().BgColour = c
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unknown SGR control sequence: (ESC[%s%sm)", params[i:], intermediate)
|
return fmt.Errorf("Unknown SGR control sequence: (ESC[%sm)", params[i:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,13 @@ package terminal
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/liamg/aminal/buffer"
|
"github.com/liamg/aminal/buffer"
|
||||||
"github.com/liamg/aminal/config"
|
"github.com/liamg/aminal/config"
|
||||||
"github.com/liamg/aminal/platform"
|
"github.com/liamg/aminal/platform"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -39,6 +40,7 @@ type Terminal struct {
|
||||||
size Winsize
|
size Winsize
|
||||||
config *config.Config
|
config *config.Config
|
||||||
titleHandlers []chan bool
|
titleHandlers []chan bool
|
||||||
|
resizeHandlers []chan bool
|
||||||
modes Modes
|
modes Modes
|
||||||
mouseMode MouseMode
|
mouseMode MouseMode
|
||||||
bracketedPasteMode bool
|
bracketedPasteMode bool
|
||||||
|
@ -194,6 +196,10 @@ func (terminal *Terminal) AttachTitleChangeHandler(handler chan bool) {
|
||||||
terminal.titleHandlers = append(terminal.titleHandlers, handler)
|
terminal.titleHandlers = append(terminal.titleHandlers, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) AttachResizeHandler(handler chan bool) {
|
||||||
|
terminal.resizeHandlers = append(terminal.resizeHandlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) Modes() Modes {
|
func (terminal *Terminal) Modes() Modes {
|
||||||
return terminal.modes
|
return terminal.modes
|
||||||
}
|
}
|
||||||
|
@ -206,6 +212,14 @@ func (terminal *Terminal) emitTitleChange() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) emitResize() {
|
||||||
|
for _, h := range terminal.resizeHandlers {
|
||||||
|
go func(c chan bool) {
|
||||||
|
c <- true
|
||||||
|
}(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) GetLogicalCursorX() uint16 {
|
func (terminal *Terminal) GetLogicalCursorX() uint16 {
|
||||||
if terminal.ActiveBuffer().CursorColumn() >= terminal.ActiveBuffer().Width() {
|
if terminal.ActiveBuffer().CursorColumn() >= terminal.ActiveBuffer().Width() {
|
||||||
return 0
|
return 0
|
||||||
|
@ -237,6 +251,14 @@ func (terminal *Terminal) Write(data []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) WriteReturn() error {
|
||||||
|
if terminal.ActiveBuffer().IsNewLineMode() {
|
||||||
|
return terminal.Write([]byte{0x0d, 0x0a})
|
||||||
|
} else {
|
||||||
|
return terminal.Write([]byte{0x0d})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) Paste(data []byte) error {
|
func (terminal *Terminal) Paste(data []byte) error {
|
||||||
|
|
||||||
if terminal.bracketedPasteMode {
|
if terminal.bracketedPasteMode {
|
||||||
|
@ -281,14 +303,20 @@ func (terminal *Terminal) SetSize(newCols uint, newLines uint) error {
|
||||||
terminal.lock.Lock()
|
terminal.lock.Lock()
|
||||||
defer terminal.lock.Unlock()
|
defer terminal.lock.Unlock()
|
||||||
|
|
||||||
terminal.size.Width = uint16(newCols)
|
if terminal.size.Width == uint16(newCols) && terminal.size.Height == uint16(newLines) {
|
||||||
terminal.size.Height = uint16(newLines)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
err := terminal.pty.Resize(int(newCols), int(newLines))
|
err := terminal.pty.Resize(int(newCols), int(newLines))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to set terminal size vai ioctl: Error no %d", err)
|
return fmt.Errorf("Failed to set terminal size vai ioctl: Error no %d", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
terminal.size.Width = uint16(newCols)
|
||||||
|
terminal.size.Height = uint16(newLines)
|
||||||
|
|
||||||
terminal.ActiveBuffer().ResizeView(terminal.size.Width, terminal.size.Height)
|
terminal.ActiveBuffer().ResizeView(terminal.size.Width, terminal.size.Height)
|
||||||
|
|
||||||
|
terminal.emitResize()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 8.1 KiB |