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
|
||||
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 {
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
70
gui/gui.go
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
22
main_test.go
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
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:])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
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 |