Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moving away from execCommand for pasting #239617

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/vs/editor/browser/controller/editContext/clipboardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@ export class InMemoryClipboardMetadataManager {
}

public set(lastCopiedValue: string, data: ClipboardStoredMetadata): void {
console.log('set in InMemoryClipboardMetadataManager');
console.log('lastCopiedValue: ', lastCopiedValue);
console.log('data: ', data);
this._lastState = { lastCopiedValue, data };
}

public get(pastedText: string): ClipboardStoredMetadata | null {
console.log('get in InMemoryClipboardMetadataManager, pastedText: ', pastedText);
if (this._lastState && this._lastState.lastCopiedValue === pastedText) {
// match!
return this._lastState.data;
Expand Down Expand Up @@ -89,6 +93,8 @@ interface InMemoryClipboardMetadata {
export const ClipboardEventUtils = {

getTextData(clipboardData: DataTransfer): [string, ClipboardStoredMetadata | null] {
console.log('getTextData');
console.log('clipboardData : ', clipboardData);
const text = clipboardData.getData(Mimes.text);
let metadata: ClipboardStoredMetadata | null = null;
const rawmetadata = clipboardData.getData('vscode-editor-data');
Expand All @@ -107,14 +113,8 @@ export const ClipboardEventUtils = {
const files: File[] = Array.prototype.slice.call(clipboardData.files, 0);
return [files.map(file => file.name).join('\n'), null];
}
console.log('text: ', text);
console.log('metadata: ', metadata);
return [text, metadata];
},

setTextData(clipboardData: DataTransfer, text: string, html: string | null | undefined, metadata: ClipboardStoredMetadata): void {
clipboardData.setData(Mimes.text, text);
if (typeof html === 'string') {
clipboardData.setData('text/html', html);
}
clipboardData.setData('vscode-editor-data', JSON.stringify(metadata));
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/

import './nativeEditContext.css';
import { isFirefox } from '../../../../../base/browser/browser.js';
import { addDisposableListener, getActiveWindow, getWindow, getWindowId } from '../../../../../base/browser/dom.js';
import { FastDomNode } from '../../../../../base/browser/fastDomNode.js';
import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js';
Expand All @@ -16,7 +15,6 @@ import { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent, ViewDecorat
import { ViewContext } from '../../../../common/viewModel/viewContext.js';
import { RestrictedRenderingContext, RenderingContext } from '../../../view/renderingContext.js';
import { ViewController } from '../../../view/viewController.js';
import { ClipboardEventUtils, ClipboardStoredMetadata, getDataToCopy, InMemoryClipboardMetadataManager } from '../clipboardUtils.js';
import { AbstractEditContext } from '../editContext.js';
import { editContextAddDisposableListener, FocusTracker, ITypeData } from './nativeEditContextUtils.js';
import { ScreenReaderSupport } from './screenReaderSupport.js';
Expand Down Expand Up @@ -101,15 +99,6 @@ export class NativeEditContext extends AbstractEditContext {

this._screenReaderSupport = instantiationService.createInstance(ScreenReaderSupport, this.domNode, context);

this._register(addDisposableListener(this.domNode.domNode, 'copy', (e) => this._ensureClipboardGetsEditorSelection(e)));
this._register(addDisposableListener(this.domNode.domNode, 'cut', (e) => {
// Pretend here we touched the text area, as the `cut` event will most likely
// result in a `selectionchange` event which we want to ignore
this._screenReaderSupport.setIgnoreSelectionChangeTime('onCut');
this._ensureClipboardGetsEditorSelection(e);
viewController.cut();
}));

this._register(addDisposableListener(this.domNode.domNode, 'keyup', (e) => viewController.emitKeyUp(new StandardKeyboardEvent(e))));
this._register(addDisposableListener(this.domNode.domNode, 'keydown', async (e) => {

Expand Down Expand Up @@ -147,31 +136,6 @@ export class NativeEditContext extends AbstractEditContext {
// Emits ViewCompositionEndEvent which can be depended on by ViewEventHandlers
this._context.viewModel.onCompositionEnd();
}));
this._register(addDisposableListener(this.textArea.domNode, 'paste', (e) => {
// Pretend here we touched the text area, as the `paste` event will most likely
// result in a `selectionchange` event which we want to ignore
this._screenReaderSupport.setIgnoreSelectionChangeTime('onPaste');
e.preventDefault();
if (!e.clipboardData) {
return;
}
let [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData);
if (!text) {
return;
}
metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text);
let pasteOnNewLine = false;
let multicursorText: string[] | null = null;
let mode: string | null = null;
if (metadata) {
const options = this._context.configuration.options;
const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard);
pasteOnNewLine = emptySelectionClipboard && !!metadata.isFromEmptySelection;
multicursorText = typeof metadata.multicursorText !== 'undefined' ? metadata.multicursorText : null;
mode = metadata.mode;
}
viewController.paste(text, pasteOnNewLine, multicursorText, mode);
}));
this._register(NativeEditContextRegistry.register(ownerID, this));
}

Expand Down Expand Up @@ -484,30 +448,6 @@ export class NativeEditContext extends AbstractEditContext {
this._editContext.updateCharacterBounds(e.rangeStart, characterBounds);
}

private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void {
const options = this._context.configuration.options;
const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard);
const copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting);
const selections = this._context.viewModel.getCursorStates().map(cursorState => cursorState.modelState.selection);
const dataToCopy = getDataToCopy(this._context.viewModel, selections, emptySelectionClipboard, copyWithSyntaxHighlighting);
const storedMetadata: ClipboardStoredMetadata = {
version: 1,
isFromEmptySelection: dataToCopy.isFromEmptySelection,
multicursorText: dataToCopy.multicursorText,
mode: dataToCopy.mode
};
InMemoryClipboardMetadataManager.INSTANCE.set(
// When writing "LINE\r\n" to the clipboard and then pasting,
// Firefox pastes "LINE\n", so let's work around this quirk
(isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text),
storedMetadata
);
e.preventDefault();
if (e.clipboardData) {
ClipboardEventUtils.setTextData(e.clipboardData, dataToCopy.text, dataToCopy.html, storedMetadata);
}
}

private _setSelectionChangeListener(viewController: ViewController): IDisposable {
// See https://github.com/microsoft/vscode/issues/27216 and https://github.com/microsoft/vscode/issues/98256
// When using a Braille display or NVDA for example, it is possible for users to reposition the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ export class TextAreaEditContext extends AbstractEditContext {
}));

this._register(this._textAreaInput.onPaste((e: IPasteData) => {
console.log('paste of TextAreaEditContext');
let pasteOnNewLine = false;
let multicursorText: string[] | null = null;
let mode: string | null = null;
Expand All @@ -316,10 +317,15 @@ export class TextAreaEditContext extends AbstractEditContext {
multicursorText = (typeof e.metadata.multicursorText !== 'undefined' ? e.metadata.multicursorText : null);
mode = e.metadata.mode;
}
console.log('e.text : ', e.text);
console.log('pasteOnNewLine : ', pasteOnNewLine);
console.log('multicursorText : ', multicursorText);
console.log('mode : ', mode);
this._viewController.paste(e.text, pasteOnNewLine, multicursorText, mode);
}));

this._register(this._textAreaInput.onCut(() => {
console.log('cut of TextAreaEditContext');
this._viewController.cut();
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Position } from '../../../../common/core/position.js';
import { Selection } from '../../../../common/core/selection.js';
import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';
import { ILogService } from '../../../../../platform/log/common/log.js';
import { ClipboardDataToCopy, ClipboardEventUtils, ClipboardStoredMetadata, InMemoryClipboardMetadataManager } from '../clipboardUtils.js';
import { ClipboardDataToCopy, ClipboardStoredMetadata } from '../clipboardUtils.js';
import { _debugComposition, ITextAreaWrapper, ITypeData, TextAreaState } from './textAreaEditContextState.js';

export namespace TextAreaSyntethicEvents {
Expand Down Expand Up @@ -141,10 +141,6 @@ export class TextAreaInput extends Disposable {
private _onSelectionChangeRequest = this._register(new Emitter<Selection>());
public readonly onSelectionChangeRequest: Event<Selection> = this._onSelectionChangeRequest.event;

// ---

private readonly _asyncTriggerCut: RunOnceScheduler;

private readonly _asyncFocusGainWriteScreenReaderContent: MutableDisposable<RunOnceScheduler> = this._register(new MutableDisposable());

private _textAreaState: TextAreaState;
Expand All @@ -167,7 +163,6 @@ export class TextAreaInput extends Disposable {
@ILogService private readonly _logService: ILogService
) {
super();
this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0));
this._textAreaState = TextAreaState.EMPTY;
this._selectionChangeListener = null;
if (this._accessibilityService.isScreenReaderOptimized()) {
Expand Down Expand Up @@ -346,46 +341,6 @@ export class TextAreaInput extends Disposable {
}
}));

// --- Clipboard operations

this._register(this._textArea.onCut((e) => {
// Pretend here we touched the text area, as the `cut` event will most likely
// result in a `selectionchange` event which we want to ignore
this._textArea.setIgnoreSelectionChangeTime('received cut event');

this._ensureClipboardGetsEditorSelection(e);
this._asyncTriggerCut.schedule();
}));

this._register(this._textArea.onCopy((e) => {
this._ensureClipboardGetsEditorSelection(e);
}));

this._register(this._textArea.onPaste((e) => {
// Pretend here we touched the text area, as the `paste` event will most likely
// result in a `selectionchange` event which we want to ignore
this._textArea.setIgnoreSelectionChangeTime('received paste event');

e.preventDefault();

if (!e.clipboardData) {
return;
}

let [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData);
if (!text) {
return;
}

// try the in-memory store
metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text);

this._onPaste.fire({
text: text,
metadata: metadata
});
}));

this._register(this._textArea.onFocus(() => {
const hadFocus = this._hasFocus;

Expand Down Expand Up @@ -594,27 +549,6 @@ export class TextAreaInput extends Disposable {
}
this._setAndWriteTextAreaState(reason, this._host.getScreenReaderContent());
}

private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void {
const dataToCopy = this._host.getDataToCopy();
const storedMetadata: ClipboardStoredMetadata = {
version: 1,
isFromEmptySelection: dataToCopy.isFromEmptySelection,
multicursorText: dataToCopy.multicursorText,
mode: dataToCopy.mode
};
InMemoryClipboardMetadataManager.INSTANCE.set(
// When writing "LINE\r\n" to the clipboard and then pasting,
// Firefox pastes "LINE\n", so let's work around this quirk
(this._browser.isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text),
storedMetadata
);

e.preventDefault();
if (e.clipboardData) {
ClipboardEventUtils.setTextData(e.clipboardData, dataToCopy.text, dataToCopy.html, storedMetadata);
}
}
}

export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrapper {
Expand Down
29 changes: 29 additions & 0 deletions src/vs/editor/browser/dnd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { Mimes } from '../../base/common/mime.js';
import { URI } from '../../base/common/uri.js';
import { CodeDataTransfers, getPathForFile } from '../../platform/dnd/browser/dnd.js';

export interface ClipboardCopyData {
'text/plain': string;
'text/html': string | null;
'vscode-editor-data': string;
'application/vnd.code.copymetadata'?: string;
}

export function toVSDataTransfer(dataTransfer: DataTransfer) {
const vsDataTransfer = new VSDataTransfer();
Expand All @@ -27,6 +33,21 @@ export function toVSDataTransfer(dataTransfer: DataTransfer) {
return vsDataTransfer;
}

export function toVSDataTransfer2(dataTransfer: ClipboardCopyData) {
const vsDataTransfer = new VSDataTransfer();
const copyMetadata = dataTransfer['application/vnd.code.copymetadata'];
if (copyMetadata) {
vsDataTransfer.append('application/vnd.code.copymetadata', createStringDataTransferItem(copyMetadata));
}
const textHTML = dataTransfer['text/html'];
if (textHTML) {
vsDataTransfer.append('text/html', createStringDataTransferItem(textHTML));
}
vsDataTransfer.append('text/plain', createStringDataTransferItem(dataTransfer['text/plain']));
vsDataTransfer.append('vscode-editor-data', createStringDataTransferItem(dataTransfer['vscode-editor-data']));
return vsDataTransfer;
}

function createFileDataTransferItemFromFile(file: File): IDataTransferItem {
const path = getPathForFile(file);
const uri = path ? URI.parse(path!) : undefined;
Expand Down Expand Up @@ -81,3 +102,11 @@ export function toExternalVSDataTransfer(sourceDataTransfer: DataTransfer, overw

return vsDataTransfer;
}

export function toExternalVSDataTransfer2(clipboardData: ClipboardCopyData, overwriteUriList = false): VSDataTransfer {
const vsDataTransfer = toVSDataTransfer2(clipboardData);
for (const internal of INTERNAL_DND_MIME_TYPES) {
vsDataTransfer.delete(internal);
}
return vsDataTransfer;
}
2 changes: 0 additions & 2 deletions src/vs/editor/browser/editorBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,6 @@ export interface IPartialEditorMouseEvent {
export interface IPasteEvent {
readonly range: Range;
readonly languageId: string | null;
readonly clipboardEvent?: ClipboardEvent;
}

/**
Expand All @@ -547,7 +546,6 @@ export interface PastePayload {
pasteOnNewLine: boolean;
multicursorText: string[] | null;
mode: string | null;
clipboardEvent?: ClipboardEvent;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/vs/editor/browser/view/viewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class ViewController {
}

public paste(text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void {
console.log('paste of ViewController');
this.commandDelegate.paste(text, pasteOnNewLine, multicursorText, mode);
}

Expand All @@ -85,6 +86,7 @@ export class ViewController {
}

public cut(): void {
console.log('cut of ViewController');
this.commandDelegate.cut();
}

Expand Down
13 changes: 10 additions & 3 deletions src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
}
case editorCommon.Handler.Paste: {
const args = <Partial<editorBrowser.PastePayload>>payload;
this._paste(source, args.text || '', args.pasteOnNewLine || false, args.multicursorText || null, args.mode || null, args.clipboardEvent);
this._paste(source, args.text || '', args.pasteOnNewLine || false, args.multicursorText || null, args.mode || null);
return;
}
case editorCommon.Handler.Cut:
Expand Down Expand Up @@ -1157,7 +1157,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
this._modelData.viewModel.compositionType(text, replacePrevCharCnt, replaceNextCharCnt, positionDelta, source);
}

private _paste(source: string | null | undefined, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null, clipboardEvent?: ClipboardEvent): void {
private _paste(source: string | null | undefined, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void {
console.log('_paste');
console.log('source : ', source);
console.log('text : ', text);
console.log('pasteOnNewLine : ', pasteOnNewLine);
console.log('multicursorText : ', multicursorText);
console.log('mode : ', mode);
if (!this._modelData) {
return;
}
Expand All @@ -1167,7 +1173,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
const endPosition = viewModel.getSelection().getStartPosition();
if (source === 'keyboard') {
this._onDidPaste.fire({
clipboardEvent,
range: new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column),
languageId: mode
});
Expand Down Expand Up @@ -1808,6 +1813,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (this.isSimpleWidget) {
commandDelegate = {
paste: (text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null) => {
console.log('paste of _createView 1');
this._paste('keyboard', text, pasteOnNewLine, multicursorText, mode);
},
type: (text: string) => {
Expand All @@ -1829,6 +1835,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
} else {
commandDelegate = {
paste: (text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null) => {
console.log('paste of _createView 2');
const payload: editorBrowser.PastePayload = { text, pasteOnNewLine, multicursorText, mode };
this._commandService.executeCommand(editorCommon.Handler.Paste, payload);
},
Expand Down
1 change: 1 addition & 0 deletions src/vs/editor/common/viewModel/viewModelImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,7 @@ export class ViewModel extends Disposable implements IViewModel {
this._executeCursorEdit(eventsCollector => this._cursor.compositionType(eventsCollector, text, replacePrevCharCnt, replaceNextCharCnt, positionDelta, source));
}
public paste(text: string, pasteOnNewLine: boolean, multicursorText?: string[] | null | undefined, source?: string | null | undefined): void {
console.log('paste of ViewModel');
this._executeCursorEdit(eventsCollector => this._cursor.paste(eventsCollector, text, pasteOnNewLine, multicursorText, source));
}
public cut(source?: string | null | undefined): void {
Expand Down
Loading