Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add priority to paste / drop apis #182109

Merged
merged 3 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 56 additions & 45 deletions extensions/ipynb/src/notebookImagePaste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ function getImageMimeType(uri: vscode.Uri): string | undefined {
return imageExtToMime.get(extname(uri.fsPath).toLowerCase());
}

const id = 'insertAttachment';
class CopyPasteEditProvider implements vscode.DocumentPasteEditProvider {
class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider {

private readonly id = 'insertAttachment';

async provideDocumentPasteEdits(
document: vscode.TextDocument,
Expand All @@ -59,77 +60,86 @@ class CopyPasteEditProvider implements vscode.DocumentPasteEditProvider {
return;
}

const insert = await createInsertImageAttachmentEdit(document, dataTransfer, token);
const insert = await this.createInsertImageAttachmentEdit(document, dataTransfer, token);
if (!insert) {
return;
}

const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, id, vscode.l10n.t('Insert Image as Attachment'));
const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, this.id, vscode.l10n.t('Insert Image as Attachment'));
pasteEdit.priority = this.getPriority(dataTransfer);
pasteEdit.additionalEdit = insert.additionalEdit;
return pasteEdit;
}
}

class DropEditProvider implements vscode.DocumentDropEditProvider {

async provideDocumentDropEdits(
document: vscode.TextDocument,
_position: vscode.Position,
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken,
): Promise<vscode.DocumentDropEdit | undefined> {
const insert = await createInsertImageAttachmentEdit(document, dataTransfer, token);
const insert = await this.createInsertImageAttachmentEdit(document, dataTransfer, token);
if (!insert) {
return;
}

const dropEdit = new vscode.DocumentDropEdit(insert.insertText);
dropEdit.id = id;
dropEdit.id = this.id;
dropEdit.priority = this.getPriority(dataTransfer);
dropEdit.additionalEdit = insert.additionalEdit;
dropEdit.label = vscode.l10n.t('Insert Image as Attachment');
return dropEdit;
}
}

