mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-10 19:58:00 +01:00
better focus handling
This commit is contained in:
parent
0bf36be982
commit
a0de45949f
32
src/main.tsx
32
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 {
|
||||
|
127
src/model.ts
127
src/model.ts
@ -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) {
|
||||
|
32
src/term.ts
32
src/term.ts
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user