xterm
[VSoRC/.git] / node_modules / xterm / src / browser / renderer / SelectionRenderLayer.ts
1 /**
2  * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3  * @license MIT
4  */
5
6 import { IRenderDimensions } from 'browser/renderer/Types';
7 import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
8 import { IColorSet } from 'browser/Types';
9 import { IBufferService, IOptionsService } from 'common/services/Services';
10
11 interface ISelectionState {
12   start?: [number, number];
13   end?: [number, number];
14   columnSelectMode?: boolean;
15   ydisp?: number;
16 }
17
18 export class SelectionRenderLayer extends BaseRenderLayer {
19   private _state!: ISelectionState;
20
21   constructor(
22     container: HTMLElement,
23     zIndex: number,
24     colors: IColorSet,
25     rendererId: number,
26     readonly bufferService: IBufferService,
27     readonly optionsService: IOptionsService
28   ) {
29     super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
30     this._clearState();
31   }
32
33   private _clearState(): void {
34     this._state = {
35       start: undefined,
36       end: undefined,
37       columnSelectMode: undefined,
38       ydisp: undefined
39     };
40   }
41
42   public resize(dim: IRenderDimensions): void {
43     super.resize(dim);
44     // Resizing the canvas discards the contents of the canvas so clear state
45     this._clearState();
46   }
47
48   public reset(): void {
49     if (this._state.start && this._state.end) {
50       this._clearState();
51       this._clearAll();
52     }
53   }
54
55   public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
56     // Selection has not changed
57     if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
58       return;
59     }
60
61     // Remove all selections
62     this._clearAll();
63
64     // Selection does not exist
65     if (!start || !end) {
66       this._clearState();
67       return;
68     }
69
70     // Translate from buffer position to viewport position
71     const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
72     const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
73     const viewportCappedStartRow = Math.max(viewportStartRow, 0);
74     const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
75
76     // No need to draw the selection
77     if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
78       return;
79     }
80
81     this._ctx.fillStyle = this._colors.selection.css;
82
83     if (columnSelectMode) {
84       const startCol = start[0];
85       const width = end[0] - startCol;
86       const height = viewportCappedEndRow - viewportCappedStartRow + 1;
87       this._fillCells(startCol, viewportCappedStartRow, width, height);
88     } else {
89       // Draw first row
90       const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
91       const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
92       this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
93
94       // Draw middle rows
95       const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
96       this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount);
97
98       // Draw final row
99       if (viewportCappedStartRow !== viewportCappedEndRow) {
100         // Only draw viewportEndRow if it's not the same as viewportStartRow
101         const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
102         this._fillCells(0, viewportCappedEndRow, endCol, 1);
103       }
104     }
105
106     // Save state for next render
107     this._state.start = [start[0], start[1]];
108     this._state.end = [end[0], end[1]];
109     this._state.columnSelectMode = columnSelectMode;
110     this._state.ydisp = this._bufferService.buffer.ydisp;
111   }
112
113   private _didStateChange(start: [number, number], end: [number, number], columnSelectMode: boolean, ydisp: number): boolean {
114     return !this._areCoordinatesEqual(start, this._state.start) ||
115       !this._areCoordinatesEqual(end, this._state.end) ||
116       columnSelectMode !== this._state.columnSelectMode ||
117       ydisp !== this._state.ydisp;
118   }
119
120   private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean {
121     if (!coord1 || !coord2) {
122       return false;
123     }
124
125     return coord1[0] === coord2[0] && coord1[1] === coord2[1];
126   }
127 }