Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions addons/xterm-addon-search/src/SearchAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export class SearchAddon implements ITerminalAddon {
}
this._lastSearchOptions = searchOptions;
if (searchOptions?.decorations) {
if (this._resultIndex !== undefined || this._cachedSearchTerm && term !== this._cachedSearchTerm) {
if (this._resultIndex !== undefined || this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm) {
this._highlightAllMatches(term, searchOptions);
}
}
Expand Down Expand Up @@ -288,7 +288,9 @@ export class SearchAddon implements ITerminalAddon {
}

if (this._searchResults) {
if (this._resultIndex === undefined) {
if (this._searchResults.size === 0) {
this._resultIndex = -1;
} else if (this._resultIndex === undefined) {
this._resultIndex = 0;
} else {
this._resultIndex++;
Expand All @@ -312,8 +314,10 @@ export class SearchAddon implements ITerminalAddon {
throw new Error('Cannot use addon until it has been loaded');
}
this._lastSearchOptions = searchOptions;
if (searchOptions?.decorations && (this._resultIndex !== undefined || term !== this._cachedSearchTerm)) {
this._highlightAllMatches(term, searchOptions);
if (searchOptions?.decorations) {
if (this._resultIndex !== undefined || this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm) {
this._highlightAllMatches(term, searchOptions);
}
}
return this._fireResults(term, this._findPreviousAndSelect(term, searchOptions), searchOptions);
}
Expand Down Expand Up @@ -408,12 +412,14 @@ export class SearchAddon implements ITerminalAddon {
}

if (this._searchResults) {
if (this._resultIndex === undefined || this._resultIndex < 0) {
this._resultIndex = this._searchResults?.size - 1;
if (this._searchResults.size === 0) {
this._resultIndex = -1;
} else if (this._resultIndex === undefined || this._resultIndex < 0) {
this._resultIndex = this._searchResults.size - 1;
} else {
this._resultIndex--;
if (this._resultIndex === -1) {
this._resultIndex = this._searchResults?.size - 1;
this._resultIndex = this._searchResults.size - 1;
}
}
}
Expand Down
181 changes: 177 additions & 4 deletions addons/xterm-addon-search/test/SearchAddon.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { assert } from 'chai';
import { readFile } from 'fs';
import { resolve } from 'path';
import { openTerminal, writeSync, launchBrowser } from '../../../out-test/api/TestUtils';
import { openTerminal, writeSync, launchBrowser, timeout } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright';

const APP = 'http://127.0.0.1:3001/test';
Expand All @@ -23,16 +23,19 @@ describe('Search Tests', function(): void {
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal(page);
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()`);
await page.evaluate(`
window.term.reset()
window.search?.dispose();
window.search = new SearchAddon();
window.term.loadAddon(window.search);
`);
});

it('Simple Search', async () => {
Expand Down Expand Up @@ -120,6 +123,176 @@ describe('Search Tests', function(): void {
});
});

describe('onDidChangeResults', async () => {
describe('findNext', () => {
it('should not fire unless the decorations option is set', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc');
assert.strictEqual(await page.evaluate(`window.search.findNext('a')`), true);
assert.strictEqual(await page.evaluate('window.calls.length'), 0);
assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate('window.calls.length'), 1);
});
it('should fire with correct event values', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc bc c');
assert.strictEqual(await page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 }
]);
assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 }
]);
assert.strictEqual(await page.evaluate(`window.search.findNext('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: -1, resultIndex: -1 }
]);
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: -1, resultIndex: -1 },
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 3, resultIndex: 1 },
{ resultCount: 3, resultIndex: 2 }
]);
});
it('should fire with correct event values (incremental)', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc aabc');
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: -1, resultIndex: -1 }
]);
});
});
describe('findPrevious', () => {
it('should not fire unless the decorations option is set', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc');
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a')`), true);
assert.strictEqual(await page.evaluate('window.calls.length'), 0);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate('window.calls.length'), 1);
});
it('should fire with correct event values', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc bc c');
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 }
]);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 }
]);
await timeout(2000);
assert.strictEqual(await page.evaluate(`debugger; window.search.findPrevious('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: -1, resultIndex: -1 }
]);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: -1, resultIndex: -1 },
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 3, resultIndex: 1 },
{ resultCount: 3, resultIndex: 0 }
]);
});
it('should fire with correct event values (incremental)', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc aabc');
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 2, resultIndex: 1 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 1 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 0 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: -1, resultIndex: -1 }
]);
});
});
});

describe('Regression tests', () => {
describe('#2444 wrapped line content not being found', () => {
let fixture: string;
Expand Down
20 changes: 18 additions & 2 deletions demo/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ const terminalContainer = document.getElementById('terminal-container');
const actionElements = {
find: <HTMLInputElement>document.querySelector('#find'),
findNext: <HTMLInputElement>document.querySelector('#find-next'),
findPrevious: <HTMLInputElement>document.querySelector('#find-previous')
findPrevious: <HTMLInputElement>document.querySelector('#find-previous'),
findResults: document.querySelector('#find-results')
};
const paddingElement = <HTMLInputElement>document.getElementById('padding');

Expand Down Expand Up @@ -398,9 +399,12 @@ function initAddons(term: TerminalType): void {
if (!addon.canChange) {
checkbox.disabled = true;
}
if(name === 'unicode11' && checkbox.checked) {
if (name === 'unicode11' && checkbox.checked) {
term.unicode.activeVersion = '11';
}
if (name === 'search' && checkbox.checked) {
addon.instance.onDidChangeResults(e => updateFindResults(e));
}
addDomListener(checkbox, 'change', () => {
if (checkbox.checked) {
addon.instance = new addon.ctor();
Expand All @@ -411,6 +415,8 @@ function initAddons(term: TerminalType): void {
}, 0);
} else if (name === 'unicode11') {
term.unicode.activeVersion = '11';
} else if (name === 'search') {
addon.instance.onDidChangeResults(e => updateFindResults(e));
}
} else {
if (name === 'webgl') {
Expand Down Expand Up @@ -439,6 +445,16 @@ function initAddons(term: TerminalType): void {
container.appendChild(fragment);
}

function updateFindResults(e: { resultIndex: number, resultCount: number } | undefined) {
let content: string;
if (e === undefined) {
content = 'undefined';
} else {
content = `index: ${e.resultIndex}, count: ${e.resultCount}`;
}
actionElements.findResults.textContent = content;
}

function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
element.addEventListener(type, handler);
term._core.register({ dispose: () => element.removeEventListener(type, handler) });
Expand Down
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ <h4>SearchAddon</h4>
<div style= "display:flex; flex-direction:column;">
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
<div>Results: <span id="find-results"></span></div>
<label><input type="checkbox" id="regex"/>Use regex</label>
<label><input type="checkbox" id="case-sensitive"/>Case sensitive</label>
<label><input type="checkbox" id="whole-word"/>Whole word</label>
Expand Down