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 API
This commit is contained in:
Matt Bierner
2024-11-19 22:14:10 -08:00
committed by GitHub
parent 8a4b2bb49b
commit c83b443da0
16 changed files with 261 additions and 105 deletions

View File

@@ -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,
}),
);

View File

@@ -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')
];
}

View File

@@ -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[];

View File

@@ -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];