]> git.proxmox.com Git - mirror_xterm.js.git/blame - src/SelectionManager.ts
Merge remote-tracking branch 'ups/master' into 207_selection_manager
[mirror_xterm.js.git] / src / SelectionManager.ts
CommitLineData
70fda994
DI
1/**
2 * @license MIT
3 */
4
5import { CharMeasure } from './utils/CharMeasure';
6import { CircularList } from './utils/CircularList';
b594407c 7import { EventEmitter } from './EventEmitter';
70fda994 8import * as Mouse from './utils/Mouse';
ad3ae67e 9import { ITerminal } from './Interfaces';
70fda994 10
b594407c
DI
11export class SelectionManager extends EventEmitter {
12 // TODO: Create a SelectionModel
70fda994
DI
13 private _selectionStart: [number, number];
14 private _selectionEnd: [number, number];
15
ab40908f 16 private _bufferTrimListener: any;
70fda994 17 private _mouseMoveListener: EventListener;
ab40908f
DI
18 private _mouseDownListener: EventListener;
19 private _mouseUpListener: EventListener;
20 private _dblClickListener: EventListener;
70fda994 21
ad3ae67e
DI
22 constructor(
23 private _terminal: ITerminal,
24 private _buffer: CircularList<any>,
25 private _rowContainer: HTMLElement,
26 private _selectionContainer: HTMLElement,
27 private _charMeasure: CharMeasure
28 ) {
b594407c 29 super();
ab40908f
DI
30 this._initListeners();
31 this.enable();
70fda994
DI
32 }
33
ab40908f
DI
34 private _initListeners() {
35 this._bufferTrimListener = (amount: number) => this._onTrim(amount);
70fda994 36 this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
ab40908f
DI
37 this._mouseDownListener = event => this._onMouseDown(<MouseEvent>event);
38 this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
39 this._dblClickListener = event => this._onDblClick(<MouseEvent>event);
40 }
70fda994 41
ab40908f
DI
42 /**
43 * Disables the selection manager. This is useful for when terminal mouse
44 * are enabled.
45 */
46 public disable() {
47 this._selectionStart = null;
48 this._selectionEnd = null;
49 this.refresh();
50 this._buffer.off('trim', this._bufferTrimListener);
51 this._rowContainer.removeEventListener('mousedown', this._mouseDownListener);
52 this._rowContainer.removeEventListener('mouseup', this._mouseUpListener);
53 this._rowContainer.removeEventListener('dblclick', this._dblClickListener);
54 this._rowContainer.removeEventListener('mousemove', this._mouseMoveListener);
55 }
56
57 /**
58 * Enable the selection manager.
59 */
60 public enable() {
61 this._buffer.on('trim', this._bufferTrimListener);
62 this._rowContainer.addEventListener('mousedown', this._mouseDownListener);
63 this._rowContainer.addEventListener('mouseup', this._mouseUpListener);
64 this._rowContainer.addEventListener('dblclick', this._dblClickListener);
70fda994
DI
65 }
66
67 public get selectionText(): string {
68 if (!this._selectionStart || !this._selectionEnd) {
293ae18a 69 return '';
70fda994 70 }
293ae18a
DI
71 const flipValues = this._selectionStart[1] > this._selectionEnd[1] ||
72 (this._selectionStart[1] === this._selectionEnd[1] && this._selectionStart[0] > this._selectionEnd[0]);
73 const start = flipValues ? this._selectionEnd : this._selectionStart;
74 const end = flipValues ? this._selectionStart : this._selectionEnd;
75 const startRowEndCol = start[1] === end[1] ? end[0] : null;
32b34cbe 76 let result: string[] = [];
293ae18a
DI
77 result.push(this._translateBufferLineToString(this._buffer.get(start[1]), start[0], startRowEndCol));
78 for (let i = start[1] + 1; i <= end[1] - 1; i++) {
32b34cbe
DI
79 result.push(this._translateBufferLineToString(this._buffer.get(i)));
80 }
293ae18a
DI
81 if (start[1] !== end[1]) {
82 result.push(this._translateBufferLineToString(this._buffer.get(end[1]), 0, end[1]));
32b34cbe 83 }
597c6939 84 console.log('selectionText result: ' + result);
32b34cbe
DI
85 return result.join('\n');
86 }
87
88 private _translateBufferLineToString(line: any, startCol: number = 0, endCol: number = null): string {
89 // TODO: This function should live in a buffer or buffer line class
90 endCol = endCol || line.length
91 let result = '';
92 for (let i = startCol; i < endCol; i++) {
93 result += line[i][1];
94 }
597c6939 95 // TODO: Trim line here instead of in handlers/Clipboard?
e63fdf58 96 // TODO: Only trim off the whitespace at the end of a line
32b34cbe 97 // TODO: Handle the double-width character case
e63fdf58 98 return result;
70fda994
DI
99 }
100
207c4cf9
DI
101 /**
102 * Redraws the selection.
103 */
104 public refresh(): void {
b594407c
DI
105 // TODO: Figure out when to refresh the selection vs when to refresh the viewport
106 this.emit('refresh', { start: this._selectionStart, end: this._selectionEnd });
207c4cf9
DI
107 }
108
109 /**
110 * Handle the buffer being trimmed, adjust the selection position.
111 * @param amount The amount the buffer is being trimmed.
112 */
70fda994 113 private _onTrim(amount: number) {
207c4cf9
DI
114 // Adjust the selection position based on the trimmed amount.
115 this._selectionStart[0] -= amount;
116 this._selectionEnd[0] -= amount;
117
118 // The selection has moved off the buffer, clear it.
119 if (this._selectionEnd[0] < 0) {
120 this._selectionStart = null;
121 this._selectionEnd = null;
122 this.refresh();
123 return;
124 }
125
126 // If the selection start is trimmed, ensure the start column is 0.
127 if (this._selectionStart[0] < 0) {
128 this._selectionStart[1] = 0;
129 }
70fda994
DI
130 }
131
32b34cbe
DI
132 // TODO: Handle splice/shiftElements in the buffer (just clear the selection?)
133
b36d8780 134 private _getMouseBufferCoords(event: MouseEvent) {
ad3ae67e
DI
135 const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure);
136 // Convert to 0-based
137 coords[0]--;
138 coords[1]--;
139 // Convert viewport coords to buffer coords
140 coords[1] += this._terminal.ydisp;
141 return coords;
b36d8780
DI
142 }
143
e63fdf58
DI
144 /**
145 * Handles te mousedown event, setting up for a new selection.
146 * @param event The mousedown event.
147 */
70fda994 148 private _onMouseDown(event: MouseEvent) {
b36d8780 149 this._selectionStart = this._getMouseBufferCoords(event);
70fda994 150 if (this._selectionStart) {
ad3ae67e 151 this._selectionEnd = null;
70fda994 152 this._rowContainer.addEventListener('mousemove', this._mouseMoveListener);
ad3ae67e 153 this.refresh();
70fda994
DI
154 }
155 }
156
e63fdf58
DI
157 /**
158 * Handles the mousemove event when the mouse button is down, recording the
159 * end of the selection and refreshing the selection.
160 * @param event The mousemove event.
161 */
70fda994 162 private _onMouseMove(event: MouseEvent) {
b36d8780 163 this._selectionEnd = this._getMouseBufferCoords(event);
207c4cf9
DI
164 // TODO: Only draw here if the selection changes
165 this.refresh();
70fda994
DI
166 }
167
e63fdf58
DI
168 /**
169 * Handles the mouseup event, removing the mousemove listener when
170 * appropriate.
171 * @param event The mouseup event.
172 */
70fda994 173 private _onMouseUp(event: MouseEvent) {
70fda994
DI
174 if (!this._selectionStart) {
175 return;
176 }
177 this._rowContainer.removeEventListener('mousemove', this._mouseMoveListener);
178 }
597c6939 179
ab40908f 180 private _onDblClick(event: MouseEvent) {
597c6939
DI
181 const coords = this._getMouseBufferCoords(event);
182 if (coords) {
183 this._selectWordAt(coords);
184 }
185 }
186
187 /**
188 * Selects the word at the coordinates specified. Words are defined as all
189 * non-whitespace characters.
190 * @param coords The coordinates to get the word at.
191 */
192 private _selectWordAt(coords: [number, number]): void {
193 // TODO: Handle double click and drag in both directions!
194
195 const line = this._translateBufferLineToString(this._buffer.get(coords[1]));
196 // Expand the string in both directions until a space is hit
197 let startCol = coords[0];
198 let endCol = coords[0];
199 while (startCol > 0 && line.charAt(startCol - 1) !== ' ') {
200 startCol--;
201 }
202 while (endCol < line.length && line.charAt(endCol) !== ' ') {
203 endCol++;
204 }
205 this._selectionStart = [startCol, coords[1]];
206 this._selectionEnd = [endCol, coords[1]];
207 this.refresh();
208 }
70fda994 209}