mirror of https://github.com/liamg/aminal.git
todying
This commit is contained in:
parent
257dd999ec
commit
a357df65b4
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue