Skip to content

Commit 011b58d

Browse files
authored
Merge branch 'master' into undefinedrowscols
2 parents aa046b7 + b9b42ef commit 011b58d

File tree

7 files changed

+330
-311
lines changed

7 files changed

+330
-311
lines changed

addons/xterm-addon-webgl/src/WebglRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
108108
for (const l of this._renderLayers) {
109109
l.dispose();
110110
}
111-
this._core.screenElement!.removeChild(this._canvas);
111+
this._canvas.parentElement?.removeChild(this._canvas);
112112
super.dispose();
113113
}
114114

demo/client.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ if (document.location.pathname === '/test') {
148148
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
149149
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
150150
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
151+
document.getElementById('load-test').addEventListener('click', loadTest);
151152
}
152153

153154
function createTerminal(): void {
@@ -481,3 +482,36 @@ function writeCustomGlyphHandler() {
481482
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
482483
window.scrollTo(0, 0);
483484
}
485+
486+
function loadTest() {
487+
const isWebglEnabled = !!addons.webgl.instance;
488+
const testData = [];
489+
let byteCount = 0;
490+
for (let i = 0; i < 50; i++) {
491+
const count = 1 + Math.floor(Math.random() * 79);
492+
byteCount += count + 2;
493+
const data = new Uint8Array(count + 2);
494+
data[0] = 0x0A; // \n
495+
for (let i = 1; i < count + 1; i++) {
496+
data[i] = 0x61 + Math.floor(Math.random() * (0x7A - 0x61));
497+
}
498+
// End each line with \r so the cursor remains constant, this is what ls/tree do and improves
499+
// performance significantly due to the cursor DOM element not needing to change
500+
data[data.length - 1] = 0x0D; // \r
501+
testData.push(data);
502+
}
503+
const start = performance.now();
504+
for (let i = 0; i < 1024; i++) {
505+
for (const d of testData) {
506+
term.write(d);
507+
}
508+
}
509+
// Wait for all data to be parsed before evaluating time
510+
term.write('', () => {
511+
const time = Math.round(performance.now() - start);
512+
const mbs = ((byteCount / 1024) * (1 / (time / 1000))).toFixed(2);
513+
term.write(`\n\r\nWrote ${byteCount}kB in ${time}ms (${mbs}MB/s) using the (${isWebglEnabled ? 'webgl' : 'canvas'} renderer)`);
514+
// Send ^C to get a new prompt
515+
term._core._onData.fire('\x03');
516+
});
517+
}

demo/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ <h3>Test</h3>
6363
<div style="display: inline-block; margin-right: 16px;">
6464
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
6565
<button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button>
66+
<button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button>
6667
</div>
6768
</div>
6869
</div>

src/browser/Terminal.test.ts

Lines changed: 70 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -732,80 +732,78 @@ describe('Terminal', () => {
732732
});
733733

734734
describe('unicode - surrogates', () => {
735-
it('2 characters per cell', async function (): Promise<void> {
736-
this.timeout(10000); // This is needed because istanbul patches code and slows it down
737-
const high = String.fromCharCode(0xD800);
738-
const cell = new CellData();
739-
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
740-
await term.writeP(high + String.fromCharCode(i));
741-
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
742-
assert.equal(tchar.getChars(), high + String.fromCharCode(i));
743-
assert.equal(tchar.getChars().length, 2);
744-
assert.equal(tchar.getWidth(), 1);
745-
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
746-
term.reset();
747-
}
748-
});
749-
it('2 characters at last cell', async () => {
750-
const high = String.fromCharCode(0xD800);
751-
const cell = new CellData();
752-
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
753-
term.buffer.x = term.cols - 1;
754-
await term.writeP(high + String.fromCharCode(i));
755-
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars(), high + String.fromCharCode(i));
756-
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length, 2);
757-
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), '');
758-
term.reset();
759-
}
760-
});
761-
it('2 characters per cell over line end with autowrap', async function (): Promise<void> {
762-
this.timeout(10000);
763-
const high = String.fromCharCode(0xD800);
764-
const cell = new CellData();
765-
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
766-
term.buffer.x = term.cols - 1;
767-
768-
await term.writeP('a' + high + String.fromCharCode(i));
769-
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), 'a');
770-
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), high + String.fromCharCode(i));
771-
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length, 2);
772-
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
773-
term.reset();
774-
}
775-
});
776-
it('2 characters per cell over line end without autowrap', async function (): Promise<void> {
777-
this.timeout(10000);
778-
const high = String.fromCharCode(0xD800);
779-
const cell = new CellData();
780-
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
735+
for (let i = 0xDC00; i <= 0xDCF0; i += 0x10) {
736+
const range = `0x${i.toString(16).toUpperCase()}-0x${(i + 0xF).toString(16).toUpperCase()}`;
737+
it(`${range}: 2 characters per cell`, async function (): Promise<void> {
738+
const high = String.fromCharCode(0xD800);
739+
const cell = new CellData();
740+
for (let j = i; j <= i + 0xF; j++) {
741+
await term.writeP(high + String.fromCharCode(j));
742+
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
743+
assert.equal(tchar.getChars(), high + String.fromCharCode(j));
744+
assert.equal(tchar.getChars().length, 2);
745+
assert.equal(tchar.getWidth(), 1);
746+
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
747+
term.reset();
748+
}
749+
});
750+
it(`${range}: 2 characters at last cell`, async () => {
751+
const high = String.fromCharCode(0xD800);
752+
const cell = new CellData();
781753
term.buffer.x = term.cols - 1;
782-
await term.writeP('\x1b[?7l'); // Disable wraparound mode
783-
const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000);
784-
if (width !== 1) {
785-
continue;
754+
for (let j = i; j <= i + 0xF; j++) {
755+
await term.writeP(high + String.fromCharCode(j));
756+
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars(), high + String.fromCharCode(j));
757+
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length, 2);
758+
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), '');
759+
term.reset();
786760
}
787-
await term.writeP('a' + high + String.fromCharCode(i));
788-
// auto wraparound mode should cut off the rest of the line
789-
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), high + String.fromCharCode(i));
790-
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length, 2);
791-
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
792-
term.reset();
793-
}
794-
});
795-
it('splitted surrogates', async function (): Promise<void> {
796-
this.timeout(10000);
797-
const high = String.fromCharCode(0xD800);
798-
const cell = new CellData();
799-
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
800-
await term.writeP(high + String.fromCharCode(i));
801-
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
802-
assert.equal(tchar.getChars(), high + String.fromCharCode(i));
803-
assert.equal(tchar.getChars().length, 2);
804-
assert.equal(tchar.getWidth(), 1);
805-
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
806-
term.reset();
807-
}
808-
});
761+
});
762+
it(`${range}: 2 characters per cell over line end with autowrap`, async function (): Promise<void> {
763+
const high = String.fromCharCode(0xD800);
764+
const cell = new CellData();
765+
for (let j = i; j <= i + 0xF; j++) {
766+
term.buffer.x = term.cols - 1;
767+
await term.writeP('a' + high + String.fromCharCode(j));
768+
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), 'a');
769+
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), high + String.fromCharCode(j));
770+
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length, 2);
771+
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
772+
term.reset();
773+
}
774+
});
775+
it(`${range}: 2 characters per cell over line end without autowrap`, async function (): Promise<void> {
776+
const high = String.fromCharCode(0xD800);
777+
const cell = new CellData();
778+
for (let j = i; j <= i + 0xF; j++) {
779+
term.buffer.x = term.cols - 1;
780+
await term.writeP('\x1b[?7l'); // Disable wraparound mode
781+
const width = wcwidth((0xD800 - 0xD800) * 0x400 + j - 0xDC00 + 0x10000);
782+
if (width !== 1) {
783+
continue;
784+
}
785+
await term.writeP('a' + high + String.fromCharCode(j));
786+
// auto wraparound mode should cut off the rest of the line
787+
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), high + String.fromCharCode(j));
788+
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length, 2);
789+
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
790+
term.reset();
791+
}
792+
});
793+
it(`${range}: splitted surrogates`, async function (): Promise<void> {
794+
const high = String.fromCharCode(0xD800);
795+
const cell = new CellData();
796+
for (let j = i; j <= i + 0xF; j++) {
797+
await term.writeP(high + String.fromCharCode(j));
798+
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
799+
assert.equal(tchar.getChars(), high + String.fromCharCode(j));
800+
assert.equal(tchar.getChars().length, 2);
801+
assert.equal(tchar.getWidth(), 1);
802+
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
803+
term.reset();
804+
}
805+
});
806+
}
809807
});
810808

