From 44ffeba23714e9333aa5e68aa53685faeb280bf5 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 1 Apr 2022 15:50:17 -0700 Subject: [PATCH] Switch to provider based model for on drop This simplifies implementing the provider and also give potentially gives us more control over how the drop happens --- .../src/extension.ts | 3 +- .../src/languageFeatures/dropIntoEditor.ts | 14 +-- src/vs/editor/common/languages.ts | 20 ++++ .../common/services/languageFeatures.ts | 4 +- .../services/languageFeaturesService.ts | 4 +- .../browser/mainThreadDocumentsAndEditors.ts | 2 +- .../api/browser/mainThreadEditors.ts | 82 +------------- .../api/browser/mainThreadLanguageFeatures.ts | 107 +++++++++++++++++- .../workbench/api/common/extHost.api.impl.ts | 8 +- .../workbench/api/common/extHost.protocol.ts | 3 +- .../api/common/extHostLanguageFeatures.ts | 38 ++++++- .../api/common/extHostTextEditors.ts | 42 +------ .../vscode.proposed.textEditorDrop.d.ts | 47 +------- 13 files changed, 188 insertions(+), 186 deletions(-) diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index eac0433c22b..abc4e70d6a4 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -49,8 +49,6 @@ export function activate(context: vscode.ExtensionContext) { logger.updateConfiguration(); previewManager.updateConfiguration(); })); - - context.subscriptions.push(registerDropIntoEditor()); } function registerMarkdownLanguageFeatures( @@ -72,6 +70,7 @@ function registerMarkdownLanguageFeatures( vscode.languages.registerReferenceProvider(selector, referencesProvider), vscode.languages.registerRenameProvider(selector, new MdRenameProvider(referencesProvider, githubSlugifier)), MdPathCompletionProvider.register(selector, engine, linkProvider), + registerDropIntoEditor(selector), ); } diff --git a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts index c1e4dbcb89d..14a2ace9db6 100644 --- a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts +++ b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts @@ -7,10 +7,10 @@ import * as path from 'path'; import * as vscode from 'vscode'; import * as URI from 'vscode-uri'; -export function registerDropIntoEditor() { - return vscode.workspace.onWillDropOnTextEditor(e => { - e.waitUntil((async (): Promise => { - const urlList = await e.dataTransfer.get('text/uri-list')?.asString(); +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 { + const urlList = await dataTransfer.get('text/uri-list')?.asString(); if (!urlList) { return undefined; } @@ -30,7 +30,7 @@ export function registerDropIntoEditor() { const snippet = new vscode.SnippetString(); uris.forEach((uri, i) => { - const rel = path.relative(URI.Utils.dirname(e.editor.document.uri).fsPath, uri.fsPath); + const rel = path.relative(URI.Utils.dirname(document.uri).fsPath, uri.fsPath); snippet.appendText('['); snippet.appendTabstop(); @@ -41,7 +41,7 @@ export function registerDropIntoEditor() { } }); - return new vscode.SnippetTextEdit(new vscode.Range(e.position, e.position), snippet); - })()); + return new vscode.SnippetTextEdit(new vscode.Range(position, position), snippet); + } }); } diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index b46e402b0f4..de092a4d1be 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1968,3 +1968,23 @@ export enum ExternalUriOpenerPriority { Default = 2, Preferred = 3, } + +/** + * @internal + */ +export interface IDataTransferItem { + asString(): Thenable; + value: any; +} + +/** + * @internal + */ +export type IDataTransfer = Map; + +/** + * @internal + */ +export interface DocumentOnDropEditProvider { + provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IDataTransfer, token: CancellationToken): ProviderResult; +} diff --git a/src/vs/editor/common/services/languageFeatures.ts b/src/vs/editor/common/services/languageFeatures.ts index 14bd7532635..e0ba2416582 100644 --- a/src/vs/editor/common/services/languageFeatures.ts +++ b/src/vs/editor/common/services/languageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ILanguageFeaturesService = createDecorator('ILanguageFeaturesService'); @@ -67,6 +67,8 @@ export interface ILanguageFeaturesService { readonly evaluatableExpressionProvider: LanguageFeatureRegistry; + readonly documentOnDropEditProvider: LanguageFeatureRegistry; + // -- setNotebookTypeResolver(resolver: NotebookInfoResolver | undefined): void; diff --git a/src/vs/editor/common/services/languageFeaturesService.ts b/src/vs/editor/common/services/languageFeaturesService.ts index 92f57d892a7..2ed7cebcf62 100644 --- a/src/vs/editor/common/services/languageFeaturesService.ts +++ b/src/vs/editor/common/services/languageFeaturesService.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -40,7 +40,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService { readonly evaluatableExpressionProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly documentRangeSemanticTokensProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly documentSemanticTokensProvider = new LanguageFeatureRegistry(this._score.bind(this)); - + readonly documentOnDropEditProvider = new LanguageFeatureRegistry(this._score.bind(this)); private _notebookTypeResolver?: NotebookInfoResolver; diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 8c2a77ecd84..12b5786f94f 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -304,7 +304,7 @@ export class MainThreadDocumentsAndEditors { this._mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService, pathService)); extHostContext.set(MainContext.MainThreadDocuments, this._mainThreadDocuments); - this._mainThreadEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, this._editorService, this._editorGroupService, instantiationService)); + this._mainThreadEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, this._editorService, this._editorGroupService)); extHostContext.set(MainContext.MainThreadTextEditors, this._mainThreadEditors); // It is expected that the ctor of the state computer calls our `_onDelta`. diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index e44487a63f7..80babfd1c70 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -14,7 +14,7 @@ import { IDecorationOptions, IDecorationRenderOptions } from 'vs/editor/common/e import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ITextEditorOptions, IResourceEditorInput, EditorActivation, EditorResolution } from 'vs/platform/editor/common/editor'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor'; import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToColumn, columnToEditorGroup, EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; @@ -27,13 +27,6 @@ import { ILineChange } from 'vs/editor/common/diff/diffComputer'; import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { IEditorControl } from 'vs/workbench/common/editor'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IDataTransfer, IDataTransferItem } from 'vs/workbench/common/dnd'; -import { extractEditorsDropData } from 'vs/workbench/browser/dnd'; -import { Mimes } from 'vs/base/common/mime'; -import { distinct } from 'vs/base/common/arrays'; -import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2'; export interface IMainThreadEditorLocator { getEditor(id: string): MainThreadTextEditor | undefined; @@ -51,7 +44,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { private _textEditorsListenersMap: { [editorId: string]: IDisposable[] }; private _editorPositionData: ITextEditorPositionData | null; private _registeredDecorationTypes: { [decorationType: string]: boolean }; - private readonly _dropIntoEditorListeners = new Map(); constructor( private readonly _editorLocator: IMainThreadEditorLocator, @@ -59,7 +51,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._instanceId = String(++MainThreadTextEditors.INSTANCE_COUNT); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditors); @@ -71,22 +62,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { this._toDispose.add(this._editorGroupService.onDidRemoveGroup(() => this._updateActiveAndVisibleTextEditors())); this._toDispose.add(this._editorGroupService.onDidMoveGroup(() => this._updateActiveAndVisibleTextEditors())); - const registerDropListenerOnEditor = (editor: ICodeEditor) => { - this._dropIntoEditorListeners.get(editor)?.dispose(); - this._dropIntoEditorListeners.set(editor, editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event))); - }; - - this._toDispose.add(_codeEditorService.onCodeEditorAdd(registerDropListenerOnEditor)); - - this._toDispose.add(_codeEditorService.onCodeEditorRemove(editor => { - this._dropIntoEditorListeners.get(editor)?.dispose(); - this._dropIntoEditorListeners.delete(editor); - })); - - for (const editor of this._codeEditorService.listCodeEditors()) { - registerDropListenerOnEditor(editor); - } - this._registeredDecorationTypes = Object.create(null); } @@ -99,8 +74,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { for (let decorationType in this._registeredDecorationTypes) { this._codeEditorService.removeDecorationType(decorationType); } - dispose(this._dropIntoEditorListeners.values()); - this._dropIntoEditorListeners.clear(); this._registeredDecorationTypes = Object.create(null); } @@ -140,59 +113,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { return result; } - private async onDropIntoEditor(editor: ICodeEditor, position: IPosition, dragEvent: DragEvent) { - if (!dragEvent.dataTransfer || !editor.hasModel()) { - return; - } - const id = this._editorLocator.getIdOfCodeEditor(editor); - if (typeof id !== 'string') { - return; - } - - const modelVersionNow = editor.getModel().getVersionId(); - - const textEditorDataTransfer: IDataTransfer = new Map(); - for (const item of dragEvent.dataTransfer.items) { - if (item.kind === 'string') { - const type = item.type; - const asStringValue = new Promise(resolve => item.getAsString(resolve)); - textEditorDataTransfer.set(type, { - asString: () => asStringValue, - value: undefined - }); - } - } - - if (!textEditorDataTransfer.has(Mimes.uriList.toLowerCase())) { - const editorData = (await this._instantiationService.invokeFunction(extractEditorsDropData, dragEvent)) - .filter(input => input.resource) - .map(input => input.resource!.toString()); - - if (editorData.length) { - const str = distinct(editorData).join('\n'); - textEditorDataTransfer.set(Mimes.uriList.toLowerCase(), { - asString: () => Promise.resolve(str), - value: undefined - }); - } - } - - if (textEditorDataTransfer.size === 0) { - return; - } - - const dataTransferDto = await DataTransferConverter.toDataTransferDTO(textEditorDataTransfer); - const edits = await this._proxy.$textEditorHandleDrop(id, position, dataTransferDto); - if (edits.length === 0) { - return; - } - - if (editor.getModel().getVersionId() === modelVersionNow) { - const [first] = edits; // TODO@jrieken define how to pick the "one snippet edit"; - performSnippetEdit(editor, first); - } - } - // --- from extension host process async $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index a4c3cfbc729..9664df7b430 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { distinct } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; import { ITextModel } from 'vs/editor/common/model'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import * as languages from 'vs/editor/common/languages'; import * as search from 'vs/workbench/contrib/search/common/search'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Position as EditorPosition } from 'vs/editor/common/core/position'; +import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, ILocationLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion, ITypeHierarchyItemDto, IInlayHintDto } from '../common/extHost.protocol'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; @@ -27,19 +28,33 @@ import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticToken import { revive } from 'vs/base/common/marshalling'; import { CancellationError } from 'vs/base/common/errors'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { Mimes } from 'vs/base/common/mime'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer'; +import { extractEditorsDropData } from 'vs/workbench/browser/dnd'; +import { IDataTransfer, IDataTransferItem } from 'vs/workbench/common/dnd'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) -export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { +export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape { private readonly _proxy: ExtHostLanguageFeaturesShape; private readonly _registrations = new Map(); + private readonly _dropIntoEditorListeners = new Map(); + constructor( extHostContext: IExtHostContext, @ILanguageService private readonly _languageService: ILanguageService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { + super(); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures); if (this._languageService) { @@ -69,13 +84,34 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }); updateAllWordDefinitions(); } + + const registerDropListenerOnEditor = (editor: ICodeEditor) => { + this._dropIntoEditorListeners.get(editor)?.dispose(); + this._dropIntoEditorListeners.set(editor, editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event))); + }; + + this._register(_codeEditorService.onCodeEditorAdd(registerDropListenerOnEditor)); + + this._register(_codeEditorService.onCodeEditorRemove(editor => { + this._dropIntoEditorListeners.get(editor)?.dispose(); + this._dropIntoEditorListeners.delete(editor); + })); + + for (const editor of this._codeEditorService.listCodeEditors()) { + registerDropListenerOnEditor(editor); + } } - dispose(): void { + override dispose(): void { for (const registration of this._registrations.values()) { registration.dispose(); } this._registrations.clear(); + + dispose(this._dropIntoEditorListeners.values()); + this._dropIntoEditorListeners.clear(); + + super.dispose(); } $unregister(handle: number): void { @@ -850,6 +886,69 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } + + // --- document drop Edits + + $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); + } + })); + } + + private async onDropIntoEditor(editor: ICodeEditor, position: IPosition, dragEvent: DragEvent) { + if (!dragEvent.dataTransfer || !editor.hasModel()) { + return; + } + + const model = editor.getModel(); + const modelVersionNow = model.getVersionId(); + + const textEditorDataTransfer: IDataTransfer = new Map(); + for (const item of dragEvent.dataTransfer.items) { + if (item.kind === 'string') { + const type = item.type; + const asStringValue = new Promise(resolve => item.getAsString(resolve)); + textEditorDataTransfer.set(type, { + asString: () => asStringValue, + value: undefined + }); + } + } + + if (!textEditorDataTransfer.has(Mimes.uriList.toLowerCase())) { + const editorData = (await this._instantiationService.invokeFunction(extractEditorsDropData, dragEvent)) + .filter(input => input.resource) + .map(input => input.resource!.toString()); + + if (editorData.length) { + const str = distinct(editorData).join('\n'); + textEditorDataTransfer.set(Mimes.uriList.toLowerCase(), { + asString: () => Promise.resolve(str), + value: undefined + }); + } + } + + if (textEditorDataTransfer.size === 0) { + return; + } + + const ordered = this._languageFeaturesService.documentOnDropEditProvider.ordered(model); + for (const provider of ordered) { + const edit = await provider.provideDocumentOnDropEdits(model, position, textEditorDataTransfer, CancellationToken.None); + if (editor.getModel().getVersionId() !== modelVersionNow) { + return; + } + + if (edit) { + performSnippetEdit(editor, edit); + return; + } + } + } } export class MainThreadDocumentSemanticTokensProvider implements languages.DocumentSemanticTokensProvider { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index bcdf64ad6bc..e59da75b06d 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -532,6 +532,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, createLanguageStatusItem(id: string, selector: vscode.DocumentSelector): vscode.LanguageStatusItem { return extHostLanguages.createLanguageStatusItem(extension, id, selector); + }, + registerDocumentOnDropProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'textEditorDrop'); + return extHostLanguageFeatures.registerDocumentOnDropProvider(extension, selector, provider); } }; @@ -872,10 +876,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onWillSaveTextDocument: (listener, thisArgs?, disposables?) => { return extHostDocumentSaveParticipant.getOnWillSaveTextDocumentEvent(extension)(listener, thisArgs, disposables); }, - onWillDropOnTextEditor: (listener, thisArgs?, disposables?) => { - checkProposedApiEnabled(extension, 'textEditorDrop'); - return extHostEditors.onWillDropOnTextEditor(listener, thisArgs, disposables); - }, get notebookDocuments(): vscode.NotebookDocument[] { return extHostNotebook.notebookDocuments.map(d => d.apiNotebook); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c5e114a3f3b..c33567f8d99 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -390,6 +390,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerDocumentOnDropProvider(handle: number, selector: IDocumentFilterDto[]): void; $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; } @@ -1329,7 +1330,6 @@ export interface ISelectionChangeEvent { export interface ExtHostEditorsShape { $acceptEditorPropertiesChanged(id: string, props: IEditorPropertiesChangeData): void; $acceptEditorPositionData(data: ITextEditorPositionData): void; - $textEditorHandleDrop(id: string, position: IPosition, dataTransferDto: DataTransferDTO): Promise>; } export interface IDocumentsAndEditorsDelta { @@ -1734,6 +1734,7 @@ export interface ExtHostLanguageFeaturesShape { $provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $releaseTypeHierarchy(handle: number, sessionId: string): void; + $provideDocumentOnDropEdits(handle: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise | undefined>; } export interface ExtHostQuickOpenShape { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index e5ca7469fb0..2cc57319c69 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -35,6 +35,8 @@ import { isCancellationError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { raceCancellationError } from 'vs/base/common/async'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { DataTransferConverter, DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer'; +import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; // --- adapter @@ -1712,6 +1714,27 @@ class TypeHierarchyAdapter { return map?.get(itemId); } } + +class DocumentOnDropAdapter { + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.DocumentOnDropProvider + ) { } + + async provideDocumentOnDropEdits(uri: URI, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise | undefined> { + const doc = this._documents.getDocument(uri); + const pos = typeConvert.Position.to(position); + const dataTransfer = DataTransferConverter.toDataTransfer(dataTransferDto); + + const edit = await this._provider.provideDocumentOnDropEdits(doc, pos, dataTransfer, token); + if (!edit) { + return undefined; + } + return typeConvert.SnippetTextEdit.from(edit); + } +} + type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter @@ -1720,7 +1743,8 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | SelectionRangeAdapter | CallHierarchyAdapter | TypeHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter - | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter | InlineCompletionAdapterNew; + | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter | InlineCompletionAdapterNew + | DocumentOnDropAdapter; class AdapterData { constructor( @@ -2341,6 +2365,18 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._withAdapter(handle, TypeHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined, undefined); } + // --- Document on drop + + registerDocumentOnDropProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropProvider) { + const handle = this._addNewAdapter(new DocumentOnDropAdapter(this._documents, provider), extension); + this._proxy.$registerDocumentOnDropProvider(handle, this._transformDocumentSelector(selector)); + return this._createDisposable(handle); + } + + $provideDocumentOnDropEdits(handle: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise | undefined> { + return this._withAdapter(handle, DocumentOnDropAdapter, adapter => Promise.resolve(adapter.provideDocumentOnDropEdits(URI.revive(resource), position, dataTransferDto, token)), undefined, undefined); + } + // --- configuration private static _serializeRegExp(regExp: RegExp): extHostProtocol.IRegExpDto { diff --git a/src/vs/workbench/api/common/extHostTextEditors.ts b/src/vs/workbench/api/common/extHostTextEditors.ts index e5373fe549c..b221ea9710c 100644 --- a/src/vs/workbench/api/common/extHostTextEditors.ts +++ b/src/vs/workbench/api/common/extHostTextEditors.ts @@ -3,20 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event'; import * as arrays from 'vs/base/common/arrays'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostEditorsShape, IEditorPropertiesChangeData, IMainContext, ITextDocumentShowOptions, ITextEditorPositionData, MainContext, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { ExtHostTextEditor, TextEditorDecorationType } from 'vs/workbench/api/common/extHostTextEditor'; import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; -import { SnippetTextEdit, TextEditorSelectionChangeKind } from 'vs/workbench/api/common/extHostTypes'; +import { TextEditorSelectionChangeKind } from 'vs/workbench/api/common/extHostTypes'; import * as vscode from 'vscode'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { DataTransferConverter, DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer'; -import { IPosition } from 'vs/editor/common/core/position'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import * as languages from 'vs/editor/common/languages'; export class ExtHostEditors implements ExtHostEditorsShape { @@ -26,7 +21,6 @@ export class ExtHostEditors implements ExtHostEditorsShape { private readonly _onDidChangeTextEditorViewColumn = new Emitter(); private readonly _onDidChangeActiveTextEditor = new Emitter(); private readonly _onDidChangeVisibleTextEditors = new Emitter(); - private readonly _onWillDropOnTextEditor = new AsyncEmitter(); readonly onDidChangeTextEditorSelection: Event = this._onDidChangeTextEditorSelection.event; readonly onDidChangeTextEditorOptions: Event = this._onDidChangeTextEditorOptions.event; @@ -34,7 +28,6 @@ export class ExtHostEditors implements ExtHostEditorsShape { readonly onDidChangeTextEditorViewColumn: Event = this._onDidChangeTextEditorViewColumn.event; readonly onDidChangeActiveTextEditor: Event = this._onDidChangeActiveTextEditor.event; readonly onDidChangeVisibleTextEditors: Event = this._onDidChangeVisibleTextEditors.event; - readonly onWillDropOnTextEditor: Event = this._onWillDropOnTextEditor.event; private readonly _proxy: MainThreadTextEditorsShape; @@ -166,33 +159,4 @@ export class ExtHostEditors implements ExtHostEditorsShape { getDiffInformation(id: string): Promise { return Promise.resolve(this._proxy.$getDiffInformation(id)); } - - // --- Text editor drag and drop - - async $textEditorHandleDrop(id: string, position: IPosition, dataTransferDto: DataTransferDTO): Promise> { - const textEditor = this._extHostDocumentsAndEditors.getEditor(id); - if (!textEditor) { - throw new Error('Unknown text editor'); - } - - const pos = TypeConverters.Position.to(position); - const dataTransfer = DataTransferConverter.toDataTransfer(dataTransferDto); - - const event = Object.freeze({ - editor: textEditor.value, - position: pos, - dataTransfer: dataTransfer - }); - - const edits: SnippetTextEdit[] = []; - - await this._onWillDropOnTextEditor.fireAsync(event, CancellationToken.None, async p => { - const value = await p; - if (value instanceof SnippetTextEdit) { - edits.push(value); - } - }); - - return edits.map(TypeConverters.SnippetTextEdit.from); - } } diff --git a/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts b/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts index 43bf20425f4..35a712c749c 100644 --- a/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts +++ b/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts @@ -13,50 +13,11 @@ declare module 'vscode' { constructor(range: Range, snippet: SnippetString); } - - export interface TextEditorDropEvent { - /** - * The {@link TextEditor} the resource was dropped onto. - */ - readonly editor: TextEditor; - - /** - * The position in the file where the drop occurred - */ - readonly position: Position; - - /** - * The {@link DataTransfer data transfer} associated with this drop. - */ - readonly dataTransfer: DataTransfer; - - /** - * Allows to pause the event to delay apply the drop. - * - * *Note:* This function can only be called during event dispatch and not - * in an asynchronous manner: - * - * ```ts - * workspace.onWillDropOnTextEditor(event => { - * // async, will *throw* an error - * setTimeout(() => event.waitUntil(promise)); - * - * // sync, OK - * event.waitUntil(promise); - * }) - * ``` - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - - token: CancellationToken; + export interface DocumentOnDropProvider { + provideDocumentOnDropEdits(document: TextDocument, position: Position, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult; } - export namespace workspace { - /** - * Event fired when the user drops a resource into a text editor. - */ - export const onWillDropOnTextEditor: Event; + export namespace languages { + export function registerDocumentOnDropProvider(selector: DocumentSelector, provider: DocumentOnDropProvider): Disposable; } }