Skip to content

Commit 4d38422

Browse files
authored
Add priority to paste / drop apis (microsoft#182109)
* Add priority to paste / drop apis Fixes microsoft#181886 Replacement for microsoft#181453 * Make notebooks prefer text over creating attachments
1 parent 46b7e7b commit 4d38422

File tree

11 files changed

+120
-68
lines changed

11 files changed

+120
-68
lines changed

extensions/ipynb/src/notebookImagePaste.ts

Lines changed: 56 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ function getImageMimeType(uri: vscode.Uri): string | undefined {
4545
return imageExtToMime.get(extname(uri.fsPath).toLowerCase());
4646
}
4747

48-
const id = 'insertAttachment';
49-
class CopyPasteEditProvider implements vscode.DocumentPasteEditProvider {
48+
class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider {
49+
50+
private readonly id = 'insertAttachment';
5051

5152
async provideDocumentPasteEdits(
5253
document: vscode.TextDocument,
@@ -59,77 +60,86 @@ class CopyPasteEditProvider implements vscode.DocumentPasteEditProvider {
5960
return;
6061
}
6162

62-
const insert = await createInsertImageAttachmentEdit(document, dataTransfer, token);
63+
const insert = await this.createInsertImageAttachmentEdit(document, dataTransfer, token);
6364
if (!insert) {
6465
return;
6566
}
6667

67-
const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, id, vscode.l10n.t('Insert Image as Attachment'));
68+
const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, this.id, vscode.l10n.t('Insert Image as Attachment'));
69+
pasteEdit.priority = this.getPriority(dataTransfer);
6870
pasteEdit.additionalEdit = insert.additionalEdit;
6971
return pasteEdit;
7072
}
71-
}
72-
73-
class DropEditProvider implements vscode.DocumentDropEditProvider {
7473

7574
async provideDocumentDropEdits(
7675
document: vscode.TextDocument,
7776
_position: vscode.Position,
7877
dataTransfer: vscode.DataTransfer,
7978
token: vscode.CancellationToken,
8079
): Promise<vscode.DocumentDropEdit | undefined> {
81-
const insert = await createInsertImageAttachmentEdit(document, dataTransfer, token);
80+
const insert = await this.createInsertImageAttachmentEdit(document, dataTransfer, token);
8281
if (!insert) {
8382
return;
8483
}
8584

8685
const dropEdit = new vscode.DocumentDropEdit(insert.insertText);
87-
dropEdit.id = id;
86+
dropEdit.id = this.id;
87+
dropEdit.priority = this.getPriority(dataTransfer);
8888
dropEdit.additionalEdit = insert.additionalEdit;
8989
dropEdit.label = vscode.l10n.t('Insert Image as Attachment');
9090
return dropEdit;
9191
}
92-
}
9392

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

104-
const currentCell = getCellFromCellDocument(document);
105-
if (!currentCell) {
106-
return undefined;
99+
// Otherwise boost priority so attachments are preferred
100+
return 5;
107101
}
108102

109-
// create updated metadata for cell (prep for WorkspaceEdit)
110-
const newAttachment = buildAttachment(currentCell, imageData);
111-
if (!newAttachment) {
112-
return;
113-
}
103+
private async createInsertImageAttachmentEdit(
104+
document: vscode.TextDocument,
105+
dataTransfer: vscode.DataTransfer,
106+
token: vscode.CancellationToken,
107+
): Promise<{ insertText: vscode.SnippetString; additionalEdit: vscode.WorkspaceEdit } | undefined> {
108+
const imageData = await getDroppedImageData(dataTransfer, token);
109+
if (!imageData.length || token.isCancellationRequested) {
110+
return;
111+
}
112+
113+
const currentCell = getCellFromCellDocument(document);
114+
if (!currentCell) {
115+
return undefined;
116+
}
114117

115-
// build edits
116-
const additionalEdit = new vscode.WorkspaceEdit();
117-
const nbEdit = vscode.NotebookEdit.updateCellMetadata(currentCell.index, newAttachment.metadata);
118-
const notebookUri = currentCell.notebook.uri;
119-
additionalEdit.set(notebookUri, [nbEdit]);
120-
121-
// create a snippet for paste
122-
const insertText = new vscode.SnippetString();
123-
newAttachment.filenames.forEach((filename, i) => {
124-
insertText.appendText('![');
125-
insertText.appendPlaceholder(`${filename}`);
126-
insertText.appendText(`](${/\s/.test(filename) ? `<attachment:${filename}>` : `attachment:${filename}`})`);
127-
if (i !== newAttachment.filenames.length - 1) {
128-
insertText.appendText(' ');
118+
// create updated metadata for cell (prep for WorkspaceEdit)
119+
const newAttachment = buildAttachment(currentCell, imageData);
120+
if (!newAttachment) {
121+
return;
129122
}
130-
});
131123

132-
return { insertText, additionalEdit };
124+
// build edits
125+
const additionalEdit = new vscode.WorkspaceEdit();
126+
const nbEdit = vscode.NotebookEdit.updateCellMetadata(currentCell.index, newAttachment.metadata);
127+
const notebookUri = currentCell.notebook.uri;
128+
additionalEdit.set(notebookUri, [nbEdit]);
129+
130+
// create a snippet for paste
131+
const insertText = new vscode.SnippetString();
132+
newAttachment.filenames.forEach((filename, i) => {
133+
insertText.appendText('![');
134+
insertText.appendPlaceholder(`${filename}`);
135+
insertText.appendText(`](${/\s/.test(filename) ? `<attachment:${filename}>` : `attachment:${filename}`})`);
136+
if (i !== newAttachment.filenames.length - 1) {
137+
insertText.appendText(' ');
138+
}
139+
});
140+
141+
return { insertText, additionalEdit };
142+
}
133143
}
134144

