Add API proposal for reading files in data transfer (#148596)

* Add experimental support for reading files in data transfer

Adds a new `DataTransfer.asFile` method which lets you get file objects from a `DataTransfer`. This is currently only hooked up for drop into editors.

A few follow ups:

- Right now the file data is also read eagerly when it is transfered to the extension host. Before shipping this we would make this happen lazily instead
- The drop into editor api does not provide a nice way to do anything with the dropped files.

    We should at least support returning a `WorkspaceEdit`. However `WorkspaceEdit` only supports text files, so we would also need to add an API that lets it deal with binary files

* Make `asFile` return a value instead of a promise

`asFile().data()` already returns a promise so `asFile` doesn't also need to be async

* Trying resolving data files transfer lazily

* Cleaning up code for lazy drop

* Remove testing code

* Remove unneeded buffer serialize

* 💄
This commit is contained in:
Matt Bierner 2022-05-04 12:59:27 -07:00 committed by GitHub
parent 6a93adb40d
commit 93fd393a0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 332 additions and 112 deletions

View file

@ -25,14 +25,19 @@ const imageFileExtensions = new Set<string>([
export function registerDropIntoEditor(selector: vscode.DocumentSelector) {
return vscode.languages.registerDocumentOnDropProvider(selector, new class implements vscode.DocumentOnDropProvider {
async provideDocumentOnDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, _token: vscode.CancellationToken): Promise<vscode.SnippetTextEdit | undefined> {
async provideDocumentOnDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetTextEdit | undefined> {
const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true);
if (!enabled) {
return;
}
const replacementRange = new vscode.Range(position, position);
return this.tryInsertUriList(document, replacementRange, dataTransfer, token);
}
private async tryInsertUriList(document: vscode.TextDocument, replacementRange: vscode.Range, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetTextEdit | undefined> {
const urlList = await dataTransfer.get('text/uri-list')?.asString();
if (!urlList) {
if (!urlList || token.isCancellationRequested) {
return undefined;
}
@ -65,7 +70,7 @@ export function registerDropIntoEditor(selector: vscode.DocumentSelector) {
}
});
return new vscode.SnippetTextEdit(new vscode.Range(position, position), snippet);
return new vscode.SnippetTextEdit(replacementRange, snippet);
}
});
}

View file

@ -7,5 +7,6 @@
"src/**/*",
"../../src/vscode-dts/vscode.d.ts",
"../../src/vscode-dts/vscode.proposed.textEditorDrop.d.ts",
"../../src/vscode-dts/vscode.proposed.dataTransferFiles.d.ts"
]
}

View file

@ -3,8 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
export interface IDataTransferFile {
readonly name: string;
readonly uri?: URI;
data(): Promise<Uint8Array>;
}
export interface IDataTransferItem {
asString(): Thenable<string>;
asFile(): IDataTransferFile | undefined;
value: any;
}

View file

