From bc476c3c237b0e8ac78f66ced9e44e2251cecda4 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 5 Sep 2022 17:21:31 -0700 Subject: [PATCH] working on resizing terminal to match window size --- package.json | 1 + src/main.tsx | 4 ++-- src/model.ts | 27 ++++++++++++++++++++++----- src/term.ts | 46 ++++++++++++++++++++++++++++++++++++---------- src/types.ts | 16 +++++++++++++++- yarn.lock | 5 +++++ 6 files changed, 81 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index b0da90f1..b1f029ca 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "react": "^18.1.0", "react-dom": "^18.1.0", "sprintf-js": "^1.1.2", + "throttle-debounce": "^5.0.0", "tsx-control-statements": "^4.1.1", "uuid": "^8.3.2", "xterm": "^4.18.0" diff --git a/src/main.tsx b/src/main.tsx index a6e06d0e..9d68bdf1 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,9 +4,9 @@ import * as mobx from "mobx"; import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; import {v4 as uuidv4} from "uuid"; -import dayjs from 'dayjs' +import dayjs from "dayjs"; import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; -import cn from "classnames" +import cn from "classnames"; import {TermWrap} from "./term"; import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts} from "./types"; import localizedFormat from 'dayjs/plugin/localizedFormat'; diff --git a/src/model.ts b/src/model.ts index 140bba44..b5aa9507 100644 --- a/src/model.ts +++ b/src/model.ts @@ -1,10 +1,11 @@ import * as mobx from "mobx"; import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; +import {debounce} from "throttle-debounce"; import {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeSimpleData} 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} 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} from "./types"; import {WSControl} from "./ws"; var GlobalUser = "sawka"; @@ -153,11 +154,11 @@ class Cmd { return; } let data = this.data.get(); - let inputPacket = { + let inputPacket : FeInputPacketType = { type: "feinput", ck: this.sessionId + "/" + this.cmdId, - inputdata: btoa(event.key), remote: this.remote, + inputdata64: btoa(event.key), }; GlobalModel.sendInputPacket(inputPacket); } @@ -253,6 +254,8 @@ class ScreenWindow { layout : OV; shouldFollow : OV = mobx.observable.box(true); width : OV = mobx.observable.box(0); + widthInCols : number; + colsCallback_debounced : (cols : number) => void; // cmdid => TermWrap terms : Record = {}; @@ -263,6 +266,8 @@ class ScreenWindow { this.windowId = swdata.windowid; this.name = mobx.observable.box(swdata.name); this.layout = mobx.observable.box(swdata.layout); + this.colsCallback_debounced = debounce(1000, this.colsCallback.bind(this)); + this.widthInCols = 0; } updatePtyData(ptyMsg : PtyDataUpdateType) { @@ -275,9 +280,21 @@ class ScreenWindow { term.updatePtyData(ptyMsg.ptypos, data); } + colsCallback(cols : number) : void { + console.log("cols set", cols); + for (let cmdid in this.terms) { + this.terms[cmdid].resizeCols(cols); + } + } + setWidth(width : number) : void { mobx.action(() => { + let oldCols = this.widthInCols; this.width.set(width); + this.widthInCols = widthToCols(width); + if (this.widthInCols > 0 && this.widthInCols != oldCols) { + this.colsCallback_debounced(this.widthInCols); + } })(); } @@ -1149,8 +1166,8 @@ class Model { return this.termUsedRowsCache[key]; } - setTUR(sessionId : string, cmdId : string, cols : number, usedRows : number) : void { - let key = sessionId + "/" + cmdId + "/" + cols; + setTUR(sessionId : string, cmdId : string, size : TermWinSize, usedRows : number) : void { + let key = sessionId + "/" + cmdId + "/" + size.cols; this.termUsedRowsCache[key] = usedRows; } diff --git a/src/term.ts b/src/term.ts index 44cc6c00..ab9e58bd 100644 --- a/src/term.ts +++ b/src/term.ts @@ -4,7 +4,7 @@ import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; import {v4 as uuidv4} from "uuid"; import {GlobalModel} from "./model"; -import type {TermOptsType} from "./types"; +import type {TermOptsType, TermWinSize} from "./types"; type DataUpdate = { data : Uint8Array, @@ -18,6 +18,18 @@ type WindowSize = { const DefaultCellWidth = 8; const DefaultCellHeight = 16; +const MinTermCols = 10; +const MaxTermCols = 1024; + +function boundInt(ival : number, minVal : number, maxVal : number) : number { + if (ival < minVal) { + return minVal; + } + if (ival > maxVal) { + return maxVal; + } + return ival; +} // cmd-instance class TermWrap { @@ -35,7 +47,7 @@ class TermWrap { loadError : mobx.IObservableValue = mobx.observable.box(false); winSize : WindowSize; numParseErrors : number = 0; - termCols : number; + termSize : TermWinSize; constructor(elem : Element, sessionId : string, cmdId : string, usedRows : number, termOpts : TermOptsType, winSize : WindowSize, keyHandler : (event : any) => void) { this.sessionId = sessionId; @@ -51,13 +63,10 @@ class TermWrap { this.atRowMax = true; this.usedRows = mobx.observable.box(termOpts.rows); } - let cols = termOpts.cols; - let maxCols = Math.trunc((winSize.width - 25) / DefaultCellWidth) - 1; - if (maxCols > cols) { - cols = maxCols; - } - this.termCols = maxCols; - this.terminal = new Terminal({rows: termOpts.rows, cols: maxCols, fontSize: 14, theme: {foreground: "#d3d7cf"}}); + let cols = Math.trunc((winSize.width - 25) / DefaultCellWidth) - 1; + cols = boundInt(cols, MinTermCols, MaxTermCols); + this.termSize = {rows: termOpts.rows, cols: cols}; + this.terminal = new Terminal({rows: this.termSize.rows, cols: this.termSize.cols, fontSize: 14, theme: {foreground: "#d3d7cf"}}); this.terminal._core._inputHandler._parser.setErrorHandler((state) => { this.numParseErrors++; return state; @@ -144,7 +153,7 @@ class TermWrap { mobx.action(() => { let oldUsedRows = this.usedRows.get(); this.usedRows.set(tur); - GlobalModel.setTUR(this.sessionId, this.cmdId, this.termCols, tur); + GlobalModel.setTUR(this.sessionId, this.cmdId, this.termSize, tur); if (this.connectedElem) { let resizeEvent = new CustomEvent("termresize", { bubbles: true, @@ -160,6 +169,23 @@ class TermWrap { })(); } + resizeCols(cols : number) { + this.resize({rows: this.termSize.rows, cols: cols}); + } + + resize(size : TermWinSize) { + if (this.terminal == null) { + return; + } + let newSize = {rows: size.rows, cols: size.cols}; + newSize.cols = boundInt(newSize.cols, MinTermCols, MaxTermCols); + if (newSize.rows == this.termSize.rows && newSize.cols == this.termSize.cols) { + return; + } + this.termSize = newSize; + this.terminal.resize(newSize.cols, newSize.rows); + } + reloadTerminal(delayMs : number) { if (this.terminal == null) { return; diff --git a/src/types.ts b/src/types.ts index 9bf9f744..18e137dc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -164,6 +164,15 @@ type FeCmdPacketType = { interactive : boolean, }; +type FeInputPacketType = { + type : string, + ck : string, + remote : RemotePtrType, + inputdata64? : string, + signum? : number, + winsize? : TermWinSize, +}; + type WatchScreenPacketType = { type : string, sessionid : string, @@ -171,6 +180,11 @@ type WatchScreenPacketType = { connect : boolean, }; +type TermWinSize = { + rows : number, + cols : number, +} + type TermOptsType = { rows : number, cols : number, @@ -268,4 +282,4 @@ type HistoryQueryOpts = { type UpdateMessage = PtyDataUpdateType | ModelUpdateType; -export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDonePacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType}; +export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDonePacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType}; diff --git a/yarn.lock b/yarn.lock index e11713d9..8da1ad25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5911,6 +5911,11 @@ terser@^5.14.1: commander "^2.20.0" source-map-support "~0.5.20" +throttle-debounce@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz#a17a4039e82a2ed38a5e7268e4132d6960d41933" + integrity sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg== + through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"