135145
async function getDroppedImageData(
@@ -296,14 +306,15 @@ function buildAttachment(
296306
}
297307

298308
export function notebookImagePasteSetup(): vscode.Disposable {
309+
const provider = new DropOrPasteEditProvider();
299310
return vscode.Disposable.from(
300-
vscode.languages.registerDocumentPasteEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, new CopyPasteEditProvider(), {
311+
vscode.languages.registerDocumentPasteEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, {
301312
pasteMimeTypes: [
302313
MimeType.png,
303314
MimeType.uriList,
304315
],
305316
}),
306-
vscode.languages.registerDocumentDropEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, new DropEditProvider(), {
317+
vscode.languages.registerDocumentDropEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, {
307318
dropMimeTypes: [
308319
...Object.values(imageExtToMime),
309320
MimeType.uriList,

extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,19 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
3232
return;
3333
}
3434

35-
const edit = await this._makeCreateImagePasteEdit(document, dataTransfer, token);
36-
if (edit) {
37-
return edit;
35+
const createEdit = await this._makeCreateImagePasteEdit(document, dataTransfer, token);
36+
if (createEdit) {
37+
return createEdit;
3838
}
3939

4040
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
41-
return snippet ? new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label) : undefined;
41+
if (!snippet) {
42+
return;
43+
}
44+
45+
const uriEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
46+
uriEdit.priority = this._getPriority(dataTransfer);
47+
return uriEdit;
4248
}
4349

4450
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 {
8995
return;
9096
}
9197

92-
const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet, '', snippet.label);
98+
const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
9399
pasteEdit.additionalEdit = workspaceEdit;
100+
pasteEdit.priority = this._getPriority(dataTransfer);
94101
return pasteEdit;
95102
}
103+
104+
private _getPriority(dataTransfer: vscode.DataTransfer): number {
105+
if (dataTransfer.get('text/plain')) {
106+
// Deprioritize in favor of normal text content
107+
return -10;
108+
}
109+
return 0;
110+
}
96111
}
97112

98113
export function registerPasteSupport(selector: vscode.DocumentSelector,) {

src/vs/editor/common/languages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,7 @@ export interface DocumentPasteEdit {
786786
readonly id: string;
787787
readonly label: string;
788788
readonly detail: string;
789+
readonly priority: number;
789790
insertText: string | { readonly snippet: string };
790791
additionalEdit?: WorkspaceEdit;
791792
}
@@ -1948,6 +1949,7 @@ export enum ExternalUriOpenerPriority {
19481949
export interface DocumentOnDropEdit {
19491950
readonly id: string;
19501951
readonly label: string;
1952+
readonly priority: number;
19511953
insertText: string | { readonly snippet: string };
19521954
additionalEdit?: WorkspaceEdit;
19531955
}

src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
391391
providers.map(provider => provider.provideDocumentPasteEdits(model, selections, dataTransfer, token))
392392
).then(coalesce),
393393
token);
394+
result?.sort((a, b) => b.priority - a.priority);
394395
return result ?? [];
395396
}
396397

src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ abstract class SimplePasteAndDropProvider implements DocumentOnDropEditProvider,
2929

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

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

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

