diff --git a/src/main.tsx b/src/main.tsx index 10f94bf7..c50367dd 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -287,7 +287,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width let model = GlobalModel; let termWrap = sw.getTermWrap(line.cmdid); if (termWrap != null) { - termWrap.terminal.focus(); + termWrap.focusTerminal(); } } @@ -334,7 +334,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width let termOpts = cmd.getTermOpts(); let lineNumStr = (line.linenumtemp ? "~" : "") + String(line.linenum); let isSelected = (sw.selectedLine.get() == line.linenum); - let isPhysicalFocused = sw.getIsFocused(line.cmdid); + let isPhysicalFocused = sw.getIsFocused(line.linenum); let swFocusType = sw.focusType.get(); let isFocused = isPhysicalFocused && (swFocusType == "cmd" || swFocusType == "cmd-fg"); let isStatic = staticRender; @@ -578,7 +578,6 @@ class TextAreaInput extends React.Component<{}, {}> { return; } if (e.code == "KeyR" && ((e.getModifierState("Meta") || e.getModifierState("Control")) && !e.getModifierState("Shift"))) { - console.log("meta-r"); e.preventDefault(); let opts = mobx.toJS(inputModel.historyQueryOpts.get()); if (opts.limitRemote) { @@ -639,7 +638,9 @@ class TextAreaInput extends React.Component<{}, {}> { let inputModel = GlobalModel.inputModel; if (inputModel.historyShow.get()) { e.preventDefault(); - inputModel.giveFocus(); + if (this.historyInputRef.current != null) { + this.historyInputRef.current.focus(); + } return; } inputModel.setPhysicalInputFocused(true); @@ -658,7 +659,9 @@ class TextAreaInput extends React.Component<{}, {}> { let inputModel = GlobalModel.inputModel; if (!inputModel.historyShow.get()) { e.preventDefault(); - inputModel.giveFocus(); + if (this.mainInputRef.current != null) { + this.mainInputRef.current.focus(); + } return; } inputModel.setPhysicalInputFocused(true); @@ -854,7 +857,7 @@ class InfoRemoteShow extends React.Component<{}, {}> { clickTermBlock(e : any) { let inputModel = GlobalModel.inputModel; if (inputModel.remoteTermWrap != null) { - inputModel.remoteTermWrap.terminal.focus(); + inputModel.remoteTermWrap.focusTerminal(); } } @@ -869,7 +872,7 @@ class InfoRemoteShow extends React.Component<{}, {}> { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); let ptyRemoteId = (infoMsg == null ? null : infoMsg.ptyremoteid); - let isTermFocused = (inputModel.remoteTermWrap == null ? false : inputModel.remoteTermWrap.isFocused.get()); + let isTermFocused = (inputModel.remoteTermWrap == null ? false : inputModel.remoteTermWrapFocus.get()); let remote : RemoteType; if (ptyRemoteId != null) { remote = GlobalModel.getRemote(ptyRemoteId); @@ -1695,13 +1698,13 @@ class LinesView extends React.Component<{sw : ScreenWindow, width : number, line @boundMethod scrollHandler() { + // console.log("scroll", this.linesRef.current.scrollTop); + this.computeVisibleMap_debounced(); // always do this if (this.ignoreNextScroll) { this.ignoreNextScroll = false; return; } - // console.log("scroll", this.linesRef.current.scrollTop); - this.computeAnchorLine_throttled(); - this.computeVisibleMap_debounced(); + this.computeAnchorLine_throttled(); // only do this when we're not ignoring the scroll } computeAnchorLine() : void { @@ -1734,7 +1737,7 @@ class LinesView extends React.Component<{sw : ScreenWindow, width : number, line } sw.anchorLine = parseInt(anchorElem.dataset.linenum); sw.anchorOffset = containerBottom - (anchorElem.offsetTop + anchorElem.offsetHeight); - // console.log("anchor", this.anchorLine, this.anchorOffset); + // console.log("anchor", sw.anchorLine, sw.anchorOffset); } computeVisibleMap() : void { @@ -1878,9 +1881,8 @@ class LinesView extends React.Component<{sw : ScreenWindow, width : number, line return null; } let newLine = sw.selectedLine.get(); - if (newLine == sw.anchorLine) { - return; - } + this.setLineVisible(newLine, true); + // console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", sw.anchorLine, sw.anchorOffset)); let viewInfo = this.getLineViewInfo(newLine); if (viewInfo == null) { return; @@ -1899,7 +1901,7 @@ class LinesView extends React.Component<{sw : ScreenWindow, width : number, line this.ignoreNextScroll = true; sw.anchorOffset = linesElem.clientHeight - viewInfo.height; } - this.setLineVisible(newLine, true); + // console.log("new anchor", sw.getAnchorStr()); } setLineVisible(lineNum : number, vis : boolean) : void { diff --git a/src/model.ts b/src/model.ts index 15a4ad37..b558c689 100644 --- a/src/model.ts +++ b/src/model.ts @@ -268,6 +268,7 @@ class ScreenWindow { focusType : OV<"input"|"cmd"|"cmd-fg">; anchorLine : number = null; anchorOffset : number = 0; + termLineNumFocus : OV; // cmdid => TermWrap terms : Record = {}; @@ -287,6 +288,14 @@ class ScreenWindow { this.anchorLine = swdata.selectedline; this.anchorOffset = 0; } + this.termLineNumFocus = mobx.observable.box(0, {name: "termLineNumFocus"}); + } + + getAnchorStr() : string { + if (this.anchorLine == null || this.anchorLine == 0) { + return "0"; + } + return sprintf("%d:%d", this.anchorLine, this.anchorOffset); } updateSelf(swdata : ScreenWindowType) { @@ -319,7 +328,7 @@ class ScreenWindow { if (sline != null && sline.cmdid != null) { let termWrap = this.getTermWrap(sline.cmdid); if (termWrap != null && termWrap.terminal != null) { - termWrap.terminal.focus(); + termWrap.focusTerminal(); } } } @@ -437,6 +446,13 @@ class ScreenWindow { return this.terms[cmdId]; } + setTermFocus(lineNum : number, focus : boolean) : void { + mobx.action(() => this.termLineNumFocus.set(focus ? lineNum : 0))(); + if (focus && this.selectedLine.get() != lineNum) { + GlobalCommandRunner.swSelectLine(String(lineNum), "cmd"); + } + } + connectElem(elem : Element, line : LineType, cmd : Cmd, width : number) { let cmdId = cmd.cmdId; let termWrap = this.getTermWrap(cmdId); @@ -446,10 +462,12 @@ class ScreenWindow { } let cols = widthToCols(width); let usedRows = GlobalModel.getTUR(this.sessionId, cmdId, cols); - termWrap = new TermWrap(elem, {sessionId: this.sessionId, cmdId: cmdId}, usedRows, cmd.getTermOpts(), {height: 0, width: width}, cmd.handleKey.bind(cmd)); + termWrap = new TermWrap( + elem, {sessionId: this.sessionId, cmdId: cmdId}, usedRows, cmd.getTermOpts(), {height: 0, width: width}, + cmd.handleKey.bind(cmd), (focus : boolean) => this.setTermFocus(line.linenum, focus)); this.terms[cmdId] = termWrap; if ((this.focusType.get() == "cmd" || this.focusType.get() == "cmd-fg") && this.selectedLine.get() == line.linenum) { - termWrap.setFocus(true); + termWrap.focusTerminal(); } return; } @@ -479,12 +497,8 @@ class ScreenWindow { return termWrap.usedRows.get(); } - getIsFocused(cmdId : string) : boolean { - let termWrap = this.getTermWrap(cmdId); - if (termWrap == null) { - return false; - } - return termWrap.isFocused.get(); + getIsFocused(lineNum : number) : boolean { + return (this.termLineNumFocus.get() == lineNum); } getWindow() : Window { @@ -757,6 +771,7 @@ class InputModel { infoMsg : OV = mobx.observable.box(null); infoTimeoutId : any = null; remoteTermWrap : TermWrap; + remoteTermWrapFocus : OV = mobx.observable.box(false, {name: "remoteTermWrapFocus"}); showNoInputMsg : OV = mobx.observable.box(false); showNoInputTimeoutId : any = null; @@ -771,6 +786,12 @@ class InputModel { }); } + setRemoteTermWrapFocus(focus : boolean) : void { + mobx.action(() => { + this.remoteTermWrapFocus.set(focus); + })(); + } + setShowNoInputMsg(val : boolean) { mobx.action(() => { if (this.showNoInputTimeoutId != null) { @@ -842,6 +863,12 @@ class InputModel { mobx.action(() => { this.physicalInputFocused.set(isFocused); })(); + let sw = GlobalModel.getActiveSW(); + if (sw != null) { + if (sw.focusType.get() != "input") { + GlobalCommandRunner.swSetFocus("input"); + } + } } getPtyRemoteId() : string { @@ -1319,7 +1346,10 @@ class InputModel { } else { let termOpts = {rows: RemotePtyRows, cols: RemotePtyCols, flexrows: false, maxptysize: 64*1024}; - this.remoteTermWrap = new TermWrap(elem, {remoteId: remoteId}, RemotePtyRows, termOpts, null, (e) => { this.termKeyHandler(remoteId, e)}); + this.remoteTermWrap = new TermWrap( + elem, {remoteId: remoteId}, RemotePtyRows, termOpts, null, + (e) => { this.termKeyHandler(remoteId, e)}, + this.setRemoteTermWrapFocus.bind(this)); } } } @@ -1389,6 +1419,7 @@ class Model { termUsedRowsCache : Record = {}; remotesModalOpen : OV = mobx.observable.box(false); addRemoteModalOpen : OV = mobx.observable.box(false); + debugCmds : boolean = false; constructor() { this.clientId = getApi().getId(); @@ -1473,13 +1504,9 @@ class Model { onICmd(e : any, mods : KeyModsType) { let sw = this.getActiveSW(); if (sw != null) { - let curLineFocus = this.getFocusedLine(); - if (curLineFocus.cmdInputFocus) { + if (sw.focusType.get() == "input") { GlobalCommandRunner.swSelectLine("E"); } - else { - GlobalCommandRunner.swSetFocus("input"); - } } this.inputModel.giveFocus(); } @@ -1546,56 +1573,6 @@ class Model { GlobalCommandRunner.swSelectLine("+1"); } - onMetaArrowUpOld() : void { - let focus = this.getFocusedLine(); - if (focus == null) { - return; - } - let sw : ScreenWindow = null; - if (focus.cmdInputFocus) { - sw = this.getActiveSW(); - } - else { - sw = this.getSWByWindowId(focus.windowid); - } - if (sw == null) { - return; - } - let win = sw.getWindow(); - if (win == null) { - return; - } - let runningLines = win.getRunningCmdLines(); - if (runningLines.length == 0) { - return; - } - let switchLine : LineType = null; - if (focus.cmdInputFocus) { - switchLine = runningLines[runningLines.length-1]; - } - else { - let foundIdx = -1; - for (let i=0; i 0) { - switchLine = runningLines[foundIdx-1]; - } - } - if (switchLine == null || switchLine.cmdid == null) { - return; - } - let termWrap = sw.getTermWrap(switchLine.cmdid); - if (termWrap == null || termWrap.terminal == null) { - return; - } - termWrap.terminal.focus(); - console.log("arrow-up", this.getFocusedLine(), "=>", switchLine); - } - onBracketCmd(e : any, arg : {relative: number}, mods : KeyModsType) { if (arg.relative == 1) { GlobalCommandRunner.switchScreen("+"); @@ -1852,6 +1829,9 @@ class Model { } submitCommandPacket(cmdPk : FeCmdPacketType, interactive : boolean) { + if (this.debugCmds) { + console.log("[cmd]", cmdPacketString(cmdPk)); + } let url = sprintf("http://localhost:8080/api/run-command"); fetch(url, {method: "post", body: JSON.stringify(cmdPk)}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { mobx.action(() => { @@ -2129,6 +2109,23 @@ class CommandRunner { } }; +function cmdPacketString(pk : FeCmdPacketType) : string { + let cmd = pk.metacmd; + if (pk.metasubcmd != null) { + cmd += ":" + pk.metasubcmd; + } + let parts = [cmd]; + if (pk.kwargs != null) { + for (let key in pk.kwargs) { + parts.push(sprintf("%s=%s", key, pk.kwargs[key])); + } + } + if (pk.args != null) { + parts.push(...pk.args); + } + return parts.join(" "); +} + let GlobalModel : Model = null; let GlobalCommandRunner : CommandRunner = null; if ((window as any).GlobalModal == null) { diff --git a/src/term.ts b/src/term.ts index e87dd561..3fc179b0 100644 --- a/src/term.ts +++ b/src/term.ts @@ -28,7 +28,6 @@ class TermWrap { termContext : TermContext; atRowMax : boolean; usedRows : mobx.IObservableValue; - isFocused : mobx.IObservableValue = mobx.observable.box(false, {name: "focus"}); flexRows : boolean; connectedElem : Element; ptyPos : number = 0; @@ -38,12 +37,14 @@ class TermWrap { winSize : WindowSize; numParseErrors : number = 0; termSize : TermWinSize; + focusHandler : (focus : boolean) => void; - constructor(elem : Element, termContext : TermContext, usedRows : number, termOpts : TermOptsType, winSize : WindowSize, keyHandler : (event : any) => void) { + constructor(elem : Element, termContext : TermContext, usedRows : number, termOpts : TermOptsType, winSize : WindowSize, keyHandler : (event : any) => void, focusHandler : (focus : boolean) => void) { this.termContext = termContext; this.connectedElem = elem; this.flexRows = termOpts.flexrows ?? false; this.winSize = winSize; + this.focusHandler = focusHandler; if (this.flexRows) { this.atRowMax = false; this.usedRows = mobx.observable.box(usedRows ?? 2); @@ -69,13 +70,17 @@ class TermWrap { this.terminal.onKey(keyHandler); } this.terminal.textarea.addEventListener("focus", () => { - this.setFocus(true); + if (this.focusHandler != null) { + this.focusHandler(true); + } }); this.terminal.textarea.addEventListener("blur", (e : any) => { if (document.activeElement == this.terminal.textarea) { return; } - this.setFocus(false); + if (this.focusHandler != null) { + this.focusHandler(false); + } }); this.reloadTerminal(0); } @@ -91,23 +96,12 @@ class TermWrap { } } - disconnectElem() { - this.connectedElem = null; + focusTerminal() { + this.terminal.focus(); } - setFocus(focus : boolean) { - mobx.action(() => { - this.isFocused.set(focus); - })(); - if (this.connectedElem != null && focus) { - let lineElem : HTMLElement = this.connectedElem.closest(".line"); - if (lineElem != null) { - let lineNum = parseInt(lineElem.dataset.linenum); - if (!isNaN(lineNum) && lineNum > 0) { - GlobalCommandRunner.swSelectLine(String(lineNum), "cmd"); - } - } - } + disconnectElem() { + this.connectedElem = null; } getTermUsedRows() : number {