mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
turning highlighted Markdown text to link to pasted URL (#185924)
* turning highlighted Mardown text to link to pasted URL * resolved comments * resolved more comments * preserved behavior of existing file pasting logic --------- Co-authored-by: Meghan Kulkarni <t-mekulkarni@microsoft.com>
This commit is contained in:
parent
680cbcc139
commit
6626f5f07a
|
@ -498,6 +498,12 @@
|
|||
"%configuration.copyIntoWorkspace.never%"
|
||||
]
|
||||
},
|
||||
"markdown.editor.pasteUrlAsFormattedLink.enabled": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%configuration.markdown.editor.pasteUrlAsFormattedLink.enabled%",
|
||||
"default": true
|
||||
},
|
||||
"markdown.validate.enabled": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
"configuration.markdown.editor.drop.copyIntoWorkspace": "Controls if files outside of the workspace that are dropped into a Markdown editor should be copied into the workspace.\n\nUse `#markdown.copyFiles.destination#` to configure where copied dropped files should be created",
|
||||
"configuration.markdown.editor.filePaste.enabled": "Enable pasting files into a Markdown editor to create Markdown links. Requires enabling `#editor.pasteAs.enabled#`.",
|
||||
"configuration.markdown.editor.filePaste.copyIntoWorkspace": "Controls if files outside of the workspace that are pasted into a Markdown editor should be copied into the workspace.\n\nUse `#markdown.copyFiles.destination#` to configure where copied files should be created.",
|
||||
"configuration.markdown.editor.pasteUrlAsFormattedLink.enabled": "Controls if a Markdown link is created when a URL is pasted into the Markdown editor.",
|
||||
"configuration.copyIntoWorkspace.mediaFiles": "Try to copy external image and video files into the workspace.",
|
||||
"configuration.copyIntoWorkspace.never": "Do not copy external files into the workspace.",
|
||||
"configuration.markdown.validate.enabled.description": "Enable all error reporting in Markdown files.",
|
||||
|
|
|
@ -76,10 +76,10 @@ async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: vscode
|
|||
await vscode.workspace.applyEdit(edit);
|
||||
}
|
||||
|
||||
function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean) {
|
||||
function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean, title = '', placeholderValue = 0) {
|
||||
const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => {
|
||||
const selectionText = activeEditor.document.getText(selection);
|
||||
const snippet = createUriListSnippet(activeEditor.document, selectedFiles, {
|
||||
const snippet = createUriListSnippet(activeEditor.document, selectedFiles, title, placeholderValue, {
|
||||
insertAsMedia,
|
||||
placeholderText: selectionText,
|
||||
placeholderStartIndex: (i + 1) * selectedFiles.length,
|
||||
|
|
|
@ -8,6 +8,7 @@ import { MdLanguageClient } from './client/client';
|
|||
import { CommandManager } from './commandManager';
|
||||
import { registerMarkdownCommands } from './commands/index';
|
||||
import { registerPasteSupport } from './languageFeatures/copyFiles/copyPaste';
|
||||
import { registerLinkPasteSupport } from './languageFeatures/copyFiles/copyPasteLinks';
|
||||
import { registerDiagnosticSupport } from './languageFeatures/diagnostics';
|
||||
import { registerDropIntoEditorSupport } from './languageFeatures/copyFiles/dropIntoEditor';
|
||||
import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences';
|
||||
|
@ -59,6 +60,7 @@ function registerMarkdownLanguageFeatures(
|
|||
registerDropIntoEditorSupport(selector),
|
||||
registerFindFileReferenceSupport(commandManager, client),
|
||||
registerPasteSupport(selector),
|
||||
registerLinkPasteSupport(selector),
|
||||
registerUpdateLinksOnRename(client),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import { Schemes } from '../../util/schemes';
|
||||
import { createEditForMediaFiles, mediaMimes, tryGetUriListSnippet } from './shared';
|
||||
import { createEditForMediaFiles, getMarkdownLink, mediaMimes } from './shared';
|
||||
|
||||
class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
|
@ -13,7 +13,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
|||
|
||||
async provideDocumentPasteEdits(
|
||||
document: vscode.TextDocument,
|
||||
_ranges: readonly vscode.Range[],
|
||||
ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
|
@ -27,12 +27,18 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
|||
return createEdit;
|
||||
}
|
||||
|
||||
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
|
||||
if (!snippet) {
|
||||
const label = vscode.l10n.t('Insert Markdown Media');
|
||||
const uriEdit = new vscode.DocumentPasteEdit('', this._id, label);
|
||||
const urlList = await dataTransfer.get('text/uri-list')?.asString();
|
||||
if (!urlList) {
|
||||
return;
|
||||
}
|
||||
const pasteEdit = await getMarkdownLink(document, ranges, urlList, token);
|
||||
if (!pasteEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uriEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
|
||||
uriEdit.additionalEdit = pasteEdit.additionalEdits;
|
||||
uriEdit.priority = this._getPriority(dataTransfer);
|
||||
return uriEdit;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { getMarkdownLink } from './shared';
|
||||
|
||||
class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
private readonly _id = 'insertMarkdownLink';
|
||||
async provideDocumentPasteEdits(
|
||||
document: vscode.TextDocument,
|
||||
ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.pasteUrlAsFormattedLink.enabled', true);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if dataTransfer contains a URL
|
||||
const item = dataTransfer.get('text/plain');
|
||||
try {
|
||||
new URL(await item?.value);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const label = vscode.l10n.t('Insert Markdown Link');
|
||||
const uriEdit = new vscode.DocumentPasteEdit('', this._id, label);
|
||||
const urlList = await item?.asString();
|
||||
if (!urlList) {
|
||||
return undefined;
|
||||
}
|
||||
const pasteEdit = await getMarkdownLink(document, ranges, urlList, token);
|
||||
if (!pasteEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
uriEdit.additionalEdit = pasteEdit.additionalEdits;
|
||||
return uriEdit;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) {
|
||||
return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteLinkEditProvider(), {
|
||||
pasteMimeTypes: [
|
||||
'text/plain',
|
||||
]
|
||||
});
|
||||
}
|
|
@ -30,7 +30,11 @@ class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider {
|
|||
}
|
||||
|
||||
private async _getUriListEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
|
||||
const urlList = await dataTransfer.get('text/uri-list')?.asString();
|
||||
if (!urlList) {
|
||||
return undefined;
|
||||
}
|
||||
const snippet = await tryGetUriListSnippet(document, urlList, token);
|
||||
if (!snippet) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -56,10 +56,32 @@ export const mediaMimes = new Set([
|
|||
'audio/x-wav',
|
||||
]);
|
||||
|
||||
export async function getMarkdownLink(document: vscode.TextDocument, ranges: readonly vscode.Range[], urlList: string, token: vscode.CancellationToken): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string } | undefined> {
|
||||
if (ranges.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> {
|
||||
const urlList = await dataTransfer.get('text/uri-list')?.asString();
|
||||
if (!urlList || token.isCancellationRequested) {
|
||||
const edits: vscode.SnippetTextEdit[] = [];
|
||||
let placeHolderValue: number = ranges.length;
|
||||
let label: string = '';
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const snippet = await tryGetUriListSnippet(document, urlList, token, document.getText(ranges[i]), placeHolderValue);
|
||||
if (!snippet) {
|
||||
return;
|
||||
}
|
||||
placeHolderValue--;
|
||||
edits.push(new vscode.SnippetTextEdit(ranges[i], snippet.snippet));
|
||||
label = snippet.label;
|
||||
}
|
||||
|
||||
const additionalEdits = new vscode.WorkspaceEdit();
|
||||
additionalEdits.set(document.uri, edits);
|
||||
|
||||
return { additionalEdits, label };
|
||||
}
|
||||
|
||||
export async function tryGetUriListSnippet(document: vscode.TextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> {
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -72,7 +94,7 @@ export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTr
|
|||
}
|
||||
}
|
||||
|
||||
return createUriListSnippet(document, uris);
|
||||
return createUriListSnippet(document, uris, title, placeHolderValue);
|
||||
}
|
||||
|
||||
interface UriListSnippetOptions {
|
||||
|
@ -90,11 +112,12 @@ interface UriListSnippetOptions {
|
|||
readonly separator?: string;
|
||||
}
|
||||
|
||||
|
||||
export function createUriListSnippet(
|
||||
document: vscode.TextDocument,
|
||||
uris: readonly vscode.Uri[],
|
||||
options?: UriListSnippetOptions
|
||||
title = '',
|
||||
placeholderValue = 0,
|
||||
options?: UriListSnippetOptions,
|
||||
): { snippet: vscode.SnippetString; label: string } | undefined {
|
||||
if (!uris.length) {
|
||||
return;
|
||||
|
@ -119,27 +142,27 @@ export function createUriListSnippet(
|
|||
if (insertAsVideo) {
|
||||
insertedAudioVideoCount++;
|
||||
snippet.appendText(`<video src="${escapeHtmlAttribute(mdPath)}" controls title="`);
|
||||
snippet.appendPlaceholder('Title');
|
||||
snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue);
|
||||
snippet.appendText('"></video>');
|
||||
} else if (insertAsAudio) {
|
||||
insertedAudioVideoCount++;
|
||||
snippet.appendText(`<audio src="${escapeHtmlAttribute(mdPath)}" controls title="`);
|
||||
snippet.appendPlaceholder('Title');
|
||||
snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue);
|
||||
snippet.appendText('"></audio>');
|
||||
} else {
|
||||
if (insertAsMedia) {
|
||||
insertedImageCount++;
|
||||
snippet.appendText('![');
|
||||
const placeholderText = options?.placeholderText ? (escapeBrackets(title) || 'Alt text') : 'label';
|
||||
const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : (placeholderValue === 0 ? undefined : placeholderValue);
|
||||
snippet.appendPlaceholder(placeholderText, placeholderIndex);
|
||||
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
|
||||
} else {
|
||||
insertedLinkCount++;
|
||||
snippet.appendText('[');
|
||||
snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue);
|
||||
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
|
||||
}
|
||||
|
||||
snippet.appendText(insertAsMedia ? '![' : '[');
|
||||
|
||||
const placeholderText = options?.placeholderText ?? (insertAsMedia ? 'Alt text' : 'label');
|
||||
const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : undefined;
|
||||
snippet.appendPlaceholder(placeholderText, placeholderIndex);
|
||||
|
||||
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
|
||||
}
|
||||
|
||||
if (i < uris.length - 1 && uris.length > 1) {
|
||||
|
@ -267,6 +290,12 @@ function escapeMarkdownLinkPath(mdPath: string): string {
|
|||
return encodeURI(mdPath);
|
||||
}
|
||||
|
||||
function escapeBrackets(value: string): string {
|
||||
value = value.replace(/[\[\]]/g, '\\$&');
|
||||
// value = value.replace(/\r\n\r\n/g, '\n\n');
|
||||
return value;
|
||||
}
|
||||
|
||||
function needsBracketLink(mdPath: string) {
|
||||
// Links with whitespace or control characters must be enclosed in brackets
|
||||
if (mdPath.startsWith('<') || /\s|[\u007F\u0000-\u001f]/.test(mdPath)) {
|
||||
|
|
|
@ -166,16 +166,24 @@ export class PostEditWidgetManager extends Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
let insertTextEdit: ResourceTextEdit[] = [];
|
||||
if (typeof edit.insertText === 'string' ? edit.insertText === '' : edit.insertText.snippet === '') {
|
||||
insertTextEdit = [];
|
||||
} else {
|
||||
insertTextEdit = ranges.map(range => new ResourceTextEdit(model.uri,
|
||||
typeof edit.insertText === 'string'
|
||||
? { range, text: edit.insertText, insertAsSnippet: false }
|
||||
: { range, text: edit.insertText.snippet, insertAsSnippet: true }
|
||||
));
|
||||
}
|
||||
|
||||
const allEdits = [
|
||||
...insertTextEdit,
|
||||
...(edit.additionalEdit?.edits ?? [])
|
||||
];
|
||||
|
||||
const combinedWorkspaceEdit: WorkspaceEdit = {
|
||||
edits: [
|
||||
...ranges.map(range =>
|
||||
new ResourceTextEdit(model.uri,
|
||||
typeof edit.insertText === 'string'
|
||||
? { range, text: edit.insertText, insertAsSnippet: false }
|
||||
: { range, text: edit.insertText.snippet, insertAsSnippet: true }
|
||||
)),
|
||||
...(edit.additionalEdit?.edits ?? [])
|
||||
]
|
||||
edits: allEdits
|
||||
};
|
||||
|
||||
// Use a decoration to track edits around the trigger range
|
||||
|
|
Loading…
Reference in a new issue