mirror of
https://github.com/Microsoft/vscode
synced 2024-10-02 17:32:41 +00:00
Add priority to paste / drop apis (#182109)
* Add priority to paste / drop apis Fixes #181886 Replacement for #181453 * Make notebooks prefer text over creating attachments
This commit is contained in:
parent
46b7e7b02c
commit
4d38422afe
|
@ -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,
|
||||
|
@ -59,18 +60,16 @@ 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,
|
||||
|
@ -78,58 +77,69 @@ class DropEditProvider implements vscode.DocumentDropEditProvider {
|
|||
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;
|
||||
}
|
||||
|
||||
const currentCell = getCellFromCellDocument(document);
|
||||
if (!currentCell) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// create updated metadata for cell (prep for WorkspaceEdit)
|
||||
const newAttachment = buildAttachment(currentCell, imageData);
|
||||
if (!newAttachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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(' ');
|
||||
private getPriority(dataTransfer: vscode.DataTransfer): number {
|
||||
if (dataTransfer.get('text/plain')) {
|
||||
// Deprioritize in favor of normal text content
|
||||
return -5;
|
||||
}
|
||||
});
|
||||
|
||||
return { insertText, additionalEdit };
|
||||
// Otherwise boost priority so attachments are preferred
|
||||
return 5;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// create updated metadata for cell (prep for WorkspaceEdit)
|
||||
const newAttachment = buildAttachment(currentCell, imageData);
|
||||
if (!newAttachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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(
|
||||
|
@ -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,
|
||||
|
|
|
@ -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> {
|
||||
|
@ -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,) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 ?? [];
|
||||
}
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
@ -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
|
||||
|
@ -107,6 +108,7 @@ class PathProvider extends SimplePasteAndDropProvider {
|
|||
|
||||
return {
|
||||
id: this.id,
|
||||
priority: 0,
|
||||
insertText,
|
||||
label,
|
||||
detail: builtInLabel,
|
||||
|
@ -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")
|
||||
|
|
|
@ -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';
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
|
|
@ -1838,6 +1838,7 @@ export interface IPasteEditDto {
|
|||
id: string;
|
||||
label: string;
|
||||
detail: string;
|
||||
priority: number;
|
||||
insertText: string | { snippet: string };
|
||||
additionalEdit?: IWorkspaceEditDto;
|
||||
}
|
||||
|
@ -1849,6 +1850,7 @@ export interface IDocumentDropEditProviderMetadata {
|
|||
export interface IDocumentOnDropEditDto {
|
||||
id: string;
|
||||
label: string;
|
||||
priority: number;
|
||||
insertText: string | { snippet: string };
|
||||
additionalEdit?: IWorkspaceEditDto;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue