From d2214b791528c39c4ef196a0bf1906091ba290de Mon Sep 17 00:00:00 2001 From: rrrooommmaaa Date: Sun, 27 Jan 2019 14:56:23 +0300 Subject: [PATCH] added Tab Stops support (#175) --- buffer/buffer.go | 19 ++++------------ buffer/buffer_test.go | 4 ++-- buffer/terminal_state.go | 47 ++++++++++++++++++++++++++++++++++++++++ terminal/ansi.go | 6 +++++ terminal/csi.go | 19 ++++++++++++++++ 5 files changed, 78 insertions(+), 17 deletions(-) diff --git a/buffer/buffer.go b/buffer/buffer.go index a0061f05..25557432 100644 --- a/buffer/buffer.go +++ b/buffer/buffer.go @@ -717,22 +717,11 @@ func (buffer *Buffer) CarriageReturn() { } func (buffer *Buffer) Tab() { - tabSize := 4 - max := tabSize - - // @todo rightMargin - if buffer.terminalState.cursorX < buffer.terminalState.viewWidth { - max = int(buffer.terminalState.viewWidth - buffer.terminalState.cursorX - 1) - } - - shift := tabSize - (int(buffer.terminalState.cursorX+1) % tabSize) - - if shift > max { - shift = max - } - - for i := 0; i < shift; i++ { + for buffer.terminalState.cursorX < buffer.terminalState.viewWidth-1 { // @todo rightMargin buffer.Write(' ') + if buffer.terminalState.IsTabSetAtCursor() { + break + } } } diff --git a/buffer/buffer_test.go b/buffer/buffer_test.go index 993bdf92..415b698c 100644 --- a/buffer/buffer_test.go +++ b/buffer/buffer_test.go @@ -26,8 +26,8 @@ func TestTabbing(t *testing.T) { b.CarriageReturn() b.NewLine() expected := ` -hello x goodbye -hell xxx good +hello x goodbye +hell xxx good ` lines := b.GetVisibleLines() diff --git a/buffer/terminal_state.go b/buffer/terminal_state.go index 97088ce1..f30ceb3d 100644 --- a/buffer/terminal_state.go +++ b/buffer/terminal_state.go @@ -15,6 +15,7 @@ type TerminalState struct { LineFeedMode bool AutoWrap bool maxLines uint64 + tabStops map[uint16]struct{} } // NewTerminalMode creates a new terminal state @@ -31,6 +32,7 @@ func NewTerminalState(viewCols uint16, viewLines uint16, attr CellAttributes, ma topMargin: 0, bottomMargin: uint(viewLines - 1), } + b.TabReset() return b } @@ -47,3 +49,48 @@ func (terminalState *TerminalState) ResetVerticalMargins() { func (terminalState *TerminalState) IsNewLineMode() bool { return terminalState.LineFeedMode == false } + +func (terminalState *TerminalState) TabZonk() { + terminalState.tabStops = make(map[uint16]struct{}) +} + +func (terminalState *TerminalState) TabSet(index uint16) { + terminalState.tabStops[index] = struct{}{} +} + +func (terminalState *TerminalState) TabClear(index uint16) { + delete(terminalState.tabStops, index) +} + +func (terminalState *TerminalState) getTabIndexFromCursor() uint16 { + index := terminalState.cursorX + if index == terminalState.viewWidth { + index = 0 + } + return index +} + +func (terminalState *TerminalState) IsTabSetAtCursor() bool { + index := terminalState.getTabIndexFromCursor() + _, ok := terminalState.tabStops[index] + return ok +} + +func (terminalState *TerminalState) TabClearAtCursor() { + terminalState.TabClear(terminalState.getTabIndexFromCursor()) +} + +func (terminalState *TerminalState) TabSetAtCursor() { + terminalState.TabSet(terminalState.getTabIndexFromCursor()) +} + +func (terminalState *TerminalState) TabReset() { + terminalState.TabZonk() + const MaxTabs uint16 = 1024 + const TabStep = 4 + var i uint16 + for i < MaxTabs { + terminalState.TabSet(i) + i += TabStep + } +} diff --git a/terminal/ansi.go b/terminal/ansi.go index e56ddb17..170b1c91 100644 --- a/terminal/ansi.go +++ b/terminal/ansi.go @@ -12,6 +12,7 @@ var ansiSequenceMap = map[rune]escapeSequenceHandler{ '8': restoreCursorHandler, 'D': indexHandler, 'E': nextLineHandler, // NEL + 'H': tabSetHandler, // HTS 'M': reverseIndexHandler, 'P': sixelHandler, 'c': risHandler, //RIS @@ -75,3 +76,8 @@ func nextLineHandler(pty chan rune, terminal *Terminal) error { terminal.ActiveBuffer().NewLineEx(true) return nil } + +func tabSetHandler(pty chan rune, terminal *Terminal) error { + terminal.terminalState.TabSetAtCursor() + return nil +} diff --git a/terminal/csi.go b/terminal/csi.go index dc199824..f0e69a43 100644 --- a/terminal/csi.go +++ b/terminal/csi.go @@ -24,6 +24,7 @@ var csiSequences = []csiMapping{ {id: 'c', handler: csiSendDeviceAttributesHandler, description: " Send Device Attributes (Primary/Secondary/Tertiary DA)"}, {id: 'd', handler: csiLinePositionAbsolute, expectedParams: &expectedParams{min: 0, max: 1}, description: "Line Position Absolute [row] (default = [1,column]) (VPA)"}, {id: 'f', handler: csiCursorPositionHandler, description: "Horizontal and Vertical Position [row;column] (default = [1,1]) (HVP)"}, + {id: 'g', handler: csiTabClearHandler, description: "Tab Clear (TBC)"}, {id: 'h', handler: csiSetModeHandler, expectedParams: &expectedParams{min: 1, max: 1}, description: "Set Mode (SM)"}, {id: 'l', handler: csiResetModeHandler, expectedParams: &expectedParams{min: 1, max: 1}, description: "Reset Mode (RM)"}, {id: 'm', handler: sgrSequenceHandler, description: "Character Attributes (SGR)"}, @@ -463,6 +464,24 @@ func csiDeleteHandler(params []string, terminal *Terminal) error { return nil } +func csiTabClearHandler(params []string, terminal *Terminal) error { + n := "0" + if len(params) > 0 { + n = params[0] + } + switch n { + + case "0", "": + terminal.terminalState.TabClearAtCursor() + case "3": + terminal.terminalState.TabZonk() + default: + return fmt.Errorf("Ignored TBC: CSI %s g", n) + } + + return nil +} + // CSI Ps J func csiEraseInDisplayHandler(params []string, terminal *Terminal) error { n := "0"