Allow overriding how markdown links are inserted using the keybinding (#234310)

Allows using the `kind` field in the `pasteAs` keybinding to force links to be inserted a certain way, such as as images
This commit is contained in:
Matt Bierner 2024-11-20 15:55:47 -08:00 коммит произвёл GitHub
Родитель d6f311dbf5
Коммит fc5cff7bb6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 75 добавлений и 24 удалений

Просмотреть файл

@ -6,7 +6,7 @@
import * as vscode from 'vscode';
import { Utils } from 'vscode-uri';
import { Command } from '../commandManager';
import { createUriListSnippet, mediaFileExtensions } from '../languageFeatures/copyFiles/shared';
import { createUriListSnippet, linkEditKind, mediaFileExtensions } from '../languageFeatures/copyFiles/shared';
import { coalesce } from '../util/arrays';
import { getParentDocumentUri } from '../util/document';
import { Schemes } from '../util/schemes';
@ -84,7 +84,7 @@ function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: re
const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => {
const selectionText = activeEditor.document.getText(selection);
const snippet = createUriListSnippet(activeEditor.document.uri, selectedFiles.map(uri => ({ uri })), {
insertAsMedia: insertAsMedia,
linkKindHint: insertAsMedia ? 'media' : linkEditKind,
placeholderText: selectionText,
placeholderStartIndex: (i + 1) * selectedFiles.length,
separator: insertAsMedia ? '\n' : ' ',

Просмотреть файл

@ -106,10 +106,10 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
document: vscode.TextDocument,
ranges: readonly vscode.Range[],
dataTransfer: vscode.DataTransfer,
settings: {
settings: Readonly<{
insert: InsertMarkdownLink;
copyIntoWorkspace: CopyFilesSettings;
},
}>,
context: vscode.DocumentPasteEditContext | undefined,
token: vscode.CancellationToken,
): Promise<DropOrPasteEdit | undefined> {
@ -172,7 +172,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
}
}
const edit = createInsertUriListEdit(document, ranges, uriList);
const edit = createInsertUriListEdit(document, ranges, uriList, { linkKindHint: context?.only });
if (!edit) {
return;
}

Просмотреть файл

@ -29,7 +29,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
document: vscode.TextDocument,
ranges: readonly vscode.Range[],
dataTransfer: vscode.DataTransfer,
_context: vscode.DocumentPasteEditContext,
context: vscode.DocumentPasteEditContext,
token: vscode.CancellationToken,
): Promise<vscode.DocumentPasteEdit[] | undefined> {
const pasteUrlSetting = vscode.workspace.getConfiguration('markdown', document)
@ -44,12 +44,17 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
return;
}
// TODO: If the user has explicitly requested to paste as a markdown link,
// try to paste even if we don't have a valid uri
const uriText = findValidUriInText(text);
if (!uriText) {
return;
}
const edit = createInsertUriListEdit(document, ranges, UriList.from(uriText), { preserveAbsoluteUris: true });
const edit = createInsertUriListEdit(document, ranges, UriList.from(uriText), {
linkKindHint: context.only,
preserveAbsoluteUris: true
});
if (!edit) {
return;
}

Просмотреть файл

@ -177,11 +177,11 @@ interface UriListSnippetOptions {
readonly placeholderStartIndex?: number;
/**
* Controls if a media link (`![](...)`) is inserted instead of a normal markdown link.
* Hints how links should be inserted, e.g. as normal markdown link or as an image.
*
* By default tries to infer this from the uri.
* By default this is inferred from the uri. If you use `media`, we will insert the resource as an image, video, or audio.
*/
readonly insertAsMedia?: boolean;
readonly linkKindHint?: vscode.DocumentDropOrPasteEditKind | 'media';
readonly separator?: string;
@ -229,12 +229,16 @@ export function createUriListSnippet(
uris.forEach((uri, i) => {
const mdPath = (!options?.preserveAbsoluteUris ? getRelativeMdPath(documentDir, uri.uri) : undefined) ?? uri.str ?? uri.uri.toString();
const ext = URI.Utils.extname(uri.uri).toLowerCase().replace('.', '');
const insertAsMedia = options?.insertAsMedia || (typeof options?.insertAsMedia === 'undefined' && mediaFileExtensions.has(ext));
const desiredKind = getDesiredLinkKind(uri.uri, options);
if (insertAsMedia) {
const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video;
const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio;
if (desiredKind === DesiredLinkKind.Link) {
insertedLinkCount++;
snippet.appendText('[');
snippet.appendPlaceholder(escapeBrackets(options?.placeholderText ?? 'text'), placeholderIndex);
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
} else {
const insertAsVideo = desiredKind === DesiredLinkKind.Video;
const insertAsAudio = desiredKind === DesiredLinkKind.Audio;
if (insertAsVideo || insertAsAudio) {
if (insertAsVideo) {
insertedVideoCount++;
@ -255,11 +259,6 @@ export function createUriListSnippet(
snippet.appendPlaceholder(placeholderText, placeholderIndex);
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
}
} else {
insertedLinkCount++;
snippet.appendText('[');
snippet.appendPlaceholder(escapeBrackets(options?.placeholderText ?? 'text'), placeholderIndex);
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
}
if (i < uris.length - 1 && uris.length > 1) {
@ -270,6 +269,37 @@ export function createUriListSnippet(
return { snippet, insertedAudioCount, insertedVideoCount, insertedImageCount, insertedLinkCount };
}
enum DesiredLinkKind {
Link,
Image,
Video,
Audio,
}
function getDesiredLinkKind(uri: vscode.Uri, options: UriListSnippetOptions | undefined): DesiredLinkKind {
if (options?.linkKindHint instanceof vscode.DocumentDropOrPasteEditKind) {
if (linkEditKind.contains(options.linkKindHint)) {
return DesiredLinkKind.Link;
} else if (imageEditKind.contains(options.linkKindHint)) {
return DesiredLinkKind.Image;
} else if (audioEditKind.contains(options.linkKindHint)) {
return DesiredLinkKind.Audio;
} else if (videoEditKind.contains(options.linkKindHint)) {
return DesiredLinkKind.Video;
}
}
const normalizedExt = URI.Utils.extname(uri).toLowerCase().replace('.', '');
if (options?.linkKindHint === 'media' || mediaFileExtensions.has(normalizedExt)) {
switch (mediaFileExtensions.get(normalizedExt)) {
case MediaKind.Video: return DesiredLinkKind.Video;
case MediaKind.Audio: return DesiredLinkKind.Audio;
default: return DesiredLinkKind.Image;
}
}
return DesiredLinkKind.Link;
}
function getRelativeMdPath(dir: vscode.Uri | undefined, file: vscode.Uri): string | undefined {
if (dir && dir.scheme === file.scheme && dir.authority === file.authority) {

Просмотреть файл

@ -6,7 +6,7 @@ import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { InMemoryDocument } from '../client/inMemoryDocument';
import { createInsertUriListEdit } from '../languageFeatures/copyFiles/shared';
import { createInsertUriListEdit, imageEditKind, linkEditKind } from '../languageFeatures/copyFiles/shared';
import { InsertMarkdownLink, findValidUriInText, shouldInsertMarkdownLinkByDefault } from '../languageFeatures/copyFiles/smartDropOrPaste';
import { noopToken } from '../util/cancellation';
import { UriList } from '../util/uriList';
@ -20,8 +20,6 @@ function makeTestDoc(contents: string) {
suite('createEditAddingLinksForUriList', () => {
test('Markdown Link Pasting should occur for a valid link (end to end)', async () => {
// createEditAddingLinksForUriList -> checkSmartPaste -> tryGetUriListSnippet -> createUriListSnippet -> createLinkSnippet
const result = createInsertUriListEdit(
new InMemoryDocument(vscode.Uri.file('test.md'), 'hello world!'), [new vscode.Range(0, 0, 0, 12)], UriList.from('https://www.microsoft.com/'));
// need to check the actual result -> snippet value
@ -110,7 +108,6 @@ suite('createEditAddingLinksForUriList', () => {
});
suite('createInsertUriListEdit', () => {
test('Should create snippet with < > when pasted link has an mismatched parentheses', () => {
const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.mic(rosoft.com'));
assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}](<https://www.mic(rosoft.com>)');
@ -135,6 +132,25 @@ suite('createEditAddingLinksForUriList', () => {
const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.example.com/path?query=value&another=value#fragment'));
assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}](https://www.example.com/path?query=value&another=value#fragment)');
});
test('Should add image for image file by default', () => {
const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.example.com/cat.png'));
assert.strictEqual(edit?.edits?.[0].snippet.value, '![${1:alt text}](https://www.example.com/cat.png)');
});
test('Should be able to override insert style to use link', () => {
const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.example.com/cat.png'), {
linkKindHint: linkEditKind,
});
assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}](https://www.example.com/cat.png)');
});
test('Should be able to override insert style to use images', () => {
const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.example.com/'), {
linkKindHint: imageEditKind,
});
assert.strictEqual(edit?.edits?.[0].snippet.value, '![${1:alt text}](https://www.example.com/)');
});
});