diff --git a/src/main.tsx b/src/main.tsx index 07c26f2e..9bee71ec 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -213,6 +213,20 @@ class Prompt extends React.Component<{rptr : RemotePtrType, festate : FeStateTyp } } +@mobxReact.observer +class TerminalRenderer extends React.Component<{sw : ScreenWindow, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : HeightChangeCallbackType}, {}> { + render() { + return null; + } +} + +@mobxReact.observer +class MarkdownRenderer extends React.Component<{sw : ScreenWindow, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : HeightChangeCallbackType}, {}> { + render() { + return null; + } +} + @mobxReact.observer class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : HeightChangeCallbackType}, {}> { termLoaded : mobx.IObservableValue = mobx.observable.box(false, {name: "linecmd-term-loaded"}); @@ -338,7 +352,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width doRefresh() { let {sw, line} = this.props; let model = GlobalModel; - let termWrap = sw.getTermWrap(line.cmdid); + let termWrap = sw.getRenderer(line.cmdid); if (termWrap != null) { termWrap.reload(500); } @@ -365,9 +379,9 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width clickTermBlock(e : any) { let {sw, line} = this.props; let model = GlobalModel; - let termWrap = sw.getTermWrap(line.cmdid); + let termWrap = sw.getRenderer(line.cmdid); if (termWrap != null) { - termWrap.focusTerminal(); + termWrap.giveFocus(); } } @@ -1038,7 +1052,7 @@ class InfoRemoteShow extends React.Component<{}, {}> { clickTermBlock(e : any) { let inputModel = GlobalModel.inputModel; if (inputModel.remoteTermWrap != null) { - inputModel.remoteTermWrap.focusTerminal(); + inputModel.remoteTermWrap.giveFocus(); } } diff --git a/src/model.ts b/src/model.ts index b45c359c..3d1bc41a 100644 --- a/src/model.ts +++ b/src/model.ts @@ -5,7 +5,7 @@ import {debounce} from "throttle-debounce"; import {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeSimpleData, boundInt, isModKeyPress} from "./util"; import {TermWrap} from "./term"; import {v4 as uuidv4} from "uuid"; -import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts, NormalTermContext} from "./types"; +import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts, RendererContext, RendererModel} from "./types"; import {WSControl} from "./ws"; var GlobalUser = "sawka"; @@ -306,7 +306,7 @@ class ScreenWindow { termLineNumFocus : OV; // cmdid => TermWrap - terms : Record = {}; + renderers : Record = {}; setAnchor_debounced : (anchorLine : number, anchorOffset : number) => void; @@ -366,9 +366,9 @@ class ScreenWindow { (document.activeElement as HTMLElement).blur(); } if (sline != null && sline.cmdid != null) { - let termWrap = this.getTermWrap(sline.cmdid); - if (termWrap != null && termWrap.terminal != null) { - termWrap.focusTerminal(); + let termWrap = this.getRenderer(sline.cmdid); + if (termWrap != null) { + termWrap.giveFocus(); } } } @@ -455,12 +455,12 @@ class ScreenWindow { updatePtyData(ptyMsg : PtyDataUpdateType) { let cmdId = ptyMsg.cmdid; - let term = this.terms[cmdId]; - if (term == null) { + let renderer = this.renderers[cmdId]; + if (renderer == null) { return; } let data = base64ToArray(ptyMsg.ptydata64); - term.updatePtyData(ptyMsg.ptypos, data, "from-sw"); + renderer.receiveData(ptyMsg.ptypos, data, "from-sw"); } isActive() : boolean { @@ -480,14 +480,14 @@ class ScreenWindow { } this.lastRows = rows; this.lastCols = cols; - for (let cmdid in this.terms) { - this.terms[cmdid].resizeCols(cols); + for (let cmdid in this.renderers) { + this.renderers[cmdid].resizeCols(cols); } GlobalCommandRunner.resizeWindow(this.windowId, rows, cols); } - getTermWrap(cmdId : string) : TermWrap { - return this.terms[cmdId]; + getRenderer(cmdId : string) : RendererModel { + return this.renderers[cmdId]; } setTermFocus(lineNum : number, focus : boolean) : void { @@ -547,7 +547,7 @@ class ScreenWindow { connectElem(elem : Element, line : LineType, cmd : Cmd, width : number) { let cmdId = cmd.cmdId; - let termWrap = this.getTermWrap(cmdId); + let termWrap = this.getRenderer(cmdId); if (termWrap != null) { console.log("term-wrap already exists for", this.screenId, this.windowId, cmdId); return; @@ -568,18 +568,18 @@ class ScreenWindow { isRunning: cmd.isRunning(), customKeyHandler: this.termCustomKeyHandler.bind(this), }); - this.terms[cmdId] = termWrap; + this.renderers[cmdId] = termWrap; if ((this.focusType.get() == "cmd" || this.focusType.get() == "cmd-fg") && this.selectedLine.get() == line.linenum) { - termWrap.focusTerminal(); + termWrap.giveFocus(); } return; } disconnectElem(cmdId : string) { - let termWrap = this.terms[cmdId]; + let termWrap = this.renderers[cmdId]; if (termWrap != null) { termWrap.dispose(); - delete this.terms[cmdId]; + delete this.renderers[cmdId]; } } @@ -588,7 +588,7 @@ class ScreenWindow { if (!termOpts.flexrows) { return termOpts.rows; } - let termWrap = this.getTermWrap(cmd.cmdId); + let termWrap = this.getRenderer(cmd.cmdId); if (termWrap == null) { let cols = widthToCols(width); let usedRows = GlobalModel.getTUR(this.sessionId, cmd.cmdId, cols); @@ -600,7 +600,7 @@ class ScreenWindow { } return (cmd.isRunning() ? 1 : 0); } - return termWrap.usedRows.get(); + return termWrap.getUsedRows(); } getIsFocused(lineNum : number) : boolean { @@ -1669,7 +1669,7 @@ class Model { return this.termUsedRowsCache[key]; } - setTUR(termContext : NormalTermContext, size : TermWinSize, usedRows : number) : void { + setTUR(termContext : RendererContext, size : TermWinSize, usedRows : number) : void { let key = termContext.sessionId + "/" + termContext.cmdId + "/" + size.cols; this.termUsedRowsCache[key] = usedRows; GlobalCommandRunner.setTermUsedRows(termContext, usedRows); @@ -1754,13 +1754,17 @@ class Model { } cmdStatusUpdate(sessionId : string, cmdId : string, origStatus : string, newStatus : string) { - // console.log("cmd status", sessionId, cmdId, origStatus, "=>", newStatus); - let lines = this.getActiveLinesByCmdId(sessionId, cmdId); - for (let ptr of lines) { - let sw = ptr.sw; - let term = sw.getTermWrap(cmdId); - if (term != null) { - term.setIsRunning(cmdStatusIsRunning(newStatus)); + let wasRunning = cmdStatusIsRunning(origStatus); + let isRunning = cmdStatusIsRunning(newStatus); + if (wasRunning && !isRunning) { + // console.log("cmd status", sessionId, cmdId, origStatus, "=>", newStatus); + let lines = this.getActiveLinesByCmdId(sessionId, cmdId); + for (let ptr of lines) { + let sw = ptr.sw; + let renderer = sw.getRenderer(cmdId); + if (renderer != null) { + renderer.cmdDone(); + } } } } @@ -1829,7 +1833,7 @@ class Model { return; } let ptyData = base64ToArray(ptyMsg.ptydata64); - this.inputModel.remoteTermWrap.updatePtyData(ptyMsg.ptypos, ptyData); + this.inputModel.remoteTermWrap.receiveData(ptyMsg.ptypos, ptyData); return; } } @@ -2360,7 +2364,7 @@ class CommandRunner { GlobalModel.submitCommand("sw", "set", null, kwargs, true); } - setTermUsedRows(termContext : NormalTermContext, height : number) { + setTermUsedRows(termContext : RendererContext, height : number) { let kwargs : Record = {}; kwargs["session"] = termContext.sessionId; kwargs["screen"] = termContext.screenId; diff --git a/src/term.ts b/src/term.ts index 0001af5c..00c81cdc 100644 --- a/src/term.ts +++ b/src/term.ts @@ -3,26 +3,21 @@ import {Terminal} from 'xterm'; import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; import {v4 as uuidv4} from "uuid"; -import {GlobalModel, widthToCols, GlobalCommandRunner} from "./model"; +import {GlobalModel, widthToCols, GlobalCommandRunner, termHeightFromRows, termRowsFromHeight} from "./model"; import {boundInt} from "./util"; -import type {TermOptsType, TermWinSize, NormalTermContext} from "./types"; +import type {TermOptsType, TermWinSize, RendererContext, WindowSize} from "./types"; type DataUpdate = { data : Uint8Array, pos : number, } -type WindowSize = { - height : number, - width: number, -}; - const MinTermCols = 10; const MaxTermCols = 1024; type RemoteTermContext = {remoteId : string}; -type TermContext = NormalTermContext | RemoteTermContext; +type TermContext = RendererContext | RemoteTermContext; type TermWrapOpts = { termContext : TermContext, @@ -112,6 +107,10 @@ class TermWrap { this.reload(0); } + getUsedRows() : number { + return this.usedRows.get(); + } + @boundMethod elemScrollHandler(e : any) { // this stops a weird behavior in the terminal @@ -132,7 +131,7 @@ class TermWrap { return null; } - getNormalTermContext() : NormalTermContext { + getRendererContext() : RendererContext { if ("remoteId" in this.termContext) { return null; } @@ -150,7 +149,10 @@ class TermWrap { } } - focusTerminal() { + giveFocus() { + if (this.terminal == null) { + return; + } this.terminal.focus(); setTimeout(() => this.terminal._core.viewport.syncScrollArea(true), 0) } @@ -191,7 +193,7 @@ class TermWrap { if (!this.flexRows) { return; } - let termContext = this.getNormalTermContext(); + let termContext = this.getRendererContext(); if ("remoteId" in termContext) { return; } @@ -218,11 +220,11 @@ class TermWrap { })(); } - resizeCols(cols : number) { + resizeCols(cols : number) : void { this.resize({rows: this.termSize.rows, cols: cols}); } - resize(size : TermWinSize) { + resize(size : TermWinSize) : void { if (this.terminal == null) { return; } @@ -236,11 +238,17 @@ class TermWrap { this.updateUsedRows(true, "resize"); } + resizeWindow(size : WindowSize) : void { + let cols = widthToCols(size.width); + let rows = termRowsFromHeight(size.height); + this.resize({rows, cols}); + } + _getReloadUrl() : string { if (this.getContextRemoteId() != null) { return sprintf(GlobalModel.getBaseHostPort() + "/api/remote-pty?remoteid=%s", this.getContextRemoteId()); } - let termContext = this.getNormalTermContext(); + let termContext = this.getRendererContext(); return sprintf(GlobalModel.getBaseHostPort() + "/api/ptyout?sessionid=%s&cmdid=%s", termContext.sessionId, termContext.cmdId); } @@ -268,9 +276,9 @@ class TermWrap { setTimeout(() => { this.reloading = false; this.ptyPos = ptyOffset; - this.updatePtyData(ptyOffset, new Uint8Array(buf), "reload-main"); + this.receiveData(ptyOffset, new Uint8Array(buf), "reload-main"); for (let i=0; i { @@ -282,7 +290,7 @@ class TermWrap { }); } - updatePtyData(pos : number, data : Uint8Array, reason? : string) { + receiveData(pos : number, data : Uint8Array, reason? : string) { // console.log("update-pty-data", pos, data.length, reason); if (this.terminal == null) { return; @@ -313,9 +321,9 @@ class TermWrap { }); } - setIsRunning(isRunning : boolean) { - this.isRunning = isRunning; - this.updateUsedRows(true, "cmd-status"); + cmdDone() : void { + this.isRunning = false; + this.updateUsedRows(true, "cmd-done"); } } diff --git a/src/types.ts b/src/types.ts index 2fdf8734..1f39af09 100644 --- a/src/types.ts +++ b/src/types.ts @@ -333,7 +333,7 @@ type ContextMenuOpts = { type UpdateMessage = PtyDataUpdateType | ModelUpdateType; -type NormalTermContext = { +type RendererContext = { sessionId : string, screenId : string, windowId : string, @@ -342,4 +342,22 @@ type NormalTermContext = { lineNum : number, }; -export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, NormalTermContext}; +type RendererModel = { + reload : (delayMs : number) => void, + dispose : () => void, + receiveData : (pos : number, data : Uint8Array, reason? : string) => void, + cmdDone : () => void, + resizeWindow : (size : WindowSize) => void, + resizeCols : (cols : number) => void, + giveFocus : () => void, + getUsedRows : () => number, + + focusHandler : (focus : boolean) => void; +}; + +type WindowSize = { + height : number, + width: number, +}; + + export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel};