mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-12 11:39:57 +01:00
Add drop feedback UX (#179434)
For #179430 Adds two new UX components: - An inline progress icon shown when a drop operation takes over 500ms. This replaces the notification. You can click on it to cancel the drop - Post drop, a drop feedback icon that lets you drop the file in a different way. This lets you drop the file as plain text for instance instead of as a markdown link
This commit is contained in:
@@ -16,7 +16,8 @@
|
||||
"Programming Languages"
|
||||
],
|
||||
"enabledApiProposals": [
|
||||
"documentPaste"
|
||||
"documentPaste",
|
||||
"dropMetadata"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage:markdown",
|
||||
|
||||
@@ -45,7 +45,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
}
|
||||
|
||||
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
|
||||
return snippet ? new vscode.DocumentPasteEdit(snippet) : undefined;
|
||||
return snippet ? new vscode.DocumentPasteEdit(snippet.snippet) : undefined;
|
||||
}
|
||||
|
||||
private async _makeCreateImagePasteEdit(document: vscode.TextDocument, file: vscode.DataTransferFile, token: vscode.CancellationToken): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
|
||||
+24
-3
@@ -38,12 +38,23 @@ export function registerDropIntoEditorSupport(selector: vscode.DocumentSelector)
|
||||
}
|
||||
|
||||
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
|
||||
return snippet ? new vscode.DocumentDropEdit(snippet) : undefined;
|
||||
if (!snippet) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const edit = new vscode.DocumentDropEdit(snippet.snippet);
|
||||
edit.label = snippet.label;
|
||||
return edit;
|
||||
}
|
||||
}, {
|
||||
id: 'vscode.markdown.insertLink',
|
||||
dropMimeTypes: [
|
||||
'text/uri-list'
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetString | undefined> {
|
||||
export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> {
|
||||
const urlList = await dataTransfer.get('text/uri-list')?.asString();
|
||||
if (!urlList || token.isCancellationRequested) {
|
||||
return undefined;
|
||||
@@ -58,7 +69,17 @@ export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTr
|
||||
}
|
||||
}
|
||||
|
||||
return createUriListSnippet(document, uris);
|
||||
const snippet = createUriListSnippet(document, uris);
|
||||
if (!snippet) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
snippet: snippet,
|
||||
label: uris.length > 1
|
||||
? vscode.l10n.t('Insert uri links')
|
||||
: vscode.l10n.t('Insert uri link')
|
||||
};
|
||||
}
|
||||
|
||||
interface UriListSnippetOptions {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.documentPaste.d.ts"
|
||||
"../../src/vscode-dts/vscode.proposed.documentPaste.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.dropMetadata.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2242,7 +2242,7 @@ class EditorDecorationsCollection implements editorCommon.IEditorDecorationsColl
|
||||
this.set([]);
|
||||
}
|
||||
|
||||
public set(newDecorations: IModelDeltaDecoration[]): void {
|
||||
public set(newDecorations: readonly IModelDeltaDecoration[]): string[] {
|
||||
try {
|
||||
this._isChangingDecorations = true;
|
||||
this._editor.changeDecorations((accessor) => {
|
||||
@@ -2251,6 +2251,7 @@ class EditorDecorationsCollection implements editorCommon.IEditorDecorationsColl
|
||||
} finally {
|
||||
this._isChangingDecorations = false;
|
||||
}
|
||||
return this._decorationIds;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4701,10 +4701,16 @@ class EditorWrappingInfoComputer extends ComputedEditorOption<EditorOption.wrapp
|
||||
*/
|
||||
export interface IDropIntoEditorOptions {
|
||||
/**
|
||||
* Enable the dropping into editor.
|
||||
* Enable dropping into editor.
|
||||
* Defaults to true.
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Controls if a widget is shown after a drop.
|
||||
* Defaults to 'afterDrop'.
|
||||
*/
|
||||
showDropSelector?: 'afterDrop' | 'never';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4715,7 +4721,7 @@ export type EditorDropIntoEditorOptions = Readonly<Required<IDropIntoEditorOptio
|
||||
class EditorDropIntoEditor extends BaseEditorOption<EditorOption.dropIntoEditor, IDropIntoEditorOptions, EditorDropIntoEditorOptions> {
|
||||
|
||||
constructor() {
|
||||
const defaults: EditorDropIntoEditorOptions = { enabled: true };
|
||||
const defaults: EditorDropIntoEditorOptions = { enabled: true, showDropSelector: 'afterDrop' };
|
||||
super(
|
||||
EditorOption.dropIntoEditor, 'dropIntoEditor', defaults,
|
||||
{
|
||||
@@ -4724,6 +4730,19 @@ class EditorDropIntoEditor extends BaseEditorOption<EditorOption.dropIntoEditor,
|
||||
default: defaults.enabled,
|
||||
markdownDescription: nls.localize('dropIntoEditor.enabled', "Controls whether you can drag and drop a file into a text editor by holding down `shift` (instead of opening the file in an editor)."),
|
||||
},
|
||||
'editor.dropIntoEditor.showDropSelector': {
|
||||
type: 'string',
|
||||
markdownDescription: nls.localize('dropIntoEditor.showDropSelector', "Controls if a widget is shown when dropping files into the editor. This widget lets you control how the file is dropped."),
|
||||
enum: [
|
||||
'afterDrop',
|
||||
'never'
|
||||
],
|
||||
enumDescriptions: [
|
||||
nls.localize('dropIntoEditor.showDropSelector.afterDrop', "Show the drop selector widget after a file is dropped into the editor."),
|
||||
nls.localize('dropIntoEditor.showDropSelector.never', "Never show the drop selector widget. Instead the default drop provider is always used."),
|
||||
],
|
||||
default: 'afterDrop',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -4734,7 +4753,8 @@ class EditorDropIntoEditor extends BaseEditorOption<EditorOption.dropIntoEditor,
|
||||
}
|
||||
const input = _input as IDropIntoEditorOptions;
|
||||
return {
|
||||
enabled: boolean(input.enabled, this.defaultValue.enabled)
|
||||
enabled: boolean(input.enabled, this.defaultValue.enabled),
|
||||
showDropSelector: stringSet(input.showDropSelector, this.defaultValue.showDropSelector, ['afterDrop', 'never']),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,7 +545,7 @@ export interface IEditorDecorationsCollection {
|
||||
/**
|
||||
* Replace all previous decorations with `newDecorations`.
|
||||
*/
|
||||
set(newDecorations: IModelDeltaDecoration[]): void;
|
||||
set(newDecorations: readonly IModelDeltaDecoration[]): string[];
|
||||
/**
|
||||
* Remove all previous decorations.
|
||||
*/
|
||||
|
||||
@@ -1917,6 +1917,8 @@ export enum ExternalUriOpenerPriority {
|
||||
* @internal
|
||||
*/
|
||||
export interface DocumentOnDropEdit {
|
||||
readonly label: string;
|
||||
|
||||
insertText: string | { snippet: string };
|
||||
additionalEdit?: WorkspaceEdit;
|
||||
}
|
||||
@@ -1925,5 +1927,8 @@ export interface DocumentOnDropEdit {
|
||||
* @internal
|
||||
*/
|
||||
export interface DocumentOnDropEditProvider {
|
||||
readonly id?: string;
|
||||
readonly dropMimeTypes?: readonly string[];
|
||||
|
||||
provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): ProviderResult<DocumentOnDropEdit>;
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ export interface IModelDecorationsChangeAccessor {
|
||||
* @param newDecorations Array describing what decorations should result after the call.
|
||||
* @return An array containing the new decorations identifiers.
|
||||
*/
|
||||
deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[];
|
||||
deltaDecorations(oldDecorations: readonly string[], newDecorations: readonly IModelDeltaDecoration[]): string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { UriList, VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { Mimes } from 'vs/base/common/mime';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { relativePath } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { DocumentOnDropEdit, DocumentOnDropEditProvider } from 'vs/editor/common/languages';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
|
||||
class DefaultTextDropProvider implements DocumentOnDropEditProvider {
|
||||
|
||||
readonly id = 'text';
|
||||
readonly dropMimeTypes = [Mimes.text, 'text'];
|
||||
|
||||
async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<DocumentOnDropEdit | undefined> {
|
||||
const textEntry = dataTransfer.get('text') ?? dataTransfer.get(Mimes.text);
|
||||
if (textEntry) {
|
||||
const text = await textEntry.asString();
|
||||
return {
|
||||
label: localize('defaultDropProvider.text.label', "Drop as plain text"),
|
||||
insertText: text
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultUriListDropProvider implements DocumentOnDropEditProvider {
|
||||
|
||||
readonly id = 'uri';
|
||||
readonly dropMimeTypes = [Mimes.uriList];
|
||||
|
||||
constructor(
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
|
||||
) { }
|
||||
|
||||
async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<DocumentOnDropEdit | undefined> {
|
||||
const urlListEntry = dataTransfer.get(Mimes.uriList);
|
||||
if (urlListEntry) {
|
||||
const urlList = await urlListEntry.asString();
|
||||
const entry = this.getUriListInsertText(urlList);
|
||||
if (entry) {
|
||||
return {
|
||||
label: entry.count > 1
|
||||
? localize('defaultDropProvider.uri.label', "Drop as uri")
|
||||
: localize('defaultDropProvider.uriList.label', "Drop as uri list"),
|
||||
insertText: entry.snippet
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getUriListInsertText(strUriList: string): { snippet: string; count: number } | undefined {
|
||||
const entries: { readonly uri: URI; readonly originalText: string }[] = [];
|
||||
for (const entry of UriList.parse(strUriList)) {
|
||||
try {
|
||||
entries.push({ uri: URI.parse(entry), originalText: entry });
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
if (!entries.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snippet = entries
|
||||
.map(({ uri, originalText }) => {
|
||||
const root = this._workspaceContextService.getWorkspaceFolder(uri);
|
||||
if (root) {
|
||||
const rel = relativePath(root.uri, uri);
|
||||
if (rel) {
|
||||
return rel;
|
||||
}
|
||||
}
|
||||
|
||||
return uri.scheme === Schemas.file ? uri.fsPath : originalText;
|
||||
})
|
||||
.join(' ');
|
||||
|
||||
return { snippet, count: entries.length };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let registeredDefaultProviders = false;
|
||||
export function registerDefaultDropProviders(
|
||||
languageFeaturesService: ILanguageFeaturesService,
|
||||
workspaceContextService: IWorkspaceContextService,
|
||||
) {
|
||||
if (!registeredDefaultProviders) {
|
||||
registeredDefaultProviders = true;
|
||||
|
||||
languageFeaturesService.documentOnDropEditProvider.register('*', new DefaultTextDropProvider());
|
||||
languageFeaturesService.documentOnDropEditProvider.register('*', new DefaultUriListDropProvider(workspaceContextService));
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,11 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { raceCancellation } from 'vs/base/common/async';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { UriList, VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { VSDataTransfer } from 'vs/base/common/dataTransfer';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Mimes } from 'vs/base/common/mime';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { relativePath } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { addExternalEditorsDropData, toVSDataTransfer } from 'vs/editor/browser/dnd';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
@@ -18,32 +15,44 @@ import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/b
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { DocumentOnDropEdit, DocumentOnDropEditProvider, WorkspaceEdit } from 'vs/editor/common/languages';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { DocumentOnDropEdit, WorkspaceEdit } from 'vs/editor/common/languages';
|
||||
import { TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { DropProgressManager as DropProgressWidgetManager } from 'vs/editor/contrib/dropIntoEditor/browser/dropProgressWidget';
|
||||
import { PostDropWidgetManager } from 'vs/editor/contrib/dropIntoEditor/browser/postDropWidget';
|
||||
import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState';
|
||||
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { registerDefaultDropProviders } from './defaultOnDropProviders';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
|
||||
export class DropIntoEditorController extends Disposable implements IEditorContribution {
|
||||
|
||||
public static readonly ID = 'editor.contrib.dropIntoEditorController';
|
||||
|
||||
private operationIdPool = 0;
|
||||
private _currentOperation?: { readonly id: number; readonly promise: CancelablePromise<void> };
|
||||
|
||||
private readonly _dropProgressWidgetManager: DropProgressWidgetManager;
|
||||
private readonly _postDropWidgetManager: PostDropWidgetManager;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||
@IProgressService private readonly _progressService: IProgressService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._dropProgressWidgetManager = this._register(new DropProgressWidgetManager(editor, instantiationService));
|
||||
this._postDropWidgetManager = this._register(new PostDropWidgetManager(editor, instantiationService));
|
||||
|
||||
this._register(editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event)));
|
||||
|
||||
this._languageFeaturesService.documentOnDropEditProvider.register('*', new DefaultOnDropProvider(workspaceContextService));
|
||||
registerDefaultDropProviders(this._languageFeaturesService, workspaceContextService);
|
||||
}
|
||||
|
||||
private async onDropIntoEditor(editor: ICodeEditor, position: IPosition, dragEvent: DragEvent) {
|
||||
@@ -51,70 +60,67 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
return;
|
||||
}
|
||||
|
||||
const model = editor.getModel();
|
||||
const initialModelVersion = model.getVersionId();
|
||||
this._currentOperation?.promise.cancel();
|
||||
this._dropProgressWidgetManager.clear();
|
||||
|
||||
const ourDataTransfer = await this.extractDataTransferData(dragEvent);
|
||||
if (ourDataTransfer.size === 0) {
|
||||
return;
|
||||
}
|
||||
editor.focus();
|
||||
editor.setPosition(position);
|
||||
|
||||
if (editor.getModel().getVersionId() !== initialModelVersion) {
|
||||
return;
|
||||
}
|
||||
const id = this.operationIdPool++;
|
||||
|
||||
const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value);
|
||||
try {
|
||||
const providers = this._languageFeaturesService.documentOnDropEditProvider.ordered(model);
|
||||
const p = createCancelablePromise(async (token) => {
|
||||
const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value, undefined, token);
|
||||
|
||||
const providerEdit = await this._progressService.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
delay: 750,
|
||||
title: localize('dropProgressTitle', "Running drop handlers..."),
|
||||
cancellable: true,
|
||||
}, () => {
|
||||
return raceCancellation((async () => {
|
||||
for (const provider of providers) {
|
||||
const edit = await provider.provideDocumentOnDropEdits(model, position, ourDataTransfer, tokenSource.token);
|
||||
if (tokenSource.token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
if (edit) {
|
||||
return edit;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
})(), tokenSource.token);
|
||||
}, () => {
|
||||
tokenSource.cancel();
|
||||
this._dropProgressWidgetManager.setAtPosition(position, {
|
||||
cancel: () => tokenSource.cancel()
|
||||
});
|
||||
|
||||
if (tokenSource.token.isCancellationRequested || editor.getModel().getVersionId() !== initialModelVersion) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const ourDataTransfer = await this.extractDataTransferData(dragEvent);
|
||||
if (ourDataTransfer.size === 0 || tokenSource.token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (providerEdit) {
|
||||
const snippet = typeof providerEdit.insertText === 'string' ? SnippetParser.escape(providerEdit.insertText) : providerEdit.insertText.snippet;
|
||||
const combinedWorkspaceEdit: WorkspaceEdit = {
|
||||
edits: [
|
||||
new ResourceTextEdit(model.uri, {
|
||||
range: new Range(position.lineNumber, position.column, position.lineNumber, position.column),
|
||||
text: snippet,
|
||||
insertAsSnippet: true,
|
||||
}),
|
||||
...(providerEdit.additionalEdit?.edits ?? [])
|
||||
]
|
||||
};
|
||||
editor.focus();
|
||||
await this._bulkEditService.apply(combinedWorkspaceEdit, { editor });
|
||||
return;
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const providers = this._languageFeaturesService.documentOnDropEditProvider
|
||||
.ordered(model)
|
||||
.filter(provider => {
|
||||
if (!provider.dropMimeTypes) {
|
||||
// Keep all providers that don't specify mime types
|
||||
return true;
|
||||
}
|
||||
return provider.dropMimeTypes.some(mime => ourDataTransfer.has(mime));
|
||||
});
|
||||
|
||||
const possibleDropEdits = await raceCancellation(Promise.all(providers.map(provider => {
|
||||
return provider.provideDocumentOnDropEdits(model, position, ourDataTransfer, tokenSource.token);
|
||||
})), tokenSource.token);
|
||||
if (tokenSource.token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (possibleDropEdits) {
|
||||
// Pass in the parent token here as it tracks cancelling the entire drop operation.
|
||||
await this.applyDropResult(editor, position, 0, coalesce(possibleDropEdits), token);
|
||||
}
|
||||
} finally {
|
||||
tokenSource.dispose();
|
||||
|
||||
if (this._currentOperation?.id === id) {
|
||||
this._dropProgressWidgetManager.clear();
|
||||
this._currentOperation = undefined;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
tokenSource.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
this._currentOperation = { id, promise: p };
|
||||
}
|
||||
|
||||
public async extractDataTransferData(dragEvent: DragEvent): Promise<VSDataTransfer> {
|
||||
private async extractDataTransferData(dragEvent: DragEvent): Promise<VSDataTransfer> {
|
||||
if (!dragEvent.dataTransfer) {
|
||||
return new VSDataTransfer();
|
||||
}
|
||||
@@ -123,63 +129,54 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
|
||||
addExternalEditorsDropData(textEditorDataTransfer, dragEvent);
|
||||
return textEditorDataTransfer;
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultOnDropProvider implements DocumentOnDropEditProvider {
|
||||
|
||||
constructor(
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
|
||||
) { }
|
||||
|
||||
async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<DocumentOnDropEdit | undefined> {
|
||||
const urlListEntry = dataTransfer.get(Mimes.uriList);
|
||||
if (urlListEntry) {
|
||||
const urlList = await urlListEntry.asString();
|
||||
const snippet = this.getUriListInsertText(urlList);
|
||||
if (snippet) {
|
||||
return { insertText: snippet };
|
||||
}
|
||||
}
|
||||
|
||||
const textEntry = dataTransfer.get('text') ?? dataTransfer.get(Mimes.text);
|
||||
if (textEntry) {
|
||||
const text = await textEntry.asString();
|
||||
return { insertText: text };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getUriListInsertText(strUriList: string): string | undefined {
|
||||
const entries: { readonly uri: URI; readonly originalText: string }[] = [];
|
||||
for (const entry of UriList.parse(strUriList)) {
|
||||
try {
|
||||
entries.push({ uri: URI.parse(entry), originalText: entry });
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
if (!entries.length) {
|
||||
private async applyDropResult(editor: ICodeEditor, position: IPosition, selectedEditIndex: number, allEdits: readonly DocumentOnDropEdit[], token: CancellationToken): Promise<void> {
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
return entries
|
||||
.map(({ uri, originalText }) => {
|
||||
const root = this._workspaceContextService.getWorkspaceFolder(uri);
|
||||
if (root) {
|
||||
const rel = relativePath(root.uri, uri);
|
||||
if (rel) {
|
||||
return rel;
|
||||
}
|
||||
}
|
||||
const edit = allEdits[selectedEditIndex];
|
||||
if (!edit) {
|
||||
return;
|
||||
}
|
||||
|
||||
return uri.scheme === Schemas.file ? uri.fsPath : originalText;
|
||||
})
|
||||
.join(' ');
|
||||
const snippet = typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet;
|
||||
const combinedWorkspaceEdit: WorkspaceEdit = {
|
||||
edits: [
|
||||
new ResourceTextEdit(model.uri, {
|
||||
range: Range.fromPositions(position),
|
||||
text: snippet,
|
||||
insertAsSnippet: true,
|
||||
}),
|
||||
...(edit.additionalEdit?.edits ?? [])
|
||||
]
|
||||
};
|
||||
|
||||
// Use a decoration to track edits around the cursor
|
||||
const editTrackingDecoration = model.deltaDecorations([], [{
|
||||
range: Range.fromPositions(position),
|
||||
options: { description: 'drop-line-suffix', stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }
|
||||
}]);
|
||||
|
||||
const editResult = await this._bulkEditService.apply(combinedWorkspaceEdit, { editor, token });
|
||||
|
||||
const editRange = model.getDecorationRange(editTrackingDecoration[0]);
|
||||
model.deltaDecorations(editTrackingDecoration, []);
|
||||
|
||||
if (editResult.isApplied && allEdits.length > 1) {
|
||||
const options = editor.getOptions().get(EditorOption.dropIntoEditor);
|
||||
if (options.showDropSelector === 'afterDrop') {
|
||||
this._postDropWidgetManager.show(editRange ?? Range.fromPositions(position), {
|
||||
activeEditIndex: selectedEditIndex,
|
||||
allEdits: allEdits,
|
||||
}, async (newEditIndex) => {
|
||||
await model.undo();
|
||||
this.applyDropResult(editor, position, newEditIndex, allEdits, token);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
registerEditorContribution(DropIntoEditorController.ID, DropIntoEditorController, EditorContributionInstantiation.BeforeFirstInteraction);
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.drop-into-editor-progress-decoration {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.inline-drop-progress-widget {
|
||||
display: flex !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.inline-drop-progress-widget .icon {
|
||||
font-size: 80% !important;
|
||||
}
|
||||
|
||||
.inline-drop-progress-widget:hover .icon {
|
||||
font-size: 90% !important;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.inline-drop-progress-widget:hover .icon::before {
|
||||
content: "\ea76"; /* codicon-x */
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { noBreakWhitespace } from 'vs/base/common/strings';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import 'vs/css!./dropProgressWidget';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon';
|
||||
import { TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
const dropIntoEditorProgress = ModelDecorationOptions.register({
|
||||
description: 'drop-into-editor-progress',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
showIfCollapsed: true,
|
||||
after: {
|
||||
content: noBreakWhitespace,
|
||||
inlineClassName: 'drop-into-editor-progress-decoration',
|
||||
inlineClassNameAffectsLetterSpacing: true,
|
||||
}
|
||||
});
|
||||
|
||||
interface DropProgressDelegate {
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
class InlineDropProgressWidget extends Disposable implements IContentWidget {
|
||||
private static readonly ID = 'editor.widget.dropProgressWidget';
|
||||
|
||||
allowEditorOverflow = false;
|
||||
suppressMouseDown = true;
|
||||
|
||||
private domNode!: HTMLElement;
|
||||
|
||||
constructor(
|
||||
private readonly editor: ICodeEditor,
|
||||
private readonly range: Range,
|
||||
private readonly delegate: DropProgressDelegate,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.create();
|
||||
|
||||
this.editor.addContentWidget(this);
|
||||
this.editor.layoutContentWidget(this);
|
||||
}
|
||||
|
||||
private create(): void {
|
||||
this.domNode = dom.$('.inline-drop-progress-widget');
|
||||
this.domNode.role = 'button';
|
||||
this.domNode.title = localize('dropIntoEditorProgress', "Running drop handlers. Click to cancel");
|
||||
|
||||
const iconElement = dom.$('span.icon');
|
||||
this.domNode.append(iconElement);
|
||||
|
||||
iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin');
|
||||
|
||||
const updateSize = () => {
|
||||
const lineHeight = this.editor.getOption(EditorOption.lineHeight);
|
||||
this.domNode.style.height = `${lineHeight}px`;
|
||||
this.domNode.style.width = `${Math.ceil(0.8 * lineHeight)}px`;
|
||||
};
|
||||
updateSize();
|
||||
|
||||
this._register(this.editor.onDidChangeConfiguration(c => {
|
||||
if (c.hasChanged(EditorOption.fontSize) || c.hasChanged(EditorOption.lineHeight)) {
|
||||
updateSize();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(dom.addDisposableListener(this.domNode, dom.EventType.CLICK, e => {
|
||||
this.delegate.cancel();
|
||||
}));
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return InlineDropProgressWidget.ID;
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
return this.domNode;
|
||||
}
|
||||
|
||||
getPosition(): IContentWidgetPosition | null {
|
||||
return {
|
||||
position: { lineNumber: this.range.startLineNumber, column: this.range.startColumn },
|
||||
preference: [ContentWidgetPositionPreference.EXACT]
|
||||
};
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this.editor.removeContentWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class DropProgressManager extends Disposable {
|
||||
|
||||
/** Delay before showing the progress widget */
|
||||
private readonly _showDelay = 500; // ms
|
||||
private readonly _showPromise = this._register(new MutableDisposable());
|
||||
|
||||
private readonly _currentDecorations: IEditorDecorationsCollection;
|
||||
private readonly _currentWidget = new MutableDisposable<InlineDropProgressWidget>();
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._currentDecorations = _editor.createDecorationsCollection();
|
||||
}
|
||||
|
||||
public setAtPosition(position: IPosition, delegate: DropProgressDelegate) {
|
||||
this.clear();
|
||||
|
||||
this._showPromise.value = disposableTimeout(() => {
|
||||
const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
|
||||
const decorationIds = this._currentDecorations.set([{
|
||||
range: range,
|
||||
options: dropIntoEditorProgress,
|
||||
}]);
|
||||
|
||||
if (decorationIds.length > 0) {
|
||||
this._currentWidget.value = this._instantiationService.createInstance(InlineDropProgressWidget, this._editor, range, delegate);
|
||||
}
|
||||
}, this._showDelay);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this._showPromise.clear();
|
||||
this._currentDecorations.clear();
|
||||
this._currentWidget.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.post-drop-widget .monaco-button {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { toAction } from 'vs/base/common/actions';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./dropProgressWidget';
|
||||
import 'vs/css!./postDropWidget';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { DocumentOnDropEdit } from 'vs/editor/common/languages';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
|
||||
|
||||
|
||||
interface DropEditSet {
|
||||
readonly activeEditIndex: number;
|
||||
readonly allEdits: readonly DocumentOnDropEdit[];
|
||||
}
|
||||
|
||||
class PostDropWidget extends Disposable implements IContentWidget {
|
||||
private static readonly ID = 'editor.widget.postDropWidget';
|
||||
|
||||
readonly allowEditorOverflow = false;
|
||||
readonly suppressMouseDown = true;
|
||||
|
||||
private domNode!: HTMLElement;
|
||||
|
||||
constructor(
|
||||
private readonly editor: ICodeEditor,
|
||||
private readonly range: Range,
|
||||
private readonly edits: DropEditSet,
|
||||
private readonly onSelectNewEdit: (editIndex: number) => void,
|
||||
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.create();
|
||||
|
||||
this.editor.addContentWidget(this);
|
||||
this.editor.layoutContentWidget(this);
|
||||
|
||||
this._register(toDisposable((() => this.editor.removeContentWidget(this))));
|
||||
|
||||
this._register(this.editor.onDidChangeCursorPosition(e => {
|
||||
if (!range.containsPosition(e.position)) {
|
||||
this.dispose();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private create(): void {
|
||||
this.domNode = dom.$('.post-drop-widget');
|
||||
|
||||
const button = this._register(new Button(this.domNode, {
|
||||
title: localize('postDropWidgetTile', "Drop options..."),
|
||||
supportIcons: true,
|
||||
...defaultButtonStyles,
|
||||
}));
|
||||
button.label = '$(clippy)';
|
||||
|
||||
this._register(dom.addDisposableListener(this.domNode, dom.EventType.CLICK, e => {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => {
|
||||
const pos = dom.getDomNodePagePosition(button.element);
|
||||
return { x: pos.left + pos.width, y: pos.top + pos.height };
|
||||
},
|
||||
getActions: () => {
|
||||
return this.edits.allEdits.map((edit, i) => toAction({
|
||||
id: '',
|
||||
label: edit.label,
|
||||
checked: i === this.edits.activeEditIndex,
|
||||
run: () => this.onSelectNewEdit(i),
|
||||
}));
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return PostDropWidget.ID;
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
return this.domNode;
|
||||
}
|
||||
|
||||
getPosition(): IContentWidgetPosition | null {
|
||||
return {
|
||||
position: this.range.getEndPosition(),
|
||||
preference: [ContentWidgetPositionPreference.BELOW]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDropWidgetManager extends Disposable {
|
||||
|
||||
private readonly _currentWidget = this._register(new MutableDisposable<PostDropWidget>());
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(_editor.onDidChangeModelContent(() => this.clear()));
|
||||
}
|
||||
|
||||
public show(range: Range, edits: DropEditSet, onDidSelectEdit: (newIndex: number) => void) {
|
||||
this.clear();
|
||||
|
||||
if (this._editor.hasModel()) {
|
||||
this._currentWidget.value = this._instantiationService.createInstance(PostDropWidget, this._editor, range, edits, onDidSelectEdit);
|
||||
}
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this._currentWidget?.clear();
|
||||
}
|
||||
}
|
||||
Vendored
+7
-2
@@ -2805,7 +2805,7 @@ declare namespace monaco.editor {
|
||||
/**
|
||||
* Replace all previous decorations with `newDecorations`.
|
||||
*/
|
||||
set(newDecorations: IModelDeltaDecoration[]): void;
|
||||
set(newDecorations: readonly IModelDeltaDecoration[]): string[];
|
||||
/**
|
||||
* Remove all previous decorations.
|
||||
*/
|
||||
@@ -4621,10 +4621,15 @@ declare namespace monaco.editor {
|
||||
*/
|
||||
export interface IDropIntoEditorOptions {
|
||||
/**
|
||||
* Enable the dropping into editor.
|
||||
* Enable dropping into editor.
|
||||
* Defaults to true.
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* Controls if a widget is shown after a drop.
|
||||
* Defaults to 'afterDrop'.
|
||||
*/
|
||||
showDropSelector?: 'afterDrop' | 'never';
|
||||
}
|
||||
|
||||
export enum EditorOption {
|
||||
|
||||
@@ -907,8 +907,8 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
|
||||
|
||||
private readonly _documentOnDropEditProviders = new Map<number, MainThreadDocumentOnDropEditProvider>();
|
||||
|
||||
$registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[]): void {
|
||||
const provider = new MainThreadDocumentOnDropEditProvider(handle, this._proxy, this._uriIdentService);
|
||||
$registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], metadata?: { id: string; dropMimeTypes?: string[] }): void {
|
||||
const provider = new MainThreadDocumentOnDropEditProvider(handle, metadata, this._proxy, this._uriIdentService);
|
||||
this._documentOnDropEditProviders.set(handle, provider);
|
||||
this._registrations.set(handle, combinedDisposable(
|
||||
this._languageFeaturesService.documentOnDropEditProvider.register(selector, provider),
|
||||
@@ -990,11 +990,18 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd
|
||||
|
||||
private readonly dataTransfers = new DataTransferCache();
|
||||
|
||||
readonly id?: string;
|
||||
readonly dropMimeTypes?: readonly string[];
|
||||
|
||||
constructor(
|
||||
private readonly handle: number,
|
||||
metadata: { id: string; dropMimeTypes?: readonly string[] } | undefined,
|
||||
private readonly _proxy: ExtHostLanguageFeaturesShape,
|
||||
@IUriIdentityService private readonly _uriIdentService: IUriIdentityService
|
||||
) { }
|
||||
) {
|
||||
this.id = metadata?.id;
|
||||
this.dropMimeTypes = metadata?.dropMimeTypes;
|
||||
}
|
||||
|
||||
async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<languages.DocumentOnDropEdit | null | undefined> {
|
||||
const request = this.dataTransfers.add(dataTransfer);
|
||||
@@ -1005,6 +1012,7 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
label: edit.label,
|
||||
insertText: edit.insertText,
|
||||
additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService),
|
||||
};
|
||||
|
||||
@@ -599,8 +599,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
createLanguageStatusItem(id: string, selector: vscode.DocumentSelector): vscode.LanguageStatusItem {
|
||||
return extHostLanguages.createLanguageStatusItem(extension, id, selector);
|
||||
},
|
||||
registerDocumentDropEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentDropEditProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerDocumentOnDropEditProvider(extension, selector, provider);
|
||||
registerDocumentDropEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentDropEditProvider, metadata?: vscode.DocumentDropEditProviderMetadata): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerDocumentOnDropEditProvider(extension, selector, provider, isProposedApiEnabled(extension, 'dropMetadata') ? metadata : undefined);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -407,7 +407,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
||||
$registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||
$registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], metadata?: { id: string; dropMimeTypes?: readonly string[] }): void;
|
||||
$resolvePasteFileData(handle: number, requestId: number, dataId: string): Promise<VSBuffer>;
|
||||
$resolveDocumentOnDropFileData(handle: number, requestId: number, dataId: string): Promise<VSBuffer>;
|
||||
$setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void;
|
||||
@@ -1817,6 +1817,7 @@ export interface IPasteEditDto {
|
||||
}
|
||||
|
||||
export interface IDocumentOnDropEditDto {
|
||||
label: string;
|
||||
insertText: string | { snippet: string };
|
||||
additionalEdit?: IWorkspaceEditDto;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import { isCancellationError, NotImplementedError } from 'vs/base/common/errors'
|
||||
import { raceCancellationError } from 'vs/base/common/async';
|
||||
import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
// --- adapter
|
||||
|
||||
@@ -1728,6 +1729,7 @@ class DocumentOnDropEditAdapter {
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.DocumentDropEditProvider,
|
||||
private readonly _handle: number,
|
||||
private readonly _extension: IExtensionDescription,
|
||||
) { }
|
||||
|
||||
async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IDocumentOnDropEditDto | undefined> {
|
||||
@@ -1742,6 +1744,7 @@ class DocumentOnDropEditAdapter {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
label: edit.label ?? localize('defaultDropLabel', "Drop using '{0}' extension", this._extension.displayName || this._extension.name),
|
||||
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
|
||||
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
|
||||
};
|
||||
@@ -2380,10 +2383,10 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
|
||||
// --- Document on drop
|
||||
|
||||
registerDocumentOnDropEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentDropEditProvider) {
|
||||
registerDocumentOnDropEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentDropEditProvider, metadata?: vscode.DocumentDropEditProviderMetadata) {
|
||||
const handle = this._nextHandle();
|
||||
this._adapter.set(handle, new AdapterData(new DocumentOnDropEditAdapter(this._proxy, this._documents, provider, handle), extension));
|
||||
this._proxy.$registerDocumentOnDropEditProvider(handle, this._transformDocumentSelector(selector));
|
||||
this._adapter.set(handle, new AdapterData(new DocumentOnDropEditAdapter(this._proxy, this._documents, provider, handle, extension), extension));
|
||||
this._proxy.$registerDocumentOnDropEditProvider(handle, this._transformDocumentSelector(selector), metadata);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ export const allApiProposals = Object.freeze({
|
||||
diffContentOptions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts',
|
||||
documentFiltersExclusive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts',
|
||||
documentPaste: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentPaste.d.ts',
|
||||
dropMetadata: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.dropMetadata.d.ts',
|
||||
editSessionIdentityProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts',
|
||||
editorInsets: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts',
|
||||
envShellEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.envShellEvent.d.ts',
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/179430
|
||||
|
||||
export interface DocumentDropEdit {
|
||||
/**
|
||||
* Human readable label that describes the edit.
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface DocumentDropEditProviderMetadata {
|
||||
/**
|
||||
* Unique identifier for the provider.
|
||||
*/
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* List of data transfer types that the provider supports.
|
||||
*/
|
||||
readonly dropMimeTypes?: readonly string[];
|
||||
}
|
||||
|
||||
export namespace languages {
|
||||
export function registerDocumentDropEditProvider(selector: DocumentSelector, provider: DocumentDropEditProvider, metadata?: DocumentDropEditProviderMetadata): Disposable;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user