mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
fix(terminal): remove command completion protocol (#12055)
This commit is contained in:
@@ -1,155 +0,0 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TabCompleter struct {
|
||||
shell string
|
||||
}
|
||||
|
||||
func NewTabCompleter() *TabCompleter {
|
||||
return &TabCompleter{
|
||||
shell: os.Getenv("SHELL"),
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *TabCompleter) GetCompletions(input string) []string {
|
||||
input = strings.TrimLeft(input, " \t")
|
||||
trailingSpace := strings.HasSuffix(input, " ") || strings.HasSuffix(input, "\t")
|
||||
if tc.shell == "/bin/bash" || tc.shell == "/usr/bin/bash" {
|
||||
return tc.bashComplete(input, trailingSpace)
|
||||
}
|
||||
if tc.shell == "/bin/zsh" || tc.shell == "/usr/bin/zsh" {
|
||||
return tc.zshComplete(input)
|
||||
}
|
||||
return tc.bashComplete(input, trailingSpace)
|
||||
}
|
||||
|
||||
func (tc *TabCompleter) bashComplete(input string, trailingSpace bool) []string {
|
||||
var completions []string
|
||||
parts := strings.Fields(input)
|
||||
if trailingSpace {
|
||||
parts = append(parts, "")
|
||||
}
|
||||
|
||||
if len(parts) <= 1 {
|
||||
cmdStr := fmt.Sprintf("compgen -c -- '%s'", input)
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
out, err := cmd.Output()
|
||||
if err == nil {
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
if line := strings.TrimSpace(scanner.Text()); line != "" {
|
||||
completions = append(completions, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mainCmd := parts[0]
|
||||
curWord := parts[len(parts)-1]
|
||||
if trailingSpace {
|
||||
curWord = ""
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf(`
|
||||
[ -f /etc/bash_completion ] && source /etc/bash_completion
|
||||
[ -f /usr/share/bash-completion/bash_completion ] &&
|
||||
source /usr/share/bash-completion/bash_completion
|
||||
|
||||
_completion_loader %s 2>/dev/null || true
|
||||
[ -f /usr/share/bash-completion/completions/%s ] &&
|
||||
source /usr/share/bash-completion/completions/%s
|
||||
|
||||
COMP_WORDS=(%s)
|
||||
COMP_CWORD=%d
|
||||
COMP_LINE='%s'
|
||||
COMP_POINT=%d
|
||||
|
||||
_%s 2>/dev/null || complete -p %s &>/dev/null || compgen -f -- '%s'
|
||||
|
||||
printf '%%s\n' "${COMPREPLY[@]}"
|
||||
`, mainCmd, mainCmd, mainCmd,
|
||||
strings.Join(parts, " "), len(parts)-1,
|
||||
input, len(input),
|
||||
mainCmd, mainCmd, curWord)
|
||||
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
out, err := cmd.Output()
|
||||
if err == nil {
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
if line := strings.TrimSpace(scanner.Text()); line != "" {
|
||||
completions = append(completions, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tc.filterCompletions(input, completions)
|
||||
}
|
||||
|
||||
func (tc *TabCompleter) filterCompletions(input string, completions []string) []string {
|
||||
var filtered []string
|
||||
seen := make(map[string]bool)
|
||||
inputLower := strings.ToLower(input)
|
||||
lastToken := inputLower
|
||||
if idx := strings.LastIndex(inputLower, " "); idx != -1 {
|
||||
lastToken = strings.TrimSpace(inputLower[idx+1:])
|
||||
}
|
||||
if lastToken == "" {
|
||||
lastToken = inputLower
|
||||
}
|
||||
|
||||
for _, comp := range completions {
|
||||
if seen[comp] {
|
||||
continue
|
||||
}
|
||||
seen[comp] = true
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(comp), lastToken) {
|
||||
filtered = append(filtered, comp)
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(filtered, func(i, j int) bool {
|
||||
if len(filtered[i]) != len(filtered[j]) {
|
||||
return len(filtered[i]) < len(filtered[j])
|
||||
}
|
||||
return filtered[i] < filtered[j]
|
||||
})
|
||||
|
||||
if len(filtered) > 20 {
|
||||
filtered = filtered[:20]
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (tc *TabCompleter) zshComplete(input string) []string {
|
||||
var completions []string
|
||||
|
||||
cmdStr := fmt.Sprintf(`
|
||||
autoload -Uz compinit
|
||||
compinit
|
||||
compset -P '%s'
|
||||
compadd -x '%s' 2>/dev/null
|
||||
`, input, input)
|
||||
|
||||
cmd := exec.Command("zsh", "-c", cmdStr)
|
||||
out, _ := cmd.Output()
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
if line := strings.TrimSpace(scanner.Text()); line != "" {
|
||||
completions = append(completions, line)
|
||||
}
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package terminal
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@@ -17,7 +16,6 @@ type LocalWsSession struct {
|
||||
|
||||
allowCtrlC bool
|
||||
writeMutex sync.Mutex
|
||||
completer *TabCompleter
|
||||
}
|
||||
|
||||
func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalCommand, allowCtrlC bool) (*LocalWsSession, error) {
|
||||
@@ -30,7 +28,6 @@ func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalComma
|
||||
wsConn: wsConn,
|
||||
|
||||
allowCtrlC: allowCtrlC,
|
||||
completer: NewTabCompleter(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -117,14 +114,6 @@ func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err)
|
||||
}
|
||||
case WsMsgComplete:
|
||||
decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("websock complete string base64 decoding failed, err: %v", err)
|
||||
break
|
||||
}
|
||||
suggestion := sws.completer.GetCompletions(string(decodeBytes))
|
||||
sws.sendComplete(wsConn, strings.Join(suggestion, "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,17 +124,3 @@ func (sws *LocalWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdByt
|
||||
global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LocalWsSession) sendComplete(wsConn *websocket.Conn, suggestion string) {
|
||||
wsData, err := json.Marshal(WsMsg{
|
||||
Type: WsMsgComplete,
|
||||
Data: base64.StdEncoding.EncodeToString([]byte(suggestion)),
|
||||
})
|
||||
if err != nil {
|
||||
global.LOG.Errorf("encoding complete output to json failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, wsData); err != nil {
|
||||
global.LOG.Errorf("sending complete output to webSocket failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -39,7 +38,6 @@ const (
|
||||
WsMsgCmd = "cmd"
|
||||
WsMsgResize = "resize"
|
||||
WsMsgHeartbeat = "heartbeat"
|
||||
WsMsgComplete = "complete"
|
||||
)
|
||||
|
||||
type WsMsg struct {
|
||||
@@ -57,7 +55,6 @@ type LogicSshWsSession struct {
|
||||
inputFilterBuff *safeBuffer
|
||||
session *ssh.Session
|
||||
wsConn *websocket.Conn
|
||||
completer *TabCompleter
|
||||
isAdmin bool
|
||||
IsFlagged bool
|
||||
}
|
||||
@@ -90,7 +87,6 @@ func NewLogicSshWsSession(cols, rows int, sshClient *ssh.Client, wsConn *websock
|
||||
if err := sshSession.Shell(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
completer := NewTabCompleter()
|
||||
if len(initCmd) != 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, _ = stdinP.Write([]byte(initCmd + "\n"))
|
||||
@@ -102,7 +98,6 @@ func NewLogicSshWsSession(cols, rows int, sshClient *ssh.Client, wsConn *websock
|
||||
inputFilterBuff: inputBuf,
|
||||
session: sshSession,
|
||||
wsConn: wsConn,
|
||||
completer: completer,
|
||||
isAdmin: true,
|
||||
IsFlagged: false,
|
||||
}, nil
|
||||
@@ -162,14 +157,6 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err)
|
||||
}
|
||||
case WsMsgComplete:
|
||||
decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("websock complete string base64 decoding failed, err: %v", err)
|
||||
break
|
||||
}
|
||||
suggestion := sws.completer.GetCompletions(string(decodeBytes))
|
||||
sws.sendComplete(wsConn, strings.Join(suggestion, "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,20 +211,6 @@ func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LogicSshWsSession) sendComplete(wsConn *websocket.Conn, suggestion string) {
|
||||
wsData, err := json.Marshal(WsMsg{
|
||||
Type: WsMsgComplete,
|
||||
Data: base64.StdEncoding.EncodeToString([]byte(suggestion)),
|
||||
})
|
||||
if err != nil {
|
||||
global.LOG.Errorf("encoding complete output to json failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, wsData); err != nil {
|
||||
global.LOG.Errorf("sending complete output to webSocket failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LogicSshWsSession) Wait(quitChan chan bool) {
|
||||
if err := sws.session.Wait(); err != nil {
|
||||
setQuit(quitChan)
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TabCompleter struct {
|
||||
shell string
|
||||
}
|
||||
|
||||
func NewTabCompleter() *TabCompleter {
|
||||
return &TabCompleter{
|
||||
shell: os.Getenv("SHELL"),
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *TabCompleter) GetCompletions(input string) []string {
|
||||
input = strings.TrimLeft(input, " \t")
|
||||
trailingSpace := strings.HasSuffix(input, " ") || strings.HasSuffix(input, "\t")
|
||||
if tc.shell == "/bin/bash" || tc.shell == "/usr/bin/bash" {
|
||||
return tc.bashComplete(input, trailingSpace)
|
||||
}
|
||||
if tc.shell == "/bin/zsh" || tc.shell == "/usr/bin/zsh" {
|
||||
return tc.zshComplete(input)
|
||||
}
|
||||
return tc.bashComplete(input, trailingSpace)
|
||||
}
|
||||
|
||||
func (tc *TabCompleter) bashComplete(input string, trailingSpace bool) []string {
|
||||
var completions []string
|
||||
parts := strings.Fields(input)
|
||||
if trailingSpace {
|
||||
parts = append(parts, "")
|
||||
}
|
||||
|
||||
if len(parts) <= 1 {
|
||||
cmdStr := fmt.Sprintf("compgen -c -- '%s'", input)
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
out, err := cmd.Output()
|
||||
if err == nil {
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
if line := strings.TrimSpace(scanner.Text()); line != "" {
|
||||
completions = append(completions, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mainCmd := parts[0]
|
||||
curWord := parts[len(parts)-1]
|
||||
if trailingSpace {
|
||||
curWord = ""
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf(`
|
||||
[ -f /etc/bash_completion ] && source /etc/bash_completion
|
||||
[ -f /usr/share/bash-completion/bash_completion ] &&
|
||||
source /usr/share/bash-completion/bash_completion
|
||||
|
||||
_completion_loader %s 2>/dev/null || true
|
||||
[ -f /usr/share/bash-completion/completions/%s ] &&
|
||||
source /usr/share/bash-completion/completions/%s
|
||||
|
||||
COMP_WORDS=(%s)
|
||||
COMP_CWORD=%d
|
||||
COMP_LINE='%s'
|
||||
COMP_POINT=%d
|
||||
|
||||
_%s 2>/dev/null || complete -p %s &>/dev/null || compgen -f -- '%s'
|
||||
|
||||
printf '%%s\n' "${COMPREPLY[@]}"
|
||||
`, mainCmd, mainCmd, mainCmd,
|
||||
strings.Join(parts, " "), len(parts)-1,
|
||||
input, len(input),
|
||||
mainCmd, mainCmd, curWord)
|
||||
|
||||
cmd := exec.Command("bash", "-c", cmdStr)
|
||||
out, err := cmd.Output()
|
||||
if err == nil {
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
if line := strings.TrimSpace(scanner.Text()); line != "" {
|
||||
completions = append(completions, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tc.filterCompletions(input, completions)
|
||||
}
|
||||
|
||||
func (tc *TabCompleter) filterCompletions(input string, completions []string) []string {
|
||||
var filtered []string
|
||||
seen := make(map[string]bool)
|
||||
inputLower := strings.ToLower(input)
|
||||
lastToken := inputLower
|
||||
if idx := strings.LastIndex(inputLower, " "); idx != -1 {
|
||||
lastToken = strings.TrimSpace(inputLower[idx+1:])
|
||||
}
|
||||
if lastToken == "" {
|
||||
lastToken = inputLower
|
||||
}
|
||||
|
||||
for _, comp := range completions {
|
||||
if seen[comp] {
|
||||
continue
|
||||
}
|
||||
seen[comp] = true
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(comp), lastToken) {
|
||||
filtered = append(filtered, comp)
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(filtered, func(i, j int) bool {
|
||||
if len(filtered[i]) != len(filtered[j]) {
|
||||
return len(filtered[i]) < len(filtered[j])
|
||||
}
|
||||
return filtered[i] < filtered[j]
|
||||
})
|
||||
|
||||
if len(filtered) > 20 {
|
||||
filtered = filtered[:20]
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (tc *TabCompleter) zshComplete(input string) []string {
|
||||
var completions []string
|
||||
|
||||
cmdStr := fmt.Sprintf(`
|
||||
autoload -Uz compinit
|
||||
compinit
|
||||
compset -P '%s'
|
||||
compadd -x '%s' 2>/dev/null
|
||||
`, input, input)
|
||||
|
||||
cmd := exec.Command("zsh", "-c", cmdStr)
|
||||
out, _ := cmd.Output()
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
for scanner.Scan() {
|
||||
if line := strings.TrimSpace(scanner.Text()); line != "" {
|
||||
completions = append(completions, line)
|
||||
}
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package terminal
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
@@ -17,7 +16,6 @@ type LocalWsSession struct {
|
||||
|
||||
allowCtrlC bool
|
||||
writeMutex sync.Mutex
|
||||
completer *TabCompleter
|
||||
}
|
||||
|
||||
func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalCommand, allowCtrlC bool) (*LocalWsSession, error) {
|
||||
@@ -30,7 +28,6 @@ func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalComma
|
||||
wsConn: wsConn,
|
||||
|
||||
allowCtrlC: allowCtrlC,
|
||||
completer: NewTabCompleter(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -119,14 +116,6 @@ func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err)
|
||||
}
|
||||
case WsMsgComplete:
|
||||
decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("websock complete string base64 decoding failed, err: %v", err)
|
||||
break
|
||||
}
|
||||
suggestion := sws.completer.GetCompletions(string(decodeBytes))
|
||||
sws.sendComplete(wsConn, strings.Join(suggestion, "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,17 +126,3 @@ func (sws *LocalWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdByt
|
||||
global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LocalWsSession) sendComplete(wsConn *websocket.Conn, suggestion string) {
|
||||
wsData, err := json.Marshal(WsMsg{
|
||||
Type: WsMsgComplete,
|
||||
Data: base64.StdEncoding.EncodeToString([]byte(suggestion)),
|
||||
})
|
||||
if err != nil {
|
||||
global.LOG.Errorf("encoding complete output to json failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, wsData); err != nil {
|
||||
global.LOG.Errorf("sending complete output to webSocket failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -39,7 +38,6 @@ const (
|
||||
WsMsgCmd = "cmd"
|
||||
WsMsgResize = "resize"
|
||||
WsMsgHeartbeat = "heartbeat"
|
||||
WsMsgComplete = "complete"
|
||||
)
|
||||
|
||||
type WsMsg struct {
|
||||
@@ -57,7 +55,6 @@ type LogicSshWsSession struct {
|
||||
inputFilterBuff *safeBuffer
|
||||
session *ssh.Session
|
||||
wsConn *websocket.Conn
|
||||
completer *TabCompleter
|
||||
isAdmin bool
|
||||
IsFlagged bool
|
||||
}
|
||||
@@ -101,7 +98,6 @@ func NewLogicSshWsSession(cols, rows int, sshClient *ssh.Client, wsConn *websock
|
||||
inputFilterBuff: inputBuf,
|
||||
session: sshSession,
|
||||
wsConn: wsConn,
|
||||
completer: NewTabCompleter(),
|
||||
isAdmin: true,
|
||||
IsFlagged: false,
|
||||
}, nil
|
||||
@@ -162,14 +158,6 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err)
|
||||
}
|
||||
case WsMsgComplete:
|
||||
decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("websock complete string base64 decoding failed, err: %v", err)
|
||||
break
|
||||
}
|
||||
suggestion := sws.completer.GetCompletions(string(decodeBytes))
|
||||
sws.sendComplete(wsConn, strings.Join(suggestion, "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,20 +212,6 @@ func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LogicSshWsSession) sendComplete(wsConn *websocket.Conn, suggestion string) {
|
||||
wsData, err := json.Marshal(WsMsg{
|
||||
Type: WsMsgComplete,
|
||||
Data: base64.StdEncoding.EncodeToString([]byte(suggestion)),
|
||||
})
|
||||
if err != nil {
|
||||
global.LOG.Errorf("encoding complete output to json failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
if err := wsConn.WriteMessage(websocket.TextMessage, wsData); err != nil {
|
||||
global.LOG.Errorf("sending complete output to webSocket failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LogicSshWsSession) Wait(quitChan chan bool) {
|
||||
if err := sws.session.Wait(); err != nil {
|
||||
setQuit(quitChan)
|
||||
|
||||
@@ -20,12 +20,6 @@ const terminalSocket = ref<WebSocket>();
|
||||
const heartbeatTimer = ref<NodeJS.Timer>();
|
||||
const latency = ref(0);
|
||||
const initCmd = ref('');
|
||||
const currentLine = ref('');
|
||||
const suggestionText = ref('');
|
||||
const ghostText = ref('');
|
||||
let suggestTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
const COMPLETION_DEBOUNCE_MS = 500;
|
||||
const COMPLETION_MIN_CHARS = 2;
|
||||
|
||||
const readyWatcher = watch(
|
||||
() => webSocketReady.value && termReady.value,
|
||||
@@ -245,7 +239,6 @@ const onWSReceive = (message: MessageEvent) => {
|
||||
const wsMsg = JSON.parse(message.data);
|
||||
switch (wsMsg.type) {
|
||||
case 'cmd': {
|
||||
clearGhost();
|
||||
term.value.element && term.value.focus();
|
||||
if (wsMsg.data) {
|
||||
let receiveMsg = Base64.decode(wsMsg.data);
|
||||
@@ -257,27 +250,6 @@ const onWSReceive = (message: MessageEvent) => {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'complete': {
|
||||
if (!currentLine.value || currentLine.value.trim().length === 0) {
|
||||
clearGhost();
|
||||
break;
|
||||
}
|
||||
if (wsMsg.data) {
|
||||
const raw = Base64.decode(wsMsg.data);
|
||||
const items = raw
|
||||
.split('\n')
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item.length > 0);
|
||||
if (items.length >= 1) {
|
||||
applySuggestion(items[0]);
|
||||
} else {
|
||||
clearGhost();
|
||||
}
|
||||
} else {
|
||||
clearGhost();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'heartbeat': {
|
||||
latency.value = new Date().getTime() - wsMsg.timestamp;
|
||||
break;
|
||||
@@ -315,125 +287,9 @@ function sendMsg(data: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function sendSuggestRequest(line: string) {
|
||||
if (!line || line.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
if (isWsOpen()) {
|
||||
terminalSocket.value!.send(
|
||||
JSON.stringify({
|
||||
type: 'complete',
|
||||
data: Base64.encode(line),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleSuggest() {
|
||||
if (suggestTimer) {
|
||||
clearTimeout(suggestTimer);
|
||||
}
|
||||
if (!currentLine.value || currentLine.value.trim().length === 0) {
|
||||
clearGhost();
|
||||
return;
|
||||
}
|
||||
const token = currentLine.value.trim().split(/\s+/).pop() || '';
|
||||
if (token.length < COMPLETION_MIN_CHARS) {
|
||||
clearGhost();
|
||||
return;
|
||||
}
|
||||
suggestTimer = setTimeout(() => {
|
||||
sendSuggestRequest(currentLine.value);
|
||||
}, COMPLETION_DEBOUNCE_MS);
|
||||
}
|
||||
|
||||
function applySuggestion(raw: string) {
|
||||
if (!raw) {
|
||||
clearGhost();
|
||||
return;
|
||||
}
|
||||
const lastTokenMatch = currentLine.value.match(/(\S+)$/);
|
||||
const lastToken = lastTokenMatch ? lastTokenMatch[1] : '';
|
||||
let suffix = raw;
|
||||
if (lastToken && raw.startsWith(lastToken)) {
|
||||
suffix = raw.slice(lastToken.length);
|
||||
}
|
||||
if (!suffix) {
|
||||
clearGhost();
|
||||
return;
|
||||
}
|
||||
suggestionText.value = suffix;
|
||||
renderGhost(suffix);
|
||||
}
|
||||
|
||||
function renderGhost(suffix: string) {
|
||||
if (!term.value) return;
|
||||
term.value.write('\x1b7');
|
||||
term.value.write('\x1b[0K');
|
||||
term.value.write(`\x1b[90m${suffix}\x1b[0m`);
|
||||
term.value.write('\x1b8');
|
||||
ghostText.value = suffix;
|
||||
}
|
||||
|
||||
function clearGhost() {
|
||||
if (!ghostText.value || !term.value) return;
|
||||
term.value.write('\x1b7');
|
||||
term.value.write('\x1b[0K');
|
||||
term.value.write('\x1b8');
|
||||
ghostText.value = '';
|
||||
suggestionText.value = '';
|
||||
}
|
||||
|
||||
function onTermData(data: string) {
|
||||
if (!data) return;
|
||||
if (data === '\t') {
|
||||
if (ghostText.value) {
|
||||
sendMsg(ghostText.value);
|
||||
currentLine.value += ghostText.value;
|
||||
clearGhost();
|
||||
scheduleSuggest();
|
||||
return;
|
||||
}
|
||||
sendMsg(data);
|
||||
return;
|
||||
}
|
||||
if (data === '\r' || data === '\n') {
|
||||
currentLine.value = '';
|
||||
clearGhost();
|
||||
sendMsg(data);
|
||||
return;
|
||||
}
|
||||
if (data === '\x7f') {
|
||||
if (currentLine.value.length > 0) {
|
||||
currentLine.value = currentLine.value.slice(0, -1);
|
||||
}
|
||||
clearGhost();
|
||||
sendMsg(data);
|
||||
scheduleSuggest();
|
||||
return;
|
||||
}
|
||||
if (data === '\x15') {
|
||||
currentLine.value = '';
|
||||
clearGhost();
|
||||
sendMsg(data);
|
||||
return;
|
||||
}
|
||||
if (data === '\x17') {
|
||||
currentLine.value = currentLine.value.replace(/\s+\S*$/, '');
|
||||
clearGhost();
|
||||
sendMsg(data);
|
||||
scheduleSuggest();
|
||||
return;
|
||||
}
|
||||
if (data.startsWith('\x1b')) {
|
||||
clearGhost();
|
||||
sendMsg(data);
|
||||
return;
|
||||
}
|
||||
currentLine.value += data;
|
||||
clearGhost();
|
||||
sendMsg(data);
|
||||
scheduleSuggest();
|
||||
}
|
||||
|
||||
// websocket 相关代码 end
|
||||
|
||||
Reference in New Issue
Block a user