terminal: implement addBindings() and others for nasftp with xterm.js
Bug: b/295745056
Change-Id: Ie32f2c6b057f62f60edbb768cd7cc56bde1a0934
Reviewed-on: https://chromium-review.googlesource.com/c/apps/libapps/+/4782290
Reviewed-by: Joel Hockey <joelhockey@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/terminal/js/terminal_emulator.js b/terminal/js/terminal_emulator.js
index 8b53cfc..9e0b63c 100644
--- a/terminal/js/terminal_emulator.js
+++ b/terminal/js/terminal_emulator.js
@@ -524,6 +524,12 @@
});
this.term.onBell(() => this.ringBell());
+ // This is for supporting `this.keyboard.bindings.addBindings()`, which is
+ // used by nasftp_cli.js.
+ this.htermKeyBindings_ = new hterm.Keyboard.Bindings();
+ this.keyboard = {
+ bindings: this.htermKeyBindings_,
+ };
/**
* A mapping from key combo (see encodeKeyCombo()) to a handler function.
*
@@ -614,13 +620,43 @@
}
/** @override */
+ clearHome() {
+ for (let i = 0; i < this.term.rows; ++i) {
+ this.xtermInternal_.eraseInBufferLine(i, 0, this.term.cols);
+ }
+ this.xtermInternal_.setCursor(0, 0);
+ }
+
+ /** @override */
+ eraseToLeft() {
+ const {cursorX, cursorY} = this.term.buffer.active;
+ this.xtermInternal_.eraseInBufferLine(cursorY, 0, cursorX + 1);
+ }
+
+ /** @override */
+ eraseLine() {
+ const {cursorY} = this.term.buffer.active;
+ this.xtermInternal_.eraseInBufferLine(cursorY, 0, this.term.cols);
+ }
+
+ /** @override */
+ setCursorPosition(row, column) {
+ this.xtermInternal_.setCursor(column, row);
+ }
+
+ /** @override */
+ setCursorColumn(column) {
+ this.xtermInternal_.setCursor(column, this.term.buffer.active.cursorY);
+ }
+
+ /** @override */
newLine() {
this.xtermInternal_.newLine();
}
/** @override */
cursorLeft(number) {
- this.xtermInternal_.cursorLeft(number ?? 1);
+ this.xtermInternal_.moveCursor(-(number ?? 1), 0);
}
/** @override */
@@ -656,24 +692,7 @@
* still runs.
*/
installUnimplementedStubs_() {
- this.keyboard = {
- keyMap: {
- keyDefs: [],
- },
- bindings: {
- clear: () => {},
- addBinding: () => {},
- addBindings: () => {},
- OsDefaults: {},
- },
- };
- this.keyboard.keyMap.keyDefs[78] = {};
- this.keyboard.keyMap.keyDefs[84] = {};
-
const methodNames = [
- 'eraseLine',
- 'setCursorColumn',
- 'setCursorPosition',
'setCursorVisible',
'uninstallKeyboard',
];
@@ -1272,6 +1291,10 @@
return true;
}
+ if (this.handleHtermKeyBindings_(ev)) {
+ return true;
+ }
+
const handler = this.keyDownHandlers_.get(
encodeKeyCombo(modifiers, ev.keyCode));
if (handler) {
@@ -1285,6 +1308,57 @@
}
/**
+ * @param {!KeyboardEvent} ev
+ * @return {boolean} Return true if the key event is handled.
+ */
+ handleHtermKeyBindings_(ev) {
+ // The logic here is a simplified version of
+ // hterm.Keyboard.prototype.onKeyDown_;
+ const htermKeyDown = {
+ keyCode: ev.keyCode,
+ shift: ev.shiftKey,
+ ctrl: ev.ctrlKey,
+ alt: ev.altKey,
+ meta: ev.metaKey,
+ };
+ const htermBinding = this.htermKeyBindings_.getBinding(htermKeyDown);
+
+ if (!htermBinding) {
+ return false;
+ }
+
+ // If there is a handler but the event is not keydown (e.g. keypress,
+ // keyup), we just do nothing.
+ if (ev.type !== 'keydown') {
+ ev.preventDefault();
+ ev.stopPropagation();
+ return true;
+ }
+
+ let action;
+ if (typeof htermBinding.action === 'function') {
+ action = htermBinding.action.call(this.keyboard, this, htermKeyDown);
+ } else {
+ action = htermBinding.action;
+ }
+
+ const KeyActions = hterm.Keyboard.KeyActions;
+ switch (action) {
+ case KeyActions.DEFAULT:
+ return false;
+ case KeyActions.PASS:
+ return true;
+ default:
+ console.warn(`KeyAction ${action} is not supported`);
+ // Fall through.
+ case KeyActions.CANCEL:
+ ev.preventDefault();
+ ev.stopPropagation();
+ return true;
+ }
+ }
+
+ /**
* Handle arrow keys and the "six pack keys" (e.g. home, insert...) because
* xterm.js does not always handle them correctly with modifier keys.
*
diff --git a/terminal/js/terminal_xterm_internal.js b/terminal/js/terminal_xterm_internal.js
index 8842868..77aadd3 100644
--- a/terminal/js/terminal_xterm_internal.js
+++ b/terminal/js/terminal_xterm_internal.js
@@ -126,7 +126,10 @@
_inputHandler: {
nextLine: function(),
print: function(!Uint32Array, number, number),
+ _eraseInBufferLine: function(number, number, number, boolean,
+ boolean),
_moveCursor: function(number, number),
+ _setCursor: function(number, number),
_parser: {
registerDcsHandler: function(!Object, !TmuxDcsPHandler),
_transitions: {
@@ -186,10 +189,35 @@
}
/**
- * @param {number} number
+ * Move the cursor relative to the current position.
+ *
+ * @param {number} x Can be negative.
+ * @param {number} y Can be negative.
*/
- cursorLeft(number) {
- this.core_._inputHandler._moveCursor(-number, 0);
+ moveCursor(x, y) {
+ this.core_._inputHandler._moveCursor(x, y);
+ this.scheduleFullRefresh_();
+ }
+
+ /**
+ * @param {number} y The row number
+ * @param {number} start The starting column
+ * @param {number} end The ending column (not inclusive)
+ */
+ eraseInBufferLine(y, start, end) {
+ this.core_._inputHandler._eraseInBufferLine(y, start, end,
+ /* clearWrap= */false, /* respectProtect= */false);
+ this.scheduleFullRefresh_();
+ }
+
+ /**
+ * Set the absolute position of the cursor.
+ *
+ * @param {number} x
+ * @param {number} y
+ */
+ setCursor(x, y) {
+ this.core_._inputHandler._setCursor(x, y);
this.scheduleFullRefresh_();
}
diff --git a/terminal/js/terminal_xterm_internal_tests.js b/terminal/js/terminal_xterm_internal_tests.js
index 7baec8b..615e55b 100644
--- a/terminal/js/terminal_xterm_internal_tests.js
+++ b/terminal/js/terminal_xterm_internal_tests.js
@@ -76,14 +76,36 @@
assert.equal(buffer.cursorY, 1);
});
- it('cursorLeft()', async function() {
- await this.write('012');
+ it('moveCursor()', async function() {
+ await this.write('012\r\n345');
const buffer = this.terminal.buffer.active;
assert.equal(buffer.cursorX, 3);
+ assert.equal(buffer.cursorY, 1);
+ this.xtermInternal.moveCursor(-1, 0);
+ assert.equal(buffer.cursorX, 2);
+ assert.equal(buffer.cursorY, 1);
+ this.xtermInternal.moveCursor(-1, -1);
+ assert.equal(buffer.cursorX, 1);
assert.equal(buffer.cursorY, 0);
- this.xtermInternal.cursorLeft(1);
- assert.equal(this.terminal.buffer.active.cursorX, 2);
+ this.xtermInternal.moveCursor(2, 3);
+ assert.equal(buffer.cursorX, 3);
+ assert.equal(buffer.cursorY, 3);
+ });
+
+ it('setCursor()', async function() {
+ const buffer = this.terminal.buffer.active;
+ assert.equal(buffer.cursorX, 0);
assert.equal(buffer.cursorY, 0);
+ this.xtermInternal.setCursor(10, 20);
+ assert.equal(buffer.cursorX, 10);
+ assert.equal(buffer.cursorY, 20);
+ });
+
+ it('eraseInBufferLine()', async function() {
+ await this.write('012345');
+ this.xtermInternal.eraseInBufferLine(0, 2, 4);
+ assert.equal(this.terminal.buffer.active.getLine(0).translateToString(true),
+ '01 45');
});
it('installEscKHandler()', async function() {