@ -19,6 +19,7 @@ import { IMarkerData } from 'vs/platform/markers/common/markers';
import { Codicon, CSSIcon } from 'vs/base/common/codicons';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { IDataTransfer } from 'vs/editor/common/dnd';
/**
* Open ended enum at runtime
@ -1984,18 +1985,6 @@ export enum ExternalUriOpenerPriority {
Preferred = 3,
}
/**
* @internal
*/
export interface IDataTransferItem {
asString(): Thenable<string>;
value: any;
}
/**
* @internal
*/
export type IDataTransfer = Map<string, IDataTransferItem>;
/**
* @internal

View file

@ -3,16 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling';
import { mixin } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position';
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import * as languages from 'vs/editor/common/languages';
@ -23,12 +23,14 @@ import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { DataTransferCache } from 'vs/workbench/api/common/shared/dataTransferCache';
import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer';
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import * as search from 'vs/workbench/contrib/search/common/search';
import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol';
import { IDataTransfer } from 'vs/editor/common/dnd';
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape {
@ -36,8 +38,6 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
private readonly _proxy: ExtHostLanguageFeaturesShape;
private readonly _registrations = new Map<number, IDisposable>();
private readonly _dropIntoEditorListeners = new Map<ICodeEditor, IDisposable>();
constructor(
extHostContext: IExtHostContext,
@ILanguageService private readonly _languageService: ILanguageService,
@ -83,9 +83,6 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
}
this._registrations.clear();
dispose(this._dropIntoEditorListeners.values());
this._dropIntoEditorListeners.clear();
super.dispose();
}
@ -864,13 +861,47 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
// --- document drop Edits
private readonly _documentOnDropProviders = new Map<number, MainThreadDocumentOnDropProvider>();
$registerDocumentOnDropProvider(handle: number, selector: IDocumentFilterDto[]): void {
this._registrations.set(handle, this._languageFeaturesService.documentOnDropEditProvider.register(selector, {
provideDocumentOnDropEdits: async (model, position, dataTransfer, token) => {
const dataTransferDto = await DataTransferConverter.toDataTransferDTO(dataTransfer);
return this._proxy.$provideDocumentOnDropEdits(handle, model.uri, position, dataTransferDto, token);
}
}));
const provider = new MainThreadDocumentOnDropProvider(handle, this._proxy);
this._documentOnDropProviders.set(handle, provider);
this._registrations.set(handle, combinedDisposable(
this._languageFeaturesService.documentOnDropEditProvider.register(selector, provider),
toDisposable(() => this._documentOnDropProviders.delete(handle)),
));
}
async $resolveDocumentOnDropFileData(handle: number, requestId: number, dataIndex: number): Promise<VSBuffer> {
const provider = this._documentOnDropProviders.get(handle);
if (!provider) {
throw new Error('Could not find provider');
}
return provider.resolveDocumentOnDropFileData(requestId, dataIndex);
}
}
class MainThreadDocumentOnDropProvider implements languages.DocumentOnDropEditProvider {
private readonly dataTransfers = new DataTransferCache();
constructor(
private readonly handle: number,
private readonly _proxy: ExtHostLanguageFeaturesShape,
) { }
async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: IDataTransfer, token: CancellationToken): Promise<languages.SnippetTextEdit | null | undefined> {
const request = this.dataTransfers.add(dataTransfer);
try {
const dataTransferDto = await DataTransferConverter.toDataTransferDTO(dataTransfer);
return await this._proxy.$provideDocumentOnDropEdits(this.handle, request.id, model.uri, position, dataTransferDto, token);
} finally {
request.dispose();
}
}
public resolveDocumentOnDropFileData(requestId: number, dataIndex: number): Promise<VSBuffer> {
return this.dataTransfers.resolveDropFileData(requestId, dataIndex);
}
}

View file

@ -15,13 +15,16 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { ILogService } from 'vs/platform/log/common/log';
import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDataTransfer } from 'vs/workbench/common/dnd';
import { IDataTransfer } from 'vs/editor/common/dnd';
import { VSBuffer } from 'vs/base/common/buffer';
import { DataTransferCache } from 'vs/workbench/api/common/shared/dataTransferCache';
@extHostNamedCustomer(MainContext.MainThreadTreeViews)
export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape {
private readonly _proxy: ExtHostTreeViewsShape;
private readonly _dataProviders: Map<string, TreeViewDataProvider> = new Map<string, TreeViewDataProvider>();
private readonly _dndControllers = new Map<string, TreeViewDragAndDropController>();
constructor(
extHostContext: IExtHostContext,
@ -49,6 +52,9 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
viewer.showCollapseAllAction = !!options.showCollapseAll;
viewer.canSelectMany = !!options.canSelectMany;
viewer.dragAndDropController = dndController;
if (dndController) {
this._dndControllers.set(treeViewId, dndController);
}
viewer.dataProvider = dataProvider;
this.registerListeners(treeViewId, viewer);
this._proxy.$setVisible(treeViewId, viewer.visible);
@ -111,6 +117,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
}
}
$resolveDropFileData(destinationViewId: string, requestId: number, dataItemIndex: number): Promise<VSBuffer> {
const controller = this._dndControllers.get(destinationViewId);
if (!controller) {
throw new Error('Unknown tree');
}
return controller.resolveDropFileData(requestId, dataItemIndex);
}
private async reveal(treeView: ITreeView, dataProvider: TreeViewDataProvider, itemIn: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise<void> {
options = options ? options : { select: false, focus: false };
const select = isUndefinedOrNull(options.select) ? false : options.select;
@ -170,6 +184,9 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
}
});
this._dataProviders.clear();
this._dndControllers.clear();
super.dispose();
}
}
@ -178,6 +195,8 @@ type TreeItemHandle = string;
class TreeViewDragAndDropController implements ITreeViewDragAndDropController {
private readonly dataTransfersCache = new DataTransferCache();
constructor(private readonly treeViewId: string,
readonly dropMimeTypes: string[],
readonly dragMimeTypes: string[],
@ -186,7 +205,12 @@ class TreeViewDragAndDropController implements ITreeViewDragAndDropController {
async handleDrop(dataTransfer: IDataTransfer, targetTreeItem: ITreeItem | undefined, token: CancellationToken,
operationUuid?: string, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise<void> {
return this._proxy.$handleDrop(this.treeViewId, await DataTransferConverter.toDataTransferDTO(dataTransfer), targetTreeItem?.handle, token, operationUuid, sourceTreeId, sourceTreeItemHandles);
const request = this.dataTransfersCache.add(dataTransfer);
try {
return await this._proxy.$handleDrop(this.treeViewId, request.id, await DataTransferConverter.toDataTransferDTO(dataTransfer), targetTreeItem?.handle, token, operationUuid, sourceTreeId, sourceTreeItemHandles);
} finally {
request.dispose();
}
}
async handleDrag(sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise<IDataTransfer | undefined> {
@ -197,7 +221,11 @@ class TreeViewDragAndDropController implements ITreeViewDragAndDropController {
if (!additionalTransferItems) {
return;
}
return DataTransferConverter.toDataTransfer(additionalTransferItems);
return DataTransferConverter.toDataTransfer(additionalTransferItems, () => { throw new Error('not supported'); });
}
public resolveDropFileData(requestId: number, dataItemIndex: number): Promise<VSBuffer> {
return this.dataTransfersCache.resolveDropFileData(requestId, dataItemIndex);
}
}

View file

@ -263,6 +263,7 @@ export interface MainThreadTreeViewsShape extends IDisposable {
$setMessage(treeViewId: string, message: string): void;
$setTitle(treeViewId: string, title: string, description: string | undefined): void;
$setBadge(treeViewId: string, badge: IViewBadge | undefined): void;
$resolveDropFileData(destinationViewId: string, requestId: number, dataItemIndex: number): Promise<VSBuffer>;
}
export interface MainThreadDownloadServiceShape extends IDisposable {
@ -391,6 +392,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerDocumentOnDropProvider(handle: number, selector: IDocumentFilterDto[]): void;
$resolveDocumentOnDropFileData(handle: number, requestId: number, dataIndex: number): Promise<VSBuffer>;
$setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void;
}
@ -1368,7 +1370,7 @@ export interface ExtHostDocumentsAndEditorsShape {
export interface ExtHostTreeViewsShape {
$getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[] | undefined>;
$handleDrop(destinationViewId: string, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
$handleDrop(destinationViewId: string, requestId: number, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
$handleDrag(sourceViewId: string, sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise<DataTransferDTO | undefined>;
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void;
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
@ -1756,7 +1758,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<ITypeHierarchyItemDto[] | undefined>;
$provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<ITypeHierarchyItemDto[] | undefined>;
$releaseTypeHierarchy(handle: number, sessionId: string): void;
$provideDocumentOnDropEdits(handle: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined>;
$provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined>;
}
export interface ExtHostQuickOpenShape {

View file

@ -1750,14 +1750,18 @@ class TypeHierarchyAdapter {
class DocumentOnDropAdapter {
constructor(
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape,
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.DocumentOnDropProvider
private readonly _provider: vscode.DocumentOnDropProvider,
private readonly _handle: number,
) { }
async provideDocumentOnDropEdits(uri: URI, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> {
async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> {
const doc = this._documents.getDocument(uri);
const pos = typeConvert.Position.to(position);
const dataTransfer = DataTransferConverter.toDataTransfer(dataTransferDto);
const dataTransfer = DataTransferConverter.toDataTransfer(dataTransferDto, async (index) => {
return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, index)).buffer;
});
const edit = await this._provider.provideDocumentOnDropEdits(doc, pos, dataTransfer, token);
if (!edit) {
@ -2400,13 +2404,15 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
// --- Document on drop
registerDocumentOnDropProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropProvider) {
const handle = this._addNewAdapter(new DocumentOnDropAdapter(this._documents, provider), extension);
const handle = this._nextHandle();
this._adapter.set(handle, new AdapterData(new DocumentOnDropAdapter(this._proxy, this._documents, provider, handle), extension));
this._proxy.$registerDocumentOnDropProvider(handle, this._transformDocumentSelector(selector));
return this._createDisposable(handle);
}
$provideDocumentOnDropEdits(handle: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> {
return this._withAdapter(handle, DocumentOnDropAdapter, adapter => Promise.resolve(adapter.provideDocumentOnDropEdits(URI.revive(resource), position, dataTransferDto, token)), undefined, undefined);
$provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> {
return this._withAdapter(handle, DocumentOnDropAdapter, adapter =>
Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined);
}
// --- configuration

View file

@ -25,7 +25,7 @@ import { Command } from 'vs/editor/common/languages';
import { DataTransferConverter, DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer';
import { ITreeViewsService, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { IDataTransfer } from 'vs/workbench/common/dnd';
import { IDataTransfer } from 'vs/editor/common/dnd';
type TreeItemHandle = string;
@ -144,14 +144,16 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
return treeView.getChildren(treeItemHandle);
}
async $handleDrop(destinationViewId: string, treeDataTransferDTO: DataTransferDTO, targetItemHandle: string | undefined, token: CancellationToken,
async $handleDrop(destinationViewId: string, requestId: number, treeDataTransferDTO: DataTransferDTO, targetItemHandle: string | undefined, token: CancellationToken,
operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise<void> {
const treeView = this.treeViews.get(destinationViewId);
if (!treeView) {
return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', destinationViewId)));
}
const treeDataTransfer = DataTransferConverter.toDataTransfer(treeDataTransferDTO);
const treeDataTransfer = DataTransferConverter.toDataTransfer(treeDataTransferDTO, async dataItemIndex => {
return (await this._proxy.$resolveDropFileData(destinationViewId, requestId, dataItemIndex)).buffer;
});
if ((sourceViewId === destinationViewId) && sourceTreeItemHandles) {
await this.addAdditionalTransferItems(treeDataTransfer, treeView, sourceTreeItemHandles, token, operationUuid);
}
@ -164,7 +166,21 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
if (existingTransferOperation) {
(await existingTransferOperation)?.forEach((value, key) => {
if (value) {
treeDataTransfer.set(key, value);
const file = value.asFile();
treeDataTransfer.set(key, {
value: value.value,
asString: value.asString,
asFile() {
if (!file) {
return undefined;
}
return {
name: file.name,
uri: file.uri,
data: async () => await file.data()
};
},
});
}
});
} else if (operationUuid && treeView.handleDrag) {

View file

@ -2393,6 +2393,10 @@ export class DataTransferItem {
return typeof this.value === 'string' ? this.value : JSON.stringify(this.value);
}
asFile(): undefined {
return undefined;
}
constructor(public readonly value: any) { }
}

View file

@ -3,23 +3,42 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDataTransfer, IDataTransferItem } from 'vs/workbench/common/dnd';
import { once } from 'vs/base/common/functional';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IDataTransfer, IDataTransferItem } from 'vs/editor/common/dnd';
export interface IDataTransferFileDTO {
readonly name: string;
readonly uri?: UriComponents;
}
interface DataTransferItemDTO {
asString: string;
readonly asString: string;
readonly fileData: IDataTransferFileDTO | undefined;
}
export interface DataTransferDTO {
types: string[];
items: DataTransferItemDTO[];
readonly types: string[];
readonly items: DataTransferItemDTO[];
}
export namespace DataTransferConverter {
export function toDataTransfer(value: DataTransferDTO): IDataTransfer {
export function toDataTransfer(value: DataTransferDTO, resolveFileData: (dataItemIndex: number) => Promise<Uint8Array>): IDataTransfer {
const newDataTransfer: IDataTransfer = new Map<string, IDataTransferItem>();
value.types.forEach((type, index) => {
newDataTransfer.set(type, {
asString: async () => value.items[index].asString,
asFile: () => {
const file = value.items[index].fileData;
if (!file) {
return undefined;
}
return {
name: file.name,
uri: URI.revive(file.uri),
data: once(() => resolveFileData(index)),
};
},
value: undefined
});
});
@ -31,11 +50,15 @@ export namespace DataTransferConverter {
types: [],
items: []
};
const entries = Array.from(value.entries());
for (const entry of entries) {
newDTO.types.push(entry[0]);
const stringValue = await entry[1].asString();
const fileValue = entry[1].asFile();
newDTO.items.push({
asString: await entry[1].asString()
asString: stringValue,
fileData: fileValue ? { name: fileValue.name, uri: fileValue.uri } : undefined,
});
}
return newDTO;

View file

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { IDataTransfer, IDataTransferItem } from 'vs/editor/common/dnd';
export class DataTransferCache {
private requestIdPool = 0;
private readonly dataTransfers = new Map</* requestId */ number, ReadonlyArray<IDataTransferItem>>();
public add(dataTransfer: IDataTransfer): { id: number; dispose: () => void } {
const requestId = this.requestIdPool++;
this.dataTransfers.set(requestId, [...dataTransfer.values()]);
return {
id: requestId,
dispose: () => {
this.dataTransfers.delete(requestId);
}
};
}
async resolveDropFileData(requestId: number, dataItemIndex: number): Promise<VSBuffer> {
const entry = this.dataTransfers.get(requestId);
if (!entry) {
throw new Error('No data transfer found');
}
const file = entry[dataItemIndex]?.asFile();
if (!file) {
throw new Error('No file item found in data transfer');
}
return VSBuffer.wrap(await file.data());
}
dispose() {
this.dataTransfers.clear();
}
}

