Merge pull request #5769 from Tyriar/4633

Only update necessary DOM range on selection change
This commit is contained in:
Daniel Imms
2026-03-12 11:44:45 +09:00
committed by GitHub
+73 -32
View File
@@ -45,6 +45,9 @@ export class DomRenderer extends Disposable implements IRenderer {
private _selectionContainer: HTMLElement; private _selectionContainer: HTMLElement;
private _widthCache: WidthCache; private _widthCache: WidthCache;
private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel(); private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
private _lastSelectionStart: [number, number] | undefined;
private _lastSelectionEnd: [number, number] | undefined;
private _lastSelectionColumnMode: boolean = false;
private _cursorBlinkStateManager: CursorBlinkStateManager; private _cursorBlinkStateManager: CursorBlinkStateManager;
private _textBlinkStateManager: TextBlinkStateManager; private _textBlinkStateManager: TextBlinkStateManager;
private _rowHasBlinkingCells: boolean[] = []; private _rowHasBlinkingCells: boolean[] = [];
@@ -374,51 +377,89 @@ export class DomRenderer extends Disposable implements IRenderer {
} }
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
const rows = this._bufferService.rows;
// Remove all selections // Remove all selections
this._selectionContainer.replaceChildren(); this._selectionContainer.replaceChildren();
this._rowFactory.handleSelectionChanged(start, end, columnSelectMode); this._rowFactory.handleSelectionChanged(start, end, columnSelectMode);
this.renderRows(0, this._bufferService.rows - 1);
// Selection does not exist // Determine old selection viewport band
let oldViewportStart = 0;
let oldViewportEnd = -1;
if (this._lastSelectionStart && this._lastSelectionEnd) {
this._selectionRenderModel.update(this._terminal, this._lastSelectionStart, this._lastSelectionEnd, this._lastSelectionColumnMode);
if (this._selectionRenderModel.hasSelection) {
oldViewportStart = this._selectionRenderModel.viewportCappedStartRow;
oldViewportEnd = this._selectionRenderModel.viewportCappedEndRow;
}
}
// Determine new selection viewport band and create overlays
let newViewportStart = 0;
let newViewportEnd = -1;
if (!start || !end) { if (!start || !end) {
return; return;
} }
this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode); this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode);
if (!this._selectionRenderModel.hasSelection) { if (this._selectionRenderModel.hasSelection) {
return; const viewportStartRow = this._selectionRenderModel.viewportStartRow;
} const viewportEndRow = this._selectionRenderModel.viewportEndRow;
const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;
// Translate from buffer position to viewport position newViewportStart = viewportCappedStartRow;
const viewportStartRow = this._selectionRenderModel.viewportStartRow; newViewportEnd = viewportCappedEndRow;
const viewportEndRow = this._selectionRenderModel.viewportEndRow;
const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;
// Create the selections // Create the selections
const documentFragment = this._document.createDocumentFragment(); const documentFragment = this._document.createDocumentFragment();
if (columnSelectMode) { if (columnSelectMode) {
const isXFlipped = start[0] > end[0]; const isXFlipped = start[0] > end[0];
documentFragment.appendChild( documentFragment.appendChild(
this._createSelectionElement(viewportCappedStartRow, isXFlipped ? end[0] : start[0], isXFlipped ? start[0] : end[0], viewportCappedEndRow - viewportCappedStartRow + 1) this._createSelectionElement(viewportCappedStartRow, isXFlipped ? end[0] : start[0], isXFlipped ? start[0] : end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
); );
} else { } else {
// Draw first row // Draw first row
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols; const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
// Draw middle rows // Draw middle rows
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount)); documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount));
// Draw final row // Draw final row
if (viewportCappedStartRow !== viewportCappedEndRow) { if (viewportCappedStartRow !== viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewporttartRow // Only draw viewportEndRow if it's not the same as viewporttartRow
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols; const finalEndCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol)); documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, finalEndCol));
}
} }
this._selectionContainer.appendChild(documentFragment);
} }
this._selectionContainer.appendChild(documentFragment);
// Compute minimal row range to redraw
let renderStartRow = Math.min(oldViewportStart, newViewportStart);
let renderEndRow = Math.max(oldViewportEnd, newViewportEnd);
if (renderEndRow >= 0) {
// Clamp to viewport
renderStartRow = Math.max(renderStartRow, 0);
renderEndRow = Math.min(renderEndRow, rows - 1);
// Ensure cursor row is included when a selection is present
const buffer = this._bufferService.buffer;
const cursorViewportRow = buffer.y;
if (this._selectionRenderModel.hasSelection && cursorViewportRow >= 0 && cursorViewportRow < rows) {
renderStartRow = Math.min(renderStartRow, cursorViewportRow);
renderEndRow = Math.max(renderEndRow, cursorViewportRow);
}
this.renderRows(renderStartRow, renderEndRow);
}
// Update last selection state
this._lastSelectionStart = start;
this._lastSelectionEnd = end;
this._lastSelectionColumnMode = columnSelectMode;
} }
/** /**