diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 326329186..0333fd208 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -173,7 +173,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.onFocus(this._terminal)); } - public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { + public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode)); this._updateSelectionModel(start, end, columnSelectMode); @@ -295,7 +295,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer.updateBackgrounds(this._model); } - private _updateSelectionModel(start: [number, number], end: [number, number], columnSelectMode: boolean): void { + private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { const terminal = this._terminal; // Selection does not exist diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 93f844927..da3b4d418 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -58,7 +58,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onFocus(terminal: Terminal): void {} public onCursorMove(terminal: Terminal): void {} public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {} - public onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {} + public onSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {} public setColors(terminal: Terminal, colorSet: IColorSet): void { this._refreshCharAtlas(terminal, colorSet); diff --git a/addons/xterm-addon-webgl/src/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts index 148ea4695..70acff340 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/Types.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/Types.ts @@ -42,7 +42,7 @@ export interface IRenderLayer extends IDisposable { /** * Calls when the selection changes. */ - onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void; + onSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; /** * Registers a handler to join characters to render as a group diff --git a/src/browser/RenderDebouncer.ts b/src/browser/RenderDebouncer.ts index 27c866dfd..814afcef4 100644 --- a/src/browser/RenderDebouncer.ts +++ b/src/browser/RenderDebouncer.ts @@ -49,15 +49,15 @@ export class RenderDebouncer implements IDisposable { } // Clamp values - this._rowStart = Math.max(this._rowStart, 0); - this._rowEnd = Math.min(this._rowEnd, this._rowCount - 1); + const start = Math.max(this._rowStart, 0); + const end = Math.min(this._rowEnd, this._rowCount - 1); - // Run render callback - this._renderCallback(this._rowStart, this._rowEnd); - - // Reset debouncer + // Reset debouncer (this happens before render callback as the render could trigger it again) this._rowStart = undefined; this._rowEnd = undefined; this._animationFrame = undefined; + + // Run render callback + this._renderCallback(start, end); } } diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 595aa1a52..837f18e42 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -77,7 +77,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onFocus(): void {} public onCursorMove(): void {} public onGridChanged(startRow: number, endRow: number): void {} - public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {} + public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {} public setColors(colorSet: IColorSet): void { this._refreshCharAtlas(colorSet); diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 0856553f1..5142e4cb4 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -119,7 +119,7 @@ export class Renderer extends Disposable implements IRenderer { this._runOperation(l => l.onFocus()); } - public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void { + public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode)); } diff --git a/src/browser/renderer/SelectionRenderLayer.ts b/src/browser/renderer/SelectionRenderLayer.ts index 257944fcb..5689ce932 100644 --- a/src/browser/renderer/SelectionRenderLayer.ts +++ b/src/browser/renderer/SelectionRenderLayer.ts @@ -52,7 +52,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { } } - public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { + public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { // Selection has not changed if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) { return; @@ -110,7 +110,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { this._state.ydisp = this._bufferService.buffer.ydisp; } - private _didStateChange(start: [number, number], end: [number, number], columnSelectMode: boolean, ydisp: number): boolean { + private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number): boolean { return !this._areCoordinatesEqual(start, this._state.start) || !this._areCoordinatesEqual(end, this._state.end) || columnSelectMode !== this._state.columnSelectMode || diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 02c62ab2e..cab14b88e 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -49,7 +49,7 @@ export interface IRenderer extends IDisposable { onCharSizeChanged(): void; onBlur(): void; onFocus(): void; - onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; + onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; onCursorMove(): void; onOptionsChanged(): void; clear(): void; @@ -104,7 +104,7 @@ export interface IRenderLayer extends IDisposable { /** * Calls when the selection changes. */ - onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; + onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; /** * Registers a handler to join characters to render as a group diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 1303ec1b5..ff9551404 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -273,7 +273,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._rowContainer.classList.add(FOCUS_CLASS); } - public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { + public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { // Remove all selections while (this._selectionContainer.children.length) { this._selectionContainer.removeChild(this._selectionContainer.children[0]); diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 27b66458b..a0ca25d2e 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -13,6 +13,12 @@ import { IColorSet } from 'browser/Types'; import { IOptionsService, IBufferService, ICoreService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; +interface ISelectionState { + start: [number, number] | undefined; + end: [number, number] | undefined; + columnSelectMode: boolean; +} + export class RenderService extends Disposable implements IRenderService { public serviceBrand: undefined; @@ -22,8 +28,14 @@ export class RenderService extends Disposable implements IRenderService { private _isPaused: boolean = false; private _needsFullRefresh: boolean = false; private _isNextRenderRedrawOnly: boolean = true; + private _needsSelectionRefresh: boolean = false; private _canvasWidth: number = 0; private _canvasHeight: number = 0; + private _selectionState: ISelectionState = { + start: undefined, + end: undefined, + columnSelectMode: false + }; private _onDimensionsChange = new EventEmitter(); public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; } @@ -91,6 +103,14 @@ export class RenderService extends Disposable implements IRenderService { private _renderRows(start: number, end: number): void { this._renderer.renderRows(start, end); + + // Update selection if needed + if (this._needsSelectionRefresh) { + this._renderer.onSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode); + this._needsSelectionRefresh = false; + } + + // Fire render event only if it was not a redraw if (!this._isNextRenderRedrawOnly) { this._onRender.fire({ start, end }); } @@ -126,7 +146,10 @@ export class RenderService extends Disposable implements IRenderService { this._renderer.dispose(); this._renderer = renderer; this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); - this.refreshRows(0, this._rowCount - 1); + + // Force a refresh + this._needsSelectionRefresh = true; + this._fullRefresh(); } private _fullRefresh(): void { @@ -165,7 +188,10 @@ export class RenderService extends Disposable implements IRenderService { this._renderer.onFocus(); } - public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { + public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { + this._selectionState.start = start; + this._selectionState.end = end; + this._selectionState.columnSelectMode = columnSelectMode; this._renderer.onSelectionChanged(start, end, columnSelectMode); } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 28b839852..445cab05f 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -63,7 +63,7 @@ export interface IRenderService extends IDisposable { onCharSizeChanged(): void; onBlur(): void; onFocus(): void; - onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; + onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; onCursorMove(): void; clear(): void; registerCharacterJoiner(handler: CharacterJoinerHandler): number; diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 6a7460a48..1ec2a68db 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -503,7 +503,7 @@ export class InputHandler extends Disposable implements IInputHandler { const wraparoundMode = this._coreService.decPrivateModes.wraparound; const insertMode = this._coreService.modes.insertMode; const curAttr = this._curAttrData; - let bufferRow = buffer.lines.get(buffer.y + buffer.ybase)!; + let bufferRow = buffer.lines.get(buffer.ybase + buffer.y)!; this._dirtyRowService.markDirty(buffer.y); @@ -567,10 +567,10 @@ export class InputHandler extends Disposable implements IInputHandler { } // The line already exists (eg. the initial viewport), mark it as a // wrapped line - buffer.lines.get(buffer.y)!.isWrapped = true; + buffer.lines.get(buffer.ybase + buffer.y)!.isWrapped = true; } // row changed, get it again - bufferRow = buffer.lines.get(buffer.y + buffer.ybase)!; + bufferRow = buffer.lines.get(buffer.ybase + buffer.y)!; } else { buffer.x = cols - 1; if (chWidth === 2) { @@ -1190,7 +1190,7 @@ export class InputHandler extends Disposable implements IInputHandler { return; } - const row: number = buffer.y + buffer.ybase; + const row: number = buffer.ybase + buffer.y; const scrollBottomRowsOffset = this._bufferService.rows - 1 - buffer.scrollBottom; const scrollBottomAbsolute = this._bufferService.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1; @@ -1225,7 +1225,7 @@ export class InputHandler extends Disposable implements IInputHandler { return; } - const row: number = buffer.y + buffer.ybase; + const row: number = buffer.ybase + buffer.y; let j: number; j = this._bufferService.rows - 1 - buffer.scrollBottom; @@ -1254,7 +1254,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public insertChars(params: IParams): void { this._restrictCursor(); - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.y + this._bufferService.buffer.ybase); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { line.insertCells( this._bufferService.buffer.x, @@ -1279,7 +1279,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public deleteChars(params: IParams): void { this._restrictCursor(); - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.y + this._bufferService.buffer.ybase); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { line.deleteCells( this._bufferService.buffer.x, @@ -1451,7 +1451,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public eraseChars(params: IParams): void { this._restrictCursor(); - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.y + this._bufferService.buffer.ybase); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { line.replaceCells( this._bufferService.buffer.x, @@ -2720,8 +2720,8 @@ export class InputHandler extends Disposable implements IInputHandler { // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' // blankLine(true) is xterm/linux behavior const scrollRegionHeight = buffer.scrollBottom - buffer.scrollTop; - buffer.lines.shiftElements(buffer.y + buffer.ybase, scrollRegionHeight, 1); - buffer.lines.set(buffer.y + buffer.ybase, buffer.getBlankLine(this._eraseAttrData())); + buffer.lines.shiftElements(buffer.ybase + buffer.y, scrollRegionHeight, 1); + buffer.lines.set(buffer.ybase + buffer.y, buffer.getBlankLine(this._eraseAttrData())); this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); } else { buffer.y--; @@ -2786,7 +2786,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._setCursor(0, 0); for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) { - const row = buffer.y + buffer.ybase + yOffset; + const row = buffer.ybase + buffer.y + yOffset; const line = buffer.lines.get(row); if (line) { line.fill(cell);