Skip to content

Commit fe0952b

Browse files
authored
Merge pull request #5769 from Tyriar/4633
Only update necessary DOM range on selection change
2 parents fb25eb8 + 5c2b19f commit fe0952b

File tree

1 file changed

+74
-33
lines changed

1 file changed

+74
-33
lines changed

src/browser/renderer/dom/DomRenderer.ts

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ export class DomRenderer extends Disposable implements IRenderer {
4545
private _selectionContainer: HTMLElement;
4646
private _widthCache: WidthCache;
4747
private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
48+
private _lastSelectionStart: [number, number] | undefined;
49+
private _lastSelectionEnd: [number, number] | undefined;
50+
private _lastSelectionColumnMode: boolean = false;
4851
private _cursorBlinkStateManager: CursorBlinkStateManager;
4952
private _textBlinkStateManager: TextBlinkStateManager;
5053
private _rowHasBlinkingCells: boolean[] = [];
@@ -374,51 +377,89 @@ export class DomRenderer extends Disposable implements IRenderer {
374377
}
375378

376379
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
380+
const rows = this._bufferService.rows;
381+
377382
// Remove all selections
378383
this._selectionContainer.replaceChildren();
379384
this._rowFactory.handleSelectionChanged(start, end, columnSelectMode);
380-
this.renderRows(0, this._bufferService.rows - 1);
381385

382-
// Selection does not exist
386+
// Determine old selection viewport band
387+
let oldViewportStart = 0;
388+
let oldViewportEnd = -1;
389+
if (this._lastSelectionStart && this._lastSelectionEnd) {
390+
this._selectionRenderModel.update(this._terminal, this._lastSelectionStart, this._lastSelectionEnd, this._lastSelectionColumnMode);
391+
if (this._selectionRenderModel.hasSelection) {
392+
oldViewportStart = this._selectionRenderModel.viewportCappedStartRow;
393+
oldViewportEnd = this._selectionRenderModel.viewportCappedEndRow;
394+
}
395+
}
396+
397+
// Determine new selection viewport band and create overlays
398+
let newViewportStart = 0;
399+
let newViewportEnd = -1;
383400
if (!start || !end) {
384401
return;
385402
}
386-
387403
this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode);
388-
if (!this._selectionRenderModel.hasSelection) {
389-
return;
404+
if (this._selectionRenderModel.hasSelection) {
405+
const viewportStartRow = this._selectionRenderModel.viewportStartRow;
406+
const viewportEndRow = this._selectionRenderModel.viewportEndRow;
407+
const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
408+
const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;
409+
410+
newViewportStart = viewportCappedStartRow;
411+
newViewportEnd = viewportCappedEndRow;
412+
413+
// Create the selections
414+
const documentFragment = this._document.createDocumentFragment();
415+
416+
if (columnSelectMode) {
417+
const isXFlipped = start[0] > end[0];
418+
documentFragment.appendChild(
419+
this._createSelectionElement(viewportCappedStartRow, isXFlipped ? end[0] : start[0], isXFlipped ? start[0] : end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
420+
);
421+
} else {
422+
// Draw first row
423+
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
424+
const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
425+
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
426+
// Draw middle rows
427+
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
428+
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount));
429+
// Draw final row
430+
if (viewportCappedStartRow !== viewportCappedEndRow) {
431+
// Only draw viewportEndRow if it's not the same as viewporttartRow
432+
const finalEndCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
433+
documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, finalEndCol));
434+
}
435+
}
436+
this._selectionContainer.appendChild(documentFragment);
390437
}
391438

392-
// Translate from buffer position to viewport position
393-
const viewportStartRow = this._selectionRenderModel.viewportStartRow;
394-
const viewportEndRow = this._selectionRenderModel.viewportEndRow;
395-
const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
396-
const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;
397-
398-
// Create the selections
399-
const documentFragment = this._document.createDocumentFragment();
400-
401-
if (columnSelectMode) {
402-
const isXFlipped = start[0] > end[0];
403-
documentFragment.appendChild(
404-
this._createSelectionElement(viewportCappedStartRow, isXFlipped ? end[0] : start[0], isXFlipped ? start[0] : end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
405-
);
406-
} else {
407-
// Draw first row
408-
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
409-
const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
410-
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
411-
// Draw middle rows
412-
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
413-
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount));
414-
// Draw final row
415-
if (viewportCappedStartRow !== viewportCappedEndRow) {
416-
// Only draw viewportEndRow if it's not the same as viewporttartRow
417-
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
418-
documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));
439+
// Compute minimal row range to redraw
440+
let renderStartRow = Math.min(oldViewportStart, newViewportStart);
441+
let renderEndRow = Math.max(oldViewportEnd, newViewportEnd);
442+
443+
if (renderEndRow >= 0) {
444+
// Clamp to viewport
445+
renderStartRow = Math.max(renderStartRow, 0);
446+
renderEndRow = Math.min(renderEndRow, rows - 1);
447+
448+
// Ensure cursor row is included when a selection is present
449+
const buffer = this._bufferService.buffer;
450+
const cursorViewportRow = buffer.y;
451+
if (this._selectionRenderModel.hasSelection && cursorViewportRow >= 0 && cursorViewportRow < rows) {
452+
renderStartRow = Math.min(renderStartRow, cursorViewportRow);
453+
renderEndRow = Math.max(renderEndRow, cursorViewportRow);
419454
}
455+
456+
this.renderRows(renderStartRow, renderEndRow);
420457
}
421-
this._selectionContainer.appendChild(documentFragment);
458+
459+
// Update last selection state
460+
this._lastSelectionStart = start;
461+
this._lastSelectionEnd = end;
462+
this._lastSelectionColumnMode = columnSelectMode;
422463
}
423464

424465
/**

0 commit comments

Comments
 (0)