From 63f0154cf5df3e1d35edf808c91aa526af19338b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 2 May 2023 23:43:26 -0700 Subject: [PATCH] Set `text/uri-list` when dragging editor resources (#181374) Fixes #181367 Fixes #181373 This moves the setting of `text/uri-list` to happen on drag instead of on drop. This allows dropping editors as `text/uri-list` across applications Note that due to https://bugs.chromium.org/p/chromium/issues/detail?id=239745, we can only set a single uri in the real `text/uri-list` type --- src/vs/base/browser/dnd.ts | 9 ++++- src/vs/editor/browser/dnd.ts | 38 +++++++++++-------- .../browser/dropIntoEditorController.ts | 5 +-- src/vs/workbench/browser/dnd.ts | 27 ++++++++++++- .../workbench/browser/parts/views/treeView.ts | 8 ++-- 5 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts index ead39defe8094..da00d44bd8d2e 100644 --- a/src/vs/base/browser/dnd.ts +++ b/src/vs/base/browser/dnd.ts @@ -71,7 +71,14 @@ export const DataTransfers = { /** * Typically transfer type for copy/paste transfers. */ - TEXT: Mimes.text + TEXT: Mimes.text, + + /** + * Internal type used to pass around text/uri-list data. + * + * This is needed to work around https://bugs.chromium.org/p/chromium/issues/detail?id=239745. + */ + INTERNAL_URI_LIST: 'application/vnd.code.uri-list', }; export function applyDragImage(event: DragEvent, label: string | null, clazz: string, backgroundColor?: string | null, foregroundColor?: string | null): void { diff --git a/src/vs/editor/browser/dnd.ts b/src/vs/editor/browser/dnd.ts index c872a52e476f8..011b1f62ce8c6 100644 --- a/src/vs/editor/browser/dnd.ts +++ b/src/vs/editor/browser/dnd.ts @@ -7,7 +7,7 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { createFileDataTransferItem, createStringDataTransferItem, IDataTransferItem, UriList, VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; -import { CodeDataTransfers, extractEditorsDropData, FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; +import { CodeDataTransfers, FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; export function toVSDataTransfer(dataTransfer: DataTransfer) { @@ -38,28 +38,36 @@ const INTERNAL_DND_MIME_TYPES = Object.freeze([ CodeDataTransfers.EDITORS, CodeDataTransfers.FILES, DataTransfers.RESOURCES, + DataTransfers.INTERNAL_URI_LIST, ]); -export function addExternalEditorsDropData(dataTransfer: VSDataTransfer, dragEvent: DragEvent, overwriteUriList = false) { - if (dragEvent.dataTransfer && (overwriteUriList || !dataTransfer.has(Mimes.uriList))) { - const editorData = extractEditorsDropData(dragEvent) - .filter(input => input.resource) - .map(input => input.resource!.toString()); +export function toExternalVSDataTransfer(sourceDataTransfer: DataTransfer, overwriteUriList = false): VSDataTransfer { + const vsDataTransfer = toVSDataTransfer(sourceDataTransfer); - // Also add in the files - for (const item of dragEvent.dataTransfer?.items) { - const file = item.getAsFile(); - if (file) { - editorData.push((file as FileAdditionalNativeProperties).path ? URI.file((file as FileAdditionalNativeProperties).path!).toString() : file.name); + // Try to expose the internal uri-list type as the standard type + const uriList = vsDataTransfer.get(DataTransfers.INTERNAL_URI_LIST); + if (uriList) { + vsDataTransfer.replace(Mimes.uriList, uriList); + } else { + if (overwriteUriList || !vsDataTransfer.has(Mimes.uriList)) { + // Otherwise, fallback to adding dragged resources to the uri list + const editorData: string[] = []; + for (const item of sourceDataTransfer.items) { + const file = item.getAsFile(); + if (file) { + editorData.push((file as FileAdditionalNativeProperties).path ? URI.file((file as FileAdditionalNativeProperties).path!).toString() : file.name); + } } - } - if (editorData.length) { - dataTransfer.replace(Mimes.uriList, createStringDataTransferItem(UriList.create(editorData))); + if (editorData.length) { + vsDataTransfer.replace(Mimes.uriList, createStringDataTransferItem(UriList.create(editorData))); + } } } for (const internal of INTERNAL_DND_MIME_TYPES) { - dataTransfer.delete(internal); + vsDataTransfer.delete(internal); } + + return vsDataTransfer; } diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts index bdc77a92bf700..1bbfc65f849df 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts @@ -7,7 +7,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async'; import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Disposable } from 'vs/base/common/lifecycle'; -import { addExternalEditorsDropData, toVSDataTransfer } from 'vs/editor/browser/dnd'; +import { toExternalVSDataTransfer } from 'vs/editor/browser/dnd'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -129,8 +129,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr return new VSDataTransfer(); } - const dataTransfer = toVSDataTransfer(dragEvent.dataTransfer); - addExternalEditorsDropData(dataTransfer, dragEvent); + const dataTransfer = toExternalVSDataTransfer(dragEvent.dataTransfer); if (this.treeItemsTransfer.hasData(DraggedTreeItemsIdentifier.prototype)) { const data = this.treeItemsTransfer.getData(DraggedTreeItemsIdentifier.prototype); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 27138048155dc..c196ac2132e91 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -28,7 +28,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWindowOpenable } from 'vs/platform/window/common/window'; import { IWorkspaceContextService, hasWorkspaceFileExtension, isTemporaryWorkspace } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { EditorResourceAccessor, GroupIdentifier, IEditorIdentifier, isEditorIdentifier } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, GroupIdentifier, IEditorIdentifier, isEditorIdentifier, isResourceDiffEditorInput, isResourceMergeEditorInput, isResourceSideBySideEditorInput } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -327,6 +327,31 @@ export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEdito if (draggedEditors.length) { event.dataTransfer.setData(CodeDataTransfers.EDITORS, stringify(draggedEditors)); + + // Add a URI list entry + const uriListEntries: URI[] = []; + for (const editor of draggedEditors) { + if (editor.resource) { + uriListEntries.push(editor.resource); + } else if (isResourceDiffEditorInput(editor)) { + if (editor.modified.resource) { + uriListEntries.push(editor.modified.resource); + } + } else if (isResourceSideBySideEditorInput(editor)) { + if (editor.primary.resource) { + uriListEntries.push(editor.primary.resource); + } + } else if (isResourceMergeEditorInput(editor)) { + uriListEntries.push(editor.result.resource); + } + } + + // Due to https://bugs.chromium.org/p/chromium/issues/detail?id=239745, we can only set + // a single uri for the real `text/uri-list` type. Otherwise all uris end up joined together + // However we write the full uri-list to an internal type so that other parts of VS Code + // can use the full list. + event.dataTransfer.setData(Mimes.uriList, UriList.create(uriListEntries.slice(0, 1))); + event.dataTransfer.setData(DataTransfers.INTERNAL_URI_LIST, UriList.create(uriListEntries)); } } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index f2176b3b3d28d..7e380beaf316d 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -65,7 +65,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; import { CodeDataTransfers, LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd'; -import { addExternalEditorsDropData, toVSDataTransfer } from 'vs/editor/browser/dnd'; +import { toExternalVSDataTransfer } from 'vs/editor/browser/dnd'; import { CheckboxStateHandler, TreeItemCheckbox } from 'vs/workbench/browser/parts/views/checkbox'; import { setTimeout0 } from 'vs/base/common/platform'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; @@ -1623,8 +1623,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { - const dataTransfer = toVSDataTransfer(originalEvent.dataTransfer!); - addExternalEditorsDropData(dataTransfer, originalEvent); + const dataTransfer = toExternalVSDataTransfer(originalEvent.dataTransfer!); const types = new Set(Array.from(dataTransfer.entries()).map(x => x[0])); @@ -1687,8 +1686,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { willDropUuid = this.treeItemsTransfer.getData(DraggedTreeItemsIdentifier.prototype)![0].identifier; } - const originalDataTransfer = toVSDataTransfer(originalEvent.dataTransfer); - addExternalEditorsDropData(originalDataTransfer, originalEvent, true); + const originalDataTransfer = toExternalVSDataTransfer(originalEvent.dataTransfer, true); const outDataTransfer = new VSDataTransfer(); for (const [type, item] of originalDataTransfer.entries()) {