@xterm/xterm 5.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/css/xterm.css +209 -0
- package/lib/xterm.js +2 -0
- package/lib/xterm.js.map +1 -0
- package/package.json +101 -0
- package/src/browser/AccessibilityManager.ts +278 -0
- package/src/browser/Clipboard.ts +93 -0
- package/src/browser/ColorContrastCache.ts +34 -0
- package/src/browser/Lifecycle.ts +33 -0
- package/src/browser/Linkifier2.ts +416 -0
- package/src/browser/LocalizableStrings.ts +12 -0
- package/src/browser/OscLinkProvider.ts +128 -0
- package/src/browser/RenderDebouncer.ts +83 -0
- package/src/browser/Terminal.ts +1317 -0
- package/src/browser/TimeBasedDebouncer.ts +86 -0
- package/src/browser/Types.d.ts +181 -0
- package/src/browser/Viewport.ts +401 -0
- package/src/browser/decorations/BufferDecorationRenderer.ts +134 -0
- package/src/browser/decorations/ColorZoneStore.ts +117 -0
- package/src/browser/decorations/OverviewRulerRenderer.ts +218 -0
- package/src/browser/input/CompositionHelper.ts +246 -0
- package/src/browser/input/Mouse.ts +54 -0
- package/src/browser/input/MoveToCell.ts +249 -0
- package/src/browser/public/Terminal.ts +260 -0
- package/src/browser/renderer/dom/DomRenderer.ts +509 -0
- package/src/browser/renderer/dom/DomRendererRowFactory.ts +526 -0
- package/src/browser/renderer/dom/WidthCache.ts +160 -0
- package/src/browser/renderer/shared/CellColorResolver.ts +137 -0
- package/src/browser/renderer/shared/CharAtlasCache.ts +96 -0
- package/src/browser/renderer/shared/CharAtlasUtils.ts +75 -0
- package/src/browser/renderer/shared/Constants.ts +14 -0
- package/src/browser/renderer/shared/CursorBlinkStateManager.ts +146 -0
- package/src/browser/renderer/shared/CustomGlyphs.ts +687 -0
- package/src/browser/renderer/shared/DevicePixelObserver.ts +41 -0
- package/src/browser/renderer/shared/README.md +1 -0
- package/src/browser/renderer/shared/RendererUtils.ts +58 -0
- package/src/browser/renderer/shared/SelectionRenderModel.ts +91 -0
- package/src/browser/renderer/shared/TextureAtlas.ts +1082 -0
- package/src/browser/renderer/shared/Types.d.ts +173 -0
- package/src/browser/selection/SelectionModel.ts +144 -0
- package/src/browser/selection/Types.d.ts +15 -0
- package/src/browser/services/CharSizeService.ts +102 -0
- package/src/browser/services/CharacterJoinerService.ts +339 -0
- package/src/browser/services/CoreBrowserService.ts +137 -0
- package/src/browser/services/MouseService.ts +46 -0
- package/src/browser/services/RenderService.ts +279 -0
- package/src/browser/services/SelectionService.ts +1031 -0
- package/src/browser/services/Services.ts +147 -0
- package/src/browser/services/ThemeService.ts +237 -0
- package/src/common/CircularList.ts +241 -0
- package/src/common/Clone.ts +23 -0
- package/src/common/Color.ts +357 -0
- package/src/common/CoreTerminal.ts +284 -0
- package/src/common/EventEmitter.ts +78 -0
- package/src/common/InputHandler.ts +3461 -0
- package/src/common/Lifecycle.ts +108 -0
- package/src/common/MultiKeyMap.ts +42 -0
- package/src/common/Platform.ts +44 -0
- package/src/common/SortedList.ts +118 -0
- package/src/common/TaskQueue.ts +166 -0
- package/src/common/TypedArrayUtils.ts +17 -0
- package/src/common/Types.d.ts +553 -0
- package/src/common/WindowsMode.ts +27 -0
- package/src/common/buffer/AttributeData.ts +196 -0
- package/src/common/buffer/Buffer.ts +654 -0
- package/src/common/buffer/BufferLine.ts +524 -0
- package/src/common/buffer/BufferRange.ts +13 -0
- package/src/common/buffer/BufferReflow.ts +223 -0
- package/src/common/buffer/BufferSet.ts +134 -0
- package/src/common/buffer/CellData.ts +94 -0
- package/src/common/buffer/Constants.ts +149 -0
- package/src/common/buffer/Marker.ts +43 -0
- package/src/common/buffer/Types.d.ts +52 -0
- package/src/common/data/Charsets.ts +256 -0
- package/src/common/data/EscapeSequences.ts +153 -0
- package/src/common/input/Keyboard.ts +398 -0
- package/src/common/input/TextDecoder.ts +346 -0
- package/src/common/input/UnicodeV6.ts +145 -0
- package/src/common/input/WriteBuffer.ts +246 -0
- package/src/common/input/XParseColor.ts +80 -0
- package/src/common/parser/Constants.ts +58 -0
- package/src/common/parser/DcsParser.ts +192 -0
- package/src/common/parser/EscapeSequenceParser.ts +792 -0
- package/src/common/parser/OscParser.ts +238 -0
- package/src/common/parser/Params.ts +229 -0
- package/src/common/parser/Types.d.ts +275 -0
- package/src/common/public/AddonManager.ts +53 -0
- package/src/common/public/BufferApiView.ts +35 -0
- package/src/common/public/BufferLineApiView.ts +29 -0
- package/src/common/public/BufferNamespaceApi.ts +36 -0
- package/src/common/public/ParserApi.ts +37 -0
- package/src/common/public/UnicodeApi.ts +27 -0
- package/src/common/services/BufferService.ts +151 -0
- package/src/common/services/CharsetService.ts +34 -0
- package/src/common/services/CoreMouseService.ts +318 -0
- package/src/common/services/CoreService.ts +87 -0
- package/src/common/services/DecorationService.ts +140 -0
- package/src/common/services/InstantiationService.ts +85 -0
- package/src/common/services/LogService.ts +124 -0
- package/src/common/services/OptionsService.ts +202 -0
- package/src/common/services/OscLinkService.ts +115 -0
- package/src/common/services/ServiceRegistry.ts +49 -0
- package/src/common/services/Services.ts +373 -0
- package/src/common/services/UnicodeService.ts +111 -0
- package/src/headless/Terminal.ts +136 -0
- package/src/headless/public/Terminal.ts +195 -0
- package/typings/xterm.d.ts +1857 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IBufferLine, ICellData, CharData } from 'common/Types';
|
|
7
|
+
import { ICharacterJoiner } from 'browser/Types';
|
|
8
|
+
import { AttributeData } from 'common/buffer/AttributeData';
|
|
9
|
+
import { WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
|
|
10
|
+
import { CellData } from 'common/buffer/CellData';
|
|
11
|
+
import { IBufferService } from 'common/services/Services';
|
|
12
|
+
import { ICharacterJoinerService } from 'browser/services/Services';
|
|
13
|
+
|
|
14
|
+
export class JoinedCellData extends AttributeData implements ICellData {
|
|
15
|
+
private _width: number;
|
|
16
|
+
// .content carries no meaning for joined CellData, simply nullify it
|
|
17
|
+
// thus we have to overload all other .content accessors
|
|
18
|
+
public content: number = 0;
|
|
19
|
+
public fg: number;
|
|
20
|
+
public bg: number;
|
|
21
|
+
public combinedData: string = '';
|
|
22
|
+
|
|
23
|
+
constructor(firstCell: ICellData, chars: string, width: number) {
|
|
24
|
+
super();
|
|
25
|
+
this.fg = firstCell.fg;
|
|
26
|
+
this.bg = firstCell.bg;
|
|
27
|
+
this.combinedData = chars;
|
|
28
|
+
this._width = width;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public isCombined(): number {
|
|
32
|
+
// always mark joined cell data as combined
|
|
33
|
+
return Content.IS_COMBINED_MASK;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public getWidth(): number {
|
|
37
|
+
return this._width;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public getChars(): string {
|
|
41
|
+
return this.combinedData;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public getCode(): number {
|
|
45
|
+
// code always gets the highest possible fake codepoint (read as -1)
|
|
46
|
+
// this is needed as code is used by caches as identifier
|
|
47
|
+
return 0x1FFFFF;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public setFromCharData(value: CharData): void {
|
|
51
|
+
throw new Error('not implemented');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public getAsCharData(): CharData {
|
|
55
|
+
return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class CharacterJoinerService implements ICharacterJoinerService {
|
|
60
|
+
public serviceBrand: undefined;
|
|
61
|
+
|
|
62
|
+
private _characterJoiners: ICharacterJoiner[] = [];
|
|
63
|
+
private _nextCharacterJoinerId: number = 0;
|
|
64
|
+
private _workCell: CellData = new CellData();
|
|
65
|
+
|
|
66
|
+
constructor(
|
|
67
|
+
@IBufferService private _bufferService: IBufferService
|
|
68
|
+
) { }
|
|
69
|
+
|
|
70
|
+
public register(handler: (text: string) => [number, number][]): number {
|
|
71
|
+
const joiner: ICharacterJoiner = {
|
|
72
|
+
id: this._nextCharacterJoinerId++,
|
|
73
|
+
handler
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
this._characterJoiners.push(joiner);
|
|
77
|
+
return joiner.id;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public deregister(joinerId: number): boolean {
|
|
81
|
+
for (let i = 0; i < this._characterJoiners.length; i++) {
|
|
82
|
+
if (this._characterJoiners[i].id === joinerId) {
|
|
83
|
+
this._characterJoiners.splice(i, 1);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public getJoinedCharacters(row: number): [number, number][] {
|
|
92
|
+
if (this._characterJoiners.length === 0) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const line = this._bufferService.buffer.lines.get(row);
|
|
97
|
+
if (!line || line.length === 0) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const ranges: [number, number][] = [];
|
|
102
|
+
const lineStr = line.translateToString(true);
|
|
103
|
+
|
|
104
|
+
// Because some cells can be represented by multiple javascript characters,
|
|
105
|
+
// we track the cell and the string indexes separately. This allows us to
|
|
106
|
+
// translate the string ranges we get from the joiners back into cell ranges
|
|
107
|
+
// for use when rendering
|
|
108
|
+
let rangeStartColumn = 0;
|
|
109
|
+
let currentStringIndex = 0;
|
|
110
|
+
let rangeStartStringIndex = 0;
|
|
111
|
+
let rangeAttrFG = line.getFg(0);
|
|
112
|
+
let rangeAttrBG = line.getBg(0);
|
|
113
|
+
|
|
114
|
+
for (let x = 0; x < line.getTrimmedLength(); x++) {
|
|
115
|
+
line.loadCell(x, this._workCell);
|
|
116
|
+
|
|
117
|
+
if (this._workCell.getWidth() === 0) {
|
|
118
|
+
// If this character is of width 0, skip it.
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// End of range
|
|
123
|
+
if (this._workCell.fg !== rangeAttrFG || this._workCell.bg !== rangeAttrBG) {
|
|
124
|
+
// If we ended up with a sequence of more than one character,
|
|
125
|
+
// look for ranges to join.
|
|
126
|
+
if (x - rangeStartColumn > 1) {
|
|
127
|
+
const joinedRanges = this._getJoinedRanges(
|
|
128
|
+
lineStr,
|
|
129
|
+
rangeStartStringIndex,
|
|
130
|
+
currentStringIndex,
|
|
131
|
+
line,
|
|
132
|
+
rangeStartColumn
|
|
133
|
+
);
|
|
134
|
+
for (let i = 0; i < joinedRanges.length; i++) {
|
|
135
|
+
ranges.push(joinedRanges[i]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Reset our markers for a new range.
|
|
140
|
+
rangeStartColumn = x;
|
|
141
|
+
rangeStartStringIndex = currentStringIndex;
|
|
142
|
+
rangeAttrFG = this._workCell.fg;
|
|
143
|
+
rangeAttrBG = this._workCell.bg;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
currentStringIndex += this._workCell.getChars().length || WHITESPACE_CELL_CHAR.length;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Process any trailing ranges.
|
|
150
|
+
if (this._bufferService.cols - rangeStartColumn > 1) {
|
|
151
|
+
const joinedRanges = this._getJoinedRanges(
|
|
152
|
+
lineStr,
|
|
153
|
+
rangeStartStringIndex,
|
|
154
|
+
currentStringIndex,
|
|
155
|
+
line,
|
|
156
|
+
rangeStartColumn
|
|
157
|
+
);
|
|
158
|
+
for (let i = 0; i < joinedRanges.length; i++) {
|
|
159
|
+
ranges.push(joinedRanges[i]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return ranges;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Given a segment of a line of text, find all ranges of text that should be
|
|
168
|
+
* joined in a single rendering unit. Ranges are internally converted to
|
|
169
|
+
* column ranges, rather than string ranges.
|
|
170
|
+
* @param line String representation of the full line of text
|
|
171
|
+
* @param startIndex Start position of the range to search in the string (inclusive)
|
|
172
|
+
* @param endIndex End position of the range to search in the string (exclusive)
|
|
173
|
+
*/
|
|
174
|
+
private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: IBufferLine, startCol: number): [number, number][] {
|
|
175
|
+
const text = line.substring(startIndex, endIndex);
|
|
176
|
+
// At this point we already know that there is at least one joiner so
|
|
177
|
+
// we can just pull its value and assign it directly rather than
|
|
178
|
+
// merging it into an empty array, which incurs unnecessary writes.
|
|
179
|
+
let allJoinedRanges: [number, number][] = [];
|
|
180
|
+
try {
|
|
181
|
+
allJoinedRanges = this._characterJoiners[0].handler(text);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error(error);
|
|
184
|
+
}
|
|
185
|
+
for (let i = 1; i < this._characterJoiners.length; i++) {
|
|
186
|
+
// We merge any overlapping ranges across the different joiners
|
|
187
|
+
try {
|
|
188
|
+
const joinerRanges = this._characterJoiners[i].handler(text);
|
|
189
|
+
for (let j = 0; j < joinerRanges.length; j++) {
|
|
190
|
+
CharacterJoinerService._mergeRanges(allJoinedRanges, joinerRanges[j]);
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error(error);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
this._stringRangesToCellRanges(allJoinedRanges, lineData, startCol);
|
|
197
|
+
return allJoinedRanges;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Modifies the provided ranges in-place to adjust for variations between
|
|
202
|
+
* string length and cell width so that the range represents a cell range,
|
|
203
|
+
* rather than the string range the joiner provides.
|
|
204
|
+
* @param ranges String ranges containing start (inclusive) and end (exclusive) index
|
|
205
|
+
* @param line Cell data for the relevant line in the terminal
|
|
206
|
+
* @param startCol Offset within the line to start from
|
|
207
|
+
*/
|
|
208
|
+
private _stringRangesToCellRanges(ranges: [number, number][], line: IBufferLine, startCol: number): void {
|
|
209
|
+
let currentRangeIndex = 0;
|
|
210
|
+
let currentRangeStarted = false;
|
|
211
|
+
let currentStringIndex = 0;
|
|
212
|
+
let currentRange = ranges[currentRangeIndex];
|
|
213
|
+
|
|
214
|
+
// If we got through all of the ranges, stop searching
|
|
215
|
+
if (!currentRange) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (let x = startCol; x < this._bufferService.cols; x++) {
|
|
220
|
+
const width = line.getWidth(x);
|
|
221
|
+
const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length;
|
|
222
|
+
|
|
223
|
+
// We skip zero-width characters when creating the string to join the text
|
|
224
|
+
// so we do the same here
|
|
225
|
+
if (width === 0) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Adjust the start of the range
|
|
230
|
+
if (!currentRangeStarted && currentRange[0] <= currentStringIndex) {
|
|
231
|
+
currentRange[0] = x;
|
|
232
|
+
currentRangeStarted = true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Adjust the end of the range
|
|
236
|
+
if (currentRange[1] <= currentStringIndex) {
|
|
237
|
+
currentRange[1] = x;
|
|
238
|
+
|
|
239
|
+
// We're finished with this range, so we move to the next one
|
|
240
|
+
currentRange = ranges[++currentRangeIndex];
|
|
241
|
+
|
|
242
|
+
// If there are no more ranges left, stop searching
|
|
243
|
+
if (!currentRange) {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Ranges can be on adjacent characters. Because the end index of the
|
|
248
|
+
// ranges are exclusive, this means that the index for the start of a
|
|
249
|
+
// range can be the same as the end index of the previous range. To
|
|
250
|
+
// account for the start of the next range, we check here just in case.
|
|
251
|
+
if (currentRange[0] <= currentStringIndex) {
|
|
252
|
+
currentRange[0] = x;
|
|
253
|
+
currentRangeStarted = true;
|
|
254
|
+
} else {
|
|
255
|
+
currentRangeStarted = false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Adjust the string index based on the character length to line up with
|
|
260
|
+
// the column adjustment
|
|
261
|
+
currentStringIndex += length;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// If there is still a range left at the end, it must extend all the way to
|
|
265
|
+
// the end of the line.
|
|
266
|
+
if (currentRange) {
|
|
267
|
+
currentRange[1] = this._bufferService.cols;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Merges the range defined by the provided start and end into the list of
|
|
273
|
+
* existing ranges. The merge is done in place on the existing range for
|
|
274
|
+
* performance and is also returned.
|
|
275
|
+
* @param ranges Existing range list
|
|
276
|
+
* @param newRange Tuple of two numbers representing the new range to merge in.
|
|
277
|
+
* @returns The ranges input with the new range merged in place
|
|
278
|
+
*/
|
|
279
|
+
private static _mergeRanges(ranges: [number, number][], newRange: [number, number]): [number, number][] {
|
|
280
|
+
let inRange = false;
|
|
281
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
282
|
+
const range = ranges[i];
|
|
283
|
+
if (!inRange) {
|
|
284
|
+
if (newRange[1] <= range[0]) {
|
|
285
|
+
// Case 1: New range is before the search range
|
|
286
|
+
ranges.splice(i, 0, newRange);
|
|
287
|
+
return ranges;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (newRange[1] <= range[1]) {
|
|
291
|
+
// Case 2: New range is either wholly contained within the
|
|
292
|
+
// search range or overlaps with the front of it
|
|
293
|
+
range[0] = Math.min(newRange[0], range[0]);
|
|
294
|
+
return ranges;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (newRange[0] < range[1]) {
|
|
298
|
+
// Case 3: New range either wholly contains the search range
|
|
299
|
+
// or overlaps with the end of it
|
|
300
|
+
range[0] = Math.min(newRange[0], range[0]);
|
|
301
|
+
inRange = true;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Case 4: New range starts after the search range
|
|
305
|
+
continue;
|
|
306
|
+
} else {
|
|
307
|
+
if (newRange[1] <= range[0]) {
|
|
308
|
+
// Case 5: New range extends from previous range but doesn't
|
|
309
|
+
// reach the current one
|
|
310
|
+
ranges[i - 1][1] = newRange[1];
|
|
311
|
+
return ranges;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (newRange[1] <= range[1]) {
|
|
315
|
+
// Case 6: New range extends from prvious range into the
|
|
316
|
+
// current range
|
|
317
|
+
ranges[i - 1][1] = Math.max(newRange[1], range[1]);
|
|
318
|
+
ranges.splice(i, 1);
|
|
319
|
+
return ranges;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Case 7: New range extends from previous range past the
|
|
323
|
+
// end of the current range
|
|
324
|
+
ranges.splice(i, 1);
|
|
325
|
+
i--;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (inRange) {
|
|
330
|
+
// Case 8: New range extends past the last existing range
|
|
331
|
+
ranges[ranges.length - 1][1] = newRange[1];
|
|
332
|
+
} else {
|
|
333
|
+
// Case 9: New range starts after the last existing range
|
|
334
|
+
ranges.push(newRange);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return ranges;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
|
7
|
+
import { ICoreBrowserService } from './Services';
|
|
8
|
+
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
|
9
|
+
import { addDisposableDomListener } from 'browser/Lifecycle';
|
|
10
|
+
|
|
11
|
+
export class CoreBrowserService extends Disposable implements ICoreBrowserService {
|
|
12
|
+
public serviceBrand: undefined;
|
|
13
|
+
|
|
14
|
+
private _isFocused = false;
|
|
15
|
+
private _cachedIsFocused: boolean | undefined = undefined;
|
|
16
|
+
private _screenDprMonitor = new ScreenDprMonitor(this._window);
|
|
17
|
+
|
|
18
|
+
private readonly _onDprChange = this.register(new EventEmitter<number>());
|
|
19
|
+
public readonly onDprChange = this._onDprChange.event;
|
|
20
|
+
private readonly _onWindowChange = this.register(new EventEmitter<Window & typeof globalThis>());
|
|
21
|
+
public readonly onWindowChange = this._onWindowChange.event;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private _textarea: HTMLTextAreaElement,
|
|
25
|
+
private _window: Window & typeof globalThis,
|
|
26
|
+
public readonly mainDocument: Document
|
|
27
|
+
) {
|
|
28
|
+
super();
|
|
29
|
+
|
|
30
|
+
// Monitor device pixel ratio
|
|
31
|
+
this.register(this.onWindowChange(w => this._screenDprMonitor.setWindow(w)));
|
|
32
|
+
this.register(forwardEvent(this._screenDprMonitor.onDprChange, this._onDprChange));
|
|
33
|
+
|
|
34
|
+
this._textarea.addEventListener('focus', () => this._isFocused = true);
|
|
35
|
+
this._textarea.addEventListener('blur', () => this._isFocused = false);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public get window(): Window & typeof globalThis {
|
|
39
|
+
return this._window;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public set window(value: Window & typeof globalThis) {
|
|
43
|
+
if (this._window !== value) {
|
|
44
|
+
this._window = value;
|
|
45
|
+
this._onWindowChange.fire(this._window);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public get dpr(): number {
|
|
50
|
+
return this.window.devicePixelRatio;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public get isFocused(): boolean {
|
|
54
|
+
if (this._cachedIsFocused === undefined) {
|
|
55
|
+
this._cachedIsFocused = this._isFocused && this._textarea.ownerDocument.hasFocus();
|
|
56
|
+
queueMicrotask(() => this._cachedIsFocused = undefined);
|
|
57
|
+
}
|
|
58
|
+
return this._cachedIsFocused;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The screen device pixel ratio monitor allows listening for when the
|
|
65
|
+
* window.devicePixelRatio value changes. This is done not with polling but with
|
|
66
|
+
* the use of window.matchMedia to watch media queries. When the event fires,
|
|
67
|
+
* the listener will be reattached using a different media query to ensure that
|
|
68
|
+
* any further changes will register.
|
|
69
|
+
*
|
|
70
|
+
* The listener should fire on both window zoom changes and switching to a
|
|
71
|
+
* monitor with a different DPI.
|
|
72
|
+
*/
|
|
73
|
+
class ScreenDprMonitor extends Disposable {
|
|
74
|
+
private _currentDevicePixelRatio: number;
|
|
75
|
+
private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined;
|
|
76
|
+
private _resolutionMediaMatchList: MediaQueryList | undefined;
|
|
77
|
+
private _windowResizeListener = this.register(new MutableDisposable());
|
|
78
|
+
|
|
79
|
+
private readonly _onDprChange = this.register(new EventEmitter<number>());
|
|
80
|
+
public readonly onDprChange = this._onDprChange.event;
|
|
81
|
+
|
|
82
|
+
constructor(private _parentWindow: Window) {
|
|
83
|
+
super();
|
|
84
|
+
|
|
85
|
+
// Initialize listener and dpr value
|
|
86
|
+
this._outerListener = () => this._setDprAndFireIfDiffers();
|
|
87
|
+
this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;
|
|
88
|
+
this._updateDpr();
|
|
89
|
+
|
|
90
|
+
// Monitor active window resize
|
|
91
|
+
this._setWindowResizeListener();
|
|
92
|
+
|
|
93
|
+
// Setup additional disposables
|
|
94
|
+
this.register(toDisposable(() => this.clearListener()));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
public setWindow(parentWindow: Window): void {
|
|
99
|
+
this._parentWindow = parentWindow;
|
|
100
|
+
this._setWindowResizeListener();
|
|
101
|
+
this._setDprAndFireIfDiffers();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private _setWindowResizeListener(): void {
|
|
105
|
+
this._windowResizeListener.value = addDisposableDomListener(this._parentWindow, 'resize', () => this._setDprAndFireIfDiffers());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _setDprAndFireIfDiffers(): void {
|
|
109
|
+
if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) {
|
|
110
|
+
this._onDprChange.fire(this._parentWindow.devicePixelRatio);
|
|
111
|
+
}
|
|
112
|
+
this._updateDpr();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private _updateDpr(): void {
|
|
116
|
+
if (!this._outerListener) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Clear listeners for old DPR
|
|
121
|
+
this._resolutionMediaMatchList?.removeListener(this._outerListener);
|
|
122
|
+
|
|
123
|
+
// Add listeners for new DPR
|
|
124
|
+
this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;
|
|
125
|
+
this._resolutionMediaMatchList = this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`);
|
|
126
|
+
this._resolutionMediaMatchList.addListener(this._outerListener);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public clearListener(): void {
|
|
130
|
+
if (!this._resolutionMediaMatchList || !this._outerListener) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this._resolutionMediaMatchList.removeListener(this._outerListener);
|
|
134
|
+
this._resolutionMediaMatchList = undefined;
|
|
135
|
+
this._outerListener = undefined;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ICharSizeService, IRenderService, IMouseService } from './Services';
|
|
7
|
+
import { getCoords, getCoordsRelativeToElement } from 'browser/input/Mouse';
|
|
8
|
+
|
|
9
|
+
export class MouseService implements IMouseService {
|
|
10
|
+
public serviceBrand: undefined;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
@IRenderService private readonly _renderService: IRenderService,
|
|
14
|
+
@ICharSizeService private readonly _charSizeService: ICharSizeService
|
|
15
|
+
) {
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
|
|
19
|
+
return getCoords(
|
|
20
|
+
window,
|
|
21
|
+
event,
|
|
22
|
+
element,
|
|
23
|
+
colCount,
|
|
24
|
+
rowCount,
|
|
25
|
+
this._charSizeService.hasValidSize,
|
|
26
|
+
this._renderService.dimensions.css.cell.width,
|
|
27
|
+
this._renderService.dimensions.css.cell.height,
|
|
28
|
+
isSelection
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public getMouseReportCoords(event: MouseEvent, element: HTMLElement): { col: number, row: number, x: number, y: number } | undefined {
|
|
33
|
+
const coords = getCoordsRelativeToElement(window, event, element);
|
|
34
|
+
if (!this._charSizeService.hasValidSize) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
coords[0] = Math.min(Math.max(coords[0], 0), this._renderService.dimensions.css.canvas.width - 1);
|
|
38
|
+
coords[1] = Math.min(Math.max(coords[1], 0), this._renderService.dimensions.css.canvas.height - 1);
|
|
39
|
+
return {
|
|
40
|
+
col: Math.floor(coords[0] / this._renderService.dimensions.css.cell.width),
|
|
41
|
+
row: Math.floor(coords[1] / this._renderService.dimensions.css.cell.height),
|
|
42
|
+
x: Math.floor(coords[0]),
|
|
43
|
+
y: Math.floor(coords[1])
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|