View file

@ -33,7 +33,7 @@ import { parse, stringify } from 'vs/base/common/marshalling';
import { ILabelService } from 'vs/platform/label/common/label';
import { hasWorkspaceFileExtension, isTemporaryWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IDataTransfer } from 'vs/workbench/common/dnd';
import { IDataTransfer } from 'vs/editor/common/dnd';
import { extractSelection } from 'vs/platform/opener/common/opener';
import { IListDragAndDrop } from 'vs/base/browser/ui/list/list';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';

View file

@ -3,69 +3,69 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import * as DOM from 'vs/base/browser/dom';
import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer';
import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeNode, ITreeRenderer, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
import { ActionRunner, IAction } from 'vs/base/common/actions';
import { timeout } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { isCancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Mimes } from 'vs/base/common/mime';
import { Schemas } from 'vs/base/common/network';
import { basename, dirname } from 'vs/base/common/resources';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { isString } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import 'vs/css!./media/views';
import { toDisposable, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IDataTransfer } from 'vs/editor/common/dnd';
import { Command } from 'vs/editor/common/languages';
import { localize } from 'vs/nls';
import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { FileKind } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { MenuId, IMenuService, registerAction2, Action2, IMenu } from 'vs/platform/actions/common/actions';
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem, ITreeViewDragAndDropController, IViewBadge } from 'vs/workbench/common/views';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IThemeService, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { Registry } from 'vs/platform/registry/common/platform';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Event, Emitter } from 'vs/base/common/event';
import { IAction, ActionRunner } from 'vs/base/common/actions';
import { createAndFillInContextMenuActions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import * as DOM from 'vs/base/browser/dom';
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
import { URI } from 'vs/base/common/uri';
import { dirname, basename } from 'vs/base/common/resources';
import { FileKind } from 'vs/platform/files/common/files';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { localize } from 'vs/nls';
import { timeout } from 'vs/base/common/async';
import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry';
import { isString } from 'vs/base/common/types';
import { ILabelService } from 'vs/platform/label/common/label';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer';
import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { Codicon } from 'vs/base/common/codicons';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Command } from 'vs/editor/common/languages';
import { isCancellationError } from 'vs/base/common/errors';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { CodeDataTransfers, convertResourceUrlsToUriList, DraggedTreeItemsIdentifier, fillEditorsDragData, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { Schemas } from 'vs/base/common/network';
import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService';
import { generateUuid } from 'vs/base/common/uuid';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { ILogService } from 'vs/platform/log/common/log';
import { Mimes } from 'vs/base/common/mime';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { Registry } from 'vs/platform/registry/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { FileThemeIcon, FolderThemeIcon, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { CodeDataTransfers, convertResourceUrlsToUriList, DraggedTreeItemsIdentifier, fillEditorsDragData, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { Extensions, ITreeItem, ITreeItemLabel, ITreeView, ITreeViewDataProvider, ITreeViewDescriptor, ITreeViewDragAndDropController, IViewBadge, IViewDescriptorService, IViewsRegistry, ResolvableTreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IDataTransfer } from 'vs/workbench/common/dnd';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService';
export class TreeViewPane extends ViewPane {
@ -1522,6 +1522,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
if (uris.length) {
treeDataTransfer.set(Mimes.uriList, {
asString: () => Promise.resolve(uris.map(uri => uri.toString()).join('\n')),
asFile: () => undefined,
value: undefined
});
}
@ -1547,6 +1548,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
const converted = this.convertKnownMimes(type, kind, dataValue);
treeDataTransfer.set(converted.type, {
asString: () => Promise.resolve(converted.value!),
asFile: () => undefined,
value: undefined
});
}

View file

@ -27,7 +27,7 @@ import { mixin } from 'vs/base/common/objects';
import { Codicon } from 'vs/base/common/codicons';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDataTransfer } from 'vs/workbench/common/dnd';
import { IDataTransfer } from 'vs/editor/common/dnd';
export const defaultViewIcon = registerIcon('default-view-icon', Codicon.window, localize('defaultViewIcon', 'Default view icon.'));