811809
describe('unicode - combining characters', () => {

src/browser/Viewport.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
88
import { IColorSet, IViewport } from 'browser/Types';
99
import { ICharSizeService, IRenderService } from 'browser/services/Services';
1010
import { IBufferService, IOptionsService } from 'common/services/Services';
11+
import { IBuffer } from 'common/buffer/Types';
12+
import { IRenderDimensions } from 'browser/renderer/Types';
1113

1214
const FALLBACK_SCROLL_BAR_WIDTH = 15;
1315

@@ -18,12 +20,15 @@ const FALLBACK_SCROLL_BAR_WIDTH = 15;
1820
export class Viewport extends Disposable implements IViewport {
1921
public scrollBarWidth: number = 0;
2022
private _currentRowHeight: number = 0;
23+
private _currentScaledCellHeight: number = 0;
2124
private _lastRecordedBufferLength: number = 0;
2225
private _lastRecordedViewportHeight: number = 0;
2326
private _lastRecordedBufferHeight: number = 0;
2427
private _lastTouchY: number = 0;
2528
private _lastScrollTop: number = 0;
2629
private _lastHadScrollBar: boolean = false;
30+
private _activeBuffer: IBuffer;
31+
private _renderDimensions: IRenderDimensions;
2732

2833
// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
2934
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
@@ -51,6 +56,12 @@ export class Viewport extends Disposable implements IViewport {
5156
this._lastHadScrollBar = true;
5257
this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._onScroll.bind(this)));
5358

59+
// Track properties used in performance critical code manually to avoid using slow getters
60+
this._activeBuffer = this._bufferService.buffer;
61+
this.register(this._bufferService.buffers.onBufferActivate(e => this._activeBuffer = e.activeBuffer));
62+
this._renderDimensions = this._renderService.dimensions;
63+
this.register(this._renderService.onDimensionsChange(e => this._renderDimensions = e));
64+
5465
// Perform this async to ensure the ICharSizeService is ready.
5566
setTimeout(() => this.syncScrollArea(), 0);
5667
}
@@ -79,6 +90,7 @@ export class Viewport extends Disposable implements IViewport {
7990
private _innerRefresh(): void {
8091
if (this._charSizeService.height > 0) {
8192
this._currentRowHeight = this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio;
93+
this._currentScaledCellHeight = this._renderService.dimensions.scaledCellHeight;
8294
this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;
8395
const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderService.dimensions.canvasHeight);
8496
if (this._lastRecordedBufferHeight !== newBufferHeight) {
@@ -126,20 +138,13 @@ export class Viewport extends Disposable implements IViewport {
126138
}
127139

128140
// If the buffer position doesn't match last scroll top
129-
const newScrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight;
130-
if (this._lastScrollTop !== newScrollTop) {
131-
this._refresh(immediate);
132-
return;
133-
}
134-
135-
// If element's scroll top changed, this can happen when hiding the element
136-
if (this._lastScrollTop !== this._viewportElement.scrollTop) {
141+
if (this._lastScrollTop !== this._activeBuffer.ydisp * this._currentRowHeight) {
137142
this._refresh(immediate);
138143
return;
139144
}
140145

141146
// If row height changed
142-
if (this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
147+
if (this._renderDimensions.scaledCellHeight !== this._currentScaledCellHeight) {
143148
this._refresh(immediate);
144149
return;
145150
}

0 commit comments

Comments
 (0)