108109
return {
109110
id: this.id,
111+
priority: 0,
110112
insertText,
111113
label,
112114
detail: builtInLabel,
@@ -143,6 +145,7 @@ class RelativePathProvider extends SimplePasteAndDropProvider {
143145

144146
return {
145147
id: this.id,
148+
priority: 0,
146149
insertText: relativeUris.join(' '),
147150
label: entries.length > 1
148151
? localize('defaultDropProvider.uriList.relativePaths', "Insert Relative Paths")

src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
1313
import { IPosition } from 'vs/editor/common/core/position';
1414
import { Range } from 'vs/editor/common/core/range';
1515
import { IEditorContribution } from 'vs/editor/common/editorCommon';
16+
import { DocumentOnDropEditProvider } from 'vs/editor/common/languages';
17+
import { ITextModel } from 'vs/editor/common/model';
1618
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
1719
import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd';
1820
import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService';
@@ -99,19 +101,15 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
99101
return provider.dropMimeTypes.some(mime => ourDataTransfer.matches(mime));
100102
});
101103

102-
const possibleDropEdits = await raceCancellation(Promise.all(providers.map(provider => {
103-
return provider.provideDocumentOnDropEdits(model, position, ourDataTransfer, tokenSource.token);
104-
})), tokenSource.token);
104+
const edits = await this.getDropEdits(providers, model, position, ourDataTransfer, tokenSource);
105105
if (tokenSource.token.isCancellationRequested) {
106106
return;
107107
}
108108

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

126+
private async getDropEdits(providers: DocumentOnDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) {
127+
const results = await raceCancellation(Promise.all(providers.map(provider => {
128+
return provider.provideDocumentOnDropEdits(model, position, dataTransfer, tokenSource.token);
129+
})), tokenSource.token);
130+
const edits = coalesce(results ?? []);
131+
edits.sort((a, b) => b.priority - a.priority);
132+
return edits;
133+
}
134+
128135
private async extractDataTransferData(dragEvent: DragEvent): Promise<VSDataTransfer> {
129136
if (!dragEvent.dataTransfer) {
130137
return new VSDataTransfer();

src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -974,10 +974,7 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider
974974
}
975975

976976
return {
977-
id: result.id,
978-
label: result.label,
979-
detail: result.detail,
980-
insertText: result.insertText,
977+
...result,
981978
additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit, this._uriIdentService, dataId => this.resolveFileData(request.id, dataId)) : undefined,
982979
};
983980
} finally {
@@ -1014,9 +1011,7 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd
10141011
return undefined;
10151012
}
10161013
return {
1017-
id: edit.id,
1018-
label: edit.label,
1019-
insertText: edit.insertText,
1014+
...edit,
10201015
additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveDocumentOnDropFileData(request.id, dataId)),
10211016
};
10221017
} finally {

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,6 +1838,7 @@ export interface IPasteEditDto {
18381838
id: string;
18391839
label: string;
18401840
detail: string;
1841+
priority: number;
18411842
insertText: string | { snippet: string };
18421843
additionalEdit?: IWorkspaceEditDto;
18431844
}
@@ -1849,6 +1850,7 @@ export interface IDocumentDropEditProviderMetadata {
18491850
export interface IDocumentOnDropEditDto {
18501851
id: string;
18511852
label: string;
1853+
priority: number;
18521854
insertText: string | { snippet: string };
18531855
additionalEdit?: IWorkspaceEditDto;
18541856
}

src/vs/workbench/api/common/extHostLanguageFeatures.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ class DocumentPasteEditProvider {
541541
id: edit.id ? this._extension.identifier.value + '.' + edit.id : this._extension.identifier.value,
542542
label: edit.label ?? localize('defaultPasteLabel', "Paste using '{0}' extension", this._extension.displayName || this._extension.name),
543543
detail: this._extension.displayName || this._extension.name,
544+
priority: edit.priority ?? 0,
544545
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
545546
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
546547
};
@@ -1754,6 +1755,7 @@ class DocumentOnDropEditAdapter {
17541755
return {
17551756
id: edit.id ? this._extension.identifier.value + '.' + edit.id : this._extension.identifier.value,
17561757
label: edit.label ?? localize('defaultDropLabel', "Drop using '{0}' extension", this._extension.displayName || this._extension.name),
1758+
priority: edit.priority ?? 0,
17571759
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
17581760
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
17591761
};

src/vscode-dts/vscode.proposed.documentPaste.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ declare module 'vscode' {
5656
*/
5757
label: string;
5858

59+
/**
60+
* The relative priority of this edit. Higher priority items are shown first in the UI.
61+
*
62+
* Defaults to `0`.
63+
*/
64+
priority?: number;
65+
5966
/**
6067
* The text or snippet to insert at the pasted locations.
6168
*/

0 commit comments

Comments
 (0)