async function createInsertImageAttachmentEdit(
document: vscode.TextDocument,
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken,
): Promise<{ insertText: vscode.SnippetString; additionalEdit: vscode.WorkspaceEdit } | undefined> {
const imageData = await getDroppedImageData(dataTransfer, token);
if (!imageData.length || token.isCancellationRequested) {
return;
}
private getPriority(dataTransfer: vscode.DataTransfer): number {
if (dataTransfer.get('text/plain')) {
// Deprioritize in favor of normal text content
return -5;
}

const currentCell = getCellFromCellDocument(document);
if (!currentCell) {
return undefined;
// Otherwise boost priority so attachments are preferred
return 5;
}

// create updated metadata for cell (prep for WorkspaceEdit)
const newAttachment = buildAttachment(currentCell, imageData);
if (!newAttachment) {
return;
}
private async createInsertImageAttachmentEdit(
document: vscode.TextDocument,
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken,
): Promise<{ insertText: vscode.SnippetString; additionalEdit: vscode.WorkspaceEdit } | undefined> {
const imageData = await getDroppedImageData(dataTransfer, token);
if (!imageData.length || token.isCancellationRequested) {
return;
}

const currentCell = getCellFromCellDocument(document);
if (!currentCell) {
return undefined;
}

// build edits
const additionalEdit = new vscode.WorkspaceEdit();
const nbEdit = vscode.NotebookEdit.updateCellMetadata(currentCell.index, newAttachment.metadata);
const notebookUri = currentCell.notebook.uri;
additionalEdit.set(notebookUri, [nbEdit]);

// create a snippet for paste
const insertText = new vscode.SnippetString();
newAttachment.filenames.forEach((filename, i) => {
insertText.appendText('![');
insertText.appendPlaceholder(`${filename}`);
insertText.appendText(`](${/\s/.test(filename) ? `<attachment:${filename}>` : `attachment:${filename}`})`);
if (i !== newAttachment.filenames.length - 1) {
insertText.appendText(' ');
// create updated metadata for cell (prep for WorkspaceEdit)
const newAttachment = buildAttachment(currentCell, imageData);
if (!newAttachment) {
return;
}
});

return { insertText, additionalEdit };
// build edits
const additionalEdit = new vscode.WorkspaceEdit();
const nbEdit = vscode.NotebookEdit.updateCellMetadata(currentCell.index, newAttachment.metadata);
const notebookUri = currentCell.notebook.uri;
additionalEdit.set(notebookUri, [nbEdit]);

// create a snippet for paste
const insertText = new vscode.SnippetString();
newAttachment.filenames.forEach((filename, i) => {
insertText.appendText('![');
insertText.appendPlaceholder(`${filename}`);
insertText.appendText(`](${/\s/.test(filename) ? `<attachment:${filename}>` : `attachment:${filename}`})`);
if (i !== newAttachment.filenames.length - 1) {
insertText.appendText(' ');
}
});

return { insertText, additionalEdit };
}
}

async function getDroppedImageData(
Expand Down Expand Up @@ -296,14 +306,15 @@ function buildAttachment(
}

export function notebookImagePasteSetup(): vscode.Disposable {
const provider = new DropOrPasteEditProvider();
return vscode.Disposable.from(
vscode.languages.registerDocumentPasteEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, new CopyPasteEditProvider(), {
vscode.languages.registerDocumentPasteEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, {
pasteMimeTypes: [
MimeType.png,
MimeType.uriList,
],
}),
vscode.languages.registerDocumentDropEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, new DropEditProvider(), {
vscode.languages.registerDocumentDropEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, {
dropMimeTypes: [
...Object.values(imageExtToMime),
MimeType.uriList,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
return;
}

const edit = await this._makeCreateImagePasteEdit(document, dataTransfer, token);
if (edit) {
return edit;
const createEdit = await this._makeCreateImagePasteEdit(document, dataTransfer, token);
if (createEdit) {
return createEdit;
}

const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
return snippet ? new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label) : undefined;
if (!snippet) {
return;
}

const uriEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
uriEdit.priority = this._getPriority(dataTransfer);
return uriEdit;
}

private async _makeCreateImagePasteEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentPasteEdit | undefined> {
Expand Down Expand Up @@ -89,10 +95,19 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
return;
}

const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet, '', snippet.label);
const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
pasteEdit.additionalEdit = workspaceEdit;
pasteEdit.priority = this._getPriority(dataTransfer);
return pasteEdit;
}

private _getPriority(dataTransfer: vscode.DataTransfer): number {
if (dataTransfer.get('text/plain')) {
// Deprioritize in favor of normal text content
return -10;
}
return 0;
}
}

export function registerPasteSupport(selector: vscode.DocumentSelector,) {
Expand Down
2 changes: 2 additions & 0 deletions src/vs/editor/common/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@ export interface DocumentPasteEdit {
readonly id: string;
readonly label: string;
readonly detail: string;
readonly priority: number;
insertText: string | { readonly snippet: string };
additionalEdit?: WorkspaceEdit;
}
Expand Down Expand Up @@ -1948,6 +1949,7 @@ export enum ExternalUriOpenerPriority {
export interface DocumentOnDropEdit {
readonly id: string;
readonly label: string;
readonly priority: number;
insertText: string | { readonly snippet: string };
additionalEdit?: WorkspaceEdit;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
providers.map(provider => provider.provideDocumentPasteEdits(model, selections, dataTransfer, token))
).then(coalesce),
token);
result?.sort((a, b) => b.priority - a.priority);
return result ?? [];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ abstract class SimplePasteAndDropProvider implements DocumentOnDropEditProvider,

async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
const edit = await this.getEdit(dataTransfer, token);
return edit ? { id: this.id, insertText: edit.insertText, label: edit.label, detail: edit.detail } : undefined;
return edit ? { id: this.id, insertText: edit.insertText, label: edit.label, detail: edit.detail, priority: edit.priority } : undefined;
}

async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<DocumentOnDropEdit | undefined> {
const edit = await this.getEdit(dataTransfer, token);
return edit ? { id: this.id, insertText: edit.insertText, label: edit.label } : undefined;
return edit ? { id: this.id, insertText: edit.insertText, label: edit.label, priority: edit.priority } : undefined;
}

protected abstract getEdit(dataTransfer: VSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined>;
Expand All @@ -61,6 +61,7 @@ class DefaultTextProvider extends SimplePasteAndDropProvider {
const insertText = await textEntry.asString();
return {
id: this.id,
priority: 0,
label: localize('text.label', "Insert Plain Text"),
detail: builtInLabel,
insertText
Expand Down Expand Up @@ -107,6 +108,7 @@ class PathProvider extends SimplePasteAndDropProvider {

return {
id: this.id,
priority: 0,
insertText,
label,
detail: builtInLabel,
Expand Down Expand Up @@ -143,6 +145,7 @@ class RelativePathProvider extends SimplePasteAndDropProvider {

return {
id: this.id,
priority: 0,
insertText: relativeUris.join(' '),
label: entries.length > 1
? localize('defaultDropProvider.uriList.relativePaths', "Insert Relative Paths")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ 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 { IEditorContribution } from 'vs/editor/common/editorCommon';
import { DocumentOnDropEditProvider } from 'vs/editor/common/languages';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd';
import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService';
Expand Down Expand Up @@ -99,19 +101,15 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
return provider.dropMimeTypes.some(mime => ourDataTransfer.matches(mime));
});

const possibleDropEdits = await raceCancellation(Promise.all(providers.map(provider => {
return provider.provideDocumentOnDropEdits(model, position, ourDataTransfer, tokenSource.token);
})), tokenSource.token);
const edits = await this.getDropEdits(providers, model, position, ourDataTransfer, tokenSource);
if (tokenSource.token.isCancellationRequested) {
return;
}

if (possibleDropEdits) {
const allEdits = coalesce(possibleDropEdits);
// Pass in the parent token here as it tracks cancelling the entire drop operation.

if (edits.length) {
const canShowWidget = editor.getOption(EditorOption.dropIntoEditor).showDropSelector === 'afterDrop';
await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex: 0, allEdits }, canShowWidget, token);
// Pass in the parent token here as it tracks cancelling the entire drop operation
await this._postDropWidgetManager.applyEditAndShowIfNeeded([Range.fromPositions(position)], { activeEditIndex: 0, allEdits: edits }, canShowWidget, token);
}
} finally {
tokenSource.dispose();
Expand All @@ -125,6 +123,15 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
this._currentOperation = p;
}

private async getDropEdits(providers: DocumentOnDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) {
const results = await raceCancellation(Promise.all(providers.map(provider => {
return provider.provideDocumentOnDropEdits(model, position, dataTransfer, tokenSource.token);
})), tokenSource.token);
const edits = coalesce(results ?? []);
edits.sort((a, b) => b.priority - a.priority);
return edits;
}

private async extractDataTransferData(dragEvent: DragEvent): Promise<VSDataTransfer> {
if (!dragEvent.dataTransfer) {
return new VSDataTransfer();
Expand Down
9 changes: 2 additions & 7 deletions src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -974,10 +974,7 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider
}

return {
id: result.id,
label: result.label,
detail: result.detail,
insertText: result.insertText,
...result,
additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit, this._uriIdentService, dataId => this.resolveFileData(request.id, dataId)) : undefined,
};
} finally {
Expand Down Expand Up @@ -1014,9 +1011,7 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd
return undefined;
}
return {
id: edit.id,
label: edit.label,
insertText: edit.insertText,
...edit,
additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveDocumentOnDropFileData(request.id, dataId)),
};
} finally {
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1838,6 +1838,7 @@ export interface IPasteEditDto {
id: string;
label: string;
detail: string;
priority: number;
insertText: string | { snippet: string };
additionalEdit?: IWorkspaceEditDto;
}
Expand All @@ -1849,6 +1850,7 @@ export interface IDocumentDropEditProviderMetadata {
export interface IDocumentOnDropEditDto {
id: string;
label: string;
priority: number;
insertText: string | { snippet: string };
additionalEdit?: IWorkspaceEditDto;
}
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/api/common/extHostLanguageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ class DocumentPasteEditProvider {
id: edit.id ? this._extension.identifier.value + '.' + edit.id : this._extension.identifier.value,
label: edit.label ?? localize('defaultPasteLabel', "Paste using '{0}' extension", this._extension.displayName || this._extension.name),
detail: this._extension.displayName || this._extension.name,
priority: edit.priority ?? 0,
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
};
Expand Down Expand Up @@ -1754,6 +1755,7 @@ class DocumentOnDropEditAdapter {
return {
id: edit.id ? this._extension.identifier.value + '.' + edit.id : this._extension.identifier.value,
label: edit.label ?? localize('defaultDropLabel', "Drop using '{0}' extension", this._extension.displayName || this._extension.name),
priority: edit.priority ?? 0,
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
};
Expand Down
7 changes: 7 additions & 0 deletions src/vscode-dts/vscode.proposed.documentPaste.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ declare module 'vscode' {
*/
label: string;

/**
* The relative priority of this edit. Higher priority items are shown first in the UI.
*
* Defaults to `0`.
*/
priority?: number;

/**
* The text or snippet to insert at the pasted locations.
*/
Expand Down
9 changes: 8 additions & 1 deletion src/vscode-dts/vscode.proposed.dropMetadata.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ declare module 'vscode' {
*
* This id should be unique within the extension but does not need to be unique across extensions.
*/
id: string;
id?: string;

/**
* The relative priority of this edit. Higher priority items are shown first in the UI.
*
* Defaults to `0`.
*/
priority?: number;

/**
* Human readable label that describes the edit.
Expand Down