View file

@ -13,14 +13,13 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IPosition } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { IDataTransfer, IDataTransferItem } from 'vs/editor/common/dnd';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { IDataTransferItem } from 'vs/editor/common/languages';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { extractEditorsDropData } from 'vs/workbench/browser/dnd';
import { IDataTransfer } from 'vs/workbench/common/dnd';
export class DropIntoEditorController extends Disposable implements IEditorContribution {
@ -48,13 +47,32 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
const textEditorDataTransfer: IDataTransfer = new Map<string, IDataTransferItem>();
for (const item of dragEvent.dataTransfer.items) {
const type = item.type;
if (item.kind === 'string') {
const type = item.type;
const asStringValue = new Promise<string>(resolve => item.getAsString(resolve));
textEditorDataTransfer.set(type, {
asString: () => asStringValue,
asFile: () => undefined,
value: undefined
});
} else if (item.kind === 'file') {
const file = item.getAsFile();
if (file) {
textEditorDataTransfer.set(type, {
asString: () => Promise.resolve(''),
asFile: () => {
const uri = file.path ? URI.parse(file.path) : undefined;
return {
name: file.name,
uri: uri,
data: async () => {
return new Uint8Array(await file.arrayBuffer());
},
};
},
value: undefined
});
}
}
}
@ -67,6 +85,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
const str = distinct(editorData).join('\n');
textEditorDataTransfer.set(Mimes.uriList.toLowerCase(), {
asString: () => Promise.resolve(str),
asFile: () => undefined,
value: undefined
});
}

