Skip to content

Commit

Permalink
Merge branch 'master' into fix-caps-lock-ime
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyriar authored Jun 26, 2022
2 parents 6ad5534 + 5d3a9e8 commit 4c7d89e
Show file tree
Hide file tree
Showing 24 changed files with 540 additions and 100 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**KubeSail**](https://kubesail.com): The Self-Hosting Company - uses xterm to allow users to exec into kubernetes pods and build github apps
- [**WiTTY**](https://github.com/syssecfsu/witty): Web-based interactive terminal emulator that allows users to easily record, share, and replay console sessions.
- [**libv86 Terminal Forwarding**](https://github.com/hello-smile6/libv86-terminal-forwarding): Peer-to-peer SSH for the web, using WebRTC via [Bugout](https://github.com/chr15m/bugout) for data transfer and [v86](https://github.com/copy/v86) for web-based virtualization.
- [**hack.courses**](https://hack.courses): Interactive Linux and command-line classes using xterm.js to expose a real terminal available for everyone.
- [**Render**](https://render.com): Platform-as-a-service for your apps, websites, and databases using xterm.js to provide a command prompt for user containers and for streaming build and runtime logs.
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)

Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
Expand Down
23 changes: 15 additions & 8 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 Expand Up @@ -604,7 +610,8 @@ export class SearchAddon implements ITerminalAddon {
break;
}
if (cell.getWidth()) {
offset += cell.getChars().length;
// Treat null characters as whitespace to align with the translateToString API
offset += cell.getCode() === 0 ? 1 : cell.getChars().length;
}
}
lineIndex++;
Expand Down
196 changes: 192 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 Expand Up @@ -206,6 +379,21 @@ describe('Search Tests', function(): void {
});
});
});
describe('#3834 lines with null characters before search terms', () => {
// This case can be triggered by the prompt when using starship under conpty
it('should find all matches on a line containing null characters', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
// Move cursor forward 1 time to create a null character, as opposed to regular whitespace
await writeSync(page, '\\x1b[CHi Hi');
assert.strictEqual(await page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 2, resultIndex: 1 }
]);
});
});
});

function makeData(length: number): string {
Expand Down
2 changes: 1 addition & 1 deletion addons/xterm-addon-serialize/test/SerializeAddon.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('SerializeAddon', () => {
const buffer3 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);

await page.evaluate(`term.reset();`);
await writeRawSync(page, '1234567890n12345');
await writeRawSync(page, '123456789012345');
const buffer4 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);

assert.throw(() => {
Expand Down
8 changes: 6 additions & 2 deletions addons/xterm-addon-webgl/src/WebglRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
y -= this._terminal.buffer.active.viewportY;
if (this._model.selection.columnSelectMode) {
return x >= this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow &&
x < this._model.selection.endCol && y < this._model.selection.viewportCappedEndRow;
if (this._model.selection.startCol <= this._model.selection.endCol) {
return x >= this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow &&
x < this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow;
}
return x < this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow &&
x >= this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow;
}
return (y > this._model.selection.viewportStartRow && y < this._model.selection.viewportEndRow) ||
(this._model.selection.viewportStartRow === this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol && x < this._model.selection.endCol) ||
Expand Down
Loading

0 comments on commit 4c7d89e

Please sign in to comment.