From a357df65b45b51d337f95982aeb30b507bfd0a3e Mon Sep 17 00:00:00 2001 From: Liam Galvin Date: Sat, 4 Aug 2018 12:19:31 +0100 Subject: [PATCH] todying --- gui/cell.go | 2 +- gui/gui.go | 4 +- terminal/escapes.go | 532 +++++-------------------------------------- terminal/terminal.go | 12 +- 4 files changed, 69 insertions(+), 481 deletions(-) diff --git a/gui/cell.go b/gui/cell.go index b46cc27..74b3a63 100644 --- a/gui/cell.go +++ b/gui/cell.go @@ -118,7 +118,7 @@ func (cell *Cell) DrawBg() { } func (cell *Cell) DrawText() { - if cell.hidden { + if cell.hidden || cell.r == ' ' { return } if cell.font != nil { diff --git a/gui/gui.go b/gui/gui.go index 4a2a5b9..80375f8 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -283,8 +283,8 @@ func (gui *GUI) Render() error { } } - gui.font.SetColor(1.0, 0.0, 0.0, 1.0) //r,g,b,a font color - gui.font.Printf(0, 0, 1, "TEST") //x,y,scale,string,printf args + gui.font.SetColor(1, 0.2, 0.2, 0.5) + gui.font.Printf(50, 50, 2, "Position=%#v", gui.terminal.GetPosition()) } diff --git a/terminal/escapes.go b/terminal/escapes.go index f41f1dc..c5c4a19 100644 --- a/terminal/escapes.go +++ b/terminal/escapes.go @@ -1,15 +1,21 @@ package terminal import ( - "strconv" - "strings" + "context" + "fmt" ) // Wish list here: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html type TerminalCharSet int -func (terminal *Terminal) processInput(buffer chan rune) { +type escapeSequenceHandler func(buffer chan rune, terminal *Terminal) error + +var escapeSequenceMap = map[rune]escapeSequenceHandler{ + 0x1b: ansiHandler, +} + +func (terminal *Terminal) processInput(ctx context.Context, buffer chan rune) { // https://en.wikipedia.org/wiki/ANSI_escape_code @@ -17,492 +23,64 @@ func (terminal *Terminal) processInput(buffer chan rune) { for { + select { + case <-ctx.Done(): + break + default: + } + b := <-buffer - if b == 0x1b { // if the byte is an escape character, read the next byte to determine which one - b = <-buffer - switch b { - case '[': // CSI: Control Sequence Introducer [ - var final rune - param := "" - intermediate := "" - CSI: - for { - b = <-buffer - switch true { - case b >= 0x30 && b <= 0x3F: - param = param + string(b) - case b >= 0x20 && b <= 0x2F: - //intermediate? useful? - intermediate += string(b) - case b >= 0x40 && b <= 0x7e: - final = b - break CSI - } - } + handler, ok := escapeSequenceMap[b] - params := strings.Split(param, ";") - - switch final { - case 'A': - distance := 1 - if len(params) > 0 { - var err error - distance, err = strconv.Atoi(params[0]) - if err != nil { - distance = 1 - } - } - if terminal.position.Line-distance >= 0 { - terminal.position.Line -= distance - } else { - terminal.position.Line = 0 - } - case 'B': - distance := 1 - if len(params) > 0 { - var err error - distance, err = strconv.Atoi(params[0]) - if err != nil { - distance = 1 - } - } - - _, h := terminal.GetSize() - if terminal.position.Line+distance >= h { - terminal.position.Line = h - 1 - } else { - terminal.position.Line += distance - } - case 'C': - - distance := 1 - if len(params) > 0 { - var err error - distance, err = strconv.Atoi(params[0]) - if err != nil { - distance = 1 - } - } - - terminal.position.Col += distance - w, _ := terminal.GetSize() - if terminal.position.Col >= w { - terminal.position.Col = w - 1 - } - - case 'D': - - distance := 1 - if len(params) > 0 { - var err error - distance, err = strconv.Atoi(params[0]) - if err != nil { - distance = 1 - } - } - - terminal.position.Col -= distance - if terminal.position.Col < 0 { - terminal.position.Col = 0 - } - - case 'E': - distance := 1 - if len(params) > 0 { - var err error - distance, err = strconv.Atoi(params[0]) - if err != nil { - distance = 1 - } - } - - terminal.position.Line += distance - terminal.position.Col = 0 - - case 'F': - - distance := 1 - if len(params) > 0 { - var err error - distance, err = strconv.Atoi(params[0]) - if err != nil { - distance = 1 - } - } - if terminal.position.Line-distance >= 0 { - terminal.position.Line -= distance - } else { - terminal.position.Line = 0 - } - terminal.position.Col = 0 - - case 'G': - - distance := 1 - if len(params) > 0 { - var err error - distance, err = strconv.Atoi(params[0]) - if err != nil || params[0] == "" { - distance = 1 - } - } - - terminal.position.Col = distance - 1 // 1 based to 0 based - - case 'H', 'f': - - x, y := 1, 1 - if len(params) == 2 { - var err error - if params[0] != "" { - y, err = strconv.Atoi(string(params[0])) - if err != nil { - y = 1 - } - } - if params[1] != "" { - x, err = strconv.Atoi(string(params[1])) - if err != nil { - x = 1 - } - } - } - terminal.position.Col = x - 1 - terminal.position.Line = y - 1 - - case 'J': - - n := "0" - if len(params) > 0 { - n = params[0] - } - - switch n { - - case "0", "": - line := terminal.getBufferedLine(terminal.position.Line) - if line != nil { - line.Cells = line.Cells[:terminal.position.Col] - } - _, h := terminal.GetSize() - for i := terminal.position.Line + 1; i < h; i++ { - line := terminal.getBufferedLine(i) - if line != nil { - line.Cells = []Cell{} - } - } - case "1": - line := terminal.getBufferedLine(terminal.position.Line) - if line != nil { - for i := 0; i <= terminal.position.Col; i++ { - if i < len(line.Cells) { - line.Cells[i].r = 0 - } - } - } - for i := 0; i < terminal.position.Line; i++ { - line := terminal.getBufferedLine(i) - if line != nil { - line.Cells = []Cell{} - } - } - - case "2": - _, h := terminal.GetSize() - for i := 0; i < h; i++ { - line := terminal.getBufferedLine(i) - if line != nil { - line.Cells = []Cell{} - } - } - case "3": - terminal.lines = []Line{} - - default: - terminal.logger.Errorf("Unknown CSI ED sequence: %s", n) - } - - case 'K': // K - EOL - Erase to end of line - n := "0" - if len(params) > 0 { - n = params[0] - } - - switch n { - case "0", "": - line := terminal.getBufferedLine(terminal.position.Line) - if line != nil { - line.Cells = line.Cells[:terminal.position.Col] - } - case "1": - line := terminal.getBufferedLine(terminal.position.Line) - if line != nil { - for i := 0; i <= terminal.position.Col; i++ { - if i < len(line.Cells) { - line.Cells[i].r = 0 - } - } - } - case "2": - line := terminal.getBufferedLine(terminal.position.Line) - if line != nil { - line.Cells = []Cell{} - } - default: - terminal.logger.Errorf("Unsupported EL: %s", n) - } - - case 'm': - // SGR: colour and shit - for i := range params { - param := params[i] - switch param { - case "00", "0", "": - terminal.cellAttr = terminal.defaultCellAttr - case "1", "01": - terminal.cellAttr.Bold = true - case "2", "02": - terminal.cellAttr.Dim = true - case "4", "04": - terminal.cellAttr.Underline = true - case "5", "05": - terminal.cellAttr.Blink = true - case "7", "07": - terminal.cellAttr.Reverse = true - case "8", "08": - terminal.cellAttr.Hidden = true - case "21": - terminal.cellAttr.Bold = false - case "22": - terminal.cellAttr.Dim = false - case "24": - terminal.cellAttr.Underline = false - case "25": - terminal.cellAttr.Blink = false - case "27": - terminal.cellAttr.Reverse = false - case "28": - terminal.cellAttr.Hidden = false - case "39": - terminal.cellAttr.FgColour = terminal.colourScheme.DefaultFg - case "30": - terminal.cellAttr.FgColour = terminal.colourScheme.BlackFg - case "31": - terminal.cellAttr.FgColour = terminal.colourScheme.RedFg - case "32": - terminal.cellAttr.FgColour = terminal.colourScheme.GreenFg - case "33": - terminal.cellAttr.FgColour = terminal.colourScheme.YellowFg - case "34": - terminal.cellAttr.FgColour = terminal.colourScheme.BlueFg - case "35": - terminal.cellAttr.FgColour = terminal.colourScheme.MagentaFg - case "36": - terminal.cellAttr.FgColour = terminal.colourScheme.CyanFg - case "37": - terminal.cellAttr.FgColour = terminal.colourScheme.WhiteFg - case "90": - terminal.cellAttr.FgColour = terminal.colourScheme.DarkGreyFg - case "91": - terminal.cellAttr.FgColour = terminal.colourScheme.LightRedFg - case "92": - terminal.cellAttr.FgColour = terminal.colourScheme.LightGreenFg - case "93": - terminal.cellAttr.FgColour = terminal.colourScheme.LightYellowFg - case "94": - terminal.cellAttr.FgColour = terminal.colourScheme.LightBlueFg - case "95": - terminal.cellAttr.FgColour = terminal.colourScheme.LightMagentaFg - case "96": - terminal.cellAttr.FgColour = terminal.colourScheme.LightCyanFg - case "97": - terminal.cellAttr.FgColour = terminal.colourScheme.WhiteFg - case "49": - terminal.cellAttr.BgColour = terminal.colourScheme.DefaultBg - case "40": - terminal.cellAttr.BgColour = terminal.colourScheme.BlackBg - case "41": - terminal.cellAttr.BgColour = terminal.colourScheme.RedBg - case "42": - terminal.cellAttr.BgColour = terminal.colourScheme.GreenBg - case "43": - terminal.cellAttr.BgColour = terminal.colourScheme.YellowBg - case "44": - terminal.cellAttr.BgColour = terminal.colourScheme.BlueBg - case "45": - terminal.cellAttr.BgColour = terminal.colourScheme.MagentaBg - case "46": - terminal.cellAttr.BgColour = terminal.colourScheme.CyanBg - case "47": - terminal.cellAttr.BgColour = terminal.colourScheme.WhiteBg - case "100": - terminal.cellAttr.BgColour = terminal.colourScheme.DarkGreyBg - case "101": - terminal.cellAttr.BgColour = terminal.colourScheme.LightRedBg - case "102": - terminal.cellAttr.BgColour = terminal.colourScheme.LightGreenBg - case "103": - terminal.cellAttr.BgColour = terminal.colourScheme.LightYellowBg - case "104": - terminal.cellAttr.BgColour = terminal.colourScheme.LightBlueBg - case "105": - terminal.cellAttr.BgColour = terminal.colourScheme.LightMagentaBg - case "106": - terminal.cellAttr.BgColour = terminal.colourScheme.LightCyanBg - case "107": - terminal.cellAttr.BgColour = terminal.colourScheme.WhiteBg - default: - - terminal.logger.Errorf("Unknown SGR control sequence: (ESC[%s%s%s)", param, intermediate, string(final)) - } - - //terminal.logger.Debugf("SGR control sequence: (ESC[%s%s%s)", param, intermediate, string(final)) - } - - default: - switch param + intermediate + string(final) { - case "?25h": - terminal.showCursor() - case "?25l": - terminal.hideCursor() - case "?12h": - // todo enable cursor blink - case "?12l": - // todo disable cursor blink - default: - terminal.logger.Errorf("Unknown CSI control sequence: 0x%02X (ESC[%s%s%s)", final, param, intermediate, string(final)) - } - - } - terminal.logger.Debugf("Received CSI control sequence: 0x%02X (ESC[%s%s%s)", final, param, intermediate, string(final)) - case 0x5d: // OSC: Operating System Command - b = <-buffer - switch b { - case rune('0'): - b = <-buffer - if b == rune(';') { - title := []rune{} - for { - b = <-buffer - if b == 0x07 || b == 0x5c { // 0x07 -> BELL, 0x5c -> ST (\) - break - } - title = append(title, b) - } - terminal.title = string(title) - } else { - terminal.logger.Errorf("Invalid OSC 0 control sequence: 0x%02X", b) - } - default: - terminal.logger.Errorf("Unknown OSC control sequence: 0x%02X", b) - } - case 'c': - terminal.logger.Errorf("RIS not yet supported") - case '(': - b = <-buffer - switch b { - case 'A': //uk @todo handle these? - //terminal.charSet = C0 - case 'B': //us - //terminal.charSet = C0 - } - case ')': - b = <-buffer - switch b { - case 'A': //uk @todo handle these? - //terminal.charSet = C1 - case 'B': //us - //terminal.charSet = C1 - } - case '*': - b = <-buffer - switch b { - case 'A': //uk @todo handle these? - //terminal.charSet = C2 - case 'B': //us - //terminal.charSet = C2 - } - case '+': - b = <-buffer - switch b { - case 'A': //uk @todo handle these? - //terminal.charSet = C3 - case 'B': //us - //terminal.charSet = C3 - } - case '>': - // numeric char selection @todo - case '=': - //alternate char selection @todo - case '?': - pm := "" - for { - b = <-buffer - switch b { - case 'h': - switch pm { - default: - terminal.logger.Errorf("Unknown private code ESC?%sh", pm) - } - case 'l': - switch pm { - default: - terminal.logger.Errorf("Unknown private code ESC?%sl", pm) - } - default: - pm += string(b) - } - } - default: - terminal.logger.Errorf("Unknown control sequence: 0x%02X [%s]", b, string(b)) + if ok { + if err := handler(buffer, terminal); err != nil { + fmt.Errorf("Error handling escape sequence 0x%X: %s", b, err) } - } else { + continue + } - //fmt.Printf("%s", string(b)) + if b != 0x0d { + lineOverflow = false + } - if b != 0x0d { + switch b { + case 0x0a: + + _, h := terminal.GetSize() + if terminal.position.Line+1 >= h { + terminal.lines = append(terminal.lines, NewLine()) + } else { + terminal.position.Line++ + } + + case 0x0d: + if terminal.position.Col == 0 && terminal.position.Line > 0 && lineOverflow { + terminal.position.Line-- + terminal.logger.Debugf("Swallowing forced new line for CR") lineOverflow = false } + terminal.position.Col = 0 - switch b { - case 0x0a: - - _, h := terminal.GetSize() - if terminal.position.Line+1 >= h { - terminal.lines = append(terminal.lines, NewLine()) - } else { - terminal.position.Line++ - } - - case 0x0d: - if terminal.position.Col == 0 && terminal.position.Line > 0 && lineOverflow { - terminal.position.Line-- - terminal.logger.Debugf("Swallowing forced new line for CR") - lineOverflow = false - } + case 0x08: + // backspace + terminal.position.Col-- + if terminal.position.Col < 0 { terminal.position.Col = 0 - - case 0x08: - // backspace - terminal.position.Col-- - if terminal.position.Col < 0 { - terminal.position.Col = 0 - } - case 0x07: - // @todo ring bell - default: - // render character at current location - // fmt.Printf("%s\n", string([]byte{b})) - if b >= 0x20 { - terminal.writeRune(b) - lineOverflow = terminal.position.Col == 0 - } else { - terminal.logger.Error("Non-readable rune received: 0x%X", b) - } } - + case 0x07: + // @todo ring bell + default: + // render character at current location + // fmt.Printf("%s\n", string([]byte{b})) + if b >= 0x20 { + terminal.writeRune(b) + lineOverflow = terminal.position.Col == 0 + } else { + terminal.logger.Error("Non-readable rune received: 0x%X", b) + } } + terminal.triggerOnUpdate() } } diff --git a/terminal/terminal.go b/terminal/terminal.go index 5c19973..1eeabbc 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -2,7 +2,9 @@ package terminal import ( "bufio" + "context" "fmt" + "io" "os" "sync" "syscall" @@ -184,16 +186,24 @@ func (terminal *Terminal) Read() error { buffer := make(chan rune, 0xffff) reader := bufio.NewReader(terminal.pty) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - go terminal.processInput(buffer) + go terminal.processInput(ctx, buffer) for { r, size, err := reader.ReadRune() if err != nil { + if err == io.EOF { + break + } return err } else if size > 0 { buffer <- r } } + + //clean exit + return nil } func (terminal *Terminal) writeRune(r rune) {