Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './nativeEditContext.css';
7
import { isFirefox } from '../../../../../base/browser/browser.js';
8
import { addDisposableListener, getActiveElement, getWindow, getWindowId } from '../../../../../base/browser/dom.js';
9
import { FastDomNode } from '../../../../../base/browser/fastDomNode.js';
10
import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js';
11
import { KeyCode } from '../../../../../base/common/keyCodes.js';
12
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
13
import { EditorOption } from '../../../../common/config/editorOptions.js';
14
import { EndOfLinePreference, IModelDeltaDecoration } from '../../../../common/model.js';
15
import { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent, ViewDecorationsChangedEvent, ViewFlushedEvent, ViewLinesChangedEvent, ViewLinesDeletedEvent, ViewLinesInsertedEvent, ViewScrollChangedEvent, ViewZonesChangedEvent } from '../../../../common/viewEvents.js';
16
import { ViewContext } from '../../../../common/viewModel/viewContext.js';
17
import { RestrictedRenderingContext, RenderingContext } from '../../../view/renderingContext.js';
18
import { ViewController } from '../../../view/viewController.js';
19
import { ClipboardEventUtils, ClipboardStoredMetadata, getDataToCopy, InMemoryClipboardMetadataManager } from '../clipboardUtils.js';
20
import { AbstractEditContext } from '../editContext.js';
21
import { editContextAddDisposableListener, FocusTracker, ITypeData } from './nativeEditContextUtils.js';
22
import { ScreenReaderSupport } from './screenReaderSupport.js';
23
import { Range } from '../../../../common/core/range.js';
24
import { Selection } from '../../../../common/core/selection.js';
25
import { Position } from '../../../../common/core/position.js';
26
import { IVisibleRangeProvider } from '../textArea/textAreaEditContext.js';
27
import { PositionOffsetTransformer } from '../../../../common/core/text/positionToOffset.js';
28
import { EditContext } from './editContextFactory.js';
29
import { NativeEditContextRegistry } from './nativeEditContextRegistry.js';
30
import { IEditorAriaOptions } from '../../../editorBrowser.js';
31
import { isHighSurrogate, isLowSurrogate } from '../../../../../base/common/strings.js';
32
import { IME } from '../../../../../base/common/ime.js';
33
import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js';
34
import { ILogService, LogLevel } from '../../../../../platform/log/common/log.js';
35
import { generateUuid } from '../../../../../base/common/uuid.js';
36
37
// Corresponds to classes in nativeEditContext.css
38
enum CompositionClassName {
39
NONE = 'edit-context-composition-none',
40
SECONDARY = 'edit-context-composition-secondary',
41
PRIMARY = 'edit-context-composition-primary',
42
}
43
44
interface ITextUpdateEvent {
45
text: string;
46
selectionStart: number;
47
selectionEnd: number;
48
updateRangeStart: number;
49
updateRangeEnd: number;
50
}
51
52
export class NativeEditContext extends AbstractEditContext {
53
54
// Text area used to handle paste events
55
public readonly domNode: FastDomNode<HTMLDivElement>;
56
private readonly _imeTextArea: FastDomNode<HTMLTextAreaElement>;
57
private readonly _editContext: EditContext;
58
private readonly _screenReaderSupport: ScreenReaderSupport;
59
private _previousEditContextSelection: OffsetRange = new OffsetRange(0, 0);
60
private _editContextPrimarySelection: Selection = new Selection(1, 1, 1, 1);
61
62
// Overflow guard container
63
private _parent: HTMLElement | undefined;
64
private _decorations: string[] = [];
65
private _primarySelection: Selection = new Selection(1, 1, 1, 1);
66
67
68
private _targetWindowId: number = -1;
69
private _scrollTop: number = 0;
70
private _scrollLeft: number = 0;
71
72
private readonly _focusTracker: FocusTracker;
73
74
constructor(
75
ownerID: string,
76
context: ViewContext,
77
overflowGuardContainer: FastDomNode<HTMLElement>,
78
private readonly _viewController: ViewController,
79
private readonly _visibleRangeProvider: IVisibleRangeProvider,
80
@IInstantiationService instantiationService: IInstantiationService,
81
@ILogService private readonly logService: ILogService
82
) {
83
super(context);
84
85
this.domNode = new FastDomNode(document.createElement('div'));
86
this.domNode.setClassName(`native-edit-context`);
87
this._imeTextArea = new FastDomNode(document.createElement('textarea'));
88
this._imeTextArea.setClassName(`ime-text-area`);
89
this._imeTextArea.setAttribute('readonly', 'true');
90
this._imeTextArea.setAttribute('tabindex', '-1');
91
this._imeTextArea.setAttribute('aria-hidden', 'true');
92
this.domNode.setAttribute('autocorrect', 'off');
93
this.domNode.setAttribute('autocapitalize', 'off');
94
this.domNode.setAttribute('autocomplete', 'off');
95
this.domNode.setAttribute('spellcheck', 'false');
96
97
this._updateDomAttributes();
98
99
overflowGuardContainer.appendChild(this.domNode);
100
overflowGuardContainer.appendChild(this._imeTextArea);
101
this._parent = overflowGuardContainer.domNode;
102
103
this._focusTracker = this._register(new FocusTracker(logService, this.domNode.domNode, (newFocusValue: boolean) => {
104
logService.trace('NativeEditContext#handleFocusChange : ', newFocusValue);
105
this._screenReaderSupport.handleFocusChange(newFocusValue);
106
this._context.viewModel.setHasFocus(newFocusValue);
107
}));
108
109
const window = getWindow(this.domNode.domNode);
110
this._editContext = EditContext.create(window);
111
this.setEditContextOnDomNode();
112
113
this._screenReaderSupport = this._register(instantiationService.createInstance(ScreenReaderSupport, this.domNode, context, this._viewController));
114
115
this._register(addDisposableListener(this.domNode.domNode, 'copy', (e) => {
116
this.logService.trace('NativeEditContext#copy');
117
this._ensureClipboardGetsEditorSelection(e);
118
}));
119
this._register(addDisposableListener(this.domNode.domNode, 'cut', (e) => {
120
this.logService.trace('NativeEditContext#cut');
121
// Pretend here we touched the text area, as the `cut` event will most likely
122
// result in a `selectionchange` event which we want to ignore
123
this._screenReaderSupport.onWillCut();
124
this._ensureClipboardGetsEditorSelection(e);
125
this.logService.trace('NativeEditContext#cut (before viewController.cut)');
126
this._viewController.cut();
127
}));
128
129
this._register(addDisposableListener(this.domNode.domNode, 'keyup', (e) => this._onKeyUp(e)));
130
this._register(addDisposableListener(this.domNode.domNode, 'keydown', async (e) => this._onKeyDown(e)));
131
this._register(addDisposableListener(this._imeTextArea.domNode, 'keyup', (e) => this._onKeyUp(e)));
132
this._register(addDisposableListener(this._imeTextArea.domNode, 'keydown', async (e) => this._onKeyDown(e)));
133
this._register(addDisposableListener(this.domNode.domNode, 'beforeinput', async (e) => {
134
if (e.inputType === 'insertParagraph' || e.inputType === 'insertLineBreak') {
135
this._onType(this._viewController, { text: '\n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 });
136
}
137
}));
138
this._register(addDisposableListener(this.domNode.domNode, 'paste', (e) => {
139
this.logService.trace('NativeEditContext#paste');
140
e.preventDefault();
141
if (!e.clipboardData) {
142
return;
143
}
144
let [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData);
145
this.logService.trace('NativeEditContext#paste with id : ', metadata?.id, ' with text.length: ', text.length);
146
if (!text) {
147
return;
148
}
149
metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text);
150
let pasteOnNewLine = false;
151
let multicursorText: string[] | null = null;
152
let mode: string | null = null;
153
if (metadata) {
154
const options = this._context.configuration.options;
155
const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard);
156
pasteOnNewLine = emptySelectionClipboard && !!metadata.isFromEmptySelection;
157
multicursorText = typeof metadata.multicursorText !== 'undefined' ? metadata.multicursorText : null;
158
mode = metadata.mode;
159
}
160
this.logService.trace('NativeEditContext#paste (before viewController.paste)');
161
this._viewController.paste(text, pasteOnNewLine, multicursorText, mode);
162
}));
163
164
// Edit context events
165
this._register(editContextAddDisposableListener(this._editContext, 'textformatupdate', (e) => this._handleTextFormatUpdate(e)));
166
this._register(editContextAddDisposableListener(this._editContext, 'characterboundsupdate', (e) => this._updateCharacterBounds(e)));
167
let highSurrogateCharacter: string | undefined;
168
this._register(editContextAddDisposableListener(this._editContext, 'textupdate', (e) => {
169
const text = e.text;
170
if (text.length === 1) {
171
const charCode = text.charCodeAt(0);
172
if (isHighSurrogate(charCode)) {
173
highSurrogateCharacter = text;
174
return;
175
}
176
if (isLowSurrogate(charCode) && highSurrogateCharacter) {
177
const textUpdateEvent: ITextUpdateEvent = {
178
text: highSurrogateCharacter + text,
179
selectionEnd: e.selectionEnd,
180
selectionStart: e.selectionStart,
181
updateRangeStart: e.updateRangeStart - 1,
182
updateRangeEnd: e.updateRangeEnd - 1
183
};
184
highSurrogateCharacter = undefined;
185
this._emitTypeEvent(this._viewController, textUpdateEvent);
186
return;
187
}
188
}
189
this._emitTypeEvent(this._viewController, e);
190
}));
191
this._register(editContextAddDisposableListener(this._editContext, 'compositionstart', (e) => {
192
this._updateEditContext();
193
// Utlimately fires onDidCompositionStart() on the editor to notify for example suggest model of composition state
194
// Updates the composition state of the cursor controller which determines behavior of typing with interceptors
195
this._viewController.compositionStart();
196
// Emits ViewCompositionStartEvent which can be depended on by ViewEventHandlers
197
this._context.viewModel.onCompositionStart();
198
}));
199
this._register(editContextAddDisposableListener(this._editContext, 'compositionend', (e) => {
200
this._updateEditContext();
201
// Utlimately fires compositionEnd() on the editor to notify for example suggest model of composition state
202
// Updates the composition state of the cursor controller which determines behavior of typing with interceptors
203
this._viewController.compositionEnd();
204
// Emits ViewCompositionEndEvent which can be depended on by ViewEventHandlers
205
this._context.viewModel.onCompositionEnd();
206
}));
207
let reenableTracking: boolean = false;
208
this._register(IME.onDidChange(() => {
209
if (IME.enabled && reenableTracking) {
210
this._focusTracker.resume();
211
this.domNode.focus();
212
reenableTracking = false;
213
}
214
if (!IME.enabled && this.isFocused()) {
215
this._focusTracker.pause();
216
this._imeTextArea.focus();
217
reenableTracking = true;
218
}
219
}));
220
this._register(NativeEditContextRegistry.register(ownerID, this));
221
}
222
223
// --- Public methods ---
224
225
public override dispose(): void {
226
// Force blue the dom node so can write in pane with no native edit context after disposal
227
this.domNode.domNode.editContext = undefined;
228
this.domNode.domNode.blur();
229
this.domNode.domNode.remove();
230
this._imeTextArea.domNode.remove();
231
super.dispose();
232
}
233
234
public setAriaOptions(options: IEditorAriaOptions): void {
235
this._screenReaderSupport.setAriaOptions(options);
236
}
237
238
/* Last rendered data needed for correct hit-testing and determining the mouse position.
239
* Without this, the selection will blink as incorrect mouse position is calculated */
240
public getLastRenderData(): Position | null {
241
return this._primarySelection.getPosition();
242
}
243
244
public prepareRender(ctx: RenderingContext): void {
245
this._screenReaderSupport.prepareRender(ctx);
246
this._updateSelectionAndControlBounds(ctx);
247
}
248
249
public render(ctx: RestrictedRenderingContext): void {
250
this._screenReaderSupport.render(ctx);
251
}
252
253
public override onCursorStateChanged(e: ViewCursorStateChangedEvent): boolean {
254
this._primarySelection = e.modelSelections[0] ?? new Selection(1, 1, 1, 1);
255
this._screenReaderSupport.onCursorStateChanged(e);
256
this._updateEditContext();
257
return true;
258
}
259
260
public override onConfigurationChanged(e: ViewConfigurationChangedEvent): boolean {
261
this._screenReaderSupport.onConfigurationChanged(e);
262
this._updateDomAttributes();
263
return true;
264
}
265
266
public override onDecorationsChanged(e: ViewDecorationsChangedEvent): boolean {
267
// true for inline decorations that can end up relayouting text
268
return true;
269
}
270
271
public override onFlushed(e: ViewFlushedEvent): boolean {
272
return true;
273
}
274
275
public override onLinesChanged(e: ViewLinesChangedEvent): boolean {
276
this._updateEditContextOnLineChange(e.fromLineNumber, e.fromLineNumber + e.count - 1);
277
return true;
278
}
279
280
public override onLinesDeleted(e: ViewLinesDeletedEvent): boolean {
281
this._updateEditContextOnLineChange(e.fromLineNumber, e.toLineNumber);
282
return true;
283
}
284
285
public override onLinesInserted(e: ViewLinesInsertedEvent): boolean {
286
this._updateEditContextOnLineChange(e.fromLineNumber, e.toLineNumber);
287
return true;
288
}
289
290
private _updateEditContextOnLineChange(fromLineNumber: number, toLineNumber: number): void {
291
if (this._editContextPrimarySelection.endLineNumber < fromLineNumber || this._editContextPrimarySelection.startLineNumber > toLineNumber) {
292
return;
293
}
294
this._updateEditContext();
295
}
296
297
public override onScrollChanged(e: ViewScrollChangedEvent): boolean {
298
this._scrollLeft = e.scrollLeft;
299
this._scrollTop = e.scrollTop;
300
return true;
301
}
302
303
public override onZonesChanged(e: ViewZonesChangedEvent): boolean {
304
return true;
305
}
306
307
public onWillPaste(): void {
308
this.logService.trace('NativeEditContext#onWillPaste');
309
this._onWillPaste();
310
}
311
312
private _onWillPaste(): void {
313
this._screenReaderSupport.onWillPaste();
314
}
315
316
public onWillCopy(): void {
317
this.logService.trace('NativeEditContext#onWillCopy');
318
this.logService.trace('NativeEditContext#isFocused : ', this.domNode.domNode === getActiveElement());
319
}
320
321
public writeScreenReaderContent(): void {
322
this._screenReaderSupport.writeScreenReaderContent();
323
}
324
325
public isFocused(): boolean {
326
return this._focusTracker.isFocused;
327
}
328
329
public focus(): void {
330
this._focusTracker.focus();
331
332
// If the editor is off DOM, focus cannot be really set, so let's double check that we have managed to set the focus
333
this.refreshFocusState();
334
}
335
336
public refreshFocusState(): void {
337
this._focusTracker.refreshFocusState();
338
}
339
340
// TODO: added as a workaround fix for https://github.com/microsoft/vscode/issues/229825
341
// When this issue will be fixed the following should be removed.
342
public setEditContextOnDomNode(): void {
343
const targetWindow = getWindow(this.domNode.domNode);
344
const targetWindowId = getWindowId(targetWindow);
345
if (this._targetWindowId !== targetWindowId) {
346
this.domNode.domNode.editContext = this._editContext;
347
this._targetWindowId = targetWindowId;
348
}
349
}
350
351
// --- Private methods ---
352
353
private _onKeyUp(e: KeyboardEvent) {
354
this._viewController.emitKeyUp(new StandardKeyboardEvent(e));
355
}
356
357
private _onKeyDown(e: KeyboardEvent) {
358
const standardKeyboardEvent = new StandardKeyboardEvent(e);
359
// When the IME is visible, the keys, like arrow-left and arrow-right, should be used to navigate in the IME, and should not be propagated further
360
if (standardKeyboardEvent.keyCode === KeyCode.KEY_IN_COMPOSITION) {
361
standardKeyboardEvent.stopPropagation();
362
}
363
this._viewController.emitKeyDown(standardKeyboardEvent);
364
}
365
366
private _updateDomAttributes(): void {
367
const options = this._context.configuration.options;
368
this.domNode.domNode.setAttribute('tabindex', String(options.get(EditorOption.tabIndex)));
369
}
370
371
private _updateEditContext(): void {
372
const editContextState = this._getNewEditContextState();
373
if (!editContextState) {
374
return;
375
}
376
this._editContext.updateText(0, Number.MAX_SAFE_INTEGER, editContextState.text ?? ' ');
377
this._editContext.updateSelection(editContextState.selectionStartOffset, editContextState.selectionEndOffset);
378
this._editContextPrimarySelection = editContextState.editContextPrimarySelection;
379
this._previousEditContextSelection = new OffsetRange(editContextState.selectionStartOffset, editContextState.selectionEndOffset);
380
}
381
382
private _emitTypeEvent(viewController: ViewController, e: ITextUpdateEvent): void {
383
if (!this._editContext) {
384
return;
385
}
386
const selectionEndOffset = this._previousEditContextSelection.endExclusive;
387
const selectionStartOffset = this._previousEditContextSelection.start;
388
this._previousEditContextSelection = new OffsetRange(e.selectionStart, e.selectionEnd);
389
390
let replaceNextCharCnt = 0;
391
let replacePrevCharCnt = 0;
392
if (e.updateRangeEnd > selectionEndOffset) {
393
replaceNextCharCnt = e.updateRangeEnd - selectionEndOffset;
394
}
395
if (e.updateRangeStart < selectionStartOffset) {
396
replacePrevCharCnt = selectionStartOffset - e.updateRangeStart;
397
}
398
let text = '';
399
if (selectionStartOffset < e.updateRangeStart) {
400
text += this._editContext.text.substring(selectionStartOffset, e.updateRangeStart);
401
}
402
text += e.text;
403
if (selectionEndOffset > e.updateRangeEnd) {
404
text += this._editContext.text.substring(e.updateRangeEnd, selectionEndOffset);
405
}
406
let positionDelta = 0;
407
if (e.selectionStart === e.selectionEnd && selectionStartOffset === selectionEndOffset) {
408
positionDelta = e.selectionStart - (e.updateRangeStart + e.text.length);
409
}
410
const typeInput: ITypeData = {
411
text,
412
replacePrevCharCnt,
413
replaceNextCharCnt,
414
positionDelta
415
};
416
this._onType(viewController, typeInput);
417
}
418
419
private _onType(viewController: ViewController, typeInput: ITypeData): void {
420
if (typeInput.replacePrevCharCnt || typeInput.replaceNextCharCnt || typeInput.positionDelta) {
421
viewController.compositionType(typeInput.text, typeInput.replacePrevCharCnt, typeInput.replaceNextCharCnt, typeInput.positionDelta);
422
} else {
423
viewController.type(typeInput.text);
424
}
425
}
426
427
private _getNewEditContextState(): { text: string; selectionStartOffset: number; selectionEndOffset: number; editContextPrimarySelection: Selection } | undefined {
428
const editContextPrimarySelection = this._primarySelection;
429
const model = this._context.viewModel.model;
430
if (!model.isValidRange(editContextPrimarySelection)) {
431
return;
432
}
433
const primarySelectionStartLine = editContextPrimarySelection.startLineNumber;
434
const primarySelectionEndLine = editContextPrimarySelection.endLineNumber;
435
const endColumnOfEndLineNumber = model.getLineMaxColumn(primarySelectionEndLine);
436
const rangeOfText = new Range(primarySelectionStartLine, 1, primarySelectionEndLine, endColumnOfEndLineNumber);
437
const text = model.getValueInRange(rangeOfText, EndOfLinePreference.TextDefined);
438
const selectionStartOffset = editContextPrimarySelection.startColumn - 1;
439
const selectionEndOffset = text.length + editContextPrimarySelection.endColumn - endColumnOfEndLineNumber;
440
return {
441
text,
442
selectionStartOffset,
443
selectionEndOffset,
444
editContextPrimarySelection
445
};
446
}
447
448
private _editContextStartPosition(): Position {
449
return new Position(this._editContextPrimarySelection.startLineNumber, 1);
450
}
451
452
private _handleTextFormatUpdate(e: TextFormatUpdateEvent): void {
453
if (!this._editContext) {
454
return;
455
}
456
const formats = e.getTextFormats();
457
const editContextStartPosition = this._editContextStartPosition();
458
const decorations: IModelDeltaDecoration[] = [];
459
formats.forEach(f => {
460
const textModel = this._context.viewModel.model;
461
const offsetOfEditContextText = textModel.getOffsetAt(editContextStartPosition);
462
const startPositionOfDecoration = textModel.getPositionAt(offsetOfEditContextText + f.rangeStart);
463
const endPositionOfDecoration = textModel.getPositionAt(offsetOfEditContextText + f.rangeEnd);
464
const decorationRange = Range.fromPositions(startPositionOfDecoration, endPositionOfDecoration);
465
const thickness = f.underlineThickness.toLowerCase();
466
let decorationClassName: string = CompositionClassName.NONE;
467
switch (thickness) {
468
case 'thin':
469
decorationClassName = CompositionClassName.SECONDARY;
470
break;
471
case 'thick':
472
decorationClassName = CompositionClassName.PRIMARY;
473
break;
474
}
475
decorations.push({
476
range: decorationRange,
477
options: {
478
description: 'textFormatDecoration',
479
inlineClassName: decorationClassName,
480
}
481
});
482
});
483
this._decorations = this._context.viewModel.model.deltaDecorations(this._decorations, decorations);
484
}
485
486
private _updateSelectionAndControlBounds(ctx: RenderingContext) {
487
if (!this._parent) {
488
return;
489
}
490
const options = this._context.configuration.options;
491
const contentLeft = options.get(EditorOption.layoutInfo).contentLeft;
492
const parentBounds = this._parent.getBoundingClientRect();
493
const viewSelection = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(this._primarySelection);
494
const verticalOffsetStart = this._context.viewLayout.getVerticalOffsetForLineNumber(viewSelection.startLineNumber);
495
496
const top = parentBounds.top + verticalOffsetStart - this._scrollTop;
497
const verticalOffsetEnd = this._context.viewLayout.getVerticalOffsetAfterLineNumber(viewSelection.endLineNumber);
498
const height = verticalOffsetEnd - verticalOffsetStart;
499
let left = parentBounds.left + contentLeft - this._scrollLeft;
500
let width: number;
501
502
if (this._primarySelection.isEmpty()) {
503
const linesVisibleRanges = ctx.visibleRangeForPosition(viewSelection.getStartPosition());
504
if (linesVisibleRanges) {
505
left += linesVisibleRanges.left;
506
}
507
width = 0;
508
} else {
509
width = parentBounds.width - contentLeft;
510
}
511
512
const selectionBounds = new DOMRect(left, top, width, height);
513
this._editContext.updateSelectionBounds(selectionBounds);
514
this._editContext.updateControlBounds(selectionBounds);
515
}
516
517
private _updateCharacterBounds(e: CharacterBoundsUpdateEvent): void {
518
if (!this._parent) {
519
return;
520
}
521
const options = this._context.configuration.options;
522
const typicalHalfWidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth;
523
const contentLeft = options.get(EditorOption.layoutInfo).contentLeft;
524
const parentBounds = this._parent.getBoundingClientRect();
525
526
const characterBounds: DOMRect[] = [];
527
const offsetTransformer = new PositionOffsetTransformer(this._editContext.text);
528
for (let offset = e.rangeStart; offset < e.rangeEnd; offset++) {
529
const editContextStartPosition = offsetTransformer.getPosition(offset);
530
const textStartLineOffsetWithinEditor = this._editContextPrimarySelection.startLineNumber - 1;
531
const characterStartPosition = new Position(textStartLineOffsetWithinEditor + editContextStartPosition.lineNumber, editContextStartPosition.column);
532
const characterEndPosition = characterStartPosition.delta(0, 1);
533
const characterModelRange = Range.fromPositions(characterStartPosition, characterEndPosition);
534
const characterViewRange = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(characterModelRange);
535
const characterLinesVisibleRanges = this._visibleRangeProvider.linesVisibleRangesForRange(characterViewRange, true) ?? [];
536
const lineNumber = characterViewRange.startLineNumber;
537
const characterVerticalOffset = this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber);
538
const top = parentBounds.top + characterVerticalOffset - this._scrollTop;
539
540
let left = 0;
541
let width = typicalHalfWidthCharacterWidth;
542
if (characterLinesVisibleRanges.length > 0) {
543
for (const visibleRange of characterLinesVisibleRanges[0].ranges) {
544
left = visibleRange.left;
545
width = visibleRange.width;
546
break;
547
}
548
}
549
const lineHeight = this._context.viewLayout.getLineHeightForLineNumber(lineNumber);
550
characterBounds.push(new DOMRect(parentBounds.left + contentLeft + left - this._scrollLeft, top, width, lineHeight));
551
}
552
this._editContext.updateCharacterBounds(e.rangeStart, characterBounds);
553
}
554
555
private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void {
556
const options = this._context.configuration.options;
557
const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard);
558
const copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting);
559
const selections = this._context.viewModel.getCursorStates().map(cursorState => cursorState.modelState.selection);
560
const dataToCopy = getDataToCopy(this._context.viewModel, selections, emptySelectionClipboard, copyWithSyntaxHighlighting);
561
let id = undefined;
562
if (this.logService.getLevel() === LogLevel.Trace) {
563
id = generateUuid();
564
}
565
const storedMetadata: ClipboardStoredMetadata = {
566
version: 1,
567
id,
568
isFromEmptySelection: dataToCopy.isFromEmptySelection,
569
multicursorText: dataToCopy.multicursorText,
570
mode: dataToCopy.mode
571
};
572
InMemoryClipboardMetadataManager.INSTANCE.set(
573
// When writing "LINE\r\n" to the clipboard and then pasting,
574
// Firefox pastes "LINE\n", so let's work around this quirk
575
(isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text),
576
storedMetadata
577
);
578
e.preventDefault();
579
if (e.clipboardData) {
580
ClipboardEventUtils.setTextData(e.clipboardData, dataToCopy.text, dataToCopy.html, storedMetadata);
581
}
582
this.logService.trace('NativeEditContext#_ensureClipboardGetsEditorSelectios with id : ', id, ' with text.length: ', dataToCopy.text.length);
583
}
584
}
585
586