Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Has been cancelled
Go + Wails v3 + Vue 3 + SQLite + FreeRDP3 (purego) 183 tests, 76 source files, 9,910 lines of code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
129 lines
3.3 KiB
Go
129 lines
3.3 KiB
Go
package ssh
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/url"
|
|
"sync"
|
|
)
|
|
|
|
// CWDTracker parses OSC 7 escape sequences from terminal output to track the
|
|
// remote working directory.
|
|
type CWDTracker struct {
|
|
currentPath string
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewCWDTracker creates a new CWDTracker.
|
|
func NewCWDTracker() *CWDTracker {
|
|
return &CWDTracker{}
|
|
}
|
|
|
|
// osc7Prefix is the escape sequence that starts an OSC 7 directive.
|
|
var osc7Prefix = []byte("\033]7;")
|
|
|
|
// stTerminator is the ST (String Terminator) escape: ESC + backslash.
|
|
var stTerminator = []byte("\033\\")
|
|
|
|
// belTerminator is the BEL character, an alternative OSC terminator.
|
|
var belTerminator = []byte{0x07}
|
|
|
|
// ProcessOutput scans data for OSC 7 sequences of the form:
|
|
//
|
|
// \033]7;file://hostname/path\033\\ (ST terminator)
|
|
// \033]7;file://hostname/path\007 (BEL terminator)
|
|
//
|
|
// It returns cleaned output with all OSC 7 sequences stripped and the new CWD
|
|
// path (or "" if no OSC 7 was found).
|
|
func (t *CWDTracker) ProcessOutput(data []byte) (cleaned []byte, newCWD string) {
|
|
var result []byte
|
|
remaining := data
|
|
var lastCWD string
|
|
|
|
for {
|
|
idx := bytes.Index(remaining, osc7Prefix)
|
|
if idx == -1 {
|
|
result = append(result, remaining...)
|
|
break
|
|
}
|
|
|
|
// Append everything before the OSC 7 sequence.
|
|
result = append(result, remaining[:idx]...)
|
|
|
|
// Find the end of the OSC 7 payload (after the prefix).
|
|
afterPrefix := remaining[idx+len(osc7Prefix):]
|
|
|
|
// Try ST terminator first (\033\\), then BEL (\007).
|
|
endIdx := -1
|
|
terminatorLen := 0
|
|
|
|
if stIdx := bytes.Index(afterPrefix, stTerminator); stIdx != -1 {
|
|
endIdx = stIdx
|
|
terminatorLen = len(stTerminator)
|
|
}
|
|
if belIdx := bytes.Index(afterPrefix, belTerminator); belIdx != -1 {
|
|
if endIdx == -1 || belIdx < endIdx {
|
|
endIdx = belIdx
|
|
terminatorLen = len(belTerminator)
|
|
}
|
|
}
|
|
|
|
if endIdx == -1 {
|
|
// No terminator found; treat the rest as literal output.
|
|
result = append(result, remaining[idx:]...)
|
|
break
|
|
}
|
|
|
|
// Extract the URI payload between prefix and terminator.
|
|
payload := string(afterPrefix[:endIdx])
|
|
if path := extractPathFromOSC7(payload); path != "" {
|
|
lastCWD = path
|
|
}
|
|
|
|
remaining = afterPrefix[endIdx+terminatorLen:]
|
|
}
|
|
|
|
if lastCWD != "" {
|
|
t.mu.Lock()
|
|
t.currentPath = lastCWD
|
|
t.mu.Unlock()
|
|
}
|
|
|
|
return result, lastCWD
|
|
}
|
|
|
|
// GetCWD returns the current tracked working directory.
|
|
func (t *CWDTracker) GetCWD() string {
|
|
t.mu.RLock()
|
|
defer t.mu.RUnlock()
|
|
return t.currentPath
|
|
}
|
|
|
|
// extractPathFromOSC7 parses a file:// URI and returns the path component.
|
|
func extractPathFromOSC7(uri string) string {
|
|
u, err := url.Parse(uri)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
if u.Scheme != "file" {
|
|
return ""
|
|
}
|
|
return u.Path
|
|
}
|
|
|
|
// ShellIntegrationCommand returns the shell command to inject for CWD tracking
|
|
// via OSC 7. The returned command sets up a prompt hook that emits the OSC 7
|
|
// escape sequence after every command.
|
|
func ShellIntegrationCommand(shellType string) string {
|
|
switch shellType {
|
|
case "bash":
|
|
return fmt.Sprintf(`PROMPT_COMMAND='printf "\033]7;file://%%s%%s\033\\" "$HOSTNAME" "$PWD"'`)
|
|
case "zsh":
|
|
return fmt.Sprintf(`precmd() { printf '\033]7;file://%%s%%s\033\\' "$HOST" "$PWD" }`)
|
|
case "fish":
|
|
return `function __wraith_osc7 --on-event fish_prompt; printf '\033]7;file://%s%s\033\\' (hostname) (pwd); end`
|
|
default:
|
|
return ""
|
|
}
|
|
}
|