resize observer + run an ls command

This commit is contained in:
sawka 2024-05-14 22:37:04 -07:00
parent 35c6b232fc
commit 5b2a5eb5eb
4 changed files with 139 additions and 12 deletions

View File

@ -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);
}

View File

@ -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)

View File

@ -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

View File

@ -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 != "" {