]>
Commit | Line | Data |
---|---|---|
70fda994 DI |
1 | /** |
2 | * @license MIT | |
3 | */ | |
4 | ||
5 | import { CharMeasure } from './utils/CharMeasure'; | |
6 | import { CircularList } from './utils/CircularList'; | |
b594407c | 7 | import { EventEmitter } from './EventEmitter'; |
70fda994 | 8 | import * as Mouse from './utils/Mouse'; |
ad3ae67e | 9 | import { ITerminal } from './Interfaces'; |
70fda994 | 10 | |
b594407c DI |
11 | export class SelectionManager extends EventEmitter { |
12 | // TODO: Create a SelectionModel | |
70fda994 DI |
13 | private _selectionStart: [number, number]; |
14 | private _selectionEnd: [number, number]; | |
15 | ||
70fda994 DI |
16 | private _mouseMoveListener: EventListener; |
17 | ||
ad3ae67e DI |
18 | constructor( |
19 | private _terminal: ITerminal, | |
20 | private _buffer: CircularList<any>, | |
21 | private _rowContainer: HTMLElement, | |
22 | private _selectionContainer: HTMLElement, | |
23 | private _charMeasure: CharMeasure | |
24 | ) { | |
b594407c | 25 | super(); |
70fda994 DI |
26 | this._attachListeners(); |
27 | } | |
28 | ||
29 | private _attachListeners() { | |
30 | this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event); | |
31 | ||
32 | this._buffer.on('trim', amount => this._onTrim(amount)); | |
33 | this._rowContainer.addEventListener('mousedown', event => this._onMouseDown(event)); | |
34 | this._rowContainer.addEventListener('mouseup', event => this._onMouseUp(event)); | |
35 | } | |
36 | ||
37 | public get selectionText(): string { | |
38 | if (!this._selectionStart || !this._selectionEnd) { | |
39 | return null; | |
40 | } | |
32b34cbe DI |
41 | // TODO: Handle first row start position properly |
42 | const startRowEndCol = this._selectionStart[1] === this._selectionEnd[1] ? this._selectionEnd[0] : null; | |
43 | let result: string[] = []; | |
44 | result.push(this._translateBufferLineToString(this._buffer.get(this._selectionStart[1]), this._selectionStart[0], startRowEndCol)); | |
45 | for (let i = this._selectionStart[1] + 1; i <= this._selectionEnd[1] - 1; i++) { | |
46 | result.push(this._translateBufferLineToString(this._buffer.get(i))); | |
47 | } | |
48 | if (this._selectionStart[1] !== this._selectionEnd[1]) { | |
49 | result.push(this._translateBufferLineToString(this._buffer.get(this._selectionEnd[1]), 0, this._selectionEnd[1])); | |
50 | } | |
51 | console.log('result: ' + result); | |
52 | return result.join('\n'); | |
53 | } | |
54 | ||
55 | private _translateBufferLineToString(line: any, startCol: number = 0, endCol: number = null): string { | |
56 | // TODO: This function should live in a buffer or buffer line class | |
57 | endCol = endCol || line.length | |
58 | let result = ''; | |
59 | for (let i = startCol; i < endCol; i++) { | |
60 | result += line[i][1]; | |
61 | } | |
62 | // TODO: Trim line? | |
63 | return result; | |
64 | // TODO: Handle the double-width character case | |
70fda994 DI |
65 | } |
66 | ||
207c4cf9 DI |
67 | /** |
68 | * Redraws the selection. | |
69 | */ | |
70 | public refresh(): void { | |
b594407c DI |
71 | // TODO: Figure out when to refresh the selection vs when to refresh the viewport |
72 | this.emit('refresh', { start: this._selectionStart, end: this._selectionEnd }); | |
207c4cf9 DI |
73 | } |
74 | ||
75 | /** | |
76 | * Handle the buffer being trimmed, adjust the selection position. | |
77 | * @param amount The amount the buffer is being trimmed. | |
78 | */ | |
70fda994 | 79 | private _onTrim(amount: number) { |
b36d8780 DI |
80 | // TODO: Somehow map the selection coordinates with the list that is constantly being trimmed |
81 | // Maybe we need an ID in the CircularList that starts from 0 for the first entry and increments | |
32b34cbe | 82 | // console.log('trimmed: ' + amount); |
207c4cf9 DI |
83 | |
84 | // Adjust the selection position based on the trimmed amount. | |
85 | this._selectionStart[0] -= amount; | |
86 | this._selectionEnd[0] -= amount; | |
87 | ||
88 | // The selection has moved off the buffer, clear it. | |
89 | if (this._selectionEnd[0] < 0) { | |
90 | this._selectionStart = null; | |
91 | this._selectionEnd = null; | |
92 | this.refresh(); | |
93 | return; | |
94 | } | |
95 | ||
96 | // If the selection start is trimmed, ensure the start column is 0. | |
97 | if (this._selectionStart[0] < 0) { | |
98 | this._selectionStart[1] = 0; | |
99 | } | |
70fda994 DI |
100 | } |
101 | ||
32b34cbe DI |
102 | // TODO: Handle splice/shiftElements in the buffer (just clear the selection?) |
103 | ||
b36d8780 | 104 | private _getMouseBufferCoords(event: MouseEvent) { |
ad3ae67e DI |
105 | const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure); |
106 | // Convert to 0-based | |
107 | coords[0]--; | |
108 | coords[1]--; | |
109 | // Convert viewport coords to buffer coords | |
110 | coords[1] += this._terminal.ydisp; | |
111 | return coords; | |
b36d8780 DI |
112 | } |
113 | ||
70fda994 | 114 | private _onMouseDown(event: MouseEvent) { |
b36d8780 | 115 | this._selectionStart = this._getMouseBufferCoords(event); |
70fda994 | 116 | if (this._selectionStart) { |
ad3ae67e | 117 | this._selectionEnd = null; |
70fda994 | 118 | this._rowContainer.addEventListener('mousemove', this._mouseMoveListener); |
ad3ae67e | 119 | this.refresh(); |
70fda994 DI |
120 | } |
121 | } | |
122 | ||
123 | private _onMouseMove(event: MouseEvent) { | |
b36d8780 | 124 | this._selectionEnd = this._getMouseBufferCoords(event); |
207c4cf9 DI |
125 | // TODO: Only draw here if the selection changes |
126 | this.refresh(); | |
70fda994 DI |
127 | } |
128 | ||
129 | private _onMouseUp(event: MouseEvent) { | |
32b34cbe DI |
130 | // console.log('mouseup'); |
131 | // console.log('start', this._selectionStart); | |
132 | // console.log('end', this._selectionEnd); | |
70fda994 DI |
133 | if (!this._selectionStart) { |
134 | return; | |
135 | } | |
136 | this._rowContainer.removeEventListener('mousemove', this._mouseMoveListener); | |
137 | } | |
138 | } |