mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-06-20 04:07:55 +08:00
fix(webconsole): record command input and ps1
This commit is contained in:
1
go.mod
1
go.mod
@@ -12,6 +12,7 @@ require (
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/DataDog/dd-trace-go v0.6.1 // indirect
|
||||
github.com/DataDog/zstd v1.3.4 // indirect
|
||||
github.com/LeeEirc/terminalparser v0.0.0-20220328021224-de16b7643ea4
|
||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
|
||||
3
go.sum
3
go.sum
@@ -77,6 +77,8 @@ github.com/DataDog/dd-trace-go v0.6.1 h1:nsZ2lohbSw1CKtfNRu3wPh1jFirv6XSz8vqNpuI
|
||||
github.com/DataDog/dd-trace-go v0.6.1/go.mod h1:SmQTTcC37XMyEm75HV0AWiZIYxDiaNhRi49zorIpW+o=
|
||||
github.com/DataDog/zstd v1.3.4 h1:LAGHkXuvC6yky+C2CUG2tD7w8QlrUwpue8XwIh0X4AY=
|
||||
github.com/DataDog/zstd v1.3.4/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/LeeEirc/terminalparser v0.0.0-20220328021224-de16b7643ea4 h1:Gk7m4Nu2jqVqJAJqNlTYqkiq96WkANAtB4fVi+t7Xv8=
|
||||
github.com/LeeEirc/terminalparser v0.0.0-20220328021224-de16b7643ea4/go.mod h1:tiLv6VBLH4Z3KdBSe2qIKRwQDGCVQ9/F5fOKpQGvyoA=
|
||||
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
@@ -482,6 +484,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
|
||||
@@ -37,7 +37,7 @@ func NewCommandLogManager() *CommandLogManager {
|
||||
modulebase.ResourceManager{
|
||||
BaseManager: *modulebase.NewBaseManager("webconsole", "", "webconsole", []string{
|
||||
"id", "ops_time", "obj_id", "obj_type", "obj_name", "user", "user_id", "tenant", "tenant_id", "owner_tenant_id", "action", "notes",
|
||||
"session_id", "accessed_at", "type", "login_user", "start_time", "command",
|
||||
"session_id", "accessed_at", "type", "login_user", "start_time", "ps1", "command",
|
||||
}, nil),
|
||||
Keyword: "commandlog", KeywordPlural: "commandlogs",
|
||||
},
|
||||
|
||||
@@ -74,6 +74,7 @@ type SCommandLog struct {
|
||||
Type string `width:"32" charset:"utf8" nullable:"true" list:"user" create:"required"`
|
||||
LoginUser string `charset:"utf8" list:"user" create:"required"`
|
||||
StartTime time.Time `list:"user" create:"required"`
|
||||
Ps1 string `charset:"utf8" list:"user" create:"optional" json:"ps1"`
|
||||
Command CommandType `charset:"utf8" list:"user" create:"required"`
|
||||
}
|
||||
|
||||
@@ -96,6 +97,7 @@ type CommandLogCreateInput struct {
|
||||
Type CommandType
|
||||
LoginUser string
|
||||
StartTime time.Time
|
||||
Ps1 string `json:"ps1"`
|
||||
Command string
|
||||
Notes jsonutils.JSONObject
|
||||
}
|
||||
|
||||
@@ -16,8 +16,11 @@ package recorder
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/LeeEirc/terminalparser"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
@@ -28,7 +31,7 @@ import (
|
||||
|
||||
type Recoder interface {
|
||||
Start()
|
||||
Write(data string)
|
||||
Write(userInput string, ptyOutput string)
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
@@ -50,44 +53,90 @@ func NewObject(id, name, oType, loginUser string, notes jsonutils.JSONObject) *O
|
||||
}
|
||||
|
||||
type cmdRecoder struct {
|
||||
cs *mcclient.ClientSession
|
||||
sessionId string
|
||||
accessedAt time.Time
|
||||
buffer []string
|
||||
curCmd string
|
||||
cmdCh chan string
|
||||
object *Object
|
||||
cs *mcclient.ClientSession
|
||||
sessionId string
|
||||
accessedAt time.Time
|
||||
userInputStart bool
|
||||
userInputBuff string
|
||||
ptyInitialOutput string
|
||||
ptyOutputBuff string
|
||||
ps1Parsed bool
|
||||
ps1 string
|
||||
cmdCh chan string
|
||||
object *Object
|
||||
wLock *sync.Mutex
|
||||
}
|
||||
|
||||
func NewCmdRecorder(s *mcclient.ClientSession, obj *Object, sessionId string, accessedAt time.Time) Recoder {
|
||||
return &cmdRecoder{
|
||||
cs: s,
|
||||
sessionId: sessionId,
|
||||
accessedAt: accessedAt,
|
||||
buffer: make([]string, 0),
|
||||
curCmd: "",
|
||||
cmdCh: make(chan string),
|
||||
object: obj,
|
||||
cs: s,
|
||||
sessionId: sessionId,
|
||||
accessedAt: accessedAt,
|
||||
userInputStart: false,
|
||||
userInputBuff: "",
|
||||
ptyOutputBuff: "",
|
||||
cmdCh: make(chan string),
|
||||
object: obj,
|
||||
wLock: new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) Write(data string) {
|
||||
if data == "\n" || data == "\r" {
|
||||
r.sendMessage(r.curCmd)
|
||||
func (r *cmdRecoder) Write(userInput string, ptyOutput string) {
|
||||
r.wLock.Lock()
|
||||
defer r.wLock.Unlock()
|
||||
|
||||
if len(userInput) != 0 {
|
||||
r.userInputStart = true
|
||||
}
|
||||
|
||||
if !r.userInputStart && userInput == "" && len(ptyOutput) > 0 {
|
||||
r.ptyInitialOutput += ptyOutput
|
||||
}
|
||||
// try parse PS1
|
||||
if !r.ps1Parsed && r.userInputBuff != "" && userInput == "\r" && r.ptyInitialOutput != "" {
|
||||
outs := r.parsePtyOutputs(r.ptyInitialOutput)
|
||||
if len(outs) != 0 && len(outs) > 1 {
|
||||
r.ps1 = outs[len(outs)-1]
|
||||
}
|
||||
r.ps1Parsed = true
|
||||
}
|
||||
|
||||
// user enter command
|
||||
if userInput == "\r" && r.userInputBuff != "" {
|
||||
r.sendMessage(r.ptyOutputBuff)
|
||||
return
|
||||
}
|
||||
r.curCmd += data
|
||||
|
||||
r.userInputBuff += userInput
|
||||
r.ptyOutputBuff += ptyOutput
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) cleanCmd() *cmdRecoder {
|
||||
r.curCmd = ""
|
||||
r.userInputBuff = ""
|
||||
r.ptyOutputBuff = ""
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) sendMessage(msg string) {
|
||||
r.cmdCh <- msg
|
||||
func (r *cmdRecoder) parsePtyOutputs(data string) []string {
|
||||
s := terminalparser.Screen{
|
||||
Rows: make([]*terminalparser.Row, 0, 1024),
|
||||
Cursor: &terminalparser.Cursor{},
|
||||
}
|
||||
return s.Parse([]byte(data))
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) sendMessage(ptyOutputBuff string) {
|
||||
ptyOuts := r.parsePtyOutputs(ptyOutputBuff)
|
||||
if len(ptyOuts) == 0 {
|
||||
return
|
||||
}
|
||||
cmd := ptyOuts[len(ptyOuts)-1]
|
||||
if r.ps1 != "" {
|
||||
cmd = strings.TrimPrefix(cmd, r.ps1)
|
||||
}
|
||||
r.cleanCmd()
|
||||
log.Infof("Message %q sended", msg)
|
||||
r.cmdCh <- cmd
|
||||
log.Debugf("sendMessage ps1: %q, ptyOuts: %#v, cmd: %q", r.ps1, ptyOuts, cmd)
|
||||
}
|
||||
|
||||
func (r *cmdRecoder) Start() {
|
||||
@@ -105,6 +154,9 @@ func (r *cmdRecoder) save(command string) error {
|
||||
if r.object == nil {
|
||||
return nil
|
||||
}
|
||||
if command == "" {
|
||||
return nil
|
||||
}
|
||||
userCred := r.cs.GetToken()
|
||||
input := r.newModelInput(userCred, command)
|
||||
_, err := models.GetCommandLogManager().Create(r.cs.GetContext(), userCred, input)
|
||||
@@ -135,6 +187,7 @@ func (r *cmdRecoder) newModelInput(userCred mcclient.TokenCredential, command st
|
||||
LoginUser: r.object.LoginUser,
|
||||
Type: models.CommandTypeSSH,
|
||||
StartTime: time.Now(),
|
||||
Ps1: r.ps1,
|
||||
Command: command,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (server *TTYServer) initEventHandler(s *session.SSession) {
|
||||
}
|
||||
|
||||
func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
// handle read
|
||||
// handle command output
|
||||
go func() {
|
||||
for !p.Exit {
|
||||
if p.IsInShellMode() {
|
||||
@@ -82,7 +82,9 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
*/
|
||||
cleanUp(so, p)
|
||||
} else {
|
||||
// log.Errorf("--p.Pty.output data: %q", data)
|
||||
so.Emit(OUTPUT_EVENT, string(data))
|
||||
go p.Session.GetRecorder().Write("", string(data))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -95,7 +97,7 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
}
|
||||
}()
|
||||
|
||||
// handle write
|
||||
// handle user input write
|
||||
so.On(INPUT_EVENT, func(data string) {
|
||||
if !p.IsInShellMode() {
|
||||
for _, d := range []byte(data) {
|
||||
@@ -120,7 +122,7 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
}
|
||||
} else {
|
||||
p.Pty.Write([]byte(data))
|
||||
go p.Session.GetRecorder().Write(data)
|
||||
go p.Session.GetRecorder().Write(data, "")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
16
vendor/github.com/LeeEirc/terminalparser/.gitignore
generated
vendored
Normal file
16
vendor/github.com/LeeEirc/terminalparser/.gitignore
generated
vendored
Normal file
@@ -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
vendor/github.com/LeeEirc/terminalparser/ascii_table.go
generated
vendored
Normal file
76
vendor/github.com/LeeEirc/terminalparser/ascii_table.go
generated
vendored
Normal file
@@ -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
vendor/github.com/LeeEirc/terminalparser/csi_func.go
generated
vendored
Normal file
327
vendor/github.com/LeeEirc/terminalparser/csi_func.go
generated
vendored
Normal file
@@ -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
vendor/github.com/LeeEirc/terminalparser/cursor.go
generated
vendored
Normal file
32
vendor/github.com/LeeEirc/terminalparser/cursor.go
generated
vendored
Normal file
@@ -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
vendor/github.com/LeeEirc/terminalparser/go.mod
generated
vendored
Normal file
5
vendor/github.com/LeeEirc/terminalparser/go.mod
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/LeeEirc/terminalparser
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/mattn/go-runewidth v0.0.9
|
||||
2
vendor/github.com/LeeEirc/terminalparser/go.sum
generated
vendored
Normal file
2
vendor/github.com/LeeEirc/terminalparser/go.sum
generated
vendored
Normal file
@@ -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
vendor/github.com/LeeEirc/terminalparser/row.go
generated
vendored
Normal file
107
vendor/github.com/LeeEirc/terminalparser/row.go
generated
vendored
Normal file
@@ -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
vendor/github.com/LeeEirc/terminalparser/screen.go
generated
vendored
Normal file
277
vendor/github.com/LeeEirc/terminalparser/screen.go
generated
vendored
Normal file
@@ -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
vendor/github.com/LeeEirc/terminalparser/util.go
generated
vendored
Normal file
37
vendor/github.com/LeeEirc/terminalparser/util.go
generated
vendored
Normal file
@@ -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:]
|
||||
}
|
||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -47,6 +47,8 @@ github.com/DataDog/dd-trace-go/tracer
|
||||
github.com/DataDog/dd-trace-go/tracer/ext
|
||||
# github.com/DataDog/zstd v1.3.4
|
||||
github.com/DataDog/zstd
|
||||
# github.com/LeeEirc/terminalparser v0.0.0-20220328021224-de16b7643ea4
|
||||
github.com/LeeEirc/terminalparser
|
||||
# github.com/Masterminds/goutils v1.1.0
|
||||
github.com/Masterminds/goutils
|
||||
# github.com/Masterminds/semver v1.5.0
|
||||
|
||||
Reference in New Issue
Block a user