reinit updates (#500)

* working on re-init when you create a tab.  some refactoring of existing reinit to make the messaging clearer.  auto-connect, etc.

* working to remove the 'default' shell states out of MShellProc.  each tab should have its own state that gets set on open.

* refactor newtab settings into individual components (and move to a new file)

* more refactoring of tab settings -- use same control in settings and newtab

* have screensettings use the same newtab settings components

* use same conn dropdown, fix classes, update some of the confirm messages to be less confusing (replace screen with tab)

* force a cr on a new tab to initialize state in a new line.  poc right now, need to add to new workspace workflow as well

* small fixups

* remove nohist from GetRawStr, make const

* update hover behavior for tabs

* fix interaction between change remote dropdown, cmdinput, error handling, and selecting a remote

* only switch screen remote if the activemainview is session (new remote flow).  don't switch it if we're on the connections page which is confusing.  also make it interactive

* fix wording on tos modal

* allow empty workspaces. also allow the last workspace to be deleted.  (prep for new startup sequence where we initialize the first session after tos modal)

* add some dead code that might come in use later (when we change how we show connection in cmdinput)

* working a cople different angles.  new settings tab-pulldown (likely orphaned).  and then allowing null activeScreen and null activeSession in workspaceview (show appropriate messages, and give buttons to create new tabs/workspaces).  prep for new startup flow

* don't call initActiveShells anymore.  also call ensureWorkspace() on TOS close

* trying to use new pulldown screen settings

* experiment with an escape keybinding

* working on tab settings close triggers

* close tab settings on tab switch

* small updates to tos popup, reorder, update button text/size, small wording updates

* when deleting a screen, send SIGHUP to all running commands

* not sure how this happened, lineid should not be passed to setLineFocus

* remove context timeouts for ReInit (it is now interactive, so it gets canceled like a normal command -- via ^C, and should not timeout on its own)

* deal with screen/session tombstones updates (ignore to quite warning)

* remove defaultfestate from remote

* fix issue with removing default ris

* remove dead code

* open the settings pulldown for new screens

* update prompt to show when the shell is still initializing (or if it failed)

* switch buttons to use wave button class, update messages, and add warning for no shell state

* all an override of rptr for dyncmds.  needed for the 'connect' command (we need to set the rptr to the *new* connection rather than the old one)

* remove old commented out code
This commit is contained in:
Mike Sawka
2024-03-27 00:22:57 -07:00
committed by GitHub
parent 6065ee931f
commit 3c3eec73aa
41 changed files with 1070 additions and 770 deletions
-5
View File
@@ -978,11 +978,6 @@ func main() {
log.Printf("[error] ensuring local remote: %v\n", err)
return
}
err = sstore.EnsureOneSession(context.Background())
if err != nil {
log.Printf("[error] ensuring default session: %v\n", err)
return
}
err = remote.LoadRemotes(context.Background())
if err != nil {
log.Printf("[error] loading remotes: %v\n", err)
+202 -76
View File
@@ -30,7 +30,6 @@ import (
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
"github.com/wavetermdev/waveterm/waveshell/pkg/server"
"github.com/wavetermdev/waveterm/waveshell/pkg/shellapi"
"github.com/wavetermdev/waveterm/waveshell/pkg/shellenv"
"github.com/wavetermdev/waveterm/waveshell/pkg/shellutil"
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec"
@@ -96,6 +95,7 @@ const (
KwArgTemplate = "template"
KwArgLang = "lang"
KwArgMinimap = "minimap"
KwArgNoHist = "nohist"
)
var ColorNames = []string{"yellow", "blue", "pink", "mint", "cyan", "violet", "orange", "green", "red", "white"}
@@ -190,6 +190,7 @@ func init() {
registerCmdFn("session:showall", SessionShowAllCommand)
registerCmdFn("session:show", SessionShowCommand)
registerCmdFn("session:openshared", SessionOpenSharedCommand)
registerCmdFn("session:ensureone", SessionEnsureOneCommand)
registerCmdFn("screen", ScreenCommand)
registerCmdFn("screen:archive", ScreenArchiveCommand)
@@ -364,6 +365,20 @@ func resolveCommaSepListToMap(arg string) map[string]bool {
return rtn
}
func resolveShellType(shellArg string, defaultShell string) (string, error) {
if shellArg == "" {
if defaultShell == "" {
shellArg = packet.ShellType_bash
} else {
shellArg = defaultShell
}
}
if shellArg != packet.ShellType_bash && shellArg != packet.ShellType_zsh {
return "", fmt.Errorf("invalid shell type %q", shellArg)
}
return shellArg, nil
}
func resolveBool(arg string, def bool) bool {
if arg == "" {
return def
@@ -723,7 +738,7 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.U
} else {
return nil, fmt.Errorf("error in Eval Meta Command: %w", rtnErr)
}
if !resolveBool(pk.Kwargs["nohist"], false) {
if !resolveBool(pk.Kwargs[KwArgNoHist], false) {
// TODO should this be "pk" or "newPk" (2nd arg)
err := addToHistory(ctx, pk, historyContext, (newPk.MetaCmd != "run"), (rtnErr != nil))
if err != nil {
@@ -816,6 +831,14 @@ func ScreenDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
if screenId == "" {
return nil, fmt.Errorf("/screen:delete no active screen or screen arg passed")
}
runningCmds, err := sstore.GetRunningScreenCmds(ctx, screenId)
if err != nil {
return nil, fmt.Errorf("/screen:delete cannot get running cmds: %v", err)
}
for _, runningCmd := range runningCmds {
// send SIGHUP to all running commands in this screen
remote.SendSignalToCmd(ctx, runningCmd, "SIGHUP")
}
update, err := sstore.DeleteScreen(ctx, screenId, false, nil)
if err != nil {
return nil, err
@@ -826,7 +849,7 @@ func ScreenDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
ids, err := resolveUiIds(ctx, pk, R_Session)
if err != nil {
return nil, fmt.Errorf("/screen:open cannot open screen: %w", err)
return nil, err
}
activate := resolveBool(pk.Kwargs["activate"], true)
newName := pk.Kwargs["name"]
@@ -836,13 +859,37 @@ func ScreenOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
return nil, err
}
}
update, err := sstore.InsertScreen(ctx, ids.SessionId, newName, sstore.ScreenCreateOpts{}, activate)
sco := sstore.ScreenCreateOpts{RtnScreenId: new(string)}
update, err := sstore.InsertScreen(ctx, ids.SessionId, newName, sco, activate)
if err != nil {
return nil, err
}
if sco.RtnScreenId == nil {
return nil, fmt.Errorf("error creating tab, no tab id returned")
}
uiContextCopy := *pk.UIContext
uiContextCopy.ScreenId = *sco.RtnScreenId
crUpdate, err := doNewTabConnectLocal(ctx, *sco.RtnScreenId, &uiContextCopy)
if err != nil {
return nil, err
}
update.Merge(crUpdate)
return update, nil
}
func doNewTabConnectLocal(ctx context.Context, screenId string, uiContext *scpacket.UIContextType) (scbus.UpdatePacket, error) {
crPk := scpacket.MakeFeCommandPacket()
crPk.MetaCmd = "connect"
crPk.Args = []string{"local"}
crPk.RawStr = "/connect local"
crPk.UIContext = uiContext
crUpdate, err := CrCommand(ctx, crPk)
if err != nil {
return nil, fmt.Errorf("error creating tab, cannot connect to remote: %w", err)
}
return crUpdate, nil
}
func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
// Resolve the UI IDs for the session and screen
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen)
@@ -1666,7 +1713,7 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
return nil, fmt.Errorf("cannot make termopts: %w", err)
}
pkTermOpts := convertTermOpts(termOpts)
cmd, err := makeDynCmd(ctx, "copy file", ids, pk.GetRawStr(), *pkTermOpts)
cmd, err := makeDynCmd(ctx, "copy file", ids, pk.GetRawStr(), *pkTermOpts, nil)
writeStringToPty(ctx, cmd, outputStr, &outputPos)
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
@@ -2462,10 +2509,16 @@ func crShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, ids re
if err != nil {
return nil, fmt.Errorf("cannot get remote instances: %w", err)
}
rmap := remote.GetRemoteMap()
if len(riArr) == 0 {
update := scbus.MakeUpdatePacket()
update.AddUpdate(sstore.InfoMsgType{
InfoMsg: "this tab has no shell states",
})
return update, nil
}
for _, ri := range riArr {
rptr := sstore.RemotePtrType{RemoteId: ri.RemoteId, Name: ri.Name}
msh := rmap[ri.RemoteId]
msh := remote.GetRemoteById(ri.RemoteId)
if msh == nil {
continue
}
@@ -2477,28 +2530,9 @@ func crShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, ids re
}
buf.WriteString(fmt.Sprintf("%-30s %-50s\n", displayName, cwdStr))
}
riBaseMap := make(map[string]bool)
for _, ri := range riArr {
if ri.Name == "" {
riBaseMap[ri.RemoteId] = true
}
}
for remoteId, msh := range rmap {
if riBaseMap[remoteId] {
continue
}
feState := msh.GetDefaultFeState(msh.GetShellPref())
if feState == nil {
continue
}
cwdStr := "-"
if feState["cwd"] != "" {
cwdStr = feState["cwd"]
}
buf.WriteString(fmt.Sprintf("%-30s %-50s (default)\n", msh.GetDisplayName(), cwdStr))
}
update := scbus.MakeUpdatePacket()
update.AddUpdate(sstore.InfoMsgType{
InfoTitle: "shell states for tab",
InfoLines: splitLinesForInfo(buf.String()),
})
return update, nil
@@ -2854,7 +2888,7 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus
return nil, fmt.Errorf("openai error, invalid 'pterm' value %q: %v", ptermVal, err)
}
termOpts := convertTermOpts(pkTermOpts)
cmd, err := makeDynCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), *termOpts)
cmd, err := makeDynCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), *termOpts, nil)
if err != nil {
return nil, fmt.Errorf("openai error, cannot make dyn cmd")
}
@@ -2936,50 +2970,104 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.Upd
if rstate.Archived {
return nil, fmt.Errorf("/%s error: remote %q cannot switch to archived remote", GetCmdStr(pk), newRemote)
}
newMsh := remote.GetRemoteById(rptr.RemoteId)
if newMsh == nil {
return nil, fmt.Errorf("/%s error: remote %q not found (msh)", GetCmdStr(pk), newRemote)
}
if !newMsh.IsConnected() {
err := newMsh.TryAutoConnect()
if err != nil {
return nil, fmt.Errorf("%q is disconnected, auto-connect failed: %w", rstate.GetBaseDisplayName(), err)
}
if !newMsh.IsConnected() {
if newMsh.GetRemoteCopy().ConnectMode == sstore.ConnectModeManual {
return nil, fmt.Errorf("%q is disconnected (must manually connect)", rstate.GetBaseDisplayName())
}
return nil, fmt.Errorf("%q is disconnected", rstate.GetBaseDisplayName())
}
}
err = sstore.UpdateCurRemote(ctx, ids.ScreenId, *rptr)
if err != nil {
return nil, fmt.Errorf("/%s error: cannot update curremote: %w", GetCmdStr(pk), err)
}
noHist := resolveBool(pk.Kwargs["nohist"], false)
if noHist {
screen, err := sstore.GetScreenById(ctx, ids.ScreenId)
ri, err := sstore.GetRemoteStatePtr(ctx, ids.SessionId, ids.ScreenId, *rptr)
if err != nil {
return nil, fmt.Errorf("/%s error looking up connection state: %w", GetCmdStr(pk), err)
}
if ri == nil {
// ok, if ri is nil we need to do a reinit
verbose := resolveBool(pk.Kwargs["verbose"], false)
shellType, err := resolveShellType(pk.Kwargs["shell"], rstate.DefaultShellType)
if err != nil {
return nil, fmt.Errorf("/%s error: cannot resolve screen for update: %w", GetCmdStr(pk), err)
return nil, err
}
update := scbus.MakeUpdatePacket()
update.AddUpdate(*screen, sstore.InteractiveUpdate(pk.Interactive))
termOpts, err := GetUITermOpts(pk.UIContext.WinSize, DefaultPTERM)
if err != nil {
return nil, fmt.Errorf("cannot make termopts: %w", err)
}
pkTermOpts := convertTermOpts(termOpts)
cmd, err := makeDynCmd(ctx, "connect", ids, pk.GetRawStr(), *pkTermOpts, &makeDynCmdOpts{OverrideRPtr: rptr})
if err != nil {
return nil, err
}
update, err := addLineForCmd(ctx, "connect", true, ids, cmd, "", nil)
if err != nil {
return nil, err
}
opts := connectOptsType{
Verbose: verbose,
ShellType: shellType,
SessionId: ids.SessionId,
ScreenId: ids.ScreenId,
RPtr: *rptr,
}
go doAsyncResetCommand(newMsh, opts, cmd)
return update, nil
} else {
outputStr := fmt.Sprintf("reconnected to %s", GetFullRemoteDisplayName(rptr, rstate))
cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr))
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil, err
}
update, err := addLineForCmd(ctx, "/"+GetCmdStr(pk), false, ids, cmd, "", nil)
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil, err
}
update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive))
return update, nil
}
outputStr := fmt.Sprintf("connected to %s", GetFullRemoteDisplayName(rptr, rstate))
cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr))
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil, err
}
update, err := addLineForCmd(ctx, "/"+GetCmdStr(pk), false, ids, cmd, "", nil)
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil, err
}
update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive))
return update, nil
}
func makeDynCmd(ctx context.Context, metaCmd string, ids resolvedIds, cmdStr string, termOpts sstore.TermOpts) (*sstore.CmdType, error) {
type makeDynCmdOpts struct {
OverrideRPtr *sstore.RemotePtrType
}
func makeDynCmd(ctx context.Context, metaCmd string, ids resolvedIds, cmdStr string, termOpts sstore.TermOpts, opts *makeDynCmdOpts) (*sstore.CmdType, error) {
var rptr scpacket.RemotePtrType
if opts != nil && opts.OverrideRPtr != nil {
rptr = *opts.OverrideRPtr
} else if ids.Remote != nil {
rptr = ids.Remote.RemotePtr
} else {
local := remote.GetLocalRemote()
rptr = scpacket.RemotePtrType{RemoteId: local.RemoteId}
}
cmd := &sstore.CmdType{
ScreenId: ids.ScreenId,
LineId: scbase.GenWaveUUID(),
CmdStr: cmdStr,
RawCmdStr: cmdStr,
Remote: ids.Remote.RemotePtr,
Remote: rptr,
TermOpts: termOpts,
Status: sstore.CmdStatusRunning,
RunOut: nil,
}
if ids.Remote.StatePtr != nil {
if ids.Remote != nil && ids.Remote.StatePtr != nil {
cmd.StatePtr = *ids.Remote.StatePtr
}
if ids.Remote.FeState != nil {
if ids.Remote != nil && ids.Remote.FeState != nil {
cmd.FeState = ids.Remote.FeState
}
err := sstore.CreateCmdPtyFile(ctx, cmd.ScreenId, cmd.LineId, cmd.TermOpts.MaxPtySize)
@@ -3369,13 +3457,32 @@ func SessionOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
return nil, err
}
}
update, err := sstore.InsertSessionWithName(ctx, newName, activate)
update, newSessionId, newScreenId, err := sstore.InsertSessionWithName(ctx, newName, activate)
if err != nil {
return nil, err
}
uiContextCopy := *pk.UIContext
uiContextCopy.SessionId = newSessionId
uiContextCopy.ScreenId = newScreenId
crUpdate, err := doNewTabConnectLocal(ctx, newScreenId, &uiContextCopy)
if err != nil {
return nil, err
}
update.Merge(crUpdate)
return update, nil
}
func SessionEnsureOneCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
numSessions, err := sstore.GetSessionCount(ctx)
if err != nil {
return nil, fmt.Errorf("cannot get number of sessions: %v", err)
}
if numSessions > 0 {
return nil, nil
}
return SessionOpenCommand(ctx, pk)
}
func makeExternLink(urlStr string) string {
return fmt.Sprintf(`https://extern?%s`, url.QueryEscape(urlStr))
}
@@ -3471,7 +3578,7 @@ func ScreenShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
if screen == nil {
return nil, fmt.Errorf("screen not found")
}
statePtr, err := remote.ResolveCurrentScreenStatePtr(ctx, ids.SessionId, ids.ScreenId, ids.Remote.RemotePtr)
statePtr, err := sstore.GetRemoteStatePtr(ctx, ids.SessionId, ids.ScreenId, ids.Remote.RemotePtr)
if err != nil {
return nil, fmt.Errorf("cannot resolve current screen stateptr: %v", err)
}
@@ -3483,8 +3590,10 @@ func ScreenShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "tabicon", screen.ScreenOpts.TabIcon))
buf.WriteString(fmt.Sprintf(" %-15s %d\n", "selectedline", screen.SelectedLine))
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "curremote", GetFullRemoteDisplayName(&screen.CurRemote, &ids.Remote.RState)))
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "stateptr-base", statePtr.BaseHash))
buf.WriteString(fmt.Sprintf(" %-15s %v\n", "stateptr-diff", statePtr.DiffHashArr))
if statePtr != nil {
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "stateptr-base", statePtr.BaseHash))
buf.WriteString(fmt.Sprintf(" %-15s %v\n", "stateptr-diff", statePtr.DiffHashArr))
}
update := scbus.MakeUpdatePacket()
update.AddUpdate(sstore.InfoMsgType{
InfoTitle: "screen info",
@@ -3682,21 +3791,17 @@ func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
if !ids.Remote.MShell.IsConnected() {
return nil, fmt.Errorf("cannot reinit, remote is not connected")
}
shellType := ids.Remote.ShellType
if pk.Kwargs["shell"] != "" {
shellArg := pk.Kwargs["shell"]
if shellArg != packet.ShellType_bash && shellArg != packet.ShellType_zsh {
return nil, fmt.Errorf("/reset invalid shell type %q", shellArg)
}
shellType = shellArg
}
verbose := resolveBool(pk.Kwargs["verbose"], false)
shellType, err := resolveShellType(pk.Kwargs["shell"], ids.Remote.ShellType)
if err != nil {
return nil, err
}
termOpts, err := GetUITermOpts(pk.UIContext.WinSize, DefaultPTERM)
if err != nil {
return nil, fmt.Errorf("cannot make termopts: %w", err)
}
pkTermOpts := convertTermOpts(termOpts)
cmd, err := makeDynCmd(ctx, "reset", ids, pk.GetRawStr(), *pkTermOpts)
cmd, err := makeDynCmd(ctx, "reset", ids, pk.GetRawStr(), *pkTermOpts, nil)
if err != nil {
return nil, err
}
@@ -3704,12 +3809,28 @@ func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
if err != nil {
return nil, err
}
go doResetCommand(ids, shellType, cmd, verbose)
opts := connectOptsType{
Verbose: verbose,
ShellType: shellType,
SessionId: ids.SessionId,
ScreenId: ids.ScreenId,
RPtr: ids.Remote.RemotePtr,
}
go doAsyncResetCommand(ids.Remote.MShell, opts, cmd)
return update, nil
}
func doResetCommand(ids resolvedIds, shellType string, cmd *sstore.CmdType, verbose bool) {
ctx, cancelFn := context.WithTimeout(context.Background(), shellapi.ReInitTimeout)
type connectOptsType struct {
ShellType string // shell type to connect with
Verbose bool // extra output (show state changes, sizes, etc.)
SessionId string
ScreenId string
RPtr sstore.RemotePtrType
}
// this does the asynchroneous part of the connection reset
func doAsyncResetCommand(msh *remote.MShellProc, opts connectOptsType, cmd *sstore.CmdType) {
ctx, cancelFn := context.WithCancel(context.Background())
defer cancelFn()
startTime := time.Now()
var outputPos int64
@@ -3725,28 +3846,30 @@ func doResetCommand(ids resolvedIds, shellType string, cmd *sstore.CmdType, verb
dataFn := func(data []byte) {
writeStringToPty(ctx, cmd, string(data), &outputPos)
}
origStatePtr := ids.Remote.MShell.GetDefaultStatePtr(shellType)
ssPk, err := ids.Remote.MShell.ReInit(ctx, base.MakeCommandKey(cmd.ScreenId, cmd.LineId), shellType, dataFn, verbose)
origStatePtr, _ := sstore.GetRemoteStatePtr(ctx, opts.SessionId, opts.ScreenId, opts.RPtr)
ssPk, err := msh.ReInit(ctx, base.MakeCommandKey(cmd.ScreenId, cmd.LineId), opts.ShellType, dataFn, opts.Verbose)
if err != nil {
rtnErr = err
return
}
if ssPk == nil || ssPk.State == nil {
rtnErr = fmt.Errorf("invalid initpk received from remote (no remote state)")
rtnErr = fmt.Errorf("no state received from connection (nil)")
return
}
feState := sstore.FeStateFromShellState(ssPk.State)
remoteInst, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.ScreenId, ids.Remote.RemotePtr, feState, ssPk.State, nil)
remoteInst, err := sstore.UpdateRemoteState(ctx, opts.SessionId, opts.ScreenId, opts.RPtr, feState, ssPk.State, nil)
if err != nil {
rtnErr = err
return
}
newStatePtr := ids.Remote.MShell.GetDefaultStatePtr(shellType)
if verbose && origStatePtr != nil && newStatePtr != nil {
newStatePtr := sstore.ShellStatePtr{
BaseHash: ssPk.State.GetHashVal(false),
}
if opts.Verbose && origStatePtr != nil {
statePtrDiff := fmt.Sprintf("oldstate: %v, newstate: %v\r\n", origStatePtr.BaseHash, newStatePtr.BaseHash)
writeStringToPty(ctx, cmd, statePtrDiff, &outputPos)
origFullState, _ := sstore.GetFullState(ctx, *origStatePtr)
newFullState, _ := sstore.GetFullState(ctx, *newStatePtr)
newFullState, _ := sstore.GetFullState(ctx, newStatePtr)
if origFullState != nil && newFullState != nil {
var diffBuf bytes.Buffer
rtnstate.DisplayStateUpdateDiff(&diffBuf, *origFullState, *newFullState)
@@ -3756,7 +3879,7 @@ func doResetCommand(ids resolvedIds, shellType string, cmd *sstore.CmdType, verb
}
}
update := scbus.MakeUpdatePacket()
update.AddUpdate(sstore.MakeSessionUpdateForRemote(ids.SessionId, remoteInst))
update.AddUpdate(sstore.MakeSessionUpdateForRemote(opts.SessionId, remoteInst))
scbus.MainUpdateBus.DoUpdate(update)
}
@@ -3765,10 +3888,13 @@ func ResetCwdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
if err != nil {
return nil, err
}
statePtr, err := remote.ResolveCurrentScreenStatePtr(ctx, ids.SessionId, ids.ScreenId, ids.Remote.RemotePtr)
statePtr, err := sstore.GetRemoteStatePtr(ctx, ids.SessionId, ids.ScreenId, ids.Remote.RemotePtr)
if err != nil {
return nil, err
}
if statePtr == nil {
return nil, fmt.Errorf("no shell state found, cannot reset cwd (run /reset)")
}
stateDiff, err := sstore.GetCurStateDiffFromPtr(ctx, statePtr)
if err != nil {
return nil, err
+3 -3
View File
@@ -41,7 +41,7 @@ type ResolvedRemote struct {
MShell *remote.MShellProc
RState remote.RemoteRuntimeState
RemoteCopy *sstore.RemoteType
ShellType string
ShellType string // default remote shell preference
StatePtr *sstore.ShellStatePtr
FeState map[string]string
}
@@ -488,8 +488,8 @@ func ResolveRemoteFromPtr(ctx context.Context, rptr *sstore.RemotePtrType, sessi
} else {
if ri == nil {
rtn.ShellType = msh.GetShellPref()
rtn.StatePtr = msh.GetDefaultStatePtr(rtn.ShellType)
rtn.FeState = msh.GetDefaultFeState(rtn.ShellType)
rtn.StatePtr = nil
rtn.FeState = nil
} else {
rtn.StatePtr = &sstore.ShellStatePtr{BaseHash: ri.StateBaseHash, DiffHashArr: ri.StateDiffHashArr}
rtn.FeState = ri.FeState
+1 -1
View File
@@ -314,7 +314,7 @@ func IsReturnStateCommand(cmdStr string) bool {
func EvalBracketArgs(origCmdStr string) (map[string]string, string, error) {
rtn := make(map[string]string)
if strings.HasPrefix(origCmdStr, " ") {
rtn["nohist"] = "1"
rtn[KwArgNoHist] = "1"
}
cmdStr := strings.TrimSpace(origCmdStr)
if !strings.HasPrefix(cmdStr, "[") {
+35 -72
View File
@@ -214,38 +214,6 @@ func (msh *MShellProc) GetStatus() string {
return msh.Status
}
func (msh *MShellProc) GetDefaultState(shellType string) *packet.ShellState {
_, state := msh.StateMap.GetCurrentState(shellType)
return state
}
func (msh *MShellProc) EnsureShellType(ctx context.Context, shellType string) error {
if msh.StateMap.HasShell(shellType) {
return nil
}
// try to reinit the shell
_, err := msh.ReInit(ctx, base.CommandKey(""), shellType, nil, false)
if err != nil {
return fmt.Errorf("error trying to initialize shell %q: %v", shellType, err)
}
return nil
}
func (msh *MShellProc) GetDefaultStatePtr(shellType string) *sstore.ShellStatePtr {
msh.Lock.Lock()
defer msh.Lock.Unlock()
hash, _ := msh.StateMap.GetCurrentState(shellType)
if hash == "" {
return nil
}
return &sstore.ShellStatePtr{BaseHash: hash}
}
func (msh *MShellProc) GetDefaultFeState(shellType string) map[string]string {
state := msh.GetDefaultState(shellType)
return sstore.FeStateFromShellState(state)
}
func (msh *MShellProc) GetRemoteId() string {
msh.Lock.Lock()
defer msh.Lock.Unlock()
@@ -489,6 +457,26 @@ func ResolveRemoteRef(remoteRef string) *RemoteRuntimeState {
return nil
}
func SendSignalToCmd(ctx context.Context, cmd *sstore.CmdType, sig string) error {
msh := GetRemoteById(cmd.Remote.RemoteId)
if msh == nil {
return fmt.Errorf("no connection found")
}
if !msh.IsConnected() {
return fmt.Errorf("not connected")
}
cmdCk := base.MakeCommandKey(cmd.ScreenId, cmd.LineId)
if !msh.IsCmdRunning(cmdCk) {
// this could also return nil (depends on use case)
// settled on coded error so we can check for this error
return base.CodedErrorf(packet.EC_CmdNotRunning, "cmd not running")
}
sigPk := packet.MakeSpecialInputPacket()
sigPk.CK = cmdCk
sigPk.SigName = sig
return msh.ServerProc.Input.SendPacket(sigPk)
}
func unquoteDQBashString(str string) (string, bool) {
if len(str) < 2 {
return str, false
@@ -588,6 +576,7 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState {
InstallStatus: msh.InstallStatus,
NeedsMShellUpgrade: msh.NeedsMShellUpgrade,
Local: msh.Remote.Local,
IsSudo: msh.Remote.IsSudo(),
NoInitPk: msh.ErrNoInitPk,
AuthType: sstore.RemoteAuthTypeNone,
ShellPref: msh.Remote.ShellPref,
@@ -661,11 +650,6 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState {
vars["besthost"] = vars["remotehost"]
vars["bestshorthost"] = vars["remoteshorthost"]
}
_, curState := msh.StateMap.GetCurrentState(shellPref)
if curState != nil {
state.DefaultFeState = sstore.FeStateFromShellState(curState)
vars["cwd"] = curState.Cwd
}
if msh.Remote.Local && msh.Remote.IsSudo() {
vars["bestuser"] = "sudo"
} else if msh.Remote.IsSudo() {
@@ -687,7 +671,6 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState {
varsCopy[key] = value
}
state.RemoteVars = varsCopy
state.ActiveShells = msh.StateMap.GetShells()
return state
}
@@ -932,9 +915,9 @@ func (msh *MShellProc) writeToPtyBuffer_nolock(strFmt string, args ...interface{
realStr = realStr + "\r\n"
}
if strings.HasPrefix(realStr, "*") {
realStr = "\033[0m\033[31mprompt>\033[0m " + realStr[1:]
realStr = "\033[0m\033[31mwave>\033[0m " + realStr[1:]
} else {
realStr = "\033[0m\033[32mprompt>\033[0m " + realStr
realStr = "\033[0m\033[32mwave>\033[0m " + realStr
}
barr := msh.PtyBuffer.Bytes()
if len(barr) > 0 && barr[len(barr)-1] != '\n' {
@@ -1509,18 +1492,18 @@ func (msh *MShellProc) ReInit(ctx context.Context, ck base.CommandKey, shellType
}
func makeShellInitOutputMsg(verbose bool, state *packet.ShellState, stats *packet.ShellStateStats, dur time.Duration, ptyMsg bool) string {
waveStr := fmt.Sprintf("%swave>%s", utilfn.AnsiGreenColor(), utilfn.AnsiResetColor())
if !verbose || ptyMsg {
if ptyMsg {
return fmt.Sprintf("initialized state shell:%s statehash:%s %dms\n", state.GetShellType(), state.GetHashVal(false), dur.Milliseconds())
} else {
return fmt.Sprintf("initialized connection state (shell:%s)\r\n", state.GetShellType())
return fmt.Sprintf("%s initialized connection state (shell:%s)\r\n", waveStr, state.GetShellType())
}
}
var buf bytes.Buffer
buf.WriteString("-----\r\n")
buf.WriteString(fmt.Sprintf("initialized connection shell:%s statehash:%s %dms\r\n", state.GetShellType(), state.GetHashVal(false), dur.Milliseconds()))
buf.WriteString(fmt.Sprintf("%s initialized connection shell:%s statehash:%s %dms\r\n", waveStr, state.GetShellType(), state.GetHashVal(false), dur.Milliseconds()))
if stats != nil {
buf.WriteString(fmt.Sprintf(" outsize:%s size:%s env:%d, vars:%d, aliases:%d, funcs:%d\r\n", scbase.NumFormatDec(stats.OutputSize), scbase.NumFormatDec(stats.StateSize), stats.EnvCount, stats.VarCount, stats.AliasCount, stats.FuncCount))
buf.WriteString(fmt.Sprintf("%s outsize:%s size:%s env:%d, vars:%d, aliases:%d, funcs:%d\r\n", waveStr, scbase.NumFormatDec(stats.OutputSize), scbase.NumFormatDec(stats.StateSize), stats.EnvCount, stats.VarCount, stats.AliasCount, stats.FuncCount))
}
return buf.String()
}
@@ -1762,7 +1745,7 @@ func (msh *MShellProc) Launch(interactive bool) {
msh.WriteToPtyBuffer("*disconnected exitcode=%d\n", exitCode)
}()
go msh.ProcessPackets()
msh.initActiveShells()
// msh.initActiveShells()
go msh.NotifyRemoteUpdate()
}
@@ -1780,7 +1763,7 @@ func (msh *MShellProc) initActiveShells() {
wg.Add(1)
go func(shellType string) {
defer wg.Done()
reinitCtx, cancelFn := context.WithTimeout(context.Background(), shellapi.ReInitTimeout)
reinitCtx, cancelFn := context.WithTimeout(context.Background(), 12*time.Second)
defer cancelFn()
_, err = msh.ReInit(reinitCtx, base.CommandKey(""), shellType, nil, false)
if err != nil {
@@ -1894,25 +1877,6 @@ func (msh *MShellProc) removePendingStateCmd(screenId string, rptr sstore.Remote
}
}
func ResolveCurrentScreenStatePtr(ctx context.Context, sessionId string, screenId string, remotePtr sstore.RemotePtrType) (*sstore.ShellStatePtr, error) {
statePtr, err := sstore.GetRemoteStatePtr(ctx, sessionId, screenId, remotePtr)
if err != nil {
return nil, fmt.Errorf("cannot get current connection stateptr: %w", err)
}
if statePtr == nil {
msh := GetRemoteById(remotePtr.RemoteId)
err := msh.EnsureShellType(ctx, msh.GetShellPref()) // make sure shellType is initialized
if err != nil {
return nil, err
}
statePtr = msh.GetDefaultStatePtr(msh.GetShellPref())
if statePtr == nil {
return nil, fmt.Errorf("no valid default connection stateptr")
}
}
return statePtr, nil
}
type RunCommandOpts struct {
SessionId string
ScreenId string
@@ -1990,10 +1954,13 @@ func RunCommand(ctx context.Context, rcOpts RunCommandOpts, runPacket *packet.Ru
statePtr = rcOpts.StatePtr
} else {
var err error
statePtr, err = ResolveCurrentScreenStatePtr(ctx, sessionId, screenId, remotePtr)
statePtr, err = sstore.GetRemoteStatePtr(ctx, sessionId, screenId, remotePtr)
if err != nil {
return nil, nil, fmt.Errorf("cannot run command: %w", err)
}
if statePtr == nil {
return nil, nil, fmt.Errorf("cannot run command: no valid shell state found")
}
}
currentState, err := sstore.GetFullState(ctx, *statePtr)
if err != nil || currentState == nil {
@@ -2002,10 +1969,6 @@ func RunCommand(ctx context.Context, rcOpts RunCommandOpts, runPacket *packet.Ru
runPacket.State = addScVarsToState(currentState)
runPacket.StateComplete = true
runPacket.ShellType = currentState.GetShellType()
err = msh.EnsureShellType(ctx, runPacket.ShellType) // make sure shellType is initialized
if err != nil {
return nil, nil, err
}
// start cmdwait. must be started before sending the run packet
// this ensures that we don't process output, or cmddone packets until we set up the line, cmd, and ptyout file
@@ -2144,7 +2107,7 @@ func (msh *MShellProc) HandleFeInput(inputPk *scpacket.FeInputPacketType) error
msh.Lock.Unlock()
if sink == nil {
// no sink and no running command
return fmt.Errorf("cannot send input, cmd is not running")
return fmt.Errorf("cannot send input, cmd is not running (%s)", inputPk.CK)
}
return sink.HandleInput(inputPk)
}
@@ -2461,7 +2424,7 @@ func (msh *MShellProc) processSinglePacket(pk packet.PacketType) {
msh.WriteToPtyBuffer("stderr> [remote %s] %s\n", msh.GetRemoteName(), rawPk.Data)
return
}
msh.WriteToPtyBuffer("MSH> [remote %s] unhandled packet %s\n", msh.GetRemoteName(), packet.AsString(pk))
msh.WriteToPtyBuffer("*[remote %s] unhandled packet %s\n", msh.GetRemoteName(), packet.AsString(pk))
}
func (msh *MShellProc) ProcessPackets() {
+11
View File
@@ -5,6 +5,7 @@ package scbus
import (
"encoding/json"
"fmt"
"reflect"
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
@@ -96,6 +97,16 @@ func (upk *ModelUpdatePacketType) AddUpdate(items ...ModelUpdateItem) {
*(upk.Data) = append(*(upk.Data), items...)
}
// adds the items from p2 to the update (p2 must be ModelUpdatePacketType)
func (upk *ModelUpdatePacketType) Merge(p2Arg UpdatePacket) error {
p2, ok := p2Arg.(*ModelUpdatePacketType)
if !ok {
return fmt.Errorf("cannot merge ModelUpdatePacketType with %T", p2Arg)
}
*(upk.Data) = append(*(upk.Data), *(p2.Data)...)
return nil
}
// Create a new model update packet
func MakeUpdatePacket() *ModelUpdatePacketType {
return &ModelUpdatePacketType{
+3
View File
@@ -106,6 +106,9 @@ func (pk *FeCommandPacketType) GetRawStr() string {
}
var args []string
for k, v := range pk.Kwargs {
if k == "nohist" {
continue
}
argStr := fmt.Sprintf("%s=%s", shellescape.Quote(k), shellescape.Quote(v))
args = append(args, argStr)
}
+8 -10
View File
@@ -386,9 +386,9 @@ func GetSessionByName(ctx context.Context, name string) (*SessionType, error) {
return session, nil
}
// returns sessionId
// returns (update, newSessionId, newScreenId, error)
// if sessionName == "", it will be generated
func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (*scbus.ModelUpdatePacketType, error) {
func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (*scbus.ModelUpdatePacketType, string, string, error) {
var newScreen *ScreenType
newSessionId := scbase.GenWaveUUID()
txErr := WithTx(ctx, func(tx *TxWrap) error {
@@ -414,11 +414,11 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo
return nil
})
if txErr != nil {
return nil, txErr
return nil, "", "", txErr
}
session, err := GetSessionById(ctx, newSessionId)
if err != nil {
return nil, err
return nil, "", "", err
}
update := scbus.MakeUpdatePacket()
update.AddUpdate(*session)
@@ -426,7 +426,7 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo
if activate {
update.AddUpdate(ActiveSessionIdUpdate(newSessionId))
}
return update, nil
return update, newSessionId, newScreen.ScreenId, nil
}
func SetActiveSessionId(ctx context.Context, sessionId string) error {
@@ -569,6 +569,9 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string,
query = `UPDATE session SET activescreenid = ? WHERE sessionid = ?`
tx.Exec(query, newScreenId, sessionId)
}
if opts.RtnScreenId != nil {
*opts.RtnScreenId = newScreenId
}
return nil
})
if txErr != nil {
@@ -1044,11 +1047,6 @@ func DeleteScreen(ctx context.Context, screenId string, sessionDel bool, update
if sessionId == "" {
return fmt.Errorf("cannot delete screen (no sessionid)")
}
query = `SELECT count(*) FROM screen WHERE sessionid = ? AND NOT archived`
numScreens := tx.GetInt(query, sessionId)
if numScreens <= 1 {
return fmt.Errorf("cannot delete the last screen in a session")
}
isActive = tx.Exists(`SELECT sessionid FROM session WHERE sessionid = ? AND activescreenid = ?`, sessionId, screenId)
if isActive {
screenIds := tx.SelectStrings(`SELECT screenid FROM screen WHERE sessionid = ? AND NOT archived ORDER BY screenidx`, sessionId)
+2 -17
View File
@@ -375,6 +375,7 @@ type ScreenCreateOpts struct {
CopyRemote bool
CopyCwd bool
CopyEnv bool
RtnScreenId *string
}
func (sco ScreenCreateOpts) HasCopy() bool {
@@ -760,7 +761,6 @@ type RemoteRuntimeState struct {
RemoteAlias string `json:"remotealias,omitempty"`
RemoteCanonicalName string `json:"remotecanonicalname"`
RemoteVars map[string]string `json:"remotevars"`
DefaultFeState map[string]string `json:"defaultfestate"`
Status string `json:"status"`
ConnectTimeout int `json:"connecttimeout,omitempty"`
CountdownActive bool `json:"countdownactive"`
@@ -779,9 +779,9 @@ type RemoteRuntimeState struct {
MShellVersion string `json:"mshellversion"`
WaitingForPassword bool `json:"waitingforpassword,omitempty"`
Local bool `json:"local,omitempty"`
IsSudo bool `json:"issudo,omitempty"`
RemoteOpts *RemoteOptsType `json:"remoteopts,omitempty"`
CanComplete bool `json:"cancomplete,omitempty"`
ActiveShells []string `json:"activeshells,omitempty"`
ShellPref string `json:"shellpref,omitempty"`
DefaultShellType string `json:"defaultshelltype,omitempty"`
}
@@ -1127,21 +1127,6 @@ func EnsureLocalRemote(ctx context.Context) error {
return nil
}
func EnsureOneSession(ctx context.Context) error {
numSessions, err := GetSessionCount(ctx)
if err != nil {
return err
}
if numSessions > 0 {
return nil
}
_, err = InsertSessionWithName(ctx, DefaultSessionName, true)
if err != nil {
return err
}
return nil
}
func createClientData(tx *TxWrap) error {
curve := elliptic.P384()
pkey, err := ecdsa.GenerateKey(curve, rand.Reader)