addons/test.js and test.js -> Terminal.test.ts

Part of #862
This commit is contained in:
Daniel Imms
2017-08-07 22:15:13 -07:00
parent f52b43b380
commit e9b0867bd2
7 changed files with 974 additions and 923 deletions
+11
View File
@@ -158,6 +158,9 @@ export interface IBufferSet {
export interface IViewport {
syncScrollArea(): void;
onWheel(ev: WheelEvent): void;
onTouchStart(ev: TouchEvent): void;
onTouchMove(ev: TouchEvent): void;
}
export interface ISelectionManager {
@@ -171,6 +174,14 @@ export interface ISelectionManager {
setSelection(row: number, col: number, length: number): void;
}
export interface ICompositionHelper {
compositionstart(): void;
compositionupdate(ev: CompositionEvent): void;
compositionend(): void;
updateCompositionElements(dontRecurse?: boolean): void;
keydown(ev: KeyboardEvent): boolean;
}
export interface ICharMeasure {
width: number;
height: number;
+907
View File
@@ -0,0 +1,907 @@
import { assert, expect } from 'chai';
import { Terminal } from './Terminal';
import { MockViewport, MockCompositionHelper } from './utils/TestUtils';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
class TestTerminal extends Terminal {
public evaluateKeyEscapeSequence(ev: any): {cancel: boolean, key: string, scrollDisp: number} { return this._evaluateKeyEscapeSequence(<KeyboardEvent>ev); }
public keyDown(ev: any): boolean { return this._keyDown(ev); }
public keyPress(ev: any): boolean { return this._keyPress(ev); }
}
describe('term.js addons', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal();
term.refresh = () => {};
term.viewport = new MockViewport();
(<any>term).compositionHelper = new MockCompositionHelper();
// Force synchronous writes
term.write = (data) => {
term.writeBuffer.push(data);
(<any>term).innerWrite();
};
(<any>term).element = {
classList: {
toggle: () => {},
remove: () => {}
}
};
});
it('should load addons with Terminal.loadAddon', () => {
Terminal.loadAddon('attach');
// Test that addon was loaded successfully, adding attach to Terminal's
// prototype.
assert.equal(typeof (<any>Terminal).prototype.attach, 'function');
});
describe('getOption', () => {
it('should retrieve the option correctly', () => {
// In the `options` namespace.
term.options.cursorBlink = true;
assert.equal(term.getOption('cursorBlink'), true);
// On the Terminal instance
delete term.options.cursorBlink;
term.options.cursorBlink = false;
assert.equal(term.getOption('cursorBlink'), false);
});
it('should throw when retrieving a non-existant option', () => {
assert.throws(term.getOption.bind(term, 'fake', true));
});
});
describe('attachCustomKeyEventHandler', () => {
let evKeyDown = <KeyboardEvent>{
preventDefault: () => {},
stopPropagation: () => {},
type: 'keydown',
keyCode: 77
};
let evKeyPress = <KeyboardEvent>{
preventDefault: () => {},
stopPropagation: () => {},
type: 'keypress',
keyCode: 77
};
beforeEach(() => {
term.handler = () => {};
term.showCursor = () => {};
term.clearSelection = () => {};
});
it('should process the keydown/keypress event based on what the handler returns', () => {
assert.equal(term.keyDown(evKeyDown), true);
assert.equal(term.keyPress(evKeyPress), true);
term.attachCustomKeyEventHandler(ev => ev.keyCode === 77);
assert.equal(term.keyDown(evKeyDown), true);
assert.equal(term.keyPress(evKeyPress), true);
term.attachCustomKeyEventHandler(ev => ev.keyCode !== 77);
assert.equal(term.keyDown(evKeyDown), false);
assert.equal(term.keyPress(evKeyPress), false);
});
it('should alive after reset(ESC c Full Reset (RIS))', () => {
term.attachCustomKeyEventHandler(ev => ev.keyCode !== 77);
assert.equal(term.keyDown(evKeyDown), false);
assert.equal(term.keyPress(evKeyPress), false);
term.reset();
assert.equal(term.keyDown(evKeyDown), false);
assert.equal(term.keyPress(evKeyPress), false);
});
});
describe('setOption', () => {
let originalWarn;
let warnCallCount;
beforeEach(() => {
originalWarn = console.warn;
warnCallCount = 0;
console.warn = () => warnCallCount++;
});
afterEach(() => {
console.warn = originalWarn;
});
it('should set the option correctly', () => {
term.setOption('cursorBlink', true);
assert.equal(term.options.cursorBlink, true);
term.setOption('cursorBlink', false);
assert.equal(term.options.cursorBlink, false);
});
it('should throw when setting a non-existant option', () => {
assert.throws(term.setOption.bind(term, 'fake', true));
});
it('should warn and do nothing when scrollback is less than number of rows', () => {
term.setOption('scrollback', term.rows - 1);
assert.equal(term.getOption('scrollback'), 1000);
assert.equal(warnCallCount, 1);
});
});
describe('clear', () => {
it('should clear a buffer equal to rows', () => {
let promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
term.clear();
assert.equal(term.buffer.y, 0);
assert.equal(term.buffer.ybase, 0);
assert.equal(term.buffer.ydisp, 0);
assert.equal(term.buffer.lines.length, term.rows);
assert.deepEqual(term.buffer.lines.get(0), promptLine);
for (let i = 1; i < term.rows; i++) {
assert.deepEqual(term.buffer.lines.get(i), term.blankLine());
}
});
it('should clear a buffer larger than rows', () => {
// Fill the buffer with dummy rows
for (let i = 0; i < term.rows * 2; i++) {
term.write('test\n');
}
let promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
term.clear();
assert.equal(term.buffer.y, 0);
assert.equal(term.buffer.ybase, 0);
assert.equal(term.buffer.ydisp, 0);
assert.equal(term.buffer.lines.length, term.rows);
assert.deepEqual(term.buffer.lines.get(0), promptLine);
for (let i = 1; i < term.rows; i++) {
assert.deepEqual(term.buffer.lines.get(i), term.blankLine());
}
});
it('should not break the prompt when cleared twice', () => {
let promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
term.clear();
term.clear();
assert.equal(term.buffer.y, 0);
assert.equal(term.buffer.ybase, 0);
assert.equal(term.buffer.ydisp, 0);
assert.equal(term.buffer.lines.length, term.rows);
assert.deepEqual(term.buffer.lines.get(0), promptLine);
for (let i = 1; i < term.rows; i++) {
assert.deepEqual(term.buffer.lines.get(i), term.blankLine());
}
});
});
describe('scroll', () => {
describe('scrollDisp', () => {
let startYDisp;
beforeEach(() => {
for (let i = 0; i < term.rows * 2; i++) {
term.writeln('test');
}
startYDisp = term.rows + 1;
});
it('should scroll a single line', () => {
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollDisp(-1);
assert.equal(term.buffer.ydisp, startYDisp - 1);
term.scrollDisp(1);
assert.equal(term.buffer.ydisp, startYDisp);
});
it('should scroll multiple lines', () => {
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollDisp(-5);
assert.equal(term.buffer.ydisp, startYDisp - 5);
term.scrollDisp(5);
assert.equal(term.buffer.ydisp, startYDisp);
});
it('should not scroll beyond the bounds of the buffer', () => {
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollDisp(1);
assert.equal(term.buffer.ydisp, startYDisp);
for (let i = 0; i < startYDisp; i++) {
term.scrollDisp(-1);
}
assert.equal(term.buffer.ydisp, 0);
term.scrollDisp(-1);
assert.equal(term.buffer.ydisp, 0);
});
});
describe('scrollPages', () => {
let startYDisp;
beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) {
term.writeln('test');
}
startYDisp = (term.rows * 2) + 1;
});
it('should scroll a single page', () => {
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollPages(-1);
assert.equal(term.buffer.ydisp, startYDisp - (term.rows - 1));
term.scrollPages(1);
assert.equal(term.buffer.ydisp, startYDisp);
});
it('should scroll a multiple pages', () => {
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollPages(-2);
assert.equal(term.buffer.ydisp, startYDisp - (term.rows - 1) * 2);
term.scrollPages(2);
assert.equal(term.buffer.ydisp, startYDisp);
});
});
describe('scrollToTop', () => {
beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) {
term.writeln('test');
}
});
it('should scroll to the top', () => {
assert.notEqual(term.buffer.ydisp, 0);
term.scrollToTop();
assert.equal(term.buffer.ydisp, 0);
});
});
describe('scrollToBottom', () => {
let startYDisp;
beforeEach(() => {
for (let i = 0; i < term.rows * 3; i++) {
term.writeln('test');
}
startYDisp = (term.rows * 2) + 1;
});
it('should scroll to the bottom', () => {
term.scrollDisp(-1);
term.scrollToBottom();
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollPages(-1);
term.scrollToBottom();
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollToTop();
term.scrollToBottom();
assert.equal(term.buffer.ydisp, startYDisp);
});
});
describe('keyDown', () => {
it('should scroll down, when a key is pressed and terminal is scrolled up', () => {
// Override _evaluateKeyEscapeSequence to return cancel code
(<any>term)._evaluateKeyEscapeSequence = () => {
return { key: 'a' };
};
let event = <KeyboardEvent>{
type: 'keydown',
keyCode: 0,
preventDefault: () => {},
stopPropagation: () => {}
};
term.buffer.ydisp = 0;
term.buffer.ybase = 40;
term.keyDown(event);
// Ensure that now the terminal is scrolled to bottom
assert.equal(term.buffer.ydisp, term.buffer.ybase);
});
it('should not scroll down, when a custom keydown handler prevents the event', () => {
// Add some output to the terminal
for (let i = 0; i < term.rows * 3; i++) {
term.writeln('test');
}
let startYDisp = (term.rows * 2) + 1;
term.attachCustomKeyEventHandler(() => {
return false;
});
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollDisp(-1);
assert.equal(term.buffer.ydisp, startYDisp - 1);
term.keyDown(<KeyboardEvent>{ keyCode: 0 });
assert.equal(term.buffer.ydisp, startYDisp - 1);
});
});
});
describe('evaluateKeyEscapeSequence', () => {
it('should return the correct escape sequence for unmodified keys', () => {
// Backspace
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 8 }).key, '\x7f'); // ^?
// Tab
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 9 }).key, '\t');
// Return/enter
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 13 }).key, '\r'); // CR
// Escape
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 27 }).key, '\x1b');
// Page up, page down
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~
// End, Home
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1b[F'); // SS3 F
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1b[H'); // SS3 H
// Left, up, right, down arrows
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 39 }).key, '\x1b[C'); // CSI C
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 40 }).key, '\x1b[B'); // CSI B
// Insert
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~
// Delete
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~
// F1-F12
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); // SS3 P
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); // SS3 R
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); // SS3 S
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~
assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~
});
it('should return \\x1b[3;5~ for ctrl+delete', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~');
});
it('should return \\x1b[3;2~ for shift+delete', () => {
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~');
});
it('should return \\x1b[3;3~ for alt+delete', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~');
});
it('should return \\x1b[5D for ctrl+left', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
});
it('should return \\x1b[5C for ctrl+right', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
});
it('should return \\x1b[5A for ctrl+up', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A
});
it('should return \\x1b[5B for ctrl+down', () => {
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
describe('On non-macOS platforms', () => {
beforeEach(() => {
term.browser.isMac = false;
});
// Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100
// http://unix.stackexchange.com/a/108106
it('should return \\x1b[5D for alt+left', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
});
it('should return \\x1b[5C for alt+right', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
});
});
describe('On macOS platforms', () => {
beforeEach(() => {
term.browser.isMac = true;
});
it('should return \\x1bb for alt+left', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1bb'); // CSI 5 D
});
it('should return \\x1bf for alt+right', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1bf'); // CSI 5 C
});
});
it('should return \\x1b[5A for alt+up', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A
});
it('should return \\x1b[5B for alt+down', () => {
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
it('should return the correct escape sequence for modified F1-F12 keys', () => {
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~');
assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~');
assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~');
assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~');
});
});
describe('Third level shift', () => {
let evKeyDown;
let evKeyPress;
beforeEach(() => {
term.handler = () => {};
term.showCursor = () => {};
term.clearSelection = () => {};
// term.compositionHelper = {
// isComposing: false,
// keydown: {
// bind: () => {
// return () => { return true; };
// }
// }
// };
evKeyDown = {
preventDefault: () => {},
stopPropagation: () => {},
type: 'keydown',
altKey: null,
keyCode: null
};
evKeyPress = {
preventDefault: () => {},
stopPropagation: () => {},
type: 'keypress',
altKey: null,
charCode: null,
keyCode: null
};
});
describe('On Mac OS', () => {
beforeEach(() => {
term.browser.isMac = true;
});
it('should not interfere with the alt key on keyDown', () => {
evKeyDown.altKey = true;
evKeyDown.keyCode = 81;
assert.equal(term.keyDown(evKeyDown), true);
evKeyDown.altKey = true;
evKeyDown.keyCode = 192;
assert.equal(term.keyDown(evKeyDown), true);
});
it('should interefere with the alt + arrow keys', () => {
evKeyDown.altKey = true;
evKeyDown.keyCode = 37;
assert.equal(term.keyDown(evKeyDown), false);
evKeyDown.altKey = true;
evKeyDown.keyCode = 39;
assert.equal(term.keyDown(evKeyDown), false);
});
it('should emit key with alt + key on keyPress', (done) => {
let keys = ['@', '@', '\\', '\\', '|', '|'];
term.on('keypress', (key) => {
if (key) {
let index = keys.indexOf(key);
assert(index !== -1, 'Emitted wrong key: ' + key);
keys.splice(index, 1);
}
if (keys.length === 0) done();
});
evKeyPress.altKey = true;
// @
evKeyPress.charCode = null;
evKeyPress.keyCode = 64;
term.keyPress(evKeyPress);
// Firefox @
evKeyPress.charCode = 64;
evKeyPress.keyCode = 0;
term.keyPress(evKeyPress);
// \
evKeyPress.charCode = null;
evKeyPress.keyCode = 92;
term.keyPress(evKeyPress);
// Firefox \
evKeyPress.charCode = 92;
evKeyPress.keyCode = 0;
term.keyPress(evKeyPress);
// |
evKeyPress.charCode = null;
evKeyPress.keyCode = 124;
term.keyPress(evKeyPress);
// Firefox |
evKeyPress.charCode = 124;
evKeyPress.keyCode = 0;
term.keyPress(evKeyPress);
});
});
describe('On MS Windows', () => {
beforeEach(() => {
term.browser.isMSWindows = true;
});
it('should not interfere with the alt + ctrl key on keyDown', () => {
evKeyPress.altKey = true;
evKeyPress.ctrlKey = true;
evKeyPress.keyCode = 81;
assert.equal(term.keyDown(evKeyPress), true);
evKeyDown.altKey = true;
evKeyDown.ctrlKey = true;
evKeyDown.keyCode = 81;
assert.equal(term.keyDown(evKeyDown), true);
});
it('should interefere with the alt + ctrl + arrow keys', () => {
evKeyDown.altKey = true;
evKeyDown.ctrlKey = true;
evKeyDown.keyCode = 37;
assert.equal(term.keyDown(evKeyDown), false);
evKeyDown.keyCode = 39;
assert.equal(term.keyDown(evKeyDown), false);
});
it('should emit key with alt + ctrl + key on keyPress', (done) => {
let keys = ['@', '@', '\\', '\\', '|', '|'];
term.on('keypress', (key) => {
if (key) {
let index = keys.indexOf(key);
assert(index !== -1, 'Emitted wrong key: ' + key);
keys.splice(index, 1);
}
if (keys.length === 0) done();
});
evKeyPress.altKey = true;
evKeyPress.ctrlKey = true;
// @
evKeyPress.charCode = null;
evKeyPress.keyCode = 64;
term.keyPress(evKeyPress);
// Firefox @
evKeyPress.charCode = 64;
evKeyPress.keyCode = 0;
term.keyPress(evKeyPress);
// \
evKeyPress.charCode = null;
evKeyPress.keyCode = 92;
term.keyPress(evKeyPress);
// Firefox \
evKeyPress.charCode = 92;
evKeyPress.keyCode = 0;
term.keyPress(evKeyPress);
// |
evKeyPress.charCode = null;
evKeyPress.keyCode = 124;
term.keyPress(evKeyPress);
// Firefox |
evKeyPress.charCode = 124;
evKeyPress.keyCode = 0;
term.keyPress(evKeyPress);
});
});
});
describe('unicode - surrogates', () => {
it('2 characters per cell', function (): void {
this.timeout(10000); // This is needed because istanbul patches code and slows it down
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.write(high + String.fromCharCode(i));
let tchar = term.buffer.lines.get(0)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
term.reset();
}
});
it('2 characters at last cell', () => {
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.write(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0)[term.buffer.x - 1][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0)[term.buffer.x - 1][CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(' ');
term.reset();
}
});
it('2 characters per cell over line end with autowrap', () => {
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = true;
term.write('a' + high + String.fromCharCode(i));
expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX]).eql('a');
expect(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(term.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
term.reset();
}
});
it('2 characters per cell over line end without autowrap', () => {
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = false;
term.write('a' + high + String.fromCharCode(i));
// auto wraparound mode should cut off the rest of the line
expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX]).eql('a');
expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(term.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
term.reset();
}
});
it('splitted surrogates', () => {
let high = String.fromCharCode(0xD800);
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.write(high);
term.write(String.fromCharCode(i));
let tchar = term.buffer.lines.get(0)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
term.reset();
}
});
});
describe('unicode - combining characters', () => {
it('café', () => {
term.write('cafe\u0301');
expect(term.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(term.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(term.buffer.lines.get(0)[3][CHAR_DATA_WIDTH_INDEX]).eql(1);
});
it('café - end of line', () => {
term.buffer.x = term.cols - 1 - 3;
term.write('cafe\u0301');
expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_WIDTH_INDEX]).eql(1);
expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(term.buffer.lines.get(0)[1][CHAR_DATA_WIDTH_INDEX]).eql(1);
});
it('multiple combined é', () => {
term.wraparoundMode = true;
term.write(Array(100).join('e\u0301'));
for (let i = 0; i < term.cols; ++i) {
let tchar = term.buffer.lines.get(0)[i];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
}
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
});
it('multiple surrogate with combined', () => {
term.wraparoundMode = true;
term.write(Array(100).join('\uD800\uDC00\u0301'));
for (let i = 0; i < term.cols; ++i) {
let tchar = term.buffer.lines.get(0)[i];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
}
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
});
});
describe('unicode - fullwidth characters', () => {
it('cursor movement even', () => {
expect(term.buffer.x).eql(0);
term.write('¥');
expect(term.buffer.x).eql(2);
});
it('cursor movement odd', () => {
term.buffer.x = 1;
expect(term.buffer.x).eql(1);
term.write('¥');
expect(term.buffer.x).eql(3);
});
it('line of ¥ even', () => {
term.wraparoundMode = true;
term.write(Array(50).join('¥'));
for (let i = 0; i < term.cols; ++i) {
let tchar = term.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of ¥ odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.write(Array(50).join('¥'));
for (let i = 1; i < term.cols - 1; ++i) {
let tchar = term.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
let tchar = term.buffer.lines.get(0)[term.cols - 1];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of ¥ with combining odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.write(Array(50).join('¥\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
let tchar = term.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
let tchar = term.buffer.lines.get(0)[term.cols - 1];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of ¥ with combining even', () => {
term.wraparoundMode = true;
term.write(Array(50).join('¥\u0301'));
for (let i = 0; i < term.cols; ++i) {
let tchar = term.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of surrogate fullwidth with combining odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
let tchar = term.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
let tchar = term.buffer.lines.get(0)[term.cols - 1];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of surrogate fullwidth with combining even', () => {
term.wraparoundMode = true;
term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 0; i < term.cols; ++i) {
let tchar = term.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
let tchar = term.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
});
describe('insert mode', () => {
it('halfwidth - all', () => {
term.write(Array(9).join('0123456789').slice(-80));
term.buffer.x = 10;
term.buffer.y = 0;
term.insertMode = true;
term.write('abcde');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('a');
expect(term.buffer.lines.get(0)[14][CHAR_DATA_CHAR_INDEX]).eql('e');
expect(term.buffer.lines.get(0)[15][CHAR_DATA_CHAR_INDEX]).eql('0');
expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql('4');
});
it('fullwidth - insert', () => {
term.write(Array(9).join('0123456789').slice(-80));
term.buffer.x = 10;
term.buffer.y = 0;
term.insertMode = true;
term.write('¥¥¥');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(term.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('');
expect(term.buffer.lines.get(0)[14][CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(term.buffer.lines.get(0)[15][CHAR_DATA_CHAR_INDEX]).eql('');
expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql('3');
});
it('fullwidth - right border', () => {
term.write(Array(41).join('¥'));
term.buffer.x = 10;
term.buffer.y = 0;
term.insertMode = true;
term.write('a');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('a');
expect(term.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql(' '); // fullwidth char got replaced
term.write('b');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('b');
expect(term.buffer.lines.get(0)[12][CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql(''); // empty cell after fullwidth
});
});
});
+21 -21
View File
@@ -29,7 +29,7 @@ import * as Mouse from './utils/Mouse';
import { CHARSETS } from './Charsets';
import { getRawByteCoords } from './utils/Mouse';
import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData, Option, StringOption, BooleanOption, StringArrayOption, NumberOption, GeometryOption, HandlerOption } from './Types';
import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions } from './Interfaces';
import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper } from './Interfaces';
// Declare for RequireJS in loadAddon
declare var define: any;
@@ -259,8 +259,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
private linkifier: Linkifier;
public buffers: BufferSet;
public buffer: Buffer;
public viewport: Viewport;
private compositionHelper: CompositionHelper;
public viewport: IViewport;
private compositionHelper: ICompositionHelper;
public charMeasure: CharMeasure;
public cols: number;
@@ -601,14 +601,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
if (document.activeElement !== this) {
return;
}
self.keyDown(ev);
self._keyDown(ev);
}, true);
on(this.element, 'keypress', function (ev: KeyboardEvent): void {
if (document.activeElement !== this) {
return;
}
self.keyPress(ev);
self._keyPress(ev);
}, true);
on(this.element, 'keyup', (ev: KeyboardEvent) => {
@@ -618,19 +618,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}, true);
on(this.textarea, 'keydown', (ev: KeyboardEvent) => {
this.keyDown(ev);
this._keyDown(ev);
}, true);
on(this.textarea, 'keypress', (ev: KeyboardEvent) => {
this.keyPress(ev);
this._keyPress(ev);
// Truncate the textarea's value, since it is not needed
this.textarea.value = '';
}, true);
on(this.textarea, 'compositionstart', this.compositionHelper.compositionstart.bind(this.compositionHelper));
on(this.textarea, 'compositionupdate', this.compositionHelper.compositionupdate.bind(this.compositionHelper));
on(this.textarea, 'compositionend', this.compositionHelper.compositionend.bind(this.compositionHelper));
this.on('refresh', this.compositionHelper.updateCompositionElements.bind(this.compositionHelper));
on(this.textarea, 'compositionstart', () => this.compositionHelper.compositionstart());
on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this.compositionHelper.compositionupdate(e));
on(this.textarea, 'compositionend', () => this.compositionHelper.compositionend());
this.on('refresh', () => this.compositionHelper.updateCompositionElements());
this.on('refresh', (data) => this.queueLinkification(data.start, data.end));
}
@@ -1474,21 +1474,21 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
* @param {KeyboardEvent} ev The keydown event to be handled.
*/
private keyDown(ev: KeyboardEvent): boolean {
protected _keyDown(ev: KeyboardEvent): boolean {
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
return false;
}
this.restartCursorBlinking();
if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
if (!this.compositionHelper.keydown(ev)) {
if (this.buffer.ybase !== this.buffer.ydisp) {
this.scrollToBottom();
}
return false;
}
const result = this.evaluateKeyEscapeSequence(ev);
const result = this._evaluateKeyEscapeSequence(ev);
if (result.key === C0.DC3) { // XOFF
this.writeStopped = true;
@@ -1501,7 +1501,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
return this.cancel(ev, true);
}
if (isThirdLevelShift(ev)) {
if (isThirdLevelShift(this.browser, ev)) {
return true;
}
@@ -1529,7 +1529,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
* @param ev The keyboard event to be translated to key escape sequence.
*/
private evaluateKeyEscapeSequence(ev: KeyboardEvent): {cancel: boolean, key: string, scrollDisp: number} {
protected _evaluateKeyEscapeSequence(ev: KeyboardEvent): {cancel: boolean, key: string, scrollDisp: number} {
const result: {cancel: boolean, key: string, scrollDisp: number} = {
// Whether to cancel event propogation (NOTE: this may not be needed since the event is
// canceled at the end of keyDown
@@ -1836,7 +1836,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
* @param {KeyboardEvent} ev The keypress event to be handled.
*/
private keyPress(ev: KeyboardEvent): boolean {
protected _keyPress(ev: KeyboardEvent): boolean {
let key;
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
@@ -1856,7 +1856,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}
if (!key || (
(ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(ev)
(ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this.browser, ev)
)) {
return false;
}
@@ -2309,10 +2309,10 @@ function off(el: any, type: string, handler: (event: Event) => any, capture: boo
el.removeEventListener(type, handler, capture);
}
function isThirdLevelShift(ev: KeyboardEvent): boolean {
function isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
const thirdLevelKey =
(Browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
(Browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
(browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
(browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
if (ev.type === 'keypress') {
return thirdLevelKey;
+1 -1
View File
@@ -4,7 +4,7 @@
import { assert } from 'chai';
import { Viewport } from './Viewport';
import {BufferSet} from './BufferSet';
import { BufferSet } from './BufferSet';
describe('Viewport', () => {
let terminal;
-10
View File
@@ -1,10 +0,0 @@
var assert = require('chai').assert;
var Terminal = require('../../xterm');
describe('xterm.js addons', function() {
it('should load addons with Terminal.loadAddon', function () {
Terminal.loadAddon('attach');
// Test that addon was loaded successfully
assert.equal(typeof Terminal.prototype.attach, 'function');
});
});
-889
View File
@@ -1,889 +0,0 @@
var assert = require('chai').assert;
var expect = require('chai').expect;
var Terminal = require('../xterm');
var CHAR_DATA_CHAR_INDEX = require('../Buffer').CHAR_DATA_CHAR_INDEX;
var CHAR_DATA_WIDTH_INDEX = require('../Buffer').CHAR_DATA_WIDTH_INDEX;
describe('xterm.js', function() {
var xterm;
beforeEach(function () {
xterm = new Terminal();
xterm.refresh = function(){};
xterm.viewport = {
syncScrollArea: function(){}
};
xterm.compositionHelper = {
keydown: function(){ return true; }
};
// Force synchronous writes
xterm.write = function(data) {
xterm.writeBuffer.push(data);
xterm.innerWrite();
};
xterm.element = {
classList: {
toggle: function(){},
remove: function(){}
}
};
});
describe('getOption', function() {
it('should retrieve the option correctly', function() {
// In the `options` namespace.
xterm.options.cursorBlink = true;
assert.equal(xterm.getOption('cursorBlink'), true);
// On the Terminal instance
delete xterm.options.cursorBlink;
xterm.cursorBlink = false;
assert.equal(xterm.getOption('cursorBlink'), false);
});
it('should throw when retrieving a non-existant option', function() {
assert.throws(xterm.getOption.bind(xterm, 'fake', true));
});
});
describe('setOption', function() {
let originalWarn;
let warnCallCount;
beforeEach(() => {
originalWarn = console.warn;
warnCallCount = 0;
console.warn = () => warnCallCount++;
});
afterEach(() => {
console.warn = originalWarn;
});
it('should set the option correctly', function() {
xterm.setOption('cursorBlink', true);
assert.equal(xterm.cursorBlink, true);
assert.equal(xterm.options.cursorBlink, true);
xterm.setOption('cursorBlink', false);
assert.equal(xterm.cursorBlink, false);
assert.equal(xterm.options.cursorBlink, false);
});
it('should throw when setting a non-existant option', function() {
assert.throws(xterm.setOption.bind(xterm, 'fake', true));
});
it('should warn and do nothing when scrollback is less than number of rows', function() {
xterm.setOption('scrollback', xterm.rows - 1);
assert.equal(xterm.getOption('scrollback'), 1000);
assert.equal(warnCallCount, 1);
});
});
describe('clear', function() {
it('should clear a buffer equal to rows', function() {
var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y);
xterm.clear();
assert.equal(xterm.buffer.y, 0);
assert.equal(xterm.buffer.ybase, 0);
assert.equal(xterm.buffer.ydisp, 0);
assert.equal(xterm.buffer.lines.length, xterm.rows);
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
for (var i = 1; i < xterm.rows; i++) {
assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine());
}
});
it('should clear a buffer larger than rows', function() {
// Fill the buffer with dummy rows
for (var i = 0; i < xterm.rows * 2; i++) {
xterm.write('test\n');
}
var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y);
xterm.clear();
assert.equal(xterm.buffer.y, 0);
assert.equal(xterm.buffer.ybase, 0);
assert.equal(xterm.buffer.ydisp, 0);
assert.equal(xterm.buffer.lines.length, xterm.rows);
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
for (var i = 1; i < xterm.rows; i++) {
assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine());
}
});
it('should not break the prompt when cleared twice', function() {
var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y);
xterm.clear();
xterm.clear();
assert.equal(xterm.buffer.y, 0);
assert.equal(xterm.buffer.ybase, 0);
assert.equal(xterm.buffer.ydisp, 0);
assert.equal(xterm.buffer.lines.length, xterm.rows);
assert.deepEqual(xterm.buffer.lines.get(0), promptLine);
for (var i = 1; i < xterm.rows; i++) {
assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine());
}
});
});
describe('scroll', function() {
describe('scrollDisp', function() {
var startYDisp;
beforeEach(function() {
for (var i = 0; i < xterm.rows * 2; i++) {
xterm.writeln('test');
}
startYDisp = xterm.rows + 1;
});
it('should scroll a single line', function() {
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollDisp(-1);
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
xterm.scrollDisp(1);
assert.equal(xterm.buffer.ydisp, startYDisp);
});
it('should scroll multiple lines', function() {
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollDisp(-5);
assert.equal(xterm.buffer.ydisp, startYDisp - 5);
xterm.scrollDisp(5);
assert.equal(xterm.buffer.ydisp, startYDisp);
});
it('should not scroll beyond the bounds of the buffer', function() {
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollDisp(1);
assert.equal(xterm.buffer.ydisp, startYDisp);
for (var i = 0; i < startYDisp; i++) {
xterm.scrollDisp(-1);
}
assert.equal(xterm.buffer.ydisp, 0);
xterm.scrollDisp(-1);
assert.equal(xterm.buffer.ydisp, 0);
});
});
describe('scrollPages', function() {
var startYDisp;
beforeEach(function() {
for (var i = 0; i < xterm.rows * 3; i++) {
xterm.writeln('test');
}
startYDisp = (xterm.rows * 2) + 1;
});
it('should scroll a single page', function() {
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollPages(-1);
assert.equal(xterm.buffer.ydisp, startYDisp - (xterm.rows - 1));
xterm.scrollPages(1);
assert.equal(xterm.buffer.ydisp, startYDisp);
});
it('should scroll a multiple pages', function() {
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollPages(-2);
assert.equal(xterm.buffer.ydisp, startYDisp - (xterm.rows - 1) * 2);
xterm.scrollPages(2);
assert.equal(xterm.buffer.ydisp, startYDisp);
});
});
describe('scrollToTop', function() {
beforeEach(function() {
for (var i = 0; i < xterm.rows * 3; i++) {
xterm.writeln('test');
}
});
it('should scroll to the top', function() {
assert.notEqual(xterm.buffer.ydisp, 0);
xterm.scrollToTop();
assert.equal(xterm.buffer.ydisp, 0);
});
});
describe('scrollToBottom', function() {
var startYDisp;
beforeEach(function() {
for (var i = 0; i < xterm.rows * 3; i++) {
xterm.writeln('test');
}
startYDisp = (xterm.rows * 2) + 1;
});
it('should scroll to the bottom', function() {
xterm.scrollDisp(-1);
xterm.scrollToBottom();
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollPages(-1);
xterm.scrollToBottom();
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollToTop();
xterm.scrollToBottom();
assert.equal(xterm.buffer.ydisp, startYDisp);
});
});
describe('keyDown', function () {
it('should scroll down, when a key is pressed and terminal is scrolled up', function () {
// Override evaluateKeyEscapeSequence to return cancel code
xterm.evaluateKeyEscapeSequence = function() {
return { key: 'a' };
};
var event = {
type: 'keydown',
keyCode: 0,
preventDefault: function(){},
stopPropagation: function(){}
};
xterm.buffer.ydisp = 0;
xterm.buffer.ybase = 40;
assert.notEqual(xterm.buffer.ydisp, xterm.buffer.ybase);
xterm.keyDown(event);
// Ensure that now the terminal is scrolled to bottom
assert.equal(xterm.buffer.ydisp, xterm.buffer.ybase);
});
it('should not scroll down, when a custom keydown handler prevents the event', function () {
// Add some output to the terminal
for (var i = 0; i < xterm.rows * 3; i++) {
xterm.writeln('test');
}
var startYDisp = (xterm.rows * 2) + 1;
xterm.attachCustomKeyEventHandler(function () {
return false;
});
assert.equal(xterm.buffer.ydisp, startYDisp);
xterm.scrollDisp(-1);
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
xterm.keyDown({ keyCode: 0 });
assert.equal(xterm.buffer.ydisp, startYDisp - 1);
});
});
});
describe('evaluateKeyEscapeSequence', function() {
it('should return the correct escape sequence for unmodified keys', function() {
// Backspace
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 8 }).key, '\x7f'); // ^?
// Tab
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 9 }).key, '\t');
// Return/enter
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 13 }).key, '\r'); // CR
// Escape
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 27 }).key, '\x1b');
// Page up, page down
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~
// End, Home
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1b[F'); // SS3 F
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1b[H'); // SS3 H
// Left, up, right, down arrows
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 39 }).key, '\x1b[C'); // CSI C
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 40 }).key, '\x1b[B'); // CSI B
// Insert
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~
// Delete
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~
// F1-F12
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); // SS3 P
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); // SS3 R
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); // SS3 S
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~
assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~
});
it('should return \\x1b[3;5~ for ctrl+delete', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~');
});
it('should return \\x1b[3;2~ for shift+delete', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~');
});
it('should return \\x1b[3;3~ for alt+delete', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~');
});
it('should return \\x1b[5D for ctrl+left', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
});
it('should return \\x1b[5C for ctrl+right', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
});
it('should return \\x1b[5A for ctrl+up', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A
});
it('should return \\x1b[5B for ctrl+down', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
describe('On non-macOS platforms', function() {
beforeEach(function() {
xterm.browser.isMac = false;
});
// Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100
// http://unix.stackexchange.com/a/108106
it('should return \\x1b[5D for alt+left', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D
});
it('should return \\x1b[5C for alt+right', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C
});
});
describe('On macOS platforms', function() {
beforeEach(function() {
xterm.browser.isMac = true;
});
it('should return \\x1bb for alt+left', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1bb'); // CSI 5 D
});
it('should return \\x1bf for alt+right', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1bf'); // CSI 5 C
});
});
it('should return \\x1b[5A for alt+up', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A
});
it('should return \\x1b[5B for alt+down', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
it('should return the correct escape sequence for modified F1-F12 keys', function() {
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~');
assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~');
assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~');
assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~');
});
});
describe('attachCustomKeyEventHandler', function () {
var evKeyDown = {
preventDefault: function() {},
stopPropagation: function() {},
type: 'keydown'
}
var evKeyPress = {
preventDefault: function() {},
stopPropagation: function() {},
type: 'keypress'
}
beforeEach(function() {
xterm.handler = function() {};
xterm.showCursor = function() {};
xterm.clearSelection = function() {};
xterm.compositionHelper = {
keydown: {
bind: function() {
return function () { return true; }
}
},
keypress: {
bind: function() {
return function () { return true; }
}
}
}
});
it('should process the keydown/keypress event based on what the handler returns', function () {
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true);
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), true);
xterm.attachCustomKeyEventHandler(function (ev) {
return ev.keyCode === 77;
});
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true);
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), true);
xterm.attachCustomKeyEventHandler(function (ev) {
return ev.keyCode !== 77;
});
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false);
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false);
});
it('should alive after reset(ESC c Full Reset (RIS))', function () {
xterm.attachCustomKeyEventHandler(function (ev) {
return ev.keyCode !== 77;
});
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false);
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false);
xterm.reset();
assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false);
assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false);
});
});
describe('Third level shift', function() {
var evKeyDown = {
preventDefault: function() {},
stopPropagation: function() {},
type: 'keydown'
},
evKeyPress = {
preventDefault: function() {},
stopPropagation: function() {},
type: 'keypress'
};
beforeEach(function() {
xterm.handler = function() {};
xterm.showCursor = function() {};
xterm.clearSelection = function() {};
xterm.compositionHelper = {
isComposing: false,
keydown: {
bind: function() {
return function() { return true; };
}
}
};
});
describe('On Mac OS', function() {
beforeEach(function() {
xterm.browser.isMac = true;
});
it('should not interfere with the alt key on keyDown', function() {
assert.equal(
xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 81 })),
true
);
assert.equal(
xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 192 })),
true
);
});
it('should interefere with the alt + arrow keys', function() {
assert.equal(
xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 37 })),
false
);
assert.equal(
xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 39 })),
false
);
});
it('should emit key with alt + key on keyPress', function(done) {
var keys = ['@', '@', '\\', '\\', '|', '|'];
xterm.on('keypress', function(key) {
if (key) {
var index = keys.indexOf(key);
assert(index !== -1, "Emitted wrong key: " + key);
keys.splice(index, 1);
}
if (keys.length === 0) done();
});
xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, keyCode: 64 })); // @
// Firefox
xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, charCode: 64, keyCode: 0 }));
xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, keyCode: 92 })); // \
xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, charCode: 92, keyCode: 0 }));
xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, keyCode: 124 })); // |
xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, charCode: 124, keyCode: 0 }));
});
});
describe('On MS Windows', function() {
beforeEach(function() {
xterm.browser.isMSWindows = true;
});
it('should not interfere with the alt + ctrl key on keyDown', function() {
assert.equal(
xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 81 })),
true
);
assert.equal(
xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 192 })),
true
);
});
it('should interefere with the alt + ctrl + arrow keys', function() {
assert.equal(
xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 37 })),
false
);
assert.equal(
xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 39 })),
false
);
});
it('should emit key with alt + ctrl + key on keyPress', function(done) {
var keys = ['@', '@', '\\', '\\', '|', '|'];
xterm.on('keypress', function(key) {
if (key) {
var index = keys.indexOf(key);
assert(index !== -1, "Emitted wrong key: " + key);
keys.splice(index, 1);
}
if (keys.length === 0) done();
});
xterm.keyPress(
Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, keyCode: 64 })
); // @
xterm.keyPress(
Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, charCode: 64, keyCode: 0 })
);
xterm.keyPress(
Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, keyCode: 92 })
); // \
xterm.keyPress(
Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, charCode: 92, keyCode: 0 })
);
xterm.keyPress(
Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, keyCode: 124 })
); // |
xterm.keyPress(
Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, charCode: 124, keyCode: 0 })
);
});
});
});
describe('unicode - surrogates', function() {
it('2 characters per cell', function () {
this.timeout(10000); // This is needed because istanbul patches code and slows it down
var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.write(high + String.fromCharCode(i));
var tchar = xterm.buffer.lines.get(0)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
xterm.reset();
}
});
it('2 characters at last cell', function() {
var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.buffer.x = xterm.cols - 1;
xterm.write(high + String.fromCharCode(i));
expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(xterm.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(' ');
xterm.reset();
}
});
it('2 characters per cell over line end with autowrap', function() {
var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.buffer.x = xterm.cols - 1;
xterm.wraparoundMode = true;
xterm.write('a' + high + String.fromCharCode(i));
expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX]).eql('a');
expect(xterm.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(xterm.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(xterm.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
xterm.reset();
}
});
it('2 characters per cell over line end without autowrap', function() {
var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.buffer.x = xterm.cols - 1;
xterm.wraparoundMode = false;
xterm.write('a' + high + String.fromCharCode(i));
// auto wraparound mode should cut off the rest of the line
expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX]).eql('a');
expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(xterm.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
xterm.reset();
}
});
it('splitted surrogates', function() {
var high = String.fromCharCode(0xD800);
for (var i=0xDC00; i<=0xDCFF; ++i) {
xterm.write(high);
xterm.write(String.fromCharCode(i));
var tchar = xterm.buffer.lines.get(0)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
xterm.reset();
}
});
});
describe('unicode - combining characters', function() {
it('café', function () {
xterm.write('cafe\u0301');
expect(xterm.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(xterm.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(xterm.buffer.lines.get(0)[3][CHAR_DATA_WIDTH_INDEX]).eql(1);
});
it('café - end of line', function() {
xterm.buffer.x = xterm.cols - 1 - 3;
xterm.write('cafe\u0301');
expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_WIDTH_INDEX]).eql(1);
expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_WIDTH_INDEX]).eql(1);
});
it('multiple combined é', function() {
xterm.wraparoundMode = true;
xterm.write(Array(100).join('e\u0301'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.buffer.lines.get(0)[i];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
}
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
});
it('multiple surrogate with combined', function() {
xterm.wraparoundMode = true;
xterm.write(Array(100).join('\uD800\uDC00\u0301'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.buffer.lines.get(0)[i];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
}
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
});
});
describe('unicode - fullwidth characters', function() {
it('cursor movement even', function() {
expect(xterm.buffer.x).eql(0);
xterm.write('¥');
expect(xterm.buffer.x).eql(2);
});
it('cursor movement odd', function() {
xterm.buffer.x = 1;
expect(xterm.buffer.x).eql(1);
xterm.write('¥');
expect(xterm.buffer.x).eql(3);
});
it('line of ¥ even', function() {
xterm.wraparoundMode = true;
xterm.write(Array(50).join('¥'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of ¥ odd', function() {
xterm.wraparoundMode = true;
xterm.buffer.x = 1;
xterm.write(Array(50).join('¥'));
for (var i=1; i<xterm.cols-1; ++i) {
var tchar = xterm.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
tchar = xterm.buffer.lines.get(0)[xterm.cols-1];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of ¥ with combining odd', function() {
xterm.wraparoundMode = true;
xterm.buffer.x = 1;
xterm.write(Array(50).join('¥\u0301'));
for (var i=1; i<xterm.cols-1; ++i) {
var tchar = xterm.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
tchar = xterm.buffer.lines.get(0)[xterm.cols-1];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of ¥ with combining even', function() {
xterm.wraparoundMode = true;
xterm.write(Array(50).join('¥\u0301'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of surrogate fullwidth with combining odd', function() {
xterm.wraparoundMode = true;
xterm.buffer.x = 1;
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
for (var i=1; i<xterm.cols-1; ++i) {
var tchar = xterm.buffer.lines.get(0)[i];
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
tchar = xterm.buffer.lines.get(0)[xterm.cols-1];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
it('line of surrogate fullwidth with combining even', function() {
xterm.wraparoundMode = true;
xterm.write(Array(50).join('\ud843\ude6d\u0301'));
for (var i=0; i<xterm.cols; ++i) {
var tchar = xterm.buffer.lines.get(0)[i];
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
}
}
tchar = xterm.buffer.lines.get(1)[0];
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
});
});
describe('insert mode', function() {
it('halfwidth - all', function () {
xterm.write(Array(9).join('0123456789').slice(-80));
xterm.buffer.x = 10;
xterm.buffer.y = 0;
xterm.insertMode = true;
xterm.write('abcde');
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
expect(xterm.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('a');
expect(xterm.buffer.lines.get(0)[14][CHAR_DATA_CHAR_INDEX]).eql('e');
expect(xterm.buffer.lines.get(0)[15][CHAR_DATA_CHAR_INDEX]).eql('0');
expect(xterm.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql('4');
});
it('fullwidth - insert', function() {
xterm.write(Array(9).join('0123456789').slice(-80));
xterm.buffer.x = 10;
xterm.buffer.y = 0;
xterm.insertMode = true;
xterm.write('¥¥¥');
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
expect(xterm.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(xterm.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('');
expect(xterm.buffer.lines.get(0)[14][CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(xterm.buffer.lines.get(0)[15][CHAR_DATA_CHAR_INDEX]).eql('');
expect(xterm.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql('3');
});
it('fullwidth - right border', function() {
xterm.write(Array(41).join('¥'));
xterm.buffer.x = 10;
xterm.buffer.y = 0;
xterm.insertMode = true;
xterm.write('a');
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
expect(xterm.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('a');
expect(xterm.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(xterm.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql(' '); // fullwidth char got replaced
xterm.write('b');
expect(xterm.buffer.lines.get(0).length).eql(xterm.cols);
expect(xterm.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('b');
expect(xterm.buffer.lines.get(0)[12][CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(xterm.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql(''); // empty cell after fullwidth
});
});
});
+34 -2
View File
@@ -2,8 +2,9 @@
* @license MIT
*/
import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList } from '../Interfaces';
import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper } from '../Interfaces';
import { LineData } from '../Types';
import * as Browser from './Browser';
export class MockTerminal implements ITerminal {
options: ITerminalOptions = {};
@@ -15,7 +16,7 @@ export class MockTerminal implements ITerminal {
textarea: HTMLTextAreaElement;
rows: number;
cols: number;
browser: IBrowser;
browser: IBrowser = <any>Browser;
writeBuffer: string[];
children: HTMLElement[];
cursorHidden: boolean;
@@ -184,3 +185,34 @@ export class MockBuffer implements IBuffer {
throw new Error('Method not implemented.');
}
}
export class MockViewport implements IViewport {
onWheel(ev: WheelEvent): void {
throw new Error('Method not implemented.');
}
onTouchStart(ev: TouchEvent): void {
throw new Error('Method not implemented.');
}
onTouchMove(ev: TouchEvent): void {
throw new Error('Method not implemented.');
}
syncScrollArea(): void { }
}
export class MockCompositionHelper implements ICompositionHelper {
compositionstart(): void {
throw new Error('Method not implemented.');
}
compositionupdate(ev: CompositionEvent): void {
throw new Error('Method not implemented.');
}
compositionend(): void {
throw new Error('Method not implemented.');
}
updateCompositionElements(dontRecurse?: boolean): void {
throw new Error('Method not implemented.');
}
keydown(ev: KeyboardEvent): boolean {
return true;
}
}