Merge branch 'master' into mouse_services

This commit is contained in:
jerch
2019-07-26 18:23:57 +02:00
committed by GitHub
5 changed files with 182 additions and 92 deletions
@@ -0,0 +1,129 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
const width = 800;
const height = 600;
describe('Search Tests', function (): void {
this.timeout(200000);
before(async function (): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
await page.goto(APP);
await openTerminal();
await page.evaluate(`window.search = new SearchAddon();`);
await page.evaluate(`window.term.loadAddon(window.search);`);
});
after(() => {
browser.close();
});
beforeEach(async () => {
await page.evaluate(`window.term.reset()`);
});
it('Simple Search', async () => {
await writeSync('dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
assert.deepEqual(await page.evaluate(`window.search.findNext('test')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'test');
});
it('Scrolling Search', async () => {
let dataString = '';
for (let i = 0; i < 100; i++) {
if (i === 52) {
dataString += '$^1_3{}test$#';
}
dataString += makeData(50);
}
await writeSync(dataString);
assert.deepEqual(await page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '$^1_3{}test$#');
});
it ('Incremental Find Previous', async () => {
await page.evaluate(`window.term.writeln('package.jsonc\\n')`);
await writeSync('package.json pack package.lock');
await page.evaluate(`window.search.findPrevious('pack', {incremental: true})`);
let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
let selectionPosition: {startColumn: number, startRow: number, endColumn: number, endRow: number} = await page.evaluate(`window.term.getSelectionPosition()`);
// We look further ahead in the line to ensure that pack was selected from package.lock
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock');
await page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json');
await page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`);
// We have to reevaluate line because it should have switched starting rows at this point
line = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
});
it ('Incremental Find Next', async () => {
await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`);
await writeSync('package.jsonc');
await page.evaluate(`window.search.findNext('pack', {incremental: true})`);
let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
let selectionPosition: {startColumn: number, startRow: number, endColumn: number, endRow: number} = await page.evaluate(`window.term.getSelectionPosition()`);
// We look further ahead in the line to ensure that pack was selected from package.lock
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock');
await page.evaluate(`window.search.findNext('package.j', {incremental: true})`);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json');
await page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`);
// We have to reevaluate line because it should have switched starting rows at this point
line = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
});
it ('Simple Regex', async () => {
await writeSync('abc123defABCD');
await page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'ABCD');
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
if (options.rendererType === 'dom') {
await page.waitForSelector('.xterm-rows');
} else {
await page.waitForSelector('.xterm-text-layer');
}
}
async function writeSync(data: string): Promise<void> {
await page.evaluate(`window.term.write('${data}');`);
while (true) {
if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) {
break;
}
}
}
function makeData(length: number): string {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
+24 -54
View File
@@ -57,8 +57,8 @@ export class SearchAddon implements ITerminalAddon {
return false;
}
let startCol: number = 0;
let startRow = this._terminal.buffer.viewportY;
let startCol = 0;
let startRow = 0;
if (this._terminal.hasSelection()) {
const incremental = searchOptions ? searchOptions.incremental : false;
@@ -71,20 +71,8 @@ export class SearchAddon implements ITerminalAddon {
this._initLinesCache();
// A row that has isWrapped = false
let findingRow = startRow;
// index of beginning column that _findInLine need to scan.
let cumulativeCols = startCol;
// If startRow is wrapped row, scan for unwrapped row above.
// So we can start matching on wrapped line from long unwrapped line.
let currentLine = this._terminal.buffer.getLine(findingRow);
while (currentLine && currentLine.isWrapped) {
cumulativeCols += this._terminal.cols;
currentLine = this._terminal.buffer.getLine(--findingRow);
}
// Search startRow
let result = this._findInLine(term, findingRow, cumulativeCols, searchOptions);
let result = this._findInLine(term, startRow, startCol, searchOptions);
// Search from startRow + 1 to end
if (!result) {
@@ -99,11 +87,9 @@ export class SearchAddon implements ITerminalAddon {
}
}
}
// Search from the top to the startRow (search the whole startRow again in
// case startCol > 0)
if (!result) {
for (let y = 0; y < findingRow; y++) {
// If we hit the bottom and didn't search from the very top wrap back up
if (!result && startRow !== 0) {
for (let y = 0; y < startRow; y++) {
result = this._findInLine(term, y, 0, searchOptions);
if (result) {
break;
@@ -133,61 +119,45 @@ export class SearchAddon implements ITerminalAddon {
}
const isReverseSearch = true;
let startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1;
let startRow = this._terminal.buffer.baseY + this._terminal.rows;
let startCol = this._terminal.cols;
let result: ISearchResult | undefined = undefined;
const incremental = searchOptions ? searchOptions.incremental : false;
if (this._terminal.hasSelection()) {
// Start from the selection start if there is a selection
const currentSelection = this._terminal.getSelectionPosition()!;
// Start from selection start if there is a selection
startRow = currentSelection.startRow;
startCol = currentSelection.startColumn;
}
this._initLinesCache();
// Search startRow
let result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
if (incremental) {
result = this._findInLine(term, startRow, startCol, searchOptions, false);
if (!(result && result.row === startRow && result.col === startCol)) {
result = this._findInLine(term, startRow, startCol, searchOptions, true);
}
} else {
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
}
// Search from startRow - 1 to top
if (!result) {
// If the line is wrapped line, increase number of columns that is needed to be scanned
// Se we can scan on wrapped line from unwrapped line
let cumulativeCols = this._terminal.cols;
if (this._terminal.buffer.getLine(startRow)!.isWrapped) {
cumulativeCols += startCol;
}
startCol = this._terminal.cols;
for (let y = startRow - 1; y >= 0; y--) {
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);
result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch);
if (result) {
break;
}
// If the current line is wrapped line, increase scanning range,
// preparing for scanning on unwrapped line
const line = this._terminal.buffer.getLine(y);
if (line && line.isWrapped) {
cumulativeCols += this._terminal.cols;
} else {
cumulativeCols = this._terminal.cols;
}
}
}
// Search from the bottom to startRow (search the whole startRow again in
// case startCol > 0)
if (!result) {
const searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1;
let cumulativeCols = this._terminal.cols;
for (let y = searchFrom; y >= startRow; y--) {
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);
// If we hit the top and didn't search from the very bottom wrap back down
if (!result && startRow !== (this._terminal.buffer.baseY + this._terminal.rows)) {
for (let y = (this._terminal.buffer.baseY + this._terminal.rows); y > startRow; y--) {
result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch);
if (result) {
break;
}
const line = this._terminal.buffer.getLine(y);
if (line && line.isWrapped) {
cumulativeCols += this._terminal.cols;
} else {
cumulativeCols = this._terminal.cols;
}
}
}
+5 -8
View File
@@ -58,11 +58,12 @@ function setPadding(): void {
term.fit();
}
function getSearchOptions(): ISearchOptions {
function getSearchOptions(e: KeyboardEvent): ISearchOptions {
return {
regex: (document.getElementById('regex') as HTMLInputElement).checked,
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked,
incremental: e.key !== `Enter`
};
}
@@ -134,15 +135,11 @@ function createTerminal(): void {
addDomListener(paddingElement, 'change', setPadding);
addDomListener(actionElements.findNext, 'keyup', (e) => {
const searchOptions = getSearchOptions();
searchOptions.incremental = e.key !== `Enter`;
searchAddon.findNext(actionElements.findNext.value, searchOptions);
searchAddon.findNext(actionElements.findNext.value, getSearchOptions(e));
});
addDomListener(actionElements.findPrevious, 'keyup', (e) => {
if (e.key === `Enter`) {
searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions());
}
searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions(e));
});
// fit is called within a setTimeout, cols and rows need this.
+11 -28
View File
@@ -556,16 +556,12 @@ describe('Terminal', () => {
afterEach(() => term.browser.isMac = originalIsMac);
it('should interfere with the alt key on keyDown', () => {
(<any>term)._keyDownHandled = false;
evKeyDown.altKey = true;
evKeyDown.keyCode = 81;
term.keyDown(evKeyDown);
assert.equal((<any>term)._keyDownHandled, true);
(<any>term)._keyDownHandled = false;
assert.equal(term.keyDown(evKeyDown), false);
evKeyDown.altKey = true;
evKeyDown.keyCode = 192;
term.keyDown(evKeyDown);
assert.equal((<any>term)._keyDownHandled, true);
assert.equal(term.keyDown(evKeyDown), false);
});
});
@@ -578,29 +574,22 @@ describe('Terminal', () => {
afterEach(() => term.browser.isMac = originalIsMac);
it('should not interfere with the alt key on keyDown', () => {
(<any>term)._keyDownHandled = false;
evKeyDown.altKey = true;
evKeyDown.keyCode = 81;
term.keyDown(evKeyDown);
assert.equal((<any>term)._keyDownHandled, false);
(<any>term)._keyDownHandled = false;
assert.equal(term.keyDown(evKeyDown), true);
evKeyDown.altKey = true;
evKeyDown.keyCode = 192;
term.keyDown(evKeyDown);
assert.equal((<any>term)._keyDownHandled, false);
assert.equal(term.keyDown(evKeyDown), true);
});
it('should interfere with the alt + arrow keys', () => {
(<any>term)._keyDownHandled = false;
evKeyDown.altKey = true;
evKeyDown.keyCode = 37;
term.keyDown(evKeyDown);
assert.equal((<any>term)._keyDownHandled, true);
(<any>term)._keyDownHandled = false;
assert.equal(term.keyDown(evKeyDown), false);
evKeyDown.altKey = true;
evKeyDown.keyCode = 39;
term.keyDown(evKeyDown);
assert.equal((<any>term)._keyDownHandled, true);
assert.equal(term.keyDown(evKeyDown), false);
});
it('should emit key with alt + key on keyPress', (done) => {
@@ -652,32 +641,26 @@ describe('Terminal', () => {
afterEach(() => term.browser.isWindows = originalIsWindows);
it('should not interfere with the alt + ctrl key on keyDown', () => {
(<any>term)._keyDownHandled = false;
evKeyPress.altKey = true;
evKeyPress.ctrlKey = true;
evKeyPress.keyCode = 81;
term.keyDown(evKeyPress);
assert.equal((<any>term)._keyDownHandled, false);
(<any>term)._keyDownHandled = false;
assert.equal(term.keyDown(evKeyPress), true);
evKeyDown.altKey = true;
evKeyDown.ctrlKey = true;
evKeyDown.keyCode = 81;
term.keyDown(evKeyDown);
assert.equal((<any>term)._keyDownHandled, false);
assert.equal(term.keyDown(evKeyPress), true);
});
it('should interefere with the alt + ctrl + arrow keys', () => {
it('should interfere with the alt + ctrl + arrow keys', () => {
evKeyDown.altKey = true;
evKeyDown.ctrlKey = true;
(<any>term)._keyDownHandled = false;
evKeyDown.keyCode = 37;
term.keyDown(evKeyDown);
assert.equal((<any>term)._keyDownHandled, true);
(<any>term)._keyDownHandled = false;
assert.equal(term.keyDown(evKeyDown), false);
evKeyDown.keyCode = 39;
term.keyDown(evKeyDown);
assert.equal((<any>term)._keyDownHandled, true);
assert.equal(term.keyDown(evKeyDown), false);
});
it('should emit key with alt + ctrl + key on keyPress', (done) => {
+13 -2
View File
@@ -266,7 +266,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._windowsMode.dispose();
this._windowsMode = undefined;
}
this._renderService.dispose();
if (this._renderService) {
this._renderService.dispose();
}
this._customKeyEventHandler = null;
this.write = () => {};
if (this.element && this.element.parentNode) {
@@ -1486,10 +1488,19 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.textarea.value = '';
}
this._keyDownHandled = true;
this._onKey.fire({ key: result.key, domEvent: event });
this.showCursor();
this._coreService.triggerDataEvent(result.key, true);
// Cancel events when not in screen reader mode so events don't get bubbled up and handled by
// other listeners. When screen reader mode is enabled, this could cause issues if the event
// is handled at a higher level, this is a compromise in order to echo keys to the screen
// reader.
if (!this.optionsService.options.screenReaderMode) {
return this.cancel(event, true);
}
this._keyDownHandled = true;
}
private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean {