Clean up support for paste edits (#234240)
- Allow setting an array of preferences for paste as keybindings - Clarifies kinds used for core and extensions - Exports text kind as APIpull/211550/head^2
parent
8a4b2bb49b
commit
c83b443da0
|
@ -9,7 +9,8 @@ import { getDocumentDir, Mimes, Schemes } from './shared';
|
|||
import { UriList } from './uriList';
|
||||
|
||||
class DropOrPasteResourceProvider implements vscode.DocumentDropEditProvider, vscode.DocumentPasteEditProvider {
|
||||
readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('css', 'url');
|
||||
|
||||
readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('css', 'link', 'url');
|
||||
|
||||
async provideDocumentDropEdits(
|
||||
document: vscode.TextDocument,
|
||||
|
|
|
@ -48,7 +48,7 @@ function getImageMimeType(uri: vscode.Uri): string | undefined {
|
|||
|
||||
class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider {
|
||||
|
||||
public static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'image', 'attachment');
|
||||
public static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'link', 'image', 'attachment');
|
||||
|
||||
async provideDocumentPasteEdits(
|
||||
document: vscode.TextDocument,
|
||||
|
@ -68,7 +68,7 @@ class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscod
|
|||
}
|
||||
|
||||
const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, vscode.l10n.t('Insert Image as Attachment'), DropOrPasteEditProvider.kind);
|
||||
pasteEdit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Empty.append('text')];
|
||||
pasteEdit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Text];
|
||||
pasteEdit.additionalEdit = insert.additionalEdit;
|
||||
return [pasteEdit];
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscod
|
|||
}
|
||||
|
||||
const dropEdit = new vscode.DocumentDropEdit(insert.insertText);
|
||||
dropEdit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Empty.append('text')];
|
||||
dropEdit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Text];
|
||||
dropEdit.additionalEdit = insert.additionalEdit;
|
||||
dropEdit.title = vscode.l10n.t('Insert Image as Attachment');
|
||||
return dropEdit;
|
||||
|
|
|
@ -10,7 +10,7 @@ import { getParentDocumentUri } from '../../util/document';
|
|||
import { Mime, mediaMimes } from '../../util/mimes';
|
||||
import { Schemes } from '../../util/schemes';
|
||||
import { NewFilePathGenerator } from './newFilePathGenerator';
|
||||
import { DropOrPasteEdit, createInsertUriListEdit, createUriListSnippet, getSnippetLabel } from './shared';
|
||||
import { DropOrPasteEdit, createInsertUriListEdit, createUriListSnippet, getSnippetLabelAndKind, baseLinkEditKind, linkEditKind, audioEditKind, videoEditKind, imageEditKind } from './shared';
|
||||
import { InsertMarkdownLink, shouldInsertMarkdownLinkByDefault } from './smartDropOrPaste';
|
||||
import { UriList } from '../../util/uriList';
|
||||
|
||||
|
@ -30,8 +30,6 @@ enum CopyFilesSettings {
|
|||
*/
|
||||
class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider {
|
||||
|
||||
public static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'link');
|
||||
|
||||
public static readonly mimeTypes = [
|
||||
Mime.textUriList,
|
||||
'files',
|
||||
|
@ -39,8 +37,8 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
|||
];
|
||||
|
||||
private readonly _yieldTo = [
|
||||
vscode.DocumentDropOrPasteEditKind.Empty.append('text'),
|
||||
vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'image', 'attachment'),
|
||||
vscode.DocumentDropOrPasteEditKind.Text,
|
||||
vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'link', 'image', 'attachment'), // Prefer notebook attachments
|
||||
];
|
||||
|
||||
constructor(
|
||||
|
@ -64,7 +62,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
|||
|
||||
const dropEdit = new vscode.DocumentDropEdit(edit.snippet);
|
||||
dropEdit.title = edit.label;
|
||||
dropEdit.kind = ResourcePasteOrDropProvider.kind;
|
||||
dropEdit.kind = edit.kind;
|
||||
dropEdit.additionalEdit = edit.additionalEdits;
|
||||
dropEdit.yieldTo = [...this._yieldTo, ...edit.yieldTo];
|
||||
return dropEdit;
|
||||
|
@ -86,7 +84,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
|||
return;
|
||||
}
|
||||
|
||||
const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, edit.label, ResourcePasteOrDropProvider.kind);
|
||||
const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, edit.label, edit.kind);
|
||||
pasteEdit.additionalEdit = edit.additionalEdits;
|
||||
pasteEdit.yieldTo = [...this._yieldTo, ...edit.yieldTo];
|
||||
return [pasteEdit];
|
||||
|
@ -162,7 +160,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
|||
if (
|
||||
uriList.entries.length === 1
|
||||
&& (uriList.entries[0].uri.scheme === Schemes.http || uriList.entries[0].uri.scheme === Schemes.https)
|
||||
&& !context?.only?.contains(ResourcePasteOrDropProvider.kind)
|
||||
&& !context?.only?.contains(baseLinkEditKind)
|
||||
) {
|
||||
const text = await dataTransfer.get(Mime.textPlain)?.asString();
|
||||
if (token.isCancellationRequested) {
|
||||
|
@ -184,6 +182,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
|||
|
||||
return {
|
||||
label: edit.label,
|
||||
kind: edit.kind,
|
||||
snippet: new vscode.SnippetString(''),
|
||||
additionalEdits,
|
||||
yieldTo: []
|
||||
|
@ -254,9 +253,11 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
|||
}
|
||||
}
|
||||
|
||||
const { label, kind } = getSnippetLabelAndKind(snippet);
|
||||
return {
|
||||
snippet: snippet.snippet,
|
||||
label: getSnippetLabel(snippet),
|
||||
label,
|
||||
kind,
|
||||
additionalEdits,
|
||||
yieldTo: [],
|
||||
};
|
||||
|
@ -277,13 +278,21 @@ function textMatchesUriList(text: string, uriList: UriList): boolean {
|
|||
}
|
||||
|
||||
export function registerResourceDropOrPasteSupport(selector: vscode.DocumentSelector, parser: IMdParser): vscode.Disposable {
|
||||
const providedEditKinds = [
|
||||
baseLinkEditKind,
|
||||
linkEditKind,
|
||||
imageEditKind,
|
||||
audioEditKind,
|
||||
videoEditKind,
|
||||
];
|
||||
|
||||
return vscode.Disposable.from(
|
||||
vscode.languages.registerDocumentPasteEditProvider(selector, new ResourcePasteOrDropProvider(parser), {
|
||||
providedPasteEditKinds: [ResourcePasteOrDropProvider.kind],
|
||||
providedPasteEditKinds: providedEditKinds,
|
||||
pasteMimeTypes: ResourcePasteOrDropProvider.mimeTypes,
|
||||
}),
|
||||
vscode.languages.registerDocumentDropEditProvider(selector, new ResourcePasteOrDropProvider(parser), {
|
||||
providedDropEditKinds: [ResourcePasteOrDropProvider.kind],
|
||||
providedDropEditKinds: providedEditKinds,
|
||||
dropMimeTypes: ResourcePasteOrDropProvider.mimeTypes,
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { IMdParser } from '../../markdownEngine';
|
||||
import { Mime } from '../../util/mimes';
|
||||
import { createInsertUriListEdit } from './shared';
|
||||
import { InsertMarkdownLink, findValidUriInText, shouldInsertMarkdownLinkByDefault } from './smartDropOrPaste';
|
||||
import { UriList } from '../../util/uriList';
|
||||
import { createInsertUriListEdit, linkEditKind } from './shared';
|
||||
import { InsertMarkdownLink, findValidUriInText, shouldInsertMarkdownLinkByDefault } from './smartDropOrPaste';
|
||||
|
||||
/**
|
||||
* Adds support for pasting text uris to create markdown links.
|
||||
|
@ -17,7 +17,7 @@ import { UriList } from '../../util/uriList';
|
|||
*/
|
||||
class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
public static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'link');
|
||||
public static readonly kind = linkEditKind;
|
||||
|
||||
public static readonly pasteMimeTypes = [Mime.textPlain];
|
||||
|
||||
|
@ -61,7 +61,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
|
|||
|
||||
if (!(await shouldInsertMarkdownLinkByDefault(this._parser, document, pasteUrlSetting, ranges, token))) {
|
||||
pasteEdit.yieldTo = [
|
||||
vscode.DocumentDropOrPasteEditKind.Empty.append('text'),
|
||||
vscode.DocumentDropOrPasteEditKind.Text,
|
||||
vscode.DocumentDropOrPasteEditKind.Empty.append('uri')
|
||||
];
|
||||
}
|
||||
|
|
|
@ -12,6 +12,16 @@ import { Schemes } from '../../util/schemes';
|
|||
import { UriList } from '../../util/uriList';
|
||||
import { resolveSnippet } from './snippets';
|
||||
|
||||
/** Base kind for any sort of markdown link, including both path and media links */
|
||||
export const baseLinkEditKind = vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'link');
|
||||
|
||||
/** Kind for normal markdown links, i.e. `[text](path/to/file.md)` */
|
||||
export const linkEditKind = baseLinkEditKind.append('uri');
|
||||
|
||||
export const imageEditKind = baseLinkEditKind.append('image');
|
||||
export const audioEditKind = baseLinkEditKind.append('audio');
|
||||
export const videoEditKind = baseLinkEditKind.append('video');
|
||||
|
||||
enum MediaKind {
|
||||
Image,
|
||||
Video,
|
||||
|
@ -45,23 +55,71 @@ export const mediaFileExtensions = new Map<string, MediaKind>([
|
|||
['wav', MediaKind.Audio],
|
||||
]);
|
||||
|
||||
export function getSnippetLabel(counter: { insertedAudioVideoCount: number; insertedImageCount: number; insertedLinkCount: number }) {
|
||||
if (counter.insertedAudioVideoCount > 0) {
|
||||
export function getSnippetLabelAndKind(counter: { readonly insertedAudioCount: number; readonly insertedVideoCount: number; readonly insertedImageCount: number; readonly insertedLinkCount: number }): {
|
||||
label: string;
|
||||
kind: vscode.DocumentDropOrPasteEditKind;
|
||||
} {
|
||||
if (counter.insertedVideoCount > 0 || counter.insertedAudioCount > 0) {
|
||||
// Any media plus links
|
||||
if (counter.insertedLinkCount > 0) {
|
||||
return vscode.l10n.t('Insert Markdown Media and Links');
|
||||
} else {
|
||||
return vscode.l10n.t('Insert Markdown Media');
|
||||
return {
|
||||
label: vscode.l10n.t('Insert Markdown Media and Links'),
|
||||
kind: baseLinkEditKind,
|
||||
};
|
||||
}
|
||||
} else if (counter.insertedImageCount > 0 && counter.insertedLinkCount > 0) {
|
||||
return vscode.l10n.t('Insert Markdown Images and Links');
|
||||
|
||||
// Any media plus images
|
||||
if (counter.insertedImageCount > 0) {
|
||||
return {
|
||||
label: vscode.l10n.t('Insert Markdown Media and Images'),
|
||||
kind: baseLinkEditKind,
|
||||
};
|
||||
}
|
||||
|
||||
// Audio only
|
||||
if (counter.insertedAudioCount > 0 && !counter.insertedVideoCount) {
|
||||
return {
|
||||
label: vscode.l10n.t('Insert Markdown Audio'),
|
||||
kind: audioEditKind,
|
||||
};
|
||||
}
|
||||
|
||||
// Video only
|
||||
if (counter.insertedVideoCount > 0 && !counter.insertedAudioCount) {
|
||||
return {
|
||||
label: vscode.l10n.t('Insert Markdown Video'),
|
||||
kind: videoEditKind,
|
||||
};
|
||||
}
|
||||
|
||||
// Mix of audio and video
|
||||
return {
|
||||
label: vscode.l10n.t('Insert Markdown Media'),
|
||||
kind: baseLinkEditKind,
|
||||
};
|
||||
} else if (counter.insertedImageCount > 0) {
|
||||
return counter.insertedImageCount > 1
|
||||
? vscode.l10n.t('Insert Markdown Images')
|
||||
: vscode.l10n.t('Insert Markdown Image');
|
||||
// Mix of images and links
|
||||
if (counter.insertedLinkCount > 0) {
|
||||
return {
|
||||
label: vscode.l10n.t('Insert Markdown Images and Links'),
|
||||
kind: baseLinkEditKind,
|
||||
};
|
||||
}
|
||||
|
||||
// Just images
|
||||
return {
|
||||
label: counter.insertedImageCount > 1
|
||||
? vscode.l10n.t('Insert Markdown Images')
|
||||
: vscode.l10n.t('Insert Markdown Image'),
|
||||
kind: imageEditKind,
|
||||
};
|
||||
} else {
|
||||
return counter.insertedLinkCount > 1
|
||||
? vscode.l10n.t('Insert Markdown Links')
|
||||
: vscode.l10n.t('Insert Markdown Link');
|
||||
return {
|
||||
label: counter.insertedLinkCount > 1
|
||||
? vscode.l10n.t('Insert Markdown Links')
|
||||
: vscode.l10n.t('Insert Markdown Link'),
|
||||
kind: linkEditKind,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +128,7 @@ export function createInsertUriListEdit(
|
|||
ranges: readonly vscode.Range[],
|
||||
urlList: UriList,
|
||||
options?: UriListSnippetOptions,
|
||||
): { edits: vscode.SnippetTextEdit[]; label: string } | undefined {
|
||||
): { edits: vscode.SnippetTextEdit[]; label: string; kind: vscode.DocumentDropOrPasteEditKind } | undefined {
|
||||
if (!ranges.length || !urlList.entries.length) {
|
||||
return;
|
||||
}
|
||||
|
@ -79,7 +137,8 @@ export function createInsertUriListEdit(
|
|||
|
||||
let insertedLinkCount = 0;
|
||||
let insertedImageCount = 0;
|
||||
let insertedAudioVideoCount = 0;
|
||||
let insertedAudioCount = 0;
|
||||
let insertedVideoCount = 0;
|
||||
|
||||
// Use 1 for all empty ranges but give non-empty range unique indices starting after 1
|
||||
let placeHolderStartIndex = 1 + urlList.entries.length;
|
||||
|
@ -100,15 +159,16 @@ export function createInsertUriListEdit(
|
|||
|
||||
insertedLinkCount += snippet.insertedLinkCount;
|
||||
insertedImageCount += snippet.insertedImageCount;
|
||||
insertedAudioVideoCount += snippet.insertedAudioVideoCount;
|
||||
insertedAudioCount += snippet.insertedAudioCount;
|
||||
insertedVideoCount += snippet.insertedVideoCount;
|
||||
|
||||
placeHolderStartIndex += urlList.entries.length;
|
||||
|
||||
edits.push(new vscode.SnippetTextEdit(range, snippet.snippet));
|
||||
}
|
||||
|
||||
const label = getSnippetLabel({ insertedAudioVideoCount, insertedImageCount, insertedLinkCount });
|
||||
return { edits, label };
|
||||
const { label, kind } = getSnippetLabelAndKind({ insertedAudioCount, insertedVideoCount, insertedImageCount, insertedLinkCount });
|
||||
return { edits, label, kind };
|
||||
}
|
||||
|
||||
interface UriListSnippetOptions {
|
||||
|
@ -134,11 +194,12 @@ interface UriListSnippetOptions {
|
|||
}
|
||||
|
||||
|
||||
interface UriSnippet {
|
||||
snippet: vscode.SnippetString;
|
||||
insertedLinkCount: number;
|
||||
insertedImageCount: number;
|
||||
insertedAudioVideoCount: number;
|
||||
export interface UriSnippet {
|
||||
readonly snippet: vscode.SnippetString;
|
||||
readonly insertedLinkCount: number;
|
||||
readonly insertedImageCount: number;
|
||||
readonly insertedVideoCount: number;
|
||||
readonly insertedAudioCount: number;
|
||||
}
|
||||
|
||||
export function createUriListSnippet(
|
||||
|
@ -159,7 +220,8 @@ export function createUriListSnippet(
|
|||
|
||||
let insertedLinkCount = 0;
|
||||
let insertedImageCount = 0;
|
||||
let insertedAudioVideoCount = 0;
|
||||
let insertedAudioCount = 0;
|
||||
let insertedVideoCount = 0;
|
||||
|
||||
const snippet = new vscode.SnippetString();
|
||||
let placeholderIndex = options?.placeholderStartIndex ?? 1;
|
||||
|
@ -174,7 +236,11 @@ export function createUriListSnippet(
|
|||
const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video;
|
||||
const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio;
|
||||
if (insertAsVideo || insertAsAudio) {
|
||||
insertedAudioVideoCount++;
|
||||
if (insertAsVideo) {
|
||||
insertedVideoCount++;
|
||||
} else {
|
||||
insertedAudioCount++;
|
||||
}
|
||||
const mediaSnippet = insertAsVideo
|
||||
? config.get<string>('editor.filePaste.videoSnippet', '<video controls src="${src}" title="${title}"></video>')
|
||||
: config.get<string>('editor.filePaste.audioSnippet', '<audio controls src="${src}" title="${title}"></audio>');
|
||||
|
@ -201,7 +267,7 @@ export function createUriListSnippet(
|
|||
}
|
||||
});
|
||||
|
||||
return { snippet, insertedAudioVideoCount, insertedImageCount, insertedLinkCount };
|
||||
return { snippet, insertedAudioCount, insertedVideoCount, insertedImageCount, insertedLinkCount };
|
||||
}
|
||||
|
||||
|
||||
|
@ -264,6 +330,7 @@ function needsBracketLink(mdPath: string): boolean {
|
|||
|
||||
export interface DropOrPasteEdit {
|
||||
readonly snippet: vscode.SnippetString;
|
||||
readonly kind: vscode.DocumentDropOrPasteEditKind;
|
||||
readonly label: string;
|
||||
readonly additionalEdits: vscode.WorkspaceEdit;
|
||||
readonly yieldTo: vscode.DocumentDropOrPasteEditKind[];
|
||||
|
|
|
@ -9,9 +9,9 @@ import { Mime } from '../util/mimes';
|
|||
|
||||
class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
public static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'updateLinks');
|
||||
public static readonly kind = vscode.DocumentDropOrPasteEditKind.Text.append('updateLinks', 'markdown');
|
||||
|
||||
public static readonly metadataMime = 'vnd.vscode.markdown.updateLinksMetadata';
|
||||
public static readonly metadataMime = 'application/vnd.vscode.markdown.updatelinks.metadata';
|
||||
|
||||
constructor(
|
||||
private readonly _client: MdLanguageClient,
|
||||
|
@ -67,7 +67,7 @@ class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider
|
|||
pasteEdit.additionalEdit = workspaceEdit;
|
||||
|
||||
if (!context.only || !UpdatePastedLinksEditProvider.kind.contains(context.only)) {
|
||||
pasteEdit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Empty.append('text')];
|
||||
pasteEdit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Text];
|
||||
}
|
||||
|
||||
return [pasteEdit];
|
||||
|
|
|
@ -42,7 +42,7 @@ const enabledSettingId = 'updateImportsOnPaste.enabled';
|
|||
|
||||
class DocumentPasteProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'updateImports', 'jsts');
|
||||
static readonly kind = vscode.DocumentDropOrPasteEditKind.Text.append('updateImports', 'jsts');
|
||||
static readonly metadataMimeType = 'application/vnd.code.jsts.metadata';
|
||||
|
||||
constructor(
|
||||
|
@ -127,7 +127,7 @@ class DocumentPasteProvider implements vscode.DocumentPasteEditProvider {
|
|||
}
|
||||
|
||||
const edit = new vscode.DocumentPasteEdit('', vscode.l10n.t("Paste with imports"), DocumentPasteProvider.kind);
|
||||
edit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'plain')];
|
||||
edit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Text.append('plain')];
|
||||
|
||||
const additionalEdit = new vscode.WorkspaceEdit();
|
||||
for (const edit of response.body.edits) {
|
||||
|
|
|
@ -937,9 +937,9 @@ export interface DocumentPasteEditsSession {
|
|||
*/
|
||||
export interface DocumentPasteEditProvider {
|
||||
readonly id?: string;
|
||||
readonly copyMimeTypes?: readonly string[];
|
||||
readonly pasteMimeTypes?: readonly string[];
|
||||
readonly providedPasteEditKinds?: readonly HierarchicalKind[];
|
||||
readonly copyMimeTypes: readonly string[];
|
||||
readonly pasteMimeTypes: readonly string[];
|
||||
readonly providedPasteEditKinds: readonly HierarchicalKind[];
|
||||
|
||||
prepareDocumentPaste?(model: model.ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<undefined | IReadonlyVSDataTransfer>;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import { ICodeEditor } from '../../../browser/editorBrowser.js';
|
|||
import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution } from '../../../browser/editorExtensions.js';
|
||||
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
|
||||
import { registerEditorFeature } from '../../../common/editorFeatures.js';
|
||||
import { CopyPasteController, changePasteTypeCommandId, pasteWidgetVisibleCtx } from './copyPasteController.js';
|
||||
import { CopyPasteController, PastePreference, changePasteTypeCommandId, pasteWidgetVisibleCtx } from './copyPasteController.js';
|
||||
import { DefaultPasteProvidersFeature, DefaultTextPasteOrDropEditProvider } from './defaultProviders.js';
|
||||
|
||||
export const pasteAsCommandId = 'editor.action.pasteAs';
|
||||
|
@ -56,13 +56,29 @@ registerEditorCommand(new class extends EditorCommand {
|
|||
|
||||
registerEditorAction(class PasteAsAction extends EditorAction {
|
||||
private static readonly argsSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
kind: {
|
||||
type: 'string',
|
||||
description: nls.localize('pasteAs.kind', "The kind of the paste edit to try applying. If not provided or there are multiple edits for this kind, the editor will show a picker."),
|
||||
oneOf: [
|
||||
{
|
||||
type: 'object',
|
||||
required: ['kind'],
|
||||
properties: {
|
||||
kind: {
|
||||
type: 'string',
|
||||
description: nls.localize('pasteAs.kind', "The kind of the paste edit to try pasting with.\nIf there are multiple edits for this kind, the editor will show a picker. If there are no edits of this kind, the editor will show an error message."),
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
required: ['preferences'],
|
||||
properties: {
|
||||
preferences: {
|
||||
type: 'array',
|
||||
description: nls.localize('pasteAs.preferences', "List of preferred paste edit kind to try applying.\nThe first edit matching the preferences will be applied."),
|
||||
items: { type: 'string' }
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
]
|
||||
} as const satisfies IJSONSchema;
|
||||
|
||||
constructor() {
|
||||
|
@ -81,13 +97,15 @@ registerEditorAction(class PasteAsAction extends EditorAction {
|
|||
}
|
||||
|
||||
public override run(_accessor: ServicesAccessor, editor: ICodeEditor, args?: SchemaToType<typeof PasteAsAction.argsSchema>) {
|
||||
let kind = typeof args?.kind === 'string' ? args.kind : undefined;
|
||||
if (!kind && args) {
|
||||
// Support old id property
|
||||
// TODO: remove this in the future
|
||||
kind = typeof (args as any).id === 'string' ? (args as any).id : undefined;
|
||||
let preference: PastePreference | undefined;
|
||||
if (args) {
|
||||
if ('kind' in args) {
|
||||
preference = { only: new HierarchicalKind(args.kind) };
|
||||
} else if ('preferences' in args) {
|
||||
preference = { preferences: args.preferences.map(kind => new HierarchicalKind(kind)) };
|
||||
}
|
||||
}
|
||||
return CopyPasteController.get(editor)?.pasteAs(kind ? new HierarchicalKind(kind) : undefined);
|
||||
return CopyPasteController.get(editor)?.pasteAs(preference);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { addDisposableListener, getActiveDocument } from '../../../../base/browser/dom.js';
|
||||
import { addDisposableListener } from '../../../../base/browser/dom.js';
|
||||
import { IAction } from '../../../../base/common/actions.js';
|
||||
import { coalesce } from '../../../../base/common/arrays.js';
|
||||
import { CancelablePromise, createCancelablePromise, DeferredPromise, raceCancellation } from '../../../../base/common/async.js';
|
||||
|
@ -18,6 +18,7 @@ import { upcast } from '../../../../base/common/types.js';
|
|||
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
|
@ -67,9 +68,11 @@ interface DocumentPasteWithProviderEditsSession {
|
|||
dispose(): void;
|
||||
}
|
||||
|
||||
type PastePreference =
|
||||
| HierarchicalKind
|
||||
| { providerId: string };
|
||||
export type PastePreference =
|
||||
| { readonly only: HierarchicalKind }
|
||||
| { readonly preferences: readonly HierarchicalKind[] }
|
||||
| { readonly providerId: string } // Only used internally
|
||||
;
|
||||
|
||||
export class CopyPasteController extends Disposable implements IEditorContribution {
|
||||
|
||||
|
@ -110,6 +113,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
@IClipboardService private readonly _clipboardService: IClipboardService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IConfigurationService private readonly _configService: IConfigurationService,
|
||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||
@IQuickInputService private readonly _quickInputService: IQuickInputService,
|
||||
|
@ -140,7 +144,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
this._editor.focus();
|
||||
try {
|
||||
this._pasteAsActionContext = { preferred };
|
||||
getActiveDocument().execCommand('paste');
|
||||
this._commandService.executeCommand('editor.action.clipboardPasteAction');
|
||||
} finally {
|
||||
this._pasteAsActionContext = undefined;
|
||||
}
|
||||
|
@ -289,7 +293,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
// Filter out providers that don't match the requested paste types
|
||||
const preference = this._pasteAsActionContext?.preferred;
|
||||
if (preference) {
|
||||
if (provider.providedPasteEditKinds && !this.providerMatchesPreference(provider, preference)) {
|
||||
if (!this.providerMatchesPreference(provider, preference)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -300,6 +304,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
if (!allProviders.length) {
|
||||
if (this._pasteAsActionContext?.preferred) {
|
||||
this.showPasteAsNoEditMessage(selections, this._pasteAsActionContext.preferred);
|
||||
|
||||
// Also prevent default paste from applying
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -318,7 +326,13 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
}
|
||||
|
||||
private showPasteAsNoEditMessage(selections: readonly Selection[], preference: PastePreference) {
|
||||
MessageController.get(this._editor)?.showMessage(localize('pasteAsError', "No paste edits for '{0}' found", preference instanceof HierarchicalKind ? preference.value : preference.providerId), selections[0].getStartPosition());
|
||||
const kindLabel = 'only' in preference
|
||||
? preference.only.value
|
||||
: 'preferences' in preference
|
||||
? (preference.preferences.length ? preference.preferences.map(preference => preference.value).join(', ') : localize('noPreferences', "empty"))
|
||||
: preference.providerId;
|
||||
|
||||
MessageController.get(this._editor)?.showMessage(localize('pasteAsError', "No paste edits for '{0}' found", kindLabel), selections[0].getStartPosition());
|
||||
}
|
||||
|
||||
private doPasteInline(allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, clipboardEvent: ClipboardEvent): void {
|
||||
|
@ -448,7 +462,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
|
||||
const context: DocumentPasteContext = {
|
||||
triggerKind: DocumentPasteTriggerKind.PasteAs,
|
||||
only: preference && preference instanceof HierarchicalKind ? preference : undefined,
|
||||
only: preference && 'only' in preference ? preference.only : undefined,
|
||||
};
|
||||
let editSession = disposables.add(await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token));
|
||||
if (tokenSource.token.isCancellationRequested) {
|
||||
|
@ -459,8 +473,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
if (preference) {
|
||||
editSession = {
|
||||
edits: editSession.edits.filter(edit => {
|
||||
if (preference instanceof HierarchicalKind) {
|
||||
return preference.contains(edit.kind);
|
||||
if ('only' in preference) {
|
||||
return preference.only.contains(edit.kind);
|
||||
} else if ('preferences' in preference) {
|
||||
return preference.preferences.some(preference => preference.contains(edit.kind));
|
||||
} else {
|
||||
return preference.providerId === edit.provider.id;
|
||||
}
|
||||
|
@ -470,8 +486,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
}
|
||||
|
||||
if (!editSession.edits.length) {
|
||||
if (context.only) {
|
||||
this.showPasteAsNoEditMessage(selections, context.only);
|
||||
if (preference) {
|
||||
this.showPasteAsNoEditMessage(selections, preference);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -650,11 +666,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi
|
|||
}
|
||||
|
||||
private providerMatchesPreference(provider: DocumentPasteEditProvider, preference: PastePreference): boolean {
|
||||
if (preference instanceof HierarchicalKind) {
|
||||
if (!provider.providedPasteEditKinds) {
|
||||
return true;
|
||||
}
|
||||
return provider.providedPasteEditKinds.some(providedKind => preference.contains(providedKind));
|
||||
if ('only' in preference) {
|
||||
return provider.providedPasteEditKinds.some(providedKind => preference.only.contains(providedKind));
|
||||
} else if ('preferences' in preference) {
|
||||
return preference.preferences.some(providedKind => preference.preferences.some(preferredKind => preferredKind.contains(providedKind)));
|
||||
} else {
|
||||
return provider.id === preference.providerId;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ abstract class SimplePasteAndDropProvider implements DocumentDropEditProvider, D
|
|||
readonly providedPasteEditKinds: HierarchicalKind[];
|
||||
|
||||
abstract readonly dropMimeTypes: readonly string[] | undefined;
|
||||
readonly copyMimeTypes = [];
|
||||
abstract readonly pasteMimeTypes: readonly string[];
|
||||
|
||||
constructor(kind: HierarchicalKind) {
|
||||
|
@ -102,7 +103,7 @@ class PathProvider extends SimplePasteAndDropProvider {
|
|||
readonly pasteMimeTypes = [Mimes.uriList];
|
||||
|
||||
constructor() {
|
||||
super(HierarchicalKind.Empty.append('uri', 'absolute'));
|
||||
super(HierarchicalKind.Empty.append('uri', 'path', 'absolute'));
|
||||
}
|
||||
|
||||
protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
|
||||
|
@ -153,7 +154,7 @@ class RelativePathProvider extends SimplePasteAndDropProvider {
|
|||
constructor(
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
|
||||
) {
|
||||
super(HierarchicalKind.Empty.append('uri', 'relative'));
|
||||
super(HierarchicalKind.Empty.append('uri', 'path', 'relative'));
|
||||
}
|
||||
|
||||
protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined> {
|
||||
|
@ -185,9 +186,9 @@ class RelativePathProvider extends SimplePasteAndDropProvider {
|
|||
class PasteHtmlProvider implements DocumentPasteEditProvider {
|
||||
|
||||
public readonly kind = new HierarchicalKind('html');
|
||||
|
||||
public readonly providedPasteEditKinds = [this.kind];
|
||||
|
||||
public readonly copyMimeTypes = [];
|
||||
public readonly pasteMimeTypes = ['text/html'];
|
||||
|
||||
private readonly _yieldTo = [{ mimeType: Mimes.text }];
|
||||
|
|
|
@ -1018,9 +1018,9 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider
|
|||
|
||||
private readonly dataTransfers = new DataTransferFileCache();
|
||||
|
||||
public readonly copyMimeTypes?: readonly string[];
|
||||
public readonly pasteMimeTypes?: readonly string[];
|
||||
public readonly providedPasteEditKinds?: readonly HierarchicalKind[];
|
||||
public readonly copyMimeTypes: readonly string[];
|
||||
public readonly pasteMimeTypes: readonly string[];
|
||||
public readonly providedPasteEditKinds: readonly HierarchicalKind[];
|
||||
|
||||
readonly prepareDocumentPaste?: languages.DocumentPasteEditProvider['prepareDocumentPaste'];
|
||||
readonly provideDocumentPasteEdits?: languages.DocumentPasteEditProvider['provideDocumentPasteEdits'];
|
||||
|
@ -1032,9 +1032,9 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider
|
|||
metadata: IPasteEditProviderMetadataDto,
|
||||
@IUriIdentityService private readonly _uriIdentService: IUriIdentityService
|
||||
) {
|
||||
this.copyMimeTypes = metadata.copyMimeTypes;
|
||||
this.pasteMimeTypes = metadata.pasteMimeTypes;
|
||||
this.providedPasteEditKinds = metadata.providedPasteEditKinds?.map(kind => new HierarchicalKind(kind));
|
||||
this.copyMimeTypes = metadata.copyMimeTypes ?? [];
|
||||
this.pasteMimeTypes = metadata.pasteMimeTypes ?? [];
|
||||
this.providedPasteEditKinds = metadata.providedPasteEditKinds?.map(kind => new HierarchicalKind(kind)) ?? [];
|
||||
|
||||
if (metadata.supportsCopy) {
|
||||
this.prepareDocumentPaste = async (model: ITextModel, selections: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<IReadonlyVSDataTransfer | undefined> => {
|
||||
|
|
|
@ -2907,6 +2907,7 @@ export enum DocumentPasteTriggerKind {
|
|||
|
||||
export class DocumentDropOrPasteEditKind {
|
||||
static Empty: DocumentDropOrPasteEditKind;
|
||||
static Text: DocumentDropOrPasteEditKind;
|
||||
|
||||
private static sep = '.';
|
||||
|
||||
|
@ -2927,6 +2928,7 @@ export class DocumentDropOrPasteEditKind {
|
|||
}
|
||||
}
|
||||
DocumentDropOrPasteEditKind.Empty = new DocumentDropOrPasteEditKind('');
|
||||
DocumentDropOrPasteEditKind.Text = new DocumentDropOrPasteEditKind('text');
|
||||
|
||||
export class DocumentPasteEdit {
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ const COPY_MIME_TYPES = 'application/vnd.code.additional-editor-data';
|
|||
export class PasteImageProvider implements DocumentPasteEditProvider {
|
||||
|
||||
public readonly kind = new HierarchicalKind('chat.attach.image');
|
||||
public readonly providedPasteEditKinds = [this.kind];
|
||||
|
||||
public readonly copyMimeTypes = [];
|
||||
public readonly pasteMimeTypes = ['image/*'];
|
||||
|
||||
constructor(
|
||||
|
@ -91,7 +94,7 @@ export class PasteImageProvider implements DocumentPasteEditProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
return getCustomPaste(model, imageContext, mimeType, new HierarchicalKind('chat.attach.image'), localize('pastedImageAttachment', 'Pasted Image Attachment'), this.chatWidgetService);
|
||||
return getCustomPaste(model, imageContext, mimeType, this.kind, localize('pastedImageAttachment', 'Pasted Image Attachment'), this.chatWidgetService);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,9 +141,9 @@ export function isImage(array: Uint8Array): boolean {
|
|||
}
|
||||
|
||||
export class CopyTextProvider implements DocumentPasteEditProvider {
|
||||
|
||||
public readonly kind = new HierarchicalKind('chat.attach.text');
|
||||
public readonly providedPasteEditKinds = [];
|
||||
public readonly copyMimeTypes = [COPY_MIME_TYPES];
|
||||
public readonly pasteMimeTypes = [];
|
||||
|
||||
async prepareDocumentPaste(model: ITextModel, ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<undefined | IReadonlyVSDataTransfer> {
|
||||
if (model.uri.scheme === ChatInputPart.INPUT_SCHEME) {
|
||||
|
@ -156,6 +159,9 @@ export class CopyTextProvider implements DocumentPasteEditProvider {
|
|||
export class PasteTextProvider implements DocumentPasteEditProvider {
|
||||
|
||||
public readonly kind = new HierarchicalKind('chat.attach.text');
|
||||
public readonly providedPasteEditKinds = [this.kind];
|
||||
|
||||
public readonly copyMimeTypes = [];
|
||||
public readonly pasteMimeTypes = [COPY_MIME_TYPES];
|
||||
|
||||
constructor(
|
||||
|
@ -194,7 +200,7 @@ export class PasteTextProvider implements DocumentPasteEditProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
return getCustomPaste(model, copiedContext, Mimes.text, new HierarchicalKind('chat.attach.text'), localize('pastedCodeAttachment', 'Pasted Code Attachment'), this.chatWidgetService);
|
||||
return getCustomPaste(model, copiedContext, Mimes.text, this.kind, localize('pastedCodeAttachment', 'Pasted Code Attachment'), this.chatWidgetService);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -139,15 +139,33 @@ export class DropOrPasteSchemaContribution extends Disposable implements IWorkbe
|
|||
then: {
|
||||
properties: {
|
||||
'args': {
|
||||
required: ['kind'],
|
||||
properties: {
|
||||
'kind': {
|
||||
anyOf: [
|
||||
{ enum: Array.from(this._allProvidedPasteKinds.map(x => x.value)) },
|
||||
{ type: 'string' },
|
||||
]
|
||||
oneOf: [
|
||||
{
|
||||
required: ['kind'],
|
||||
properties: {
|
||||
'kind': {
|
||||
anyOf: [
|
||||
{ enum: Array.from(this._allProvidedPasteKinds.map(x => x.value)) },
|
||||
{ type: 'string' },
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
required: ['preferences'],
|
||||
properties: {
|
||||
'preferences': {
|
||||
type: 'array',
|
||||
items: {
|
||||
anyOf: [
|
||||
{ enum: Array.from(this._allProvidedPasteKinds.map(x => x.value)) },
|
||||
{ type: 'string' },
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,20 @@ declare module 'vscode' {
|
|||
class DocumentDropOrPasteEditKind {
|
||||
static readonly Empty: DocumentDropOrPasteEditKind;
|
||||
|
||||
/**
|
||||
* The root kind for basic text edits.
|
||||
*
|
||||
* This kind should be used for edits that insert basic text into the document. A good example of this is
|
||||
* an edit that pastes the clipboard text while also updating imports in the file based on the pasted text.
|
||||
* For this we could use a kind such as `text.updateImports.someLanguageId`.
|
||||
*
|
||||
* Even though most drop/paste edits ultimately insert text, you should not use {@linkcode Text} as the base kind
|
||||
* for every edit as this is redundant. Instead a more specific kind that describes the type of content being
|
||||
* inserted should be used instead For example, if the edit adds a Markdown link, use `markdown.link` since even
|
||||
* though the content being inserted is text, it's more important to know that the edit inserts Markdown syntax.
|
||||
*/
|
||||
static readonly Text: DocumentDropOrPasteEditKind;
|
||||
|
||||
private constructor(value: string);
|
||||
|
||||
/**
|
||||
|
@ -66,13 +80,18 @@ declare module 'vscode' {
|
|||
/**
|
||||
* Additional information about the paste operation.
|
||||
*/
|
||||
|
||||
// TODO: Should we also have this for drop?
|
||||
export interface DocumentPasteEditContext {
|
||||
/**
|
||||
* Requested kind of paste edits to return.
|
||||
*
|
||||
* When a explicit kind if requested by {@linkcode DocumentPasteTriggerKind.PasteAs PasteAs}, providers are
|
||||
* encourage to be more flexible when generating an edit of the requested kind.
|
||||
*/
|
||||
readonly only: DocumentDropOrPasteEditKind | undefined;
|
||||
|
||||
// TODO: should we also expose preferences?
|
||||
|
||||
/**
|
||||
* The reason why paste edits were requested.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue