From f07abd224bf29609d00cb3b56cbbbf5ce0a409e4 Mon Sep 17 00:00:00 2001 From: Meghan Kulkarni Date: Mon, 10 Jul 2023 14:11:49 -0700 Subject: [PATCH] Make markdown link pasting feature smarter (#187170) * making markdown link pasting feature smarter * Update settings description Co-authored-by: Joyce Er * made checkPaste more concise * won't paste md link in fenced code or math --------- Co-authored-by: Joyce Er --- .../markdown-language-features/package.json | 14 ++++- .../package.nls.json | 5 +- .../src/commands/insertResource.ts | 4 +- .../copyFiles/copyPasteLinks.ts | 5 +- .../src/languageFeatures/copyFiles/shared.ts | 51 +++++++++++++++---- 5 files changed, 62 insertions(+), 17 deletions(-) diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 1f4fcbc6034..1afabaf9dff 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -499,10 +499,20 @@ ] }, "markdown.editor.pasteUrlAsFormattedLink.enabled": { - "type": "boolean", + "type": "string", "scope": "resource", "markdownDescription": "%configuration.markdown.editor.pasteUrlAsFormattedLink.enabled%", - "default": true + "default":"smart", + "enum": [ + "always", + "smart", + "never" + ], + "markdownEnumDescriptions": [ + "%configuration.pasteUrlAsFormattedLink.always%", + "%configuration.pasteUrlAsFormattedLink.smart%", + "%configuration.pasteUrlAsFormattedLink.never%" + ] }, "markdown.validate.enabled": { "type": "boolean", diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index e39e597b02f..0468fbf1d79 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -41,9 +41,12 @@ "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. Requires enabling `#editor.pasteAs.enabled#`.", "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.editor.pasteUrlAsFormattedLink.enabled": "Controls how a Markdown link is created when a URL is pasted into the Markdown editor. Requires enabling `#editor.pasteAs.enabled#`.", + "configuration.pasteUrlAsFormattedLink.always": "Always create a Markdown link when a URL is pasted into the Markdown editor.", + "configuration.pasteUrlAsFormattedLink.smart": "Does not create a Markdown link within a link snippet or code bracket.", + "configuration.pasteUrlAsFormattedLink.never": "Never create a Markdown link when a URL is pasted into the Markdown editor.", "configuration.markdown.validate.enabled.description": "Enable all error reporting in Markdown files.", "configuration.markdown.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, for example: `[link][ref]`. Requires enabling `#markdown.validate.enabled#`.", "configuration.markdown.validate.fragmentLinks.enabled.description": "Validate fragment links to headers in the current Markdown file, for example: `[link](#header)`. Requires enabling `#markdown.validate.enabled#`.", diff --git a/extensions/markdown-language-features/src/commands/insertResource.ts b/extensions/markdown-language-features/src/commands/insertResource.ts index 2e7b97c3048..da50c83c991 100644 --- a/extensions/markdown-language-features/src/commands/insertResource.ts +++ b/extensions/markdown-language-features/src/commands/insertResource.ts @@ -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, title = '', placeholderValue = 0) { +function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean, title = '', placeholderValue = 0, smartPaste = false) { const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => { const selectionText = activeEditor.document.getText(selection); - const snippet = createUriListSnippet(activeEditor.document, selectedFiles, title, placeholderValue, { + const snippet = createUriListSnippet(activeEditor.document, selectedFiles, title, placeholderValue, smartPaste, { insertAsMedia, placeholderText: selectionText, placeholderStartIndex: (i + 1) * selectedFiles.length, diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts index 313a3916688..90755d990fc 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts @@ -5,7 +5,6 @@ import * as vscode from 'vscode'; import { getMarkdownLink } from './shared'; - class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { readonly id = 'insertMarkdownLink'; @@ -15,8 +14,8 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken, ): Promise { - const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.pasteUrlAsFormattedLink.enabled', true); - if (!enabled) { + const enabled = vscode.workspace.getConfiguration('markdown', document).get<'always' | 'smart' | 'never'>('editor.pasteUrlAsFormattedLink.enabled', 'smart'); + if (enabled === 'never') { return; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index 28a86a22d70..f40fea3556f 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -65,15 +65,26 @@ export async function getMarkdownLink(document: vscode.TextDocument, ranges: rea if (ranges.length === 0) { return; } + const enabled = vscode.workspace.getConfiguration('markdown', document).get<'always' | 'smart' | 'never'>('editor.pasteUrlAsFormattedLink.enabled', 'always'); const edits: vscode.SnippetTextEdit[] = []; let placeHolderValue: number = ranges.length; let label: string = ''; + let smartPaste: boolean = false; for (let i = 0; i < ranges.length; i++) { - const snippet = await tryGetUriListSnippet(document, urlList, token, document.getText(ranges[i]), placeHolderValue); + if (enabled === 'smart') { + const inMarkdownLink = checkPaste(document, ranges, /\[([^\]]*)\]\(([^)]*)\)/g, i); + const inFencedCode = checkPaste(document, ranges, /^```[\s\S]*?```$/gm, i); + const inFencedMath = checkPaste(document, ranges, /^\$\$[\s\S]*?\$\$$/gm, i); + smartPaste = (inMarkdownLink || inFencedCode || inFencedMath); + } + + const snippet = await tryGetUriListSnippet(document, urlList, token, document.getText(ranges[i]), placeHolderValue, smartPaste); if (!snippet) { return; } + + smartPaste = false; placeHolderValue--; edits.push(new vscode.SnippetTextEdit(ranges[i], snippet.snippet)); label = snippet.label; @@ -85,7 +96,20 @@ export async function getMarkdownLink(document: vscode.TextDocument, ranges: rea 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> { +function checkPaste(document: vscode.TextDocument, ranges: readonly vscode.Range[], regex: RegExp, index: number): boolean { + const rangeStartOffset = document.offsetAt(ranges[index].start); + const rangeEndOffset = document.offsetAt(ranges[index].end); + const matches = [...document.getText().matchAll(regex)]; + for (const match of matches) { + if (match.index !== undefined && rangeStartOffset > match.index && rangeEndOffset < match.index + match[0].length) { + return true; + } + } + + return false; +} + +export async function tryGetUriListSnippet(document: vscode.TextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0, smartPaste = false): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> { if (token.isCancellationRequested) { return undefined; } @@ -99,7 +123,7 @@ export async function tryGetUriListSnippet(document: vscode.TextDocument, urlLis } } - return createUriListSnippet(document, uris, title, placeHolderValue); + return createUriListSnippet(document, uris, title, placeHolderValue, smartPaste); } interface UriListSnippetOptions { @@ -122,6 +146,7 @@ export function createUriListSnippet( uris: readonly vscode.Uri[], title = '', placeholderValue = 0, + smartPaste = false, options?: UriListSnippetOptions, ): { snippet: vscode.SnippetString; label: string } | undefined { if (!uris.length) { @@ -164,13 +189,21 @@ export function createUriListSnippet( snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); } else { insertedLinkCount++; - snippet.appendText('['); - snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue); - if (externalUriSchemes.includes(uri.scheme)) { - const uriString = uri.toString(true); - snippet.appendText(`](${uriString})`); + if (smartPaste) { + if (externalUriSchemes.includes(uri.scheme)) { + snippet.appendText(uri.toString(true)); + } else { + snippet.appendText(escapeMarkdownLinkPath(mdPath)); + } } else { - snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); + snippet.appendText('['); + snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue); + if (externalUriSchemes.includes(uri.scheme)) { + const uriString = uri.toString(true); + snippet.appendText(`](${uriString})`); + } else { + snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); + } } } }