mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-10-24 02:26:39 +08:00
fix(webconsole): record command input and ps1
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.idea
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package terminalparser
|
||||
|
||||
/*
|
||||
Control Character - A single character with an ASCII code with the range
|
||||
of 000 to 037 and 200 to 237 octal, 00 to 1F and 80 to 9F hex.
|
||||
*/
|
||||
|
||||
/*
|
||||
C0 Control 000-037 octal, 00-1F hex (G0 is 041-176 octal, 21-7E hex)
|
||||
SPACE 040+240 octal, 20+A0 hex Always and everywhere a blank space
|
||||
Intermediate 040-057 octal, 20-2F hex !"#$%&'()*+,-./
|
||||
Parameters 060-077 octal, 30-3F hex 0123456789:;<=>?
|
||||
Uppercase 100-137 octal, 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
|
||||
Lowercase 140-176 octal, 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
|
||||
Alphabetic 100-176 octal, 40-7E hex (all of upper and lower case)
|
||||
Delete 177 octal, 7F hex Always and everywhere ignored
|
||||
C1 Control 200-237 octal, 80-9F hex 32 additional control characters
|
||||
G1 Displayable 241-376 octal, A1-FE hex 94 additional displayable characters
|
||||
Special 240+377 octal, A0+FF hex Same as SPACE and DELETE
|
||||
*/
|
||||
|
||||
var ASCIITable = [128]rune{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
|
||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
|
||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
|
||||
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
|
||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
|
||||
}
|
||||
|
||||
var C0Control = ASCIITable[0:32]
|
||||
|
||||
var Spaces = []rune{0x20, 0xa0}
|
||||
|
||||
var Intermediate = ASCIITable[32:48]
|
||||
var Parameters = ASCIITable[48:64]
|
||||
var Uppercase = ASCIITable[64:96]
|
||||
var Lowercase = ASCIITable[96:128]
|
||||
var Alphabetic = ASCIITable[64:128]
|
||||
|
||||
// Always and everywhere ignored
|
||||
var Delete rune = 0x7f
|
||||
|
||||
var C1Control = []rune{
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
|
||||
}
|
||||
|
||||
var G1Display = []rune{
|
||||
0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
|
||||
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
|
||||
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
|
||||
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
|
||||
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe,
|
||||
}
|
||||
|
||||
var SpecialSpace = 0xa0
|
||||
var SpecialDelete = 0xff
|
||||
|
||||
const (
|
||||
NUL rune = 0x00 + iota
|
||||
SOH
|
||||
STX
|
||||
ETX
|
||||
EOT
|
||||
ENQ
|
||||
ACK
|
||||
BEL
|
||||
BS
|
||||
|
||||
ST rune = 0x9c
|
||||
ESCKey = 0x1b
|
||||
)
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
package terminalparser
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type screenCsiFunc func(screen *Screen, params []rune)
|
||||
|
||||
var CSIFuncMap = map[rune]screenCsiFunc{
|
||||
'@': func(s *Screen, params []rune) {
|
||||
currentRow := s.GetCursorRow()
|
||||
switch len(params) {
|
||||
case 1:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
insertData := make([]rune, ps)
|
||||
for i := 0; i < ps; i++ {
|
||||
insertData[i] = Spaces[0]
|
||||
}
|
||||
currentRow.changeCursorToX(s.Cursor.X)
|
||||
currentRow.insertCharacters(insertData)
|
||||
}
|
||||
case 2:
|
||||
if params[len(params)-1] == Spaces[0] {
|
||||
if _, err := strconv.Atoi(string(params[0])); err == nil {
|
||||
log.Printf("Screen 不支持解析 CSI `%s` @\n", string(params))
|
||||
}
|
||||
}
|
||||
default:
|
||||
currentRow.changeCursorToX(s.Cursor.X)
|
||||
currentRow.insertCharacters([]rune{Spaces[0]})
|
||||
}
|
||||
},
|
||||
'A': func(s *Screen, params []rune) {
|
||||
/*
|
||||
CSI Ps A Cursor Up Ps Times (default = 1) (CUU).
|
||||
|
||||
CSI Ps SP A
|
||||
Shift right Ps columns(s) (default = 1) (SR), ECMA-48.
|
||||
*/
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.Cursor.MoveUp(1)
|
||||
case 1:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.Cursor.MoveUp(ps)
|
||||
}
|
||||
case 2:
|
||||
if params[len(params)-1] == Spaces[0] {
|
||||
if ps, err := strconv.Atoi(string(params[0])); err == nil {
|
||||
s.Cursor.MoveRight(ps)
|
||||
log.Printf("Shift right %d columns(s) \n", ps)
|
||||
}
|
||||
}
|
||||
default:
|
||||
if params[len(params)-1] == Spaces[0] {
|
||||
if ps, err := strconv.Atoi(string(params[0])); err == nil {
|
||||
s.Cursor.MoveRight(ps)
|
||||
log.Printf("Shift right %d columns(s) \n", ps)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
'B': func(s *Screen, params []rune) {
|
||||
/*
|
||||
CSI Ps B Cursor Down Ps Times (default = 1) (CUD).
|
||||
*/
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.Cursor.MoveDown(1)
|
||||
default:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.Cursor.MoveDown(ps)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
'C': func(s *Screen, params []rune) {
|
||||
/*
|
||||
CSI Ps C Cursor Forward Ps Times (default = 1) (CUF).
|
||||
*/
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.Cursor.MoveRight(1)
|
||||
default:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.Cursor.MoveRight(ps)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
'D': func(s *Screen, params []rune) {
|
||||
/*
|
||||
CSI Ps D Cursor Backward Ps Times (default = 1) (CUB).
|
||||
*/
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.Cursor.MoveLeft(1)
|
||||
default:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.Cursor.MoveLeft(ps)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'E': func(s *Screen, params []rune) {
|
||||
/*
|
||||
CSI Ps E Cursor Next Line Ps Times (default = 1) (CNL).
|
||||
*/
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.Cursor.MoveDown(1)
|
||||
default:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.Cursor.MoveDown(ps)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
'F': func(s *Screen, params []rune) {
|
||||
/*
|
||||
CSI Ps F Cursor Preceding Line Ps Times (default = 1) (CPL).
|
||||
*/
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.Cursor.MoveUp(1)
|
||||
case 1:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.Cursor.MoveUp(ps)
|
||||
}
|
||||
default:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.Cursor.MoveUp(ps)
|
||||
}
|
||||
}
|
||||
},
|
||||
'I': func(screen *Screen, params []rune) {
|
||||
log.Println("Screen 不支持 I")
|
||||
},
|
||||
'G': func(s *Screen, params []rune) {
|
||||
switch len(params) {
|
||||
case 1:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.Cursor.X = ps
|
||||
}
|
||||
}
|
||||
},
|
||||
'H': func(s *Screen, params []rune) {
|
||||
if len(params) == 3 && params[1] == ';' {
|
||||
if row, err := strconv.Atoi(string(params[0])); err == nil {
|
||||
s.Cursor.Y = row
|
||||
}
|
||||
if column, err := strconv.Atoi(string(params[2])); err == nil {
|
||||
s.Cursor.X = column
|
||||
}
|
||||
}
|
||||
if len(params) == 0 {
|
||||
s.Cursor.MoveHome()
|
||||
}
|
||||
},
|
||||
'J': func(s *Screen, params []rune) {
|
||||
/*
|
||||
ESC J Erase from the cursor to the end of the screen.
|
||||
CSI Ps J Erase in Display (ED), VT100.
|
||||
Ps = 0 ⇒ Erase Below (default).
|
||||
Ps = 1 ⇒ Erase Above.
|
||||
Ps = 2 ⇒ Erase All.
|
||||
Ps = 3 ⇒ Erase Saved Lines, xterm.
|
||||
*/
|
||||
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.eraseFromCursor()
|
||||
case 1:
|
||||
if ps, err := strconv.Atoi(string(params[0])); err == nil {
|
||||
switch ps {
|
||||
case 0:
|
||||
s.eraseBelow()
|
||||
case 1:
|
||||
s.eraseAbove()
|
||||
case 2:
|
||||
s.eraseAll()
|
||||
case 3:
|
||||
log.Println("screen 未处理Erase Saved Lines, xterm.")
|
||||
default:
|
||||
log.Printf("screen 未处理 Erase.%d \n", ps)
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
log.Printf("screen 未处理 Erase in Display (DECSED), VT220. %s\n", string(params))
|
||||
default:
|
||||
log.Printf("screen 未处理 Erase %s\n", string(params))
|
||||
}
|
||||
},
|
||||
'K': func(s *Screen, params []rune) {
|
||||
|
||||
/*
|
||||
ESC K Erase from the cursor to the end of the line.
|
||||
CSI Ps K Erase in Line (EL), VT100.
|
||||
Ps = 0 ⇒ Erase to Right (default).
|
||||
Ps = 1 ⇒ Erase to Left.
|
||||
Ps = 2 ⇒ Erase All.
|
||||
|
||||
CSI ? Ps K
|
||||
Erase in Line (DECSEL), VT220.
|
||||
Ps = 0 ⇒ Selective Erase to Right (default).
|
||||
Ps = 1 ⇒ Selective Erase to Left.
|
||||
Ps = 2 ⇒ Selective Erase All.
|
||||
*/
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.eraseEndToLine()
|
||||
default:
|
||||
paramsS := string(params)
|
||||
if strings.HasPrefix(paramsS, "?") {
|
||||
log.Printf("Screen不支持解析 CSI `%s` K\n", paramsS)
|
||||
return
|
||||
}
|
||||
if ps, err := strconv.Atoi(paramsS); err == nil {
|
||||
switch ps {
|
||||
case 0:
|
||||
s.eraseRight()
|
||||
case 1:
|
||||
s.eraseLeft()
|
||||
case 2:
|
||||
s.eraseAll()
|
||||
default:
|
||||
log.Printf("未处理erase %d\n", ps)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
'L': func(o *Screen, params []rune) {
|
||||
/*
|
||||
CSI Ps L Insert Ps Line(s) (default = 1) (IL).
|
||||
*/
|
||||
log.Println("Screen不支持解析L")
|
||||
},
|
||||
'M': func(o *Screen, params []rune) {
|
||||
/*
|
||||
CSI Ps M Delete Ps Line(s) (default = 1) (DL).
|
||||
*/
|
||||
log.Println("Screen不支持解析M")
|
||||
},
|
||||
'P': func(s *Screen, params []rune) {
|
||||
/*
|
||||
CSI Ps P Delete Ps Character(s) (default = 1) (DCH).
|
||||
*/
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.deleteChars(1)
|
||||
default:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.deleteChars(ps)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
'X': func(s *Screen, params []rune) {
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.deleteChars(1)
|
||||
default:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.deleteChars(ps)
|
||||
}
|
||||
}
|
||||
},
|
||||
'd': func(s *Screen, params []rune) {
|
||||
switch len(params) {
|
||||
case 0:
|
||||
s.Cursor.Y = 1
|
||||
default:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
s.Cursor.Y = ps
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
'l': func(screen *Screen, params []rune) {
|
||||
log.Println("Screen不支持解析l")
|
||||
screen.pasteMode = false
|
||||
},
|
||||
'h': func(screen *Screen, params []rune) {
|
||||
log.Println("Screen不支持解析h")
|
||||
screen.pasteMode = true
|
||||
},
|
||||
|
||||
'm': func(s *Screen, params []rune) {
|
||||
switch len(params) {
|
||||
case 0:
|
||||
default:
|
||||
if ps, err := strconv.Atoi(string(params)); err == nil {
|
||||
switch ps {
|
||||
case 30:
|
||||
/*
|
||||
针对fish的环境特殊处理
|
||||
*/
|
||||
if s.Cursor.Y >= 1 && len(s.Rows) > 0 {
|
||||
index := s.Cursor.Y - 1
|
||||
if index >= len(s.Rows) {
|
||||
index = len(s.Rows) - 1
|
||||
}
|
||||
s.Rows[index].stopRecord()
|
||||
}
|
||||
|
||||
case 90:
|
||||
if s.Cursor.Y >= 1 && len(s.Rows) > 0 {
|
||||
index := s.Cursor.Y - 1
|
||||
if index >= len(s.Rows) {
|
||||
index = len(s.Rows) - 1
|
||||
}
|
||||
s.Rows[index].startRecord()
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package terminalparser
|
||||
|
||||
type Cursor struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
func (c *Cursor) MoveHome() {
|
||||
c.X = 0
|
||||
c.Y = 0
|
||||
}
|
||||
|
||||
func (c *Cursor) MoveUp(ps int) {
|
||||
c.Y -= ps
|
||||
if c.Y < 0 {
|
||||
c.Y = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cursor) MoveDown(ps int) {
|
||||
c.Y += ps
|
||||
}
|
||||
|
||||
func (c *Cursor) MoveRight(ps int) {
|
||||
c.X += ps
|
||||
}
|
||||
|
||||
func (c *Cursor) MoveLeft(ps int) {
|
||||
c.X -= ps
|
||||
if c.X < 0 {
|
||||
c.X = 0
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module github.com/LeeEirc/terminalparser
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/mattn/go-runewidth v0.0.9
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package terminalparser
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
type Row struct {
|
||||
dataRune []rune
|
||||
currentX int
|
||||
currentRuneIndex int
|
||||
|
||||
// fish shell 补全提示
|
||||
tipRune []rune
|
||||
tipRecord bool
|
||||
}
|
||||
|
||||
func (r *Row) String() string {
|
||||
return strings.TrimSuffix(string(r.dataRune), string(r.tipRune))
|
||||
}
|
||||
|
||||
func (r *Row) appendCharacter(code rune) {
|
||||
width := runewidth.StringWidth(string(code))
|
||||
if r.currentRuneIndex < len(r.dataRune) {
|
||||
r.dataRune[r.currentRuneIndex] = code
|
||||
} else {
|
||||
r.dataRune = append(r.dataRune, code)
|
||||
|
||||
}
|
||||
r.currentRuneIndex++
|
||||
r.currentX += width
|
||||
r.addTipRune(code)
|
||||
}
|
||||
|
||||
func (r *Row) insertCharacters(data []rune) {
|
||||
result := make([]rune, len(r.dataRune)+len(data))
|
||||
copy(result, r.dataRune[:r.currentRuneIndex])
|
||||
copy(result[r.currentRuneIndex:], data)
|
||||
copy(result[r.currentRuneIndex+len(data):], r.dataRune[r.currentRuneIndex:])
|
||||
for i := range data {
|
||||
r.currentRuneIndex++
|
||||
r.currentX += runewidth.StringWidth(string(data[i]))
|
||||
}
|
||||
r.dataRune = result
|
||||
}
|
||||
|
||||
func (r *Row) eraseRight() {
|
||||
r.dataRune = r.dataRune[:r.currentRuneIndex]
|
||||
}
|
||||
|
||||
func (r *Row) deleteChars(ps int) {
|
||||
result := make([]rune, r.currentRuneIndex, len(r.dataRune))
|
||||
copy(result, r.dataRune[:r.currentRuneIndex])
|
||||
rest := r.dataRune[r.currentRuneIndex:]
|
||||
inits := ps
|
||||
for i := range rest {
|
||||
inits -= runewidth.StringWidth(string(rest[i]))
|
||||
if inits == 0 {
|
||||
result = append(result, rest[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
r.dataRune = result
|
||||
}
|
||||
|
||||
func (r *Row) changeCurrentRuneIndex() {
|
||||
if r.currentX < 0 {
|
||||
r.currentX = 0
|
||||
}
|
||||
currentRuneIndex := 0
|
||||
for i := range r.dataRune {
|
||||
currentRuneIndex += runewidth.StringWidth(string(r.dataRune[i]))
|
||||
if currentRuneIndex > r.currentX {
|
||||
r.currentRuneIndex = i
|
||||
return
|
||||
}
|
||||
}
|
||||
r.currentRuneIndex = len(r.dataRune)
|
||||
}
|
||||
|
||||
func (r *Row) changeCursorToX(x int) {
|
||||
if r.currentX == x {
|
||||
return
|
||||
}
|
||||
r.currentX = x
|
||||
r.changeCurrentRuneIndex()
|
||||
}
|
||||
|
||||
func (r *Row) startRecord() {
|
||||
r.tipRecord = true
|
||||
r.tipRune = make([]rune, 0, 100)
|
||||
}
|
||||
|
||||
func (r *Row) stopRecord() {
|
||||
if !r.tipRecord {
|
||||
r.tipRune = make([]rune, 0, 100)
|
||||
}
|
||||
r.tipRecord = false
|
||||
}
|
||||
|
||||
func (r *Row) addTipRune(code rune) {
|
||||
if r.tipRecord {
|
||||
r.tipRune = append(r.tipRune, code)
|
||||
}
|
||||
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
package terminalparser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
type Screen struct {
|
||||
Rows []*Row
|
||||
|
||||
Cursor *Cursor
|
||||
|
||||
pasteMode bool // Set bracketed paste mode, xterm. ?2004h reset ?2004l
|
||||
|
||||
title string
|
||||
}
|
||||
|
||||
func (s *Screen) Parse(data []byte) []string {
|
||||
s.Cursor.Y = 1
|
||||
s.Rows = append(s.Rows, &Row{
|
||||
dataRune: make([]rune, 0, 1024),
|
||||
})
|
||||
rest := data
|
||||
for len(rest) > 0 {
|
||||
code, size := utf8.DecodeRune(rest)
|
||||
rest = rest[size:]
|
||||
switch code {
|
||||
case ESCKey:
|
||||
code, size = utf8.DecodeRune(rest)
|
||||
rest = rest[size:]
|
||||
switch code {
|
||||
case '[':
|
||||
// CSI
|
||||
rest = s.parseCSISequence(rest)
|
||||
continue
|
||||
case ']':
|
||||
// OSC
|
||||
rest = s.parseOSCSequence(rest)
|
||||
continue
|
||||
default:
|
||||
if existIndex := bytes.IndexRune([]byte(string(Intermediate)), code); existIndex >= 0 {
|
||||
// ESC
|
||||
rest = s.parseIntermediate(code, rest)
|
||||
continue
|
||||
}
|
||||
if existIndex := bytes.IndexRune([]byte(string(Parameters)), code); existIndex >= 0 {
|
||||
|
||||
log.Printf("Screen 未解析 ESC `%q` %xParameters字符\n", code, code)
|
||||
continue
|
||||
}
|
||||
if existIndex := bytes.IndexRune([]byte(string(Uppercase)), code); existIndex >= 0 {
|
||||
log.Printf("Screen 未解析 ESC `%q` %x Uppercase字符\n", code, code)
|
||||
continue
|
||||
}
|
||||
|
||||
if existIndex := bytes.IndexRune([]byte(string(Lowercase)), code); existIndex >= 0 {
|
||||
log.Printf("Screen 未解析 ESC `%q` %x Lowercase字符\n", code, code)
|
||||
continue
|
||||
}
|
||||
log.Printf("Screen 未解析 ESC `%q` %x\n", code, code)
|
||||
}
|
||||
continue
|
||||
case Delete:
|
||||
continue
|
||||
default:
|
||||
if existIndex := bytes.IndexRune([]byte(string(C0Control)), code); existIndex >= 0 {
|
||||
s.parseC0Sequence(code)
|
||||
} else {
|
||||
if len(s.Rows) == 0 && s.Cursor.Y == 0 {
|
||||
s.Rows = append(s.Rows, &Row{
|
||||
dataRune: make([]rune, 0, 1024),
|
||||
})
|
||||
s.Cursor.Y++
|
||||
}
|
||||
s.appendCharacter(code)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
result := make([]string, len(s.Rows))
|
||||
for i := range s.Rows {
|
||||
result[i] = s.Rows[i].String()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Screen) parseC0Sequence(code rune) {
|
||||
switch code {
|
||||
case 0x07:
|
||||
//bell 忽略
|
||||
case 0x08:
|
||||
// 后退1光标
|
||||
s.Cursor.MoveLeft(1)
|
||||
case 0x0d:
|
||||
/*
|
||||
\r
|
||||
*/
|
||||
s.Cursor.X = 0
|
||||
s.Cursor.Y++
|
||||
if s.Cursor.Y > len(s.Rows) {
|
||||
s.Rows = append(s.Rows, &Row{
|
||||
dataRune: make([]rune, 0, 1024),
|
||||
})
|
||||
}
|
||||
case 0x0a:
|
||||
/*
|
||||
\n
|
||||
*/
|
||||
s.Cursor.Y++
|
||||
if s.Cursor.Y > len(s.Rows) {
|
||||
s.Rows = append(s.Rows, &Row{
|
||||
dataRune: make([]rune, 0, 1024),
|
||||
})
|
||||
}
|
||||
default:
|
||||
log.Printf("未处理的字符 %q %v\n", code, code)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *Screen) parseCSISequence(p []byte) []byte {
|
||||
endIndex := bytes.IndexFunc(p, IsAlphabetic)
|
||||
params := []rune(string(p[:endIndex]))
|
||||
switch rune(p[endIndex]) {
|
||||
case 'Y':
|
||||
// /*
|
||||
// ESC Y Ps Ps
|
||||
// Move the cursor to given row and column.
|
||||
// */
|
||||
if len(p[endIndex+1:]) < 2 {
|
||||
return p[endIndex+1:]
|
||||
}
|
||||
if row, err := strconv.Atoi(string(p[endIndex+1])); err == nil {
|
||||
s.Cursor.Y = row
|
||||
}
|
||||
if col, err := strconv.Atoi(string(p[endIndex+2])); err == nil {
|
||||
s.Cursor.X = col
|
||||
}
|
||||
return p[endIndex+3:]
|
||||
|
||||
}
|
||||
|
||||
funcName, ok := CSIFuncMap[rune(p[endIndex])]
|
||||
if ok {
|
||||
funcName(s, params)
|
||||
} else {
|
||||
log.Printf("screen未处理的CSI %s %q\n", DebugString(string(params)), p[endIndex])
|
||||
}
|
||||
|
||||
return p[endIndex+1:]
|
||||
}
|
||||
|
||||
func (s *Screen) parseIntermediate(code rune, p []byte) []byte {
|
||||
switch code {
|
||||
case '(':
|
||||
terminationIndex := bytes.IndexFunc(p, func(r rune) bool {
|
||||
if insideIndex := bytes.IndexRune([]byte(string(Alphabetic)), r); insideIndex < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
params := p[:terminationIndex+1]
|
||||
switch string(params) {
|
||||
case "B":
|
||||
/*
|
||||
ESC ( C Designate G0 Character Set, VT100, ISO 2022.
|
||||
C = B ⇒ United States (USASCII), VT100.
|
||||
*/
|
||||
}
|
||||
p = p[terminationIndex+1:]
|
||||
return p
|
||||
case ')':
|
||||
terminationIndex := bytes.IndexFunc(p, func(r rune) bool {
|
||||
if insideIndex := bytes.IndexRune([]byte(string(Alphabetic)), r); insideIndex < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
p = p[terminationIndex+1:]
|
||||
default:
|
||||
log.Printf("Screen 未解析 ESC `%q` %x Intermediate字符\n", code, code)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *Screen) parseOSCSequence(p []byte) []byte {
|
||||
if endIndex := bytes.IndexRune(p, BEL); endIndex >= 0 {
|
||||
return p[endIndex+1:]
|
||||
}
|
||||
|
||||
if endIndex := bytes.IndexRune(p, ST); endIndex >= 0 {
|
||||
return p[endIndex+1:]
|
||||
}
|
||||
log.Println("未处理的 parseOSCSequence")
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *Screen) appendCharacter(code rune) {
|
||||
currentRow := s.GetCursorRow()
|
||||
currentRow.changeCursorToX(s.Cursor.X)
|
||||
currentRow.appendCharacter(code)
|
||||
width := runewidth.StringWidth(string(code))
|
||||
s.Cursor.X += width
|
||||
}
|
||||
|
||||
func (s *Screen) eraseEndToLine() {
|
||||
currentRow := s.GetCursorRow()
|
||||
currentRow.changeCursorToX(s.Cursor.X)
|
||||
currentRow.eraseRight()
|
||||
|
||||
}
|
||||
|
||||
func (s *Screen) eraseRight() {
|
||||
currentRow := s.GetCursorRow()
|
||||
currentRow.changeCursorToX(s.Cursor.X)
|
||||
currentRow.eraseRight()
|
||||
}
|
||||
|
||||
func (s *Screen) eraseLeft() {
|
||||
log.Printf("Screen %s Erase Left cursor(%d,%d) 总Row数量 %d",
|
||||
UnsupportedMsg, s.Cursor.X, s.Cursor.Y, len(s.Rows))
|
||||
}
|
||||
|
||||
func (s *Screen) eraseAbove() {
|
||||
s.Rows = s.Rows[s.Cursor.Y-1:]
|
||||
}
|
||||
|
||||
func (s *Screen) eraseBelow() {
|
||||
s.Rows = s.Rows[:s.Cursor.Y]
|
||||
}
|
||||
|
||||
func (s *Screen) eraseAll() {
|
||||
s.Rows = s.Rows[:0]
|
||||
//htop?
|
||||
s.Cursor.X = 0
|
||||
s.Cursor.Y = 0
|
||||
}
|
||||
|
||||
func (s *Screen) eraseFromCursor() {
|
||||
if s.Cursor.Y > len(s.Rows) {
|
||||
s.Cursor.Y = len(s.Rows)
|
||||
}
|
||||
s.Rows = s.Rows[:s.Cursor.Y]
|
||||
currentRow := s.GetCursorRow()
|
||||
currentRow.changeCursorToX(s.Cursor.X)
|
||||
currentRow.eraseRight()
|
||||
}
|
||||
|
||||
func (s *Screen) deleteChars(ps int) {
|
||||
currentRow := s.GetCursorRow()
|
||||
currentRow.changeCursorToX(s.Cursor.X)
|
||||
currentRow.deleteChars(ps)
|
||||
}
|
||||
|
||||
func (s *Screen) GetCursorRow() *Row {
|
||||
if s.Cursor.Y == 0 {
|
||||
s.Cursor.Y++
|
||||
}
|
||||
if len(s.Rows) == 0 {
|
||||
s.Rows = append(s.Rows, &Row{
|
||||
dataRune: make([]rune, 0, 1024),
|
||||
})
|
||||
}
|
||||
index := s.Cursor.Y - 1
|
||||
if index >= len(s.Rows) {
|
||||
log.Printf("总行数 %d 比当前行 %d 小,可能存在解析错误 \n", len(s.Rows), s.Cursor.Y)
|
||||
return s.Rows[len(s.Rows)-1]
|
||||
}
|
||||
return s.Rows[s.Cursor.Y-1]
|
||||
}
|
||||
|
||||
const UnsupportedMsg = "Unsupported"
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package terminalparser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func DebugString(p string) string {
|
||||
var s strings.Builder
|
||||
for _, v := range []rune(p) {
|
||||
if unicode.IsPrint(v) {
|
||||
s.WriteRune(v)
|
||||
} else {
|
||||
s.WriteString(fmt.Sprintf("%q", v))
|
||||
}
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func IsAlphabetic(r rune) bool {
|
||||
index := bytes.IndexRune([]byte(string(Alphabetic)), r)
|
||||
if index < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ReadRunePacket(p []byte) (code rune, rest []byte) {
|
||||
r, l := utf8.DecodeRune(p)
|
||||
if r == utf8.RuneError {
|
||||
return utf8.RuneError, p
|
||||
}
|
||||
return r, p[l:]
|
||||
}
|
||||
Reference in New Issue
Block a user