diff --git a/frontend/app/view/term.tsx b/frontend/app/view/term.tsx index e49685ddb..c06cc0372 100644 --- a/frontend/app/view/term.tsx +++ b/frontend/app/view/term.tsx @@ -40,14 +40,21 @@ function getThemeFromCSSVars(el: Element): ITheme { return theme; } +type InitialLoadDataType = { + loaded: boolean; + heldData: Uint8Array[]; +}; + const TerminalView = ({ blockId }: { blockId: string }) => { const connectElemRef = React.useRef(null); const termRef = React.useRef(null); + const initialLoadRef = React.useRef({ loaded: false, heldData: [] }); React.useEffect(() => { if (!connectElemRef.current) { return; } + console.log("terminal created"); const term = new Terminal({ theme: getThemeFromCSSVars(connectElemRef.current), fontSize: 12, @@ -89,7 +96,11 @@ const TerminalView = ({ blockId }: { blockId: string }) => { blockSubject.subscribe((data) => { // base64 decode const decodedData = base64ToArray(data.ptydata); - term.write(decodedData); + if (initialLoadRef.current.loaded) { + term.write(decodedData); + } else { + initialLoadRef.current.heldData.push(decodedData); + } }); return () => { @@ -99,6 +110,39 @@ const TerminalView = ({ blockId }: { blockId: string }) => { }; }, [connectElemRef.current]); + React.useEffect(() => { + if (!termRef.current) { + return; + } + // load data from blockfile + const startTs = Date.now(); + let loadedBytes = 0; + const localTerm = termRef.current; // avoids devmode double effect running issue (terminal gets created twice) + const usp = new URLSearchParams(); + usp.set("blockid", blockId); + usp.set("name", "main"); + fetch("/wave/blockfile?" + usp.toString()) + .then((resp) => { + if (resp.ok) { + return resp.arrayBuffer(); + } + console.log("error loading blockfile", resp.status, resp.statusText); + }) + .then((data: ArrayBuffer) => { + const uint8View = new Uint8Array(data); + localTerm.write(uint8View); + loadedBytes = uint8View.byteLength; + }) + .finally(() => { + initialLoadRef.current.heldData.forEach((data) => { + localTerm.write(data); + }); + initialLoadRef.current.loaded = true; + initialLoadRef.current.heldData = []; + console.log(`terminal loaded blockfile ${loadedBytes} bytes, ${Date.now() - startTs}ms`); + }); + }, [termRef.current]); + return (
diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go index 69f951dd4..df82bc49d 100644 --- a/pkg/blockcontroller/blockcontroller.go +++ b/pkg/blockcontroller/blockcontroller.go @@ -4,6 +4,7 @@ package blockcontroller import ( + "bytes" "context" "encoding/base64" "encoding/json" @@ -108,6 +109,21 @@ func (bc *BlockController) handleShellProcData(data []byte, seqNum int) error { return nil } +func (bc *BlockController) resetTerminalState() { + ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) + defer cancelFn() + var buf bytes.Buffer + // buf.WriteString("\x1b[?1049l") // disable alternative buffer + buf.WriteString("\x1b[0m") // reset attributes + buf.WriteString("\x1b[?25h") // show cursor + buf.WriteString("\x1b[?1000l") // disable mouse tracking + buf.WriteString("\r\n\r\n(restored terminal state)\r\n\r\n") + err := blockstore.GBS.AppendData(ctx, bc.BlockId, "main", buf.Bytes()) + if err != nil { + log.Printf("error appending to blockfile (terminal reset): %v\n", err) + } +} + func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts) error { // create a circular blockfile for the output ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) @@ -116,6 +132,10 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts) error { if err != nil && err != blockstore.ErrAlreadyExists { return fmt.Errorf("error creating blockfile: %w", err) } + if err == blockstore.ErrAlreadyExists { + // reset the terminal state + bc.resetTerminalState() + } if bc.getShellProc() != nil { return nil } diff --git a/pkg/service/objectservice/objectservice.go b/pkg/service/objectservice/objectservice.go index 1d047acdf..6fb3c844b 100644 --- a/pkg/service/objectservice/objectservice.go +++ b/pkg/service/objectservice/objectservice.go @@ -118,7 +118,7 @@ func (svc *ObjectService) SetActiveTab(uiContext wstore.UIContext, tabId string) blockErr := blockcontroller.StartBlockController(ctx, blockId) if blockErr != nil { // we don't want to fail the set active tab operation if a block controller fails to start - log.Printf("error starting block controller (blockid:%s): %w", blockId, blockErr) + log.Printf("error starting block controller (blockid:%s): %v", blockId, blockErr) continue } }