xterm
Version:
Full xterm terminal, in your browser
128 lines (107 loc) • 4.32 kB
text/typescript
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IRenderDimensions } from 'browser/renderer/Types';
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
import { IColorSet } from 'browser/Types';
import { IBufferService, IOptionsService } from 'common/services/Services';
interface ISelectionState {
start?: [number, number];
end?: [number, number];
columnSelectMode?: boolean;
ydisp?: number;
}
export class SelectionRenderLayer extends BaseRenderLayer {
private _state!: ISelectionState;
constructor(
container: HTMLElement,
zIndex: number,
colors: IColorSet,
rendererId: number,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
) {
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
this._clearState();
}
private _clearState(): void {
this._state = {
start: undefined,
end: undefined,
columnSelectMode: undefined,
ydisp: undefined
};
}
public resize(dim: IRenderDimensions): void {
super.resize(dim);
// Resizing the canvas discards the contents of the canvas so clear state
this._clearState();
}
public reset(): void {
if (this._state.start && this._state.end) {
this._clearState();
this._clearAll();
}
}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
// Selection has not changed
if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
return;
}
// Remove all selections
this._clearAll();
// Selection does not exist
if (!start || !end) {
this._clearState();
return;
}
// Translate from buffer position to viewport position
const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
// No need to draw the selection
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
return;
}
this._ctx.fillStyle = this._colors.selection.css;
if (columnSelectMode) {
const startCol = start[0];
const width = end[0] - startCol;
const height = viewportCappedEndRow - viewportCappedStartRow + 1;
this._fillCells(startCol, viewportCappedStartRow, width, height);
} else {
// Draw first row
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
// Draw middle rows
const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount);
// Draw final row
if (viewportCappedStartRow !== viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewportStartRow
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
this._fillCells(0, viewportCappedEndRow, endCol, 1);
}
}
// Save state for next render
this._state.start = [start[0], start[1]];
this._state.end = [end[0], end[1]];
this._state.columnSelectMode = columnSelectMode;
this._state.ydisp = this._bufferService.buffer.ydisp;
}
private _didStateChange(start: [number, number], end: [number, number], columnSelectMode: boolean, ydisp: number): boolean {
return !this._areCoordinatesEqual(start, this._state.start) ||
!this._areCoordinatesEqual(end, this._state.end) ||
columnSelectMode !== this._state.columnSelectMode ||
ydisp !== this._state.ydisp;
}
private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean {
if (!coord1 || !coord2) {
return false;
}
return coord1[0] === coord2[0] && coord1[1] === coord2[1];
}
}