diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index 0ab2ea634..ab845c496 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -46,6 +46,7 @@ type TermWrapOptions = { export class TermWrap { blockId: string; ptyOffset: number; + pendingPtyOffset: number; dataBytesProcessed: number; terminal: Terminal; connectElem: HTMLDivElement; @@ -56,6 +57,7 @@ export class TermWrap { heldData: Uint8Array[]; handleResize_debounced: () => void; hasResized: boolean; + isLoadingCache: boolean; constructor( blockId: string, @@ -64,6 +66,7 @@ export class TermWrap { waveOptions: TermWrapOptions ) { this.loaded = false; + this.isLoadingCache = false; this.blockId = blockId; this.ptyOffset = 0; this.dataBytesProcessed = 0; @@ -165,11 +168,20 @@ export class TermWrap { } handleTermData(data: string) { + if (this.isLoadingCache) { + return; + } if (!this.loaded) { return; } const b64data = util.stringToBase64(data); - RpcApi.ControllerInputCommand(TabRpcClient, { blockid: this.blockId, inputdata64: b64data }); + const actionId = util.getNextActionId(); + RpcApi.ControllerInputCommand(TabRpcClient, { + blockid: this.blockId, + inputdata64: b64data, + feactionid: actionId, + pendingptyoffset: this.pendingPtyOffset, + }); } addFocusListener(focusFn: () => void) { @@ -198,11 +210,14 @@ export class TermWrap { let prtn = new Promise((presolve, _) => { resolve = presolve; }); + if (setPtyOffset != null) { + this.pendingPtyOffset = setPtyOffset; + } else { + this.pendingPtyOffset = this.ptyOffset + data.length; + } this.terminal.write(data, () => { - if (setPtyOffset != null) { - this.ptyOffset = setPtyOffset; - } else { - this.ptyOffset += data.length; + this.ptyOffset = this.pendingPtyOffset; + if (setPtyOffset == null) { this.dataBytesProcessed += data.length; } resolve(); @@ -217,20 +232,25 @@ export class TermWrap { if (cacheFile != null) { ptyOffset = cacheFile.meta["ptyoffset"] ?? 0; if (cacheData.byteLength > 0) { - const curTermSize: TermSize = { rows: this.terminal.rows, cols: this.terminal.cols }; - const fileTermSize: TermSize = cacheFile.meta["termsize"]; - let didResize = false; - if ( - fileTermSize != null && - (fileTermSize.rows != curTermSize.rows || fileTermSize.cols != curTermSize.cols) - ) { - console.log("terminal restore size mismatch, temp resize", fileTermSize, curTermSize); - this.terminal.resize(fileTermSize.cols, fileTermSize.rows); - didResize = true; - } - this.doTerminalWrite(cacheData, ptyOffset); - if (didResize) { - this.terminal.resize(curTermSize.cols, curTermSize.rows); + try { + this.isLoadingCache = true; + const curTermSize: TermSize = { rows: this.terminal.rows, cols: this.terminal.cols }; + const fileTermSize: TermSize = cacheFile.meta["termsize"]; + let didResize = false; + if ( + fileTermSize != null && + (fileTermSize.rows != curTermSize.rows || fileTermSize.cols != curTermSize.cols) + ) { + console.log("terminal restore size mismatch, temp resize", fileTermSize, curTermSize); + this.terminal.resize(fileTermSize.cols, fileTermSize.rows); + didResize = true; + } + await this.doTerminalWrite(cacheData, ptyOffset); + if (didResize) { + this.terminal.resize(curTermSize.cols, curTermSize.rows); + } + } finally { + this.isLoadingCache = false; } } } diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index c0149d01e..4a893746b 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -116,6 +116,8 @@ declare global { blockid: string; inputdata64?: string; signame?: string; + pendingptyoffset?: number; + feactionid?: string; termsize?: TermSize; }; diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go index 35d32eb68..083175d23 100644 --- a/pkg/blockcontroller/blockcontroller.go +++ b/pkg/blockcontroller/blockcontroller.go @@ -62,24 +62,27 @@ var globalLock = &sync.Mutex{} var blockControllerMap = make(map[string]*BlockController) type BlockInputUnion struct { - InputData []byte `json:"inputdata,omitempty"` - SigName string `json:"signame,omitempty"` - TermSize *waveobj.TermSize `json:"termsize,omitempty"` + InputData []byte `json:"inputdata,omitempty"` + SigName string `json:"signame,omitempty"` + TermSize *waveobj.TermSize `json:"termsize,omitempty"` + FeActionId string `json:"feactionid,omitempty"` } type BlockController struct { - Lock *sync.Mutex - ControllerType string - TabId string - BlockId string - BlockDef *waveobj.BlockDef - CreatedHtmlFile bool - ShellProc *shellexec.ShellProc - ShellInputCh chan *BlockInputUnion - ShellProcStatus string - ShellProcExitCode int - RunLock *atomic.Bool - StatusVersion int + Lock *sync.Mutex + ControllerType string + TabId string + BlockId string + BlockDef *waveobj.BlockDef + CreatedHtmlFile bool + ShellProc *shellexec.ShellProc + ShellInputCh chan *BlockInputUnion + ShellProcStatus string + ShellProcExitCode int + RunLock *atomic.Bool + StatusVersion int + ProcessedToOffset int64 + LastResizeActionId string } type BlockControllerRuntimeStatus struct { @@ -117,6 +120,16 @@ func (bc *BlockController) getShellProc() *shellexec.ShellProc { return bc.ShellProc } +func (bc *BlockController) TestAndSetResizeActionId(actionId string) bool { + bc.Lock.Lock() + defer bc.Lock.Unlock() + if actionId <= bc.LastResizeActionId { + return false + } + bc.LastResizeActionId = actionId + return true +} + type RunShellOpts struct { TermSize waveobj.TermSize `json:"termsize,omitempty"` } @@ -469,13 +482,16 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj shellProc.Cmd.Write(ic.InputData) } if ic.TermSize != nil { - err = setTermSize(ctx, bc.BlockId, *ic.TermSize) - if err != nil { - log.Printf("error setting pty size: %v\n", err) - } - err = shellProc.Cmd.SetSize(ic.TermSize.Rows, ic.TermSize.Cols) - if err != nil { - log.Printf("error setting pty size: %v\n", err) + ok := bc.TestAndSetResizeActionId(ic.FeActionId) + if ok { + err = setTermSize(ctx, bc.BlockId, *ic.TermSize) + if err != nil { + log.Printf("error setting pty size: %v\n", err) + } + err = shellProc.Cmd.SetSize(ic.TermSize.Rows, ic.TermSize.Cols) + if err != nil { + log.Printf("error setting pty size: %v\n", err) + } } } } diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 71806f6b0..077af53b0 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -312,10 +312,12 @@ type CommandControllerResyncData struct { } type CommandBlockInputData struct { - BlockId string `json:"blockid" wshcontext:"BlockId"` - InputData64 string `json:"inputdata64,omitempty"` - SigName string `json:"signame,omitempty"` - TermSize *waveobj.TermSize `json:"termsize,omitempty"` + BlockId string `json:"blockid" wshcontext:"BlockId"` + InputData64 string `json:"inputdata64,omitempty"` + SigName string `json:"signame,omitempty"` + PendingPtyOffset int64 `json:"pendingptyoffset,omitempty"` + FeActionId string `json:"feactionid,omitempty"` + TermSize *waveobj.TermSize `json:"termsize,omitempty"` } type CommandFileDataAt struct { diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go index b4e2acfec..56dbc58cf 100644 --- a/pkg/wshrpc/wshserver/wshserver.go +++ b/pkg/wshrpc/wshserver/wshserver.go @@ -242,8 +242,9 @@ func (ws *WshServer) ControllerInputCommand(ctx context.Context, data wshrpc.Com return fmt.Errorf("block controller not found for block %q", data.BlockId) } inputUnion := &blockcontroller.BlockInputUnion{ - SigName: data.SigName, - TermSize: data.TermSize, + SigName: data.SigName, + TermSize: data.TermSize, + FeActionId: data.FeActionId, } if len(data.InputData64) > 0 { inputBuf := make([]byte, base64.StdEncoding.DecodedLen(len(data.InputData64)))