Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix term esc #1583

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions frontend/app/view/term/term.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
} from "@/store/global";
import * as services from "@/store/services";
import * as keyutil from "@/util/keyutil";
import { boundNumber, fireAndForget, stringToBase64, useAtomValueSafe } from "@/util/util";
import { boundNumber, fireAndForget, getNextActionId, stringToBase64, useAtomValueSafe } from "@/util/util";
import { computeBgStyleFromMeta } from "@/util/waveutil";
import { ISearchOptions } from "@xterm/addon-search";
import clsx from "clsx";
Expand Down Expand Up @@ -350,7 +350,13 @@ class TermViewModel implements ViewModel {

sendDataToController(data: string) {
const b64data = stringToBase64(data);
RpcApi.ControllerInputCommand(TabRpcClient, { blockid: this.blockId, inputdata64: b64data });
const actionId = getNextActionId();
RpcApi.ControllerInputCommand(TabRpcClient, {
blockid: this.blockId,
inputdata64: b64data,
feactionid: actionId,
pendingptyoffset: this.termRef.current.pendingPtyOffset,
});
}

setTermMode(mode: "term" | "vdom") {
Expand Down Expand Up @@ -980,12 +986,7 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
if (blockData?.meta?.["term:scrollback"]) {
termScrollback = Math.floor(blockData.meta["term:scrollback"]);
}
if (termScrollback < 0) {
termScrollback = 0;
}
if (termScrollback > 50000) {
termScrollback = 50000;
}
termScrollback = boundNumber(termScrollback, 0, 50000);
const termAllowBPM = globalStore.get(termBPMAtom) ?? false;
const wasFocused = model.termRef.current != null && globalStore.get(model.nodeModel.isFocused);
const termWrap = new TermWrap(
Expand Down
62 changes: 38 additions & 24 deletions frontend/app/view/term/termwrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
// SPDX-License-Identifier: Apache-2.0

import { getFileSubject } from "@/app/store/wps";
import { sendWSCommand } from "@/app/store/ws";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { PLATFORM, WOS, atoms, fetchWaveFile, getSettingsKeyAtom, globalStore, openLink } from "@/store/global";
import * as services from "@/store/services";
import { base64ToArray, fireAndForget } from "@/util/util";
import { base64ToArray, fireAndForget, getNextActionId } from "@/util/util";
import { SearchAddon } from "@xterm/addon-search";
import { SerializeAddon } from "@xterm/addon-serialize";
import { WebLinksAddon } from "@xterm/addon-web-links";
Expand Down Expand Up @@ -136,6 +135,7 @@ function handleOsc7Command(data: string, blockId: string, loaded: boolean): bool
export class TermWrap {
blockId: string;
ptyOffset: number;
pendingPtyOffset: number;
dataBytesProcessed: number;
terminal: Terminal;
connectElem: HTMLDivElement;
Expand All @@ -147,6 +147,7 @@ export class TermWrap {
heldData: Uint8Array[];
handleResize_debounced: () => void;
hasResized: boolean;
isLoadingCache: boolean;
multiInputCallback: (data: string) => void;
sendDataHandler: (data: string) => void;
onSearchResultsDidChange?: (result: { resultIndex: number; resultCount: number }) => void;
Expand All @@ -159,6 +160,7 @@ export class TermWrap {
waveOptions: TermWrapOptions
) {
this.loaded = false;
this.isLoadingCache = false;
this.blockId = blockId;
this.sendDataHandler = waveOptions.sendDataHandler;
this.ptyOffset = 0;
Expand Down Expand Up @@ -259,6 +261,9 @@ export class TermWrap {
}

handleTermData(data: string) {
if (this.isLoadingCache) {
return;
}
if (!this.loaded) {
return;
}
Expand Down Expand Up @@ -297,11 +302,14 @@ export class TermWrap {
let prtn = new Promise<void>((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();
Expand All @@ -316,20 +324,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;
}
}
}
Expand Down Expand Up @@ -363,12 +376,13 @@ export class TermWrap {
this.fitAddon.fit();
if (oldRows !== this.terminal.rows || oldCols !== this.terminal.cols) {
const termSize: TermSize = { rows: this.terminal.rows, cols: this.terminal.cols };
const wsCommand: SetBlockTermSizeWSCommand = {
wscommand: "setblocktermsize",
const actionId = getNextActionId();
RpcApi.ControllerInputCommand(TabRpcClient, {
blockid: this.blockId,
feactionid: actionId,
termsize: termSize,
};
sendWSCommand(wsCommand);
pendingptyoffset: this.pendingPtyOffset,
});
}
dlog("resize", `${this.terminal.rows}x${this.terminal.cols}`, `${oldRows}x${oldCols}`, this.hasResized);
if (!this.hasResized) {
Expand Down
18 changes: 3 additions & 15 deletions frontend/types/gotypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,6 @@ declare global {
files: FileInfo[];
};

// webcmd.BlockInputWSCommand
type BlockInputWSCommand = {
wscommand: "blockinput";
blockid: string;
inputdata64: string;
};

// waveobj.Client
type Client = WaveObj & {
windowids: string[];
Expand Down Expand Up @@ -123,6 +116,8 @@ declare global {
blockid: string;
inputdata64?: string;
signame?: string;
pendingptyoffset?: number;
feactionid?: string;
termsize?: TermSize;
};

Expand Down Expand Up @@ -668,13 +663,6 @@ declare global {
winsize?: WinSize;
};

// webcmd.SetBlockTermSizeWSCommand
type SetBlockTermSizeWSCommand = {
wscommand: "setblocktermsize";
blockid: string;
termsize: TermSize;
};

// wconfig.SettingsType
type SettingsType = {
"app:*"?: boolean;
Expand Down Expand Up @@ -1131,7 +1119,7 @@ declare global {

type WSCommandType = {
wscommand: string;
} & ( SetBlockTermSizeWSCommand | BlockInputWSCommand | WSRpcCommand );
} & ( WSRpcCommand );

// eventbus.WSEventType
type WSEventType = {
Expand Down
19 changes: 19 additions & 0 deletions frontend/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,24 @@ function makeConnRoute(conn: string): string {
return "conn:" + conn;
}

let lastTimestamp = 0;
let counter = 0;

// guaranteed to be monotonically increasing for each call within the same tab
function getNextActionId(): string {
const now = Date.now();

if (now === lastTimestamp) {
counter += 1;
} else {
lastTimestamp = now;
counter = 0;
}

const paddedCounter = String(counter).padStart(5, "0");
return `${now}:${paddedCounter}`;
}

function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
Expand Down Expand Up @@ -409,6 +427,7 @@ export {
countGraphemes,
deepCompareReturnPrev,
fireAndForget,
getNextActionId,
getPrefixedSettings,
getPromiseState,
getPromiseValue,
Expand Down
75 changes: 53 additions & 22 deletions pkg/blockcontroller/blockcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,28 @@ 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"`
PtyProcessedToOffset int64 `json:"ptyprocessedtooffset,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 *atomic.Int64
LastResizeActionId string
}

type BlockControllerRuntimeStatus struct {
Expand Down Expand Up @@ -123,6 +127,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"`
}
Expand Down Expand Up @@ -731,7 +745,23 @@ func (bc *BlockController) manageRunningShellProcess(shellProc *shellexec.ShellP
shellProc.Cmd.Write(ic.InputData)
}
if ic.TermSize != nil {
updateTermSize(shellProc, bc.BlockId, *ic.TermSize)
ok := bc.TestAndSetResizeActionId(ic.FeActionId)
if ok {
updateTermSize(shellProc, bc.BlockId, *ic.TermSize)
} else {
log.Printf("resize action id already processed or out of order: %s %s %v\n", bc.BlockId, ic.FeActionId, *ic.TermSize)
}
}
if ic.PtyProcessedToOffset != 0 {
for {
curOffset := bc.ProcessedToOffset.Load()
if ic.PtyProcessedToOffset <= curOffset {
break
}
if bc.ProcessedToOffset.CompareAndSwap(curOffset, ic.PtyProcessedToOffset) {
break
}
}
}
}
}()
Expand Down Expand Up @@ -997,12 +1027,13 @@ func getOrCreateBlockController(tabId string, blockId string, controllerName str
bc = blockControllerMap[blockId]
if bc == nil {
bc = &BlockController{
Lock: &sync.Mutex{},
ControllerType: controllerName,
TabId: tabId,
BlockId: blockId,
ShellProcStatus: Status_Init,
RunLock: &atomic.Bool{},
Lock: &sync.Mutex{},
ControllerType: controllerName,
TabId: tabId,
BlockId: blockId,
ShellProcStatus: Status_Init,
RunLock: &atomic.Bool{},
ProcessedToOffset: &atomic.Int64{},
}
blockControllerMap[blockId] = bc
createdController = true
Expand Down
Loading