diff --git a/buffer/buffer.go b/buffer/buffer.go index 7bad518..7f5085e 100644 --- a/buffer/buffer.go +++ b/buffer/buffer.go @@ -199,6 +199,44 @@ func (buffer *Buffer) ViewHeight() uint16 { return buffer.viewHeight } +func (buffer *Buffer) insertLine() { + if !buffer.InScrollableRegion() { + pos := buffer.RawLine() + out := make([]Line, len(buffer.lines)+1) + copy(out[:pos], buffer.lines[:pos]) + out[pos] = newLine() + copy(out[pos+1:], buffer.lines[pos:]) + buffer.lines = out + } else { + topIndex := buffer.convertViewLineToRawLine(uint16(buffer.topMargin)) + bottomIndex := buffer.convertViewLineToRawLine(uint16(buffer.bottomMargin)) + before := buffer.lines[:topIndex] + after := buffer.lines[bottomIndex+1:] + scrollable := buffer.lines[topIndex : bottomIndex+1] + out := make([]Line, len(buffer.lines)) + copy(out[0:], before) + copy(out[topIndex+1:], scrollable[0:len(scrollable)-1]) + copy(out[bottomIndex:], after) + out[topIndex] = newLine() + buffer.lines = out + } +} + +func (buffer *Buffer) InsertLines(count int) { + + if buffer.HasScrollableRegion() && !buffer.InScrollableRegion() { + // should have no effect outside of scrollable region + return + } + + buffer.cursorX = 0 + + for i := 0; i < count; i++ { + buffer.insertLine() + } + +} + func (buffer *Buffer) Index() { // This sequence causes the active position to move downward one line without changing the column position. @@ -208,12 +246,12 @@ func (buffer *Buffer) Index() { if uint(buffer.cursorY) < buffer.bottomMargin { buffer.cursorY++ - return } - for i := buffer.topMargin; i < uint(buffer.cursorY); i++ { + for i := buffer.topMargin; i < uint(buffer.bottomMargin); i++ { buffer.lines[i] = buffer.lines[i+1] } + buffer.lines[buffer.cursorY] = newLine() return @@ -231,19 +269,18 @@ func (buffer *Buffer) ReverseIndex() { if uint(buffer.cursorY) > buffer.topMargin { buffer.cursorY-- - return } - for i := buffer.bottomMargin; i > uint(buffer.cursorY); i-- { + for i := buffer.bottomMargin; i > uint(buffer.topMargin); i-- { buffer.lines[i] = buffer.lines[i-1] } + buffer.lines[buffer.cursorY] = newLine() return } if buffer.cursorY > 0 { - buffer.cursorY-- } } @@ -325,7 +362,7 @@ func (buffer *Buffer) incrementCursorPosition() { buffer.cursorX++ } else { - panic("cursor position invalid") + fmt.Println("cursor position invalid") } } @@ -352,7 +389,6 @@ func (buffer *Buffer) NewLine() { defer buffer.emitDisplayChange() buffer.cursorX = 0 - buffer.Index() } @@ -459,7 +495,9 @@ func (buffer *Buffer) EraseLineFromCursor() { defer buffer.emitDisplayChange() line := buffer.getCurrentLine() - line.cells = line.cells[:buffer.cursorX] + if len(line.cells) > 0 { + line.cells = line.cells[:buffer.cursorX] + } max := int(buffer.ViewWidth()) - len(line.cells) diff --git a/demo/CSI-L.sh b/demo/CSI-L.sh new file mode 100755 index 0000000..22dfc3c --- /dev/null +++ b/demo/CSI-L.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +echo "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +echo "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" +echo "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" +echo "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" +echo "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" + +echo -ne "\x1b[A" # up +echo -ne "\x1b[A" # up +echo -ne "\x1b[C" # right +echo -ne "\x1b[C" # rightt +sleep 2 +echo -ne "\x1b[2L" # insert 2 lines +sleep 2 +echo -ne "\x1b[B" # down +echo -ne "\x1b[B" # down +sleep 2 \ No newline at end of file diff --git a/terminal/ansi.go b/terminal/ansi.go index 65b78da..0fc7542 100644 --- a/terminal/ansi.go +++ b/terminal/ansi.go @@ -12,7 +12,7 @@ var ansiSequenceMap = map[rune]escapeSequenceHandler{ '8': restoreCursorHandler, 'D': indexHandler, 'M': reverseIndexHandler, - 'c': swallowHandler(0), //RIS + 'c': risHandler, //RIS '(': swallowHandler(1), // character set bullshit ')': swallowHandler(1), // character set bullshit '*': swallowHandler(1), // character set bullshit @@ -30,6 +30,11 @@ func swallowHandler(n int) func(pty chan rune, terminal *Terminal) error { } } +func risHandler(pty chan rune, terminal *Terminal) error { + terminal.ActiveBuffer().Clear() + return nil +} + func indexHandler(pty chan rune, terminal *Terminal) error { terminal.ActiveBuffer().Index() return nil diff --git a/terminal/csi.go b/terminal/csi.go index b4b6412..b122c5f 100644 --- a/terminal/csi.go +++ b/terminal/csi.go @@ -21,19 +21,15 @@ type expectedParams struct { } var csiSequences = []csiMapping{ + csiMapping{id: 'c', handler: csiSendDeviceAttributesHandler, description: " Send Device Attributes (Primary/Secondary/Tertiary DA)"}, csiMapping{id: 'd', handler: csiLinePositionAbsolute, expectedParams: &expectedParams{min: 0, max: 1}, description: "Line Position Absolute [row] (default = [1,column]) (VPA)"}, + csiMapping{id: 'f', handler: csiCursorPositionHandler, description: "Horizontal and Vertical Position [row;column] (default = [1,1]) (HVP)"}, csiMapping{id: 'h', handler: csiSetModeHandler, expectedParams: &expectedParams{min: 1, max: 1}, description: "Set Mode (SM)"}, csiMapping{id: 'l', handler: csiResetModeHandler, expectedParams: &expectedParams{min: 1, max: 1}, description: "Reset Mode (RM)"}, csiMapping{id: 'm', handler: sgrSequenceHandler, description: "Character Attributes (SGR)"}, + csiMapping{id: 'n', handler: csiDeviceStatusReportHandler, description: "Device Status Report (DSR)"}, csiMapping{id: 'r', handler: csiSetMarginsHandler, expectedParams: &expectedParams{min: 2, max: 2}, description: "Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM), VT100"}, csiMapping{id: 't', handler: csiWindowManipulation, description: "Window manipulation"}, - csiMapping{id: 'J', handler: csiEraseInDisplayHandler, description: "Erase in Display (ED), VT100"}, - csiMapping{id: 'K', handler: csiEraseInLineHandler, description: "Erase in Line (EL), VT100"}, - csiMapping{id: 'L', handler: csiInsertLinesHandler, description: "Insert Ps Line(s) (default = 1) (IL)"}, - csiMapping{id: 'P', handler: csiDeleteHandler, description: " Delete Ps Character(s) (default = 1) (DCH)"}, - csiMapping{id: 'S', handler: csiScrollUpHandler, description: "Scroll up Ps lines (default = 1) (SU), VT420, ECMA-48"}, - csiMapping{id: 'T', handler: csiScrollDownHandler, description: "Scroll down Ps lines (default = 1) (SD), VT420"}, - csiMapping{id: 'X', handler: csiEraseCharactersHandler, description: "Erase Ps Character(s) (default = 1) (ECH"}, csiMapping{id: 'A', handler: csiCursorUpHandler, description: "Cursor Up Ps Times (default = 1) (CUU)"}, csiMapping{id: 'B', handler: csiCursorDownHandler, description: "Cursor Down Ps Times (default = 1) (CUD)"}, csiMapping{id: 'C', handler: csiCursorForwardHandler, description: "Cursor Forward Ps Times (default = 1) (CUF)"}, @@ -42,7 +38,13 @@ var csiSequences = []csiMapping{ csiMapping{id: 'F', handler: csiCursorPrecedingLineHandler, description: "Cursor Preceding Line Ps Times (default = 1) (CPL)"}, csiMapping{id: 'G', handler: csiCursorCharacterAbsoluteHandler, description: "Cursor Character Absolute [column] (default = [row,1]) (CHA)"}, csiMapping{id: 'H', handler: csiCursorPositionHandler, description: "Cursor Position [row;column] (default = [1,1]) (CUP)"}, - csiMapping{id: 'f', handler: csiCursorPositionHandler, description: "Horizontal and Vertical Position [row;column] (default = [1,1]) (HVP)"}, + csiMapping{id: 'J', handler: csiEraseInDisplayHandler, description: "Erase in Display (ED), VT100"}, + csiMapping{id: 'K', handler: csiEraseInLineHandler, description: "Erase in Line (EL), VT100"}, + csiMapping{id: 'L', handler: csiInsertLinesHandler, description: "Insert Ps Line(s) (default = 1) (IL)"}, + csiMapping{id: 'P', handler: csiDeleteHandler, description: " Delete Ps Character(s) (default = 1) (DCH)"}, + csiMapping{id: 'S', handler: csiScrollUpHandler, description: "Scroll up Ps lines (default = 1) (SU), VT420, ECMA-48"}, + csiMapping{id: 'T', handler: csiScrollDownHandler, description: "Scroll down Ps lines (default = 1) (SD), VT420"}, + csiMapping{id: 'X', handler: csiEraseCharactersHandler, description: "Erase Ps Character(s) (default = 1) (ECH"}, } func csiHandler(pty chan rune, terminal *Terminal) error { @@ -77,13 +79,43 @@ CSI: } terminal.logger.Debugf("CSI 0x%02X (ESC[%s%s%s) %s", final, param, intermediate, string(final), sequence.description) err := sequence.handler(params, intermediate, terminal) - terminal.logger.Debugf("After CSI, state: Col %d, Line %d, Top: %d, Bottom %d", terminal.ActiveBuffer().CursorColumn(), terminal.ActiveBuffer().CursorLine(), terminal.ActiveBuffer().TopMargin(), terminal.ActiveBuffer().BottomMargin()) return err } } return fmt.Errorf("Unknown CSI control sequence: 0x%02X (ESC[%s%s%s)", final, param, intermediate, string(final)) +} +func csiSendDeviceAttributesHandler(params []string, intermediate string, terminal *Terminal) error { + + if len(params) > 0 && len(params[0]) > 0 && params[0][0] == '>' { // secondary + _ = terminal.Write([]byte("\x1b[0;0;0c")) // report VT100 + return nil + } + + return fmt.Errorf("Unsupported SDA identifier") +} + +func csiDeviceStatusReportHandler(params []string, intermediate string, terminal *Terminal) error { + + if len(params) == 0 { + return fmt.Errorf("Missing Device Status Report identifier") + } + + switch params[0] { + case "5": + _ = terminal.Write([]byte("\x1b[0n")) // everything is cool + case "6": // report cursor position + _ = terminal.Write([]byte(fmt.Sprintf( + "\x1b[%d;%dR", + terminal.ActiveBuffer().CursorLine()+1, + terminal.ActiveBuffer().CursorColumn()+1, + ))) + default: + return fmt.Errorf("Unknown Device Status Report identifier: %s", params[0]) + } + + return nil } func csiCursorUpHandler(params []string, intermediate string, terminal *Terminal) error { @@ -237,10 +269,9 @@ func csiInsertLinesHandler(params []string, intermediate string, terminal *Termi count = 1 } } - terminal.logger.Debugf("Inserting %d lines", count) - for i := 0; i < count; i++ { - terminal.ActiveBuffer().Index() - } + + terminal.ActiveBuffer().InsertLines(count) + return nil } diff --git a/terminal/modes.go b/terminal/modes.go index 099163f..1b5dd09 100644 --- a/terminal/modes.go +++ b/terminal/modes.go @@ -75,6 +75,10 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error { } else { terminal.UseMainBuffer() } + case "?2004": + if enabled { + return fmt.Errorf("Bracketed paste mode is not yet supported") + } default: return fmt.Errorf("Unsupported CSI %sl code", modeStr) } diff --git a/terminal/osc.go b/terminal/osc.go index 81cb475..0a0b44e 100644 --- a/terminal/osc.go +++ b/terminal/osc.go @@ -39,6 +39,18 @@ func oscHandler(pty chan rune, terminal *Terminal) error { switch pS[0] { case "0", "2": terminal.SetTitle(pT) + case "10": // get/set foreground colour + if len(pS) > 1 { + if pS[1] == "?" { + terminal.Write([]byte("\x1b]10;15")) + } + } + case "11": // get/set background colour + if len(pS) > 1 { + if pS[1] == "?" { + terminal.Write([]byte("\x1b]10;0")) + } + } default: return fmt.Errorf("Unknown OSC control sequence: %s", strings.Join(params, ";")) } diff --git a/terminal/output.go b/terminal/output.go index e605362..99b6064 100644 --- a/terminal/output.go +++ b/terminal/output.go @@ -98,6 +98,7 @@ func (terminal *Terminal) processInput(ctx context.Context, pty chan rune) { } else { //terminal.logger.Debugf("Received character 0x%X: %q", b, string(b)) if b >= 0x20 { + terminal.logger.Debugf("%c", b) terminal.ActiveBuffer().Write(b) } else { terminal.logger.Error("Non-readable rune received: 0x%X", b)