* 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
This commit is contained in:
rrrooommmaaa 2019-01-24 16:03:47 +03:00 committed by Liam Galvin
parent 27424d40dc
commit 9c60167ca8
19 changed files with 375 additions and 111 deletions

View File

@ -23,6 +23,8 @@ type Buffer struct {
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
selectionStart *Position
@ -317,6 +319,15 @@ 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
}
@ -330,6 +341,11 @@ func (buffer *Buffer) SetVerticalMargins(top uint, bottom uint) {
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 {
return buffer.scrollLinesFromBottom
}
@ -419,11 +435,19 @@ func (buffer *Buffer) emitDisplayChange() {
// Column returns cursor column
func (buffer *Buffer) CursorColumn() uint16 {
// @todo originMode and left margin
return buffer.cursorX
}
// Line returns cursor line
func (buffer *Buffer) CursorLine() uint16 {
if buffer.originMode {
result := buffer.cursorY - uint16(buffer.topMargin)
if result < 0 {
result = 0
}
return result
}
return buffer.cursorY
}
@ -492,8 +516,8 @@ func (buffer *Buffer) insertLine() {
out := make([]Line, newLineCount)
copy(
out[ : pos - ( uint64(len(buffer.lines)) + 1 - newLineCount )],
buffer.lines[ uint64(len(buffer.lines)) + 1 - newLineCount : pos] )
out[:pos-(uint64(len(buffer.lines))+1-newLineCount)],
buffer.lines[uint64(len(buffer.lines))+1-newLineCount:pos])
out[pos] = newLine()
copy(out[pos+1:], buffer.lines[pos:])
buffer.lines = out
@ -655,13 +679,13 @@ func (buffer *Buffer) Write(runes ...rune) {
if buffer.autoWrap {
buffer.NewLine()
buffer.NewLineEx(true)
newLine := buffer.getCurrentLine()
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.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() {
if buffer.cursorX == 0 {
@ -703,6 +734,9 @@ func (buffer *Buffer) Backspace() {
} else {
//@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 {
buffer.MovePosition(-1, 0)
}
@ -727,15 +761,33 @@ func (buffer *Buffer) CarriageReturn() {
func (buffer *Buffer) Tab() {
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)
if shift > max {
shift = max
}
for i := 0; i < shift; i++ {
buffer.Write(' ')
}
}
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()
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) {
var toX uint16
var toY uint16
if int16(buffer.cursorX)+x < 0 {
if int16(buffer.CursorColumn())+x < 0 {
toX = 0
} 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
} else {
toY = uint16(int16(buffer.cursorY) + y)
toY = uint16(int16(buffer.CursorLine()) + y)
}
buffer.SetPosition(toX, toY)
@ -770,17 +835,26 @@ func (buffer *Buffer) MovePosition(x int16, y int16) {
func (buffer *Buffer) SetPosition(col uint16, line uint16) {
defer buffer.emitDisplayChange()
if col >= buffer.ViewWidth() {
col = buffer.ViewWidth() - 1
//logrus.Errorf("Cannot set cursor position: column %d is outside of the current view width (%d columns)", col, buffer.ViewWidth())
useCol := col
useLine := line
maxLine := buffer.ViewHeight() - 1
if buffer.originMode {
useLine += uint16(buffer.topMargin)
maxLine = uint16(buffer.bottomMargin)
// @todo left and right margins
}
if line >= buffer.ViewHeight() {
line = buffer.ViewHeight() - 1
//logrus.Errorf("Cannot set cursor position: line %d is outside of the current view height (%d lines)", line, buffer.ViewHeight())
if useLine > maxLine {
useLine = maxLine
}
buffer.cursorX = col
buffer.cursorY = line
if useCol >= buffer.ViewWidth() {
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 {
@ -928,7 +1002,7 @@ func (buffer *Buffer) EraseDisplayToCursor() {
defer buffer.emitDisplayChange()
line := buffer.getCurrentLine()
for i := 0; i < int(buffer.cursorX); i++ {
for i := 0; i <= int(buffer.cursorX); i++ {
if i >= len(line.cells) {
break
}
@ -1042,7 +1116,7 @@ func (buffer *Buffer) ResizeView(width uint16, height uint16) {
line = buffer.getCurrentLine()
buffer.cursorX = uint16((len(line.cells) - cXFromEndOfLine) - 1)
buffer.SetVerticalMargins(0, uint(buffer.viewHeight-1))
buffer.ResetVerticalMargins()
}
func (buffer *Buffer) getMaxLines() uint64 {

View File

@ -508,7 +508,7 @@ func TestEraseDisplayToCursor(t *testing.T) {
lines := b.GetVisibleLines()
assert.Equal(t, "", lines[0].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())
}
@ -634,4 +634,4 @@ func TestBufferMaxLines(t *testing.T) {
assert.Equal(t, 2, len(b.lines))
assert.Equal(t, "funny", b.lines[0].String())
assert.Equal(t, "world", b.lines[1].String())
}
}

View File

@ -2,6 +2,7 @@ package gui
import (
"fmt"
"math"
"image"
"image/png"
"os"
@ -28,8 +29,9 @@ type GUI struct {
logger *zap.SugaredLogger
config *config.Config
terminal *terminal.Terminal
width int //window width in pixels
height int //window height in pixels
width int //window width in pixels
height int //window height in pixels
resizeCache *ResizeCache // resize cache formed by resizeToTerminal()
dpiScale float32
fontMap *FontMap
fontScale float32
@ -59,6 +61,13 @@ func Max(x, y int) int {
return y
}
type ResizeCache struct {
Width int
Height int
Cols uint
Rows uint
}
func (g *GUI) GetMonitor() *glfw.Monitor {
if g.window == nil {
@ -78,8 +87,8 @@ func (g *GUI) GetMonitor() *glfw.Monitor {
for _, monitor := range monitors {
mode := monitor.GetVideoMode()
mx, my := monitor.GetPos()
overlap := Max(0, Min(x + w, mx + mode.Width) - Max(x, mx)) *
Max(0, Min(y + h, my + mode.Height) - Max(y, my))
overlap := Max(0, Min(x+w, mx+mode.Width)-Max(x, mx)) *
Max(0, Min(y+h, my+mode.Height)-Max(y, my))
if bestMatch < overlap {
bestMatch = overlap
currentMonitor = monitor
@ -153,6 +162,35 @@ func (gui *GUI) scale() float32 {
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
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.renderer.SetArea(0, 0, gui.Width(), gui.Height())
gui.logger.Debugf("Calculating size in cols/rows...")
cols, rows := gui.renderer.GetTermSize()
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)
if gui.resizeCache != nil && gui.resizeCache.Width == width && gui.resizeCache.Height == height {
gui.logger.Debugf("No need to resize internal terminal!")
} else {
gui.logger.Debugf("Calculating size in cols/rows...")
cols, rows := gui.renderer.GetTermSize()
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...")
gl.Viewport(0, 0, int32(gui.Width()), int32(gui.Height()))
@ -234,6 +277,7 @@ func (gui *GUI) Render() error {
}
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)
@ -283,6 +327,7 @@ func (gui *GUI) Render() error {
)
gui.terminal.AttachTitleChangeHandler(titleChan)
gui.terminal.AttachResizeHandler(resizeChan)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
@ -316,6 +361,9 @@ func (gui *GUI) Render() error {
select {
case <-titleChan:
gui.window.SetTitle(gui.terminal.GetTitle())
case <-resizeChan:
cols, rows := gui.terminal.GetSize()
gui.resizeToTerminal(uint(cols), uint(rows))
default:
// this is more efficient than glfw.PollEvents()
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")
}
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.Show()
window.Focus()

View File

@ -212,9 +212,7 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
0x09,
})
case glfw.KeyEnter:
gui.terminal.Write([]byte{
0x0d,
})
gui.terminal.WriteReturn()
case glfw.KeyKPEnter:
if gui.terminal.IsApplicationCursorKeysModeEnabled() {
gui.terminal.Write([]byte{
@ -223,9 +221,7 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
'M',
})
} else {
gui.terminal.Write([]byte{
0x0d,
})
gui.terminal.WriteReturn()
}
case glfw.KeyBackspace:
if modsPressed(mods, glfw.ModAlt) {

View File

@ -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)))
}
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 {
x := float32(float32(col) * r.cellWidth)
y := float32(float32(row) * r.cellHeight) + r.cellHeight

View File

@ -65,11 +65,33 @@ func TestCursorMovement(t *testing.T) {
if term.ActiveBuffer().Compare("vttest/test-cursor-movement-1") == false {
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)
sleep()
g.Screenshot ("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)
}

View File

@ -11,9 +11,11 @@ var ansiSequenceMap = map[rune]escapeSequenceHandler{
'7': saveCursorHandler,
'8': restoreCursorHandler,
'D': indexHandler,
'E': nextLineHandler, // NEL
'M': reverseIndexHandler,
'P': sixelHandler,
'c': risHandler, //RIS
'c': risHandler, //RIS
'#': screenStateHandler,
'(': 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))
}
func nextLineHandler(pty chan rune, terminal *Terminal) error {
terminal.ActiveBuffer().NewLineEx(true)
return nil
}

View File

@ -6,7 +6,7 @@ import (
"strings"
)
type csiSequenceHandler func(params []string, intermediate string, terminal *Terminal) error
type csiSequenceHandler func(params []string, terminal *Terminal) error
type csiMapping struct {
id rune
@ -56,19 +56,18 @@ type runeRange struct {
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
param = ""
intermediate = ""
intermediate = []rune{}
CSI:
for {
b = <-pty
switch true {
case b >= 0x30 && b <= 0x3F:
param = param + string(b)
case b >= 0x20 && b <= 0x2F:
//intermediate? useful?
intermediate += string(b)
case b > 0 && b <= 0x2F:
intermediate = append(intermediate, b)
case b >= csiTerminators.min && b <= csiTerminators.max:
final = b
break CSI
@ -89,6 +88,11 @@ func splitParams(paramString string) []string {
func csiHandler(pty chan rune, terminal *Terminal) error {
final, param, intermediate := loadCSI(pty)
// process intermediate control codes before the CSI
for _, b := range intermediate {
terminal.processRune(b)
}
params := splitParams(param)
for _, sequence := range csiSequences {
@ -97,16 +101,16 @@ func csiHandler(pty chan rune, terminal *Terminal) error {
continue
}
x, y := terminal.ActiveBuffer().CursorColumn(), terminal.ActiveBuffer().CursorLine()
err := sequence.handler(params, intermediate, 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())
err := sequence.handler(params, terminal)
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 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
// for DA1 we'll respond 1;2
@ -132,7 +136,7 @@ func csiSendDeviceAttributesHandler(params []string, intermediate string, termin
return nil
}
func csiDeviceStatusReportHandler(params []string, intermediate string, terminal *Terminal) error {
func csiDeviceStatusReportHandler(params []string, terminal *Terminal) error {
if len(params) == 0 {
return fmt.Errorf("Missing Device Status Report identifier")
@ -154,7 +158,7 @@ func csiDeviceStatusReportHandler(params []string, intermediate string, terminal
return nil
}
func csiCursorUpHandler(params []string, intermediate string, terminal *Terminal) error {
func csiCursorUpHandler(params []string, terminal *Terminal) error {
distance := 1
if len(params) > 0 {
var err error
@ -167,7 +171,7 @@ func csiCursorUpHandler(params []string, intermediate string, terminal *Terminal
return nil
}
func csiCursorDownHandler(params []string, intermediate string, terminal *Terminal) error {
func csiCursorDownHandler(params []string, terminal *Terminal) error {
distance := 1
if len(params) > 0 {
var err error
@ -181,7 +185,7 @@ func csiCursorDownHandler(params []string, intermediate string, terminal *Termin
return nil
}
func csiCursorForwardHandler(params []string, intermediate string, terminal *Terminal) error {
func csiCursorForwardHandler(params []string, terminal *Terminal) error {
distance := 1
if len(params) > 0 {
var err error
@ -195,7 +199,7 @@ func csiCursorForwardHandler(params []string, intermediate string, terminal *Ter
return nil
}
func csiCursorBackwardHandler(params []string, intermediate string, terminal *Terminal) error {
func csiCursorBackwardHandler(params []string, terminal *Terminal) error {
distance := 1
if len(params) > 0 {
var err error
@ -209,7 +213,7 @@ func csiCursorBackwardHandler(params []string, intermediate string, terminal *Te
return nil
}
func csiCursorNextLineHandler(params []string, intermediate string, terminal *Terminal) error {
func csiCursorNextLineHandler(params []string, terminal *Terminal) error {
distance := 1
if len(params) > 0 {
@ -225,7 +229,7 @@ func csiCursorNextLineHandler(params []string, intermediate string, terminal *Te
return nil
}
func csiCursorPrecedingLineHandler(params []string, intermediate string, terminal *Terminal) error {
func csiCursorPrecedingLineHandler(params []string, terminal *Terminal) error {
distance := 1
if len(params) > 0 {
@ -240,7 +244,7 @@ func csiCursorPrecedingLineHandler(params []string, intermediate string, termina
return nil
}
func csiCursorCharacterAbsoluteHandler(params []string, intermediate string, terminal *Terminal) error {
func csiCursorCharacterAbsoluteHandler(params []string, terminal *Terminal) error {
distance := 1
if len(params) > 0 {
var err error
@ -274,14 +278,14 @@ func parseCursorPosition(params []string) (x, y int) {
return x, y
}
func csiCursorPositionHandler(params []string, intermediate string, terminal *Terminal) error {
func csiCursorPositionHandler(params []string, terminal *Terminal) error {
x, y := parseCursorPosition(params)
terminal.ActiveBuffer().SetPosition(uint16(x-1), uint16(y-1))
return nil
}
func csiScrollUpHandler(params []string, intermediate string, terminal *Terminal) error {
func csiScrollUpHandler(params []string, terminal *Terminal) error {
distance := 1
if len(params) > 1 {
return fmt.Errorf("Not supported")
@ -298,7 +302,7 @@ func csiScrollUpHandler(params []string, intermediate string, terminal *Terminal
return nil
}
func csiInsertBlankCharactersHandler(params []string, intermediate string, terminal *Terminal) error {
func csiInsertBlankCharactersHandler(params []string, terminal *Terminal) error {
count := 1
if len(params) > 1 {
return fmt.Errorf("Not supported")
@ -316,7 +320,7 @@ func csiInsertBlankCharactersHandler(params []string, intermediate string, termi
return nil
}
func csiInsertLinesHandler(params []string, intermediate string, terminal *Terminal) error {
func csiInsertLinesHandler(params []string, terminal *Terminal) error {
count := 1
if len(params) > 1 {
return fmt.Errorf("Not supported")
@ -334,7 +338,7 @@ func csiInsertLinesHandler(params []string, intermediate string, terminal *Termi
return nil
}
func csiDeleteLinesHandler(params []string, intermediate string, terminal *Terminal) error {
func csiDeleteLinesHandler(params []string, terminal *Terminal) error {
count := 1
if len(params) > 1 {
return fmt.Errorf("Not supported")
@ -352,7 +356,7 @@ func csiDeleteLinesHandler(params []string, intermediate string, terminal *Termi
return nil
}
func csiScrollDownHandler(params []string, intermediate string, terminal *Terminal) error {
func csiScrollDownHandler(params []string, terminal *Terminal) error {
distance := 1
if len(params) > 1 {
return fmt.Errorf("Not supported")
@ -370,7 +374,7 @@ func csiScrollDownHandler(params []string, intermediate string, terminal *Termin
}
// DECSTBM
func csiSetMarginsHandler(params []string, intermediate string, terminal *Terminal) error {
func csiSetMarginsHandler(params []string, terminal *Terminal) error {
top := 1
bottom := int(terminal.ActiveBuffer().ViewHeight())
@ -402,7 +406,7 @@ func csiSetMarginsHandler(params []string, intermediate string, terminal *Termin
return nil
}
func csiEraseCharactersHandler(params []string, intermediate string, terminal *Terminal) error {
func csiEraseCharactersHandler(params []string, terminal *Terminal) error {
count := 1
if len(params) > 0 {
var err error
@ -417,19 +421,19 @@ func csiEraseCharactersHandler(params []string, intermediate string, terminal *T
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)
}
func csiSetModeHandler(params []string, intermediate string, terminal *Terminal) error {
func csiSetModeHandler(params []string, terminal *Terminal) error {
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")
}
func csiLinePositionAbsolute(params []string, intermediate string, terminal *Terminal) error {
func csiLinePositionAbsolute(params []string, terminal *Terminal) error {
row := 1
if len(params) > 0 {
var err error
@ -444,7 +448,7 @@ func csiLinePositionAbsolute(params []string, intermediate string, terminal *Ter
return nil
}
func csiDeleteHandler(params []string, intermediate string, terminal *Terminal) error {
func csiDeleteHandler(params []string, terminal *Terminal) error {
n := 1
if len(params) >= 1 {
var err error
@ -459,7 +463,7 @@ func csiDeleteHandler(params []string, intermediate string, terminal *Terminal)
}
// CSI Ps J
func csiEraseInDisplayHandler(params []string, intermediate string, terminal *Terminal) error {
func csiEraseInDisplayHandler(params []string, terminal *Terminal) error {
n := "0"
if len(params) > 0 {
n = params[0]
@ -481,7 +485,7 @@ func csiEraseInDisplayHandler(params []string, intermediate string, terminal *Te
}
// CSI Ps K
func csiEraseInLineHandler(params []string, intermediate string, terminal *Terminal) error {
func csiEraseInLineHandler(params []string, terminal *Terminal) error {
n := "0"
if len(params) > 0 {

View File

@ -29,8 +29,39 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error {
} else {
terminal.ActiveBuffer().SetReplaceMode()
}
case "20":
if enabled {
terminal.ActiveBuffer().SetNewLineMode()
} else {
terminal.ActiveBuffer().SetLineFeedMode()
}
case "?1":
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":
// auto-wrap mode
//DECAWM
@ -78,7 +109,13 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error {
case "?2004":
terminal.SetBracketedPasteMode(enabled)
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

View File

@ -8,66 +8,81 @@ import (
type TerminalCharSet int
// single rune handler
type runeHandler func(terminal *Terminal) error
type escapeSequenceHandler func(pty chan rune, terminal *Terminal) error
var escapeSequenceMap = map[rune]escapeSequenceHandler{
0x05: enqSequenceHandler,
0x07: bellSequenceHandler,
0x08: backspaceSequenceHandler,
0x09: tabSequenceHandler,
0x0a: newLineSequenceHandler,
0x0b: newLineSequenceHandler,
0x0c: newLineSequenceHandler,
0x0d: carriageReturnSequenceHandler,
0x0e: shiftOutSequenceHandler,
0x0f: shiftInSequenceHandler,
0x1b: ansiHandler,
var runeMap = map[rune]runeHandler{
0x05: enqHandler,
0x07: bellHandler,
0x08: backspaceHandler,
0x09: tabHandler,
0x0a: newLineHandler,
0x0b: newLineHandler,
0x0c: newLineHandler,
0x0d: carriageReturnHandler,
0x0e: shiftOutHandler,
0x0f: shiftInHandler,
}
func newLineSequenceHandler(pty chan rune, terminal *Terminal) error {
func newLineHandler(terminal *Terminal) error {
terminal.ActiveBuffer().NewLine()
terminal.isDirty = true
return nil
}
func tabSequenceHandler(pty chan rune, terminal *Terminal) error {
func tabHandler(terminal *Terminal) error {
terminal.ActiveBuffer().Tab()
terminal.isDirty = true
return nil
}
func carriageReturnSequenceHandler(pty chan rune, terminal *Terminal) error {
func carriageReturnHandler(terminal *Terminal) error {
terminal.ActiveBuffer().CarriageReturn()
terminal.isDirty = true
return nil
}
func backspaceSequenceHandler(pty chan rune, terminal *Terminal) error {
func backspaceHandler(terminal *Terminal) error {
terminal.ActiveBuffer().Backspace()
terminal.isDirty = true
return nil
}
func bellSequenceHandler(pty chan rune, terminal *Terminal) error {
func bellHandler(terminal *Terminal) error {
// @todo ring bell - flash red or some shit?
return nil
}
func enqSequenceHandler(pty chan rune, terminal *Terminal) error {
func enqHandler(terminal *Terminal) error {
terminal.logger.Errorf("Received ENQ!")
return nil
}
func shiftOutSequenceHandler(pty chan rune, terminal *Terminal) error {
func shiftOutHandler(terminal *Terminal) error {
terminal.logger.Errorf("Received shift out")
return nil
}
func shiftInSequenceHandler(pty chan rune, terminal *Terminal) error {
func shiftInHandler(terminal *Terminal) error {
terminal.logger.Errorf("Received shift in")
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) {
// https://en.wikipedia.org/wiki/ANSI_escape_code
@ -82,19 +97,15 @@ func (terminal *Terminal) processInput(pty chan rune) {
b = <-pty
if b < 0x20 {
if handler, ok := escapeSequenceMap[b]; ok {
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
if err := handler(pty, terminal); err != nil {
terminal.logger.Errorf("Error handling escape sequence: %s", err)
}
terminal.isDirty = true
continue
if b == 0x1b {
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
if err := ansiHandler(pty, terminal); err != nil {
terminal.logger.Errorf("Error handling escape sequence: %s", err)
}
terminal.isDirty = true
continue
}
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
terminal.ActiveBuffer().Write(b)
terminal.isDirty = true
terminal.processRune(b)
}
}

30
terminal/scr_state.go Normal file
View File

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

View File

@ -9,7 +9,7 @@ import (
"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 {
params = []string{"0"}
@ -137,7 +137,7 @@ func sgrSequenceHandler(params []string, intermediate string, terminal *Terminal
terminal.ActiveBuffer().CursorAttr().BgColour = c
return nil
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:])
}
}

View File

@ -3,12 +3,13 @@ package terminal
import (
"bufio"
"fmt"
"io"
"sync"
"github.com/liamg/aminal/buffer"
"github.com/liamg/aminal/config"
"github.com/liamg/aminal/platform"
"go.uber.org/zap"
"io"
"sync"
)
const (
@ -39,6 +40,7 @@ type Terminal struct {
size Winsize
config *config.Config
titleHandlers []chan bool
resizeHandlers []chan bool
modes Modes
mouseMode MouseMode
bracketedPasteMode bool
@ -194,6 +196,10 @@ func (terminal *Terminal) AttachTitleChangeHandler(handler chan bool) {
terminal.titleHandlers = append(terminal.titleHandlers, handler)
}
func (terminal *Terminal) AttachResizeHandler(handler chan bool) {
terminal.resizeHandlers = append(terminal.resizeHandlers, handler)
}
func (terminal *Terminal) Modes() 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 {
if terminal.ActiveBuffer().CursorColumn() >= terminal.ActiveBuffer().Width() {
return 0
@ -237,6 +251,14 @@ func (terminal *Terminal) Write(data []byte) error {
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 {
if terminal.bracketedPasteMode {
@ -281,14 +303,20 @@ func (terminal *Terminal) SetSize(newCols uint, newLines uint) error {
terminal.lock.Lock()
defer terminal.lock.Unlock()
terminal.size.Width = uint16(newCols)
terminal.size.Height = uint16(newLines)
if terminal.size.Width == uint16(newCols) && terminal.size.Height == uint16(newLines) {
return nil
}
err := terminal.pty.Resize(int(newCols), int(newLines))
if err != nil {
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.emitResize()
return nil
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB