]> git.proxmox.com Git - mirror_xterm.js.git/blame - src/SelectionManager.ts
Temp fix until mouse getCoords PR merged
[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
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));
597c6939 35 this._rowContainer.addEventListener('dblclick', event => this._onDblclick(event));
70fda994
DI
36 }
37
38 public get selectionText(): string {
39 if (!this._selectionStart || !this._selectionEnd) {
293ae18a 40 return '';
70fda994 41 }
293ae18a
DI
42 const flipValues = this._selectionStart[1] > this._selectionEnd[1] ||
43 (this._selectionStart[1] === this._selectionEnd[1] && this._selectionStart[0] > this._selectionEnd[0]);
44 const start = flipValues ? this._selectionEnd : this._selectionStart;
45 const end = flipValues ? this._selectionStart : this._selectionEnd;
46 const startRowEndCol = start[1] === end[1] ? end[0] : null;
32b34cbe 47 let result: string[] = [];
293ae18a
DI
48 result.push(this._translateBufferLineToString(this._buffer.get(start[1]), start[0], startRowEndCol));
49 for (let i = start[1] + 1; i <= end[1] - 1; i++) {
32b34cbe
DI
50 result.push(this._translateBufferLineToString(this._buffer.get(i)));
51 }
293ae18a
DI
52 if (start[1] !== end[1]) {
53 result.push(this._translateBufferLineToString(this._buffer.get(end[1]), 0, end[1]));
32b34cbe 54 }
597c6939 55 console.log('selectionText result: ' + result);
32b34cbe
DI
56 return result.join('\n');
57 }
58
59 private _translateBufferLineToString(line: any, startCol: number = 0, endCol: number = null): string {
60 // TODO: This function should live in a buffer or buffer line class
61 endCol = endCol || line.length
62 let result = '';
63 for (let i = startCol; i < endCol; i++) {
64 result += line[i][1];
65 }
597c6939 66 // TODO: Trim line here instead of in handlers/Clipboard?
e63fdf58 67 // TODO: Only trim off the whitespace at the end of a line
32b34cbe 68 // TODO: Handle the double-width character case
e63fdf58 69 return result;
70fda994
DI
70 }
71
207c4cf9
DI
72 /**
73 * Redraws the selection.
74 */
75 public refresh(): void {
b594407c
DI
76 // TODO: Figure out when to refresh the selection vs when to refresh the viewport
77 this.emit('refresh', { start: this._selectionStart, end: this._selectionEnd });
207c4cf9
DI
78 }
79
80 /**
81 * Handle the buffer being trimmed, adjust the selection position.
82 * @param amount The amount the buffer is being trimmed.
83 */
70fda994 84 private _onTrim(amount: number) {
207c4cf9
DI
85 // Adjust the selection position based on the trimmed amount.
86 this._selectionStart[0] -= amount;
87 this._selectionEnd[0] -= amount;
88
89 // The selection has moved off the buffer, clear it.
90 if (this._selectionEnd[0] < 0) {
91 this._selectionStart = null;
92 this._selectionEnd = null;
93 this.refresh();
94 return;
95 }
96
97 // If the selection start is trimmed, ensure the start column is 0.
98 if (this._selectionStart[0] < 0) {
99 this._selectionStart[1] = 0;
100 }
70fda994
DI
101 }
102
32b34cbe
DI
103 // TODO: Handle splice/shiftElements in the buffer (just clear the selection?)
104
b36d8780 105 private _getMouseBufferCoords(event: MouseEvent) {
ad3ae67e
DI
106 const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure);
107 // Convert to 0-based
108 coords[0]--;
109 coords[1]--;
110 // Convert viewport coords to buffer coords
111 coords[1] += this._terminal.ydisp;
112 return coords;
b36d8780
DI
113 }
114
e63fdf58
DI
115 /**
116 * Handles te mousedown event, setting up for a new selection.
117 * @param event The mousedown event.
118 */
70fda994 119 private _onMouseDown(event: MouseEvent) {
b36d8780 120 this._selectionStart = this._getMouseBufferCoords(event);
70fda994 121 if (this._selectionStart) {
ad3ae67e 122 this._selectionEnd = null;
70fda994 123 this._rowContainer.addEventListener('mousemove', this._mouseMoveListener);
ad3ae67e 124 this.refresh();
70fda994
DI
125 }
126 }
127
e63fdf58
DI
128 /**
129 * Handles the mousemove event when the mouse button is down, recording the
130 * end of the selection and refreshing the selection.
131 * @param event The mousemove event.
132 */
70fda994 133 private _onMouseMove(event: MouseEvent) {
b36d8780 134 this._selectionEnd = this._getMouseBufferCoords(event);
207c4cf9
DI
135 // TODO: Only draw here if the selection changes
136 this.refresh();
70fda994
DI
137 }
138
e63fdf58
DI
139 /**
140 * Handles the mouseup event, removing the mousemove listener when
141 * appropriate.
142 * @param event The mouseup event.
143 */
70fda994 144 private _onMouseUp(event: MouseEvent) {
70fda994
DI
145 if (!this._selectionStart) {
146 return;
147 }
148 this._rowContainer.removeEventListener('mousemove', this._mouseMoveListener);
149 }
597c6939
DI
150
151 private _onDblclick(event: MouseEvent) {
152 const coords = this._getMouseBufferCoords(event);
153 if (coords) {
154 this._selectWordAt(coords);
155 }
156 }
157
158 /**
159 * Selects the word at the coordinates specified. Words are defined as all
160 * non-whitespace characters.
161 * @param coords The coordinates to get the word at.
162 */
163 private _selectWordAt(coords: [number, number]): void {
164 // TODO: Handle double click and drag in both directions!
165
166 const line = this._translateBufferLineToString(this._buffer.get(coords[1]));
167 // Expand the string in both directions until a space is hit
168 let startCol = coords[0];
169 let endCol = coords[0];
170 while (startCol > 0 && line.charAt(startCol - 1) !== ' ') {
171 startCol--;
172 }
173 while (endCol < line.length && line.charAt(endCol) !== ' ') {
174 endCol++;
175 }
176 this._selectionStart = [startCol, coords[1]];
177 this._selectionEnd = [endCol, coords[1]];
178 this.refresh();
179 }
70fda994 180}