mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
resize observer + run an ls command
This commit is contained in:
parent
35c6b232fc
commit
5b2a5eb5eb
@ -64,29 +64,33 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
|
||||
fitAddon.fit();
|
||||
term.write("Hello, world!\r\n");
|
||||
setTerm(term);
|
||||
return () => {
|
||||
term.dispose();
|
||||
};
|
||||
}, [connectElemRef.current]);
|
||||
React.useEffect(() => {
|
||||
if (!term) {
|
||||
return;
|
||||
}
|
||||
|
||||
// resize observer
|
||||
const rszObs = new ResizeObserver(() => {
|
||||
fitAddon.fit();
|
||||
});
|
||||
rszObs.observe(connectElemRef.current);
|
||||
|
||||
// block subject
|
||||
const blockSubject = getBlockSubject(blockId);
|
||||
blockSubject.subscribe((data) => {
|
||||
// base64 decode
|
||||
const decodedData = base64ToArray(data.ptydata);
|
||||
term.write(decodedData);
|
||||
});
|
||||
|
||||
return () => {
|
||||
term.dispose();
|
||||
rszObs.disconnect();
|
||||
blockSubject.release();
|
||||
};
|
||||
}, [term]);
|
||||
}, [connectElemRef.current]);
|
||||
|
||||
async function handleRunClick() {
|
||||
try {
|
||||
await BlockService.StartBlock(blockId);
|
||||
await BlockService.SendCommand(blockId, { command: "message", message: "Run clicked" });
|
||||
let termSize = { rows: term.rows, cols: term.cols };
|
||||
await BlockService.SendCommand(blockId, { command: "run", cmdstr: "ls -l", termsize: termSize });
|
||||
} catch (e) {
|
||||
console.log("run click error: ", e);
|
||||
}
|
||||
|
@ -7,10 +7,14 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wavetermdev/thenextwave/pkg/eventbus"
|
||||
"github.com/wavetermdev/thenextwave/pkg/shellexec"
|
||||
"github.com/wavetermdev/thenextwave/pkg/util/shellutil"
|
||||
)
|
||||
|
||||
var globalLock = &sync.Mutex{}
|
||||
@ -29,6 +33,16 @@ func (mc *MessageCommand) GetCommand() string {
|
||||
return "message"
|
||||
}
|
||||
|
||||
type RunCommand struct {
|
||||
Command string `json:"command"`
|
||||
CmdStr string `json:"cmdstr"`
|
||||
TermSize shellexec.TermSize `json:"termsize"`
|
||||
}
|
||||
|
||||
func (rc *RunCommand) GetCommand() string {
|
||||
return "run"
|
||||
}
|
||||
|
||||
type BlockController struct {
|
||||
BlockId string
|
||||
InputCh chan BlockCommand
|
||||
@ -51,11 +65,45 @@ func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) {
|
||||
return nil, fmt.Errorf("error unmarshalling message command: %w", err)
|
||||
}
|
||||
return &cmd, nil
|
||||
case "run":
|
||||
var cmd RunCommand
|
||||
err := json.Unmarshal(mapJson, &cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling run command: %w", err)
|
||||
}
|
||||
return &cmd, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown command type %q", cmdType)
|
||||
}
|
||||
}
|
||||
|
||||
func (bc *BlockController) StartShellCommand(rc *RunCommand) error {
|
||||
cmdStr := rc.CmdStr
|
||||
shellPath := shellutil.DetectLocalShellPath()
|
||||
ecmd := exec.Command(shellPath, "-c", cmdStr)
|
||||
log.Printf("running shell command: %q %q\n", shellPath, cmdStr)
|
||||
barr, err := shellexec.RunSimpleCmdInPty(ecmd, rc.TermSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for len(barr) > 0 {
|
||||
part := barr
|
||||
if len(part) > 4096 {
|
||||
part = part[:4096]
|
||||
}
|
||||
eventbus.SendEvent(application.WailsEvent{
|
||||
Name: "block:ptydata",
|
||||
Data: map[string]any{
|
||||
"blockid": bc.BlockId,
|
||||
"blockfile": "main",
|
||||
"ptydata": base64.StdEncoding.EncodeToString(part),
|
||||
},
|
||||
})
|
||||
barr = barr[len(part):]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *BlockController) Run() {
|
||||
defer func() {
|
||||
eventbus.SendEvent(application.WailsEvent{
|
||||
@ -81,6 +129,14 @@ func (bc *BlockController) Run() {
|
||||
"ptydata": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("message %d\r\n", messageCount))),
|
||||
},
|
||||
})
|
||||
case *RunCommand:
|
||||
fmt.Printf("RUN: %s | %q\n", bc.BlockId, cmd.CmdStr)
|
||||
go func() {
|
||||
err := bc.StartShellCommand(cmd)
|
||||
if err != nil {
|
||||
log.Printf("error running shell command: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
default:
|
||||
fmt.Printf("unknown command type %T\n", cmd)
|
||||
|
@ -15,14 +15,26 @@ import (
|
||||
"github.com/wavetermdev/thenextwave/pkg/util/shellutil"
|
||||
)
|
||||
|
||||
func RunSimpleCmdInPty(ecmd *exec.Cmd) ([]byte, error) {
|
||||
type TermSize struct {
|
||||
Rows int `json:"rows"`
|
||||
Cols int `json:"cols"`
|
||||
}
|
||||
|
||||
func RunSimpleCmdInPty(ecmd *exec.Cmd, termSize TermSize) ([]byte, error) {
|
||||
ecmd.Env = os.Environ()
|
||||
shellutil.UpdateCmdEnv(ecmd, shellutil.WaveshellEnvVars(shellutil.DefaultTermType))
|
||||
cmdPty, cmdTty, err := pty.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening new pty: %w", err)
|
||||
}
|
||||
pty.Setsize(cmdPty, &pty.Winsize{Rows: shellutil.DefaultTermRows, Cols: shellutil.DefaultTermCols})
|
||||
if termSize.Rows == 0 || termSize.Cols == 0 {
|
||||
termSize.Rows = shellutil.DefaultTermRows
|
||||
termSize.Cols = shellutil.DefaultTermCols
|
||||
}
|
||||
if termSize.Rows <= 0 || termSize.Cols <= 0 {
|
||||
return nil, fmt.Errorf("invalid term size: %v", termSize)
|
||||
}
|
||||
pty.Setsize(cmdPty, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
|
||||
ecmd.Stdin = cmdTty
|
||||
ecmd.Stdout = cmdTty
|
||||
ecmd.Stderr = cmdTty
|
||||
|
@ -4,9 +4,15 @@
|
||||
package shellutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/wavetermdev/thenextwave/pkg/wavebase"
|
||||
)
|
||||
@ -15,6 +21,55 @@ const DefaultTermType = "xterm-256color"
|
||||
const DefaultTermRows = 24
|
||||
const DefaultTermCols = 80
|
||||
|
||||
var cachedMacUserShell string
|
||||
var macUserShellOnce = &sync.Once{}
|
||||
var userShellRegexp = regexp.MustCompile(`^UserShell: (.*)$`)
|
||||
|
||||
const DefaultShellPath = "/bin/bash"
|
||||
|
||||
func DetectLocalShellPath() string {
|
||||
shellPath := GetMacUserShell()
|
||||
if shellPath == "" {
|
||||
shellPath = os.Getenv("SHELL")
|
||||
}
|
||||
if shellPath == "" {
|
||||
return DefaultShellPath
|
||||
}
|
||||
return shellPath
|
||||
}
|
||||
|
||||
func GetMacUserShell() string {
|
||||
if runtime.GOOS != "darwin" {
|
||||
return ""
|
||||
}
|
||||
macUserShellOnce.Do(func() {
|
||||
cachedMacUserShell = internalMacUserShell()
|
||||
})
|
||||
return cachedMacUserShell
|
||||
}
|
||||
|
||||
// dscl . -read /User/[username] UserShell
|
||||
// defaults to /bin/bash
|
||||
func internalMacUserShell() string {
|
||||
osUser, err := user.Current()
|
||||
if err != nil {
|
||||
return DefaultShellPath
|
||||
}
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancelFn()
|
||||
userStr := "/Users/" + osUser.Username
|
||||
out, err := exec.CommandContext(ctx, "dscl", ".", "-read", userStr, "UserShell").CombinedOutput()
|
||||
if err != nil {
|
||||
return DefaultShellPath
|
||||
}
|
||||
outStr := strings.TrimSpace(string(out))
|
||||
m := userShellRegexp.FindStringSubmatch(outStr)
|
||||
if m == nil {
|
||||
return DefaultShellPath
|
||||
}
|
||||
return m[1]
|
||||
}
|
||||
|
||||
func WaveshellEnvVars(termType string) map[string]string {
|
||||
rtn := make(map[string]string)
|
||||
if termType != "" {
|
||||
|
Loading…
Reference in New Issue
Block a user