View file

@ -15,6 +15,7 @@ export const allApiProposals = Object.freeze({
contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts',
contribViewsWelcome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts',
customEditorMove: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts',
dataTransferFiles: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.dataTransferFiles.d.ts',
diffCommand: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts',
documentFiltersExclusive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts',
editorInsets: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts',

View file

@ -5,7 +5,7 @@
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDataTransfer } from 'vs/workbench/common/dnd';
import { IDataTransfer } from 'vs/editor/common/dnd';
import { ITreeItem } from 'vs/workbench/common/views';
import { ITreeViewsService as ITreeViewsServiceCommon, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService';

View file

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
// https://github.com/microsoft/vscode/issues/TODO
/**
* A file associated with a {@linkcode DataTransferItem}.
*/
interface DataTransferFile {
/**
* The name of the file.
*/
readonly name: string;
/**
* The full file path of the file.
*
* May be undefined on web.
*/
readonly uri?: Uri;
/**
* The full file contents of the file.
*/
data(): Thenable<Uint8Array>;
}
export interface DataTransferItem {
/**
* Try getting the file associated with this data transfer item.
*
* Note that the file object is only valid for the scope of the drag and drop operation.
*
* @returns The file for the data transfer or `undefined` if the item is not a file.
*/
asFile(): DataTransferFile | undefined;
}
}