better focus handling

This commit is contained in:
sawka 2022-10-11 13:25:23 -07:00
parent 0bf36be982
commit a0de45949f
3 changed files with 92 additions and 99 deletions

View File

@ -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 {

View File

@ -268,6 +268,7 @@ class ScreenWindow {
focusType : OV<"input"|"cmd"|"cmd-fg">;
anchorLine : number = null;
anchorOffset : number = 0;
termLineNumFocus : OV<number>;
// cmdid => TermWrap
terms : Record<string, TermWrap> = {};
@ -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<InfoType> = mobx.observable.box(null);
infoTimeoutId : any = null;
remoteTermWrap : TermWrap;
remoteTermWrapFocus : OV<boolean> = mobx.observable.box(false, {name: "remoteTermWrapFocus"});
showNoInputMsg : OV<boolean> = 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<string, number> = {};
remotesModalOpen : OV<boolean> = mobx.observable.box(false);
addRemoteModalOpen : OV<boolean> = 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<runningLines.length; i++) {
if (runningLines[i].lineid == focus.lineid) {
foundIdx = i;
break;
}
}
if (foundIdx > 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) {

View File

@ -28,7 +28,6 @@ class TermWrap {
termContext : TermContext;
atRowMax : boolean;
usedRows : mobx.IObservableValue<number>;
isFocused : mobx.IObservableValue<boolean> = 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 {