diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 5dcce219bfa..160ddbb3a7b 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -517,15 +517,17 @@ "type": "string", "scope": "resource", "markdownDescription": "%configuration.markdown.editor.pasteUrlAsFormattedLink.enabled%", - "default": "smart", + "default": "smartWithSelection", "enum": [ "always", "smart", + "smartWithSelection", "never" ], "markdownEnumDescriptions": [ "%configuration.pasteUrlAsFormattedLink.always%", "%configuration.pasteUrlAsFormattedLink.smart%", + "%configuration.pasteUrlAsFormattedLink.smartWithSelection%", "%configuration.pasteUrlAsFormattedLink.never%" ] }, diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 02d80098b9a..b99593a6301 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -45,7 +45,8 @@ "configuration.copyIntoWorkspace.never": "Do not copy external files into the workspace.", "configuration.markdown.editor.pasteUrlAsFormattedLink.enabled": "Controls if Markdown links are created when URLs are pasted into a Markdown editor. Requires enabling `#editor.pasteAs.enabled#`.", "configuration.pasteUrlAsFormattedLink.always": "Always insert Markdown links.", - "configuration.pasteUrlAsFormattedLink.smart": "Smartly create Markdown links by default when you have selected text and are not pasting into a code block or other special element. Use the paste widget to switch between pasting as plain text or as Markdown links.", + "configuration.pasteUrlAsFormattedLink.smart": "Smartly create Markdown links by default when not pasting into a code block or other special element. Use the paste widget to switch between pasting as plain text or as Markdown links.", + "configuration.pasteUrlAsFormattedLink.smartWithSelection": "Smartly create Markdown links by default when you have selected text and are not pasting into a code block or other special element. Use the paste widget to switch between pasting as plain text or as Markdown links.", "configuration.pasteUrlAsFormattedLink.never": "Never create Markdown links.", "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#`.", diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 1515dbdee85..a4998e787f8 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -8,15 +8,16 @@ import { ITextDocument } from '../../types/textDocument'; import { Mime } from '../../util/mimes'; import { createInsertUriListEdit, externalUriSchemes } from './shared'; -enum PasteUrlAsFormattedLink { +export enum PasteUrlAsMarkdownLink { Always = 'always', + SmartWithSelection = 'smartWithSelection', Smart = 'smart', Never = 'never' } -function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument): PasteUrlAsFormattedLink { +function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument): PasteUrlAsMarkdownLink { return vscode.workspace.getConfiguration('markdown', document) - .get('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsFormattedLink.Smart); + .get('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsMarkdownLink.SmartWithSelection); } /** @@ -37,17 +38,17 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { token: vscode.CancellationToken, ): Promise { const pasteUrlSetting = getPasteUrlAsFormattedLinkSetting(document); - if (pasteUrlSetting === PasteUrlAsFormattedLink.Never) { + if (pasteUrlSetting === PasteUrlAsMarkdownLink.Never) { return; } const item = dataTransfer.get(Mime.textPlain); - const urlList = await item?.asString(); - if (token.isCancellationRequested || !urlList) { + const text = await item?.asString(); + if (token.isCancellationRequested || !text) { return; } - const uriText = findValidUriInText(urlList); + const uriText = findValidUriInText(text); if (!uriText) { return; } @@ -62,14 +63,10 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { workspaceEdit.set(document.uri, edit.edits); pasteEdit.additionalEdit = workspaceEdit; - // If smart pasting is enabled, deprioritize this provider when: - // - The user has no selection - // - At least one of the ranges occurs in a context where smart pasting is disabled (such as a fenced code block) - if (pasteUrlSetting === PasteUrlAsFormattedLink.Smart) { - if (!ranges.every(range => shouldSmartPaste(document, range))) { - pasteEdit.yieldTo = [{ mimeType: Mime.textPlain }]; - } + if (!shouldInsertMarkdownLinkByDefault(document, pasteUrlSetting, ranges)) { + pasteEdit.yieldTo = [{ mimeType: Mime.textPlain }]; } + return pasteEdit; } } @@ -90,18 +87,35 @@ const smartPasteRegexes = [ { regex: /\$[^$]*\$/g }, // In inline math ]; -export function shouldSmartPaste(document: ITextDocument, selectedRange: vscode.Range): boolean { - // Disable for empty selections and multi-line selections - if (selectedRange.isEmpty || selectedRange.start.line !== selectedRange.end.line) { +export function shouldInsertMarkdownLinkByDefault(document: ITextDocument, pasteUrlSetting: PasteUrlAsMarkdownLink, ranges: readonly vscode.Range[]): boolean { + switch (pasteUrlSetting) { + case PasteUrlAsMarkdownLink.Always: { + return true; + } + case PasteUrlAsMarkdownLink.Smart: { + return ranges.every(range => shouldSmartPasteForSelection(document, range)); + } + case PasteUrlAsMarkdownLink.SmartWithSelection: { + return ( + // At least one range must not be empty + ranges.some(range => document.getText(range).trim().length > 0) + // And all ranges must be smart + && ranges.every(range => shouldSmartPasteForSelection(document, range)) + ); + } + default: { + return false; + } + } +} + +function shouldSmartPasteForSelection(document: ITextDocument, selectedRange: vscode.Range): boolean { + // Disable for multi-line selections + if (selectedRange.start.line !== selectedRange.end.line) { return false; } const rangeText = document.getText(selectedRange); - // Disable for whitespace only selections - if (rangeText.trim().length === 0) { - return false; - } - // Disable when the selection is already a link if (findValidUriInText(rangeText)) { return false; diff --git a/extensions/markdown-language-features/src/test/markdownLink.test.ts b/extensions/markdown-language-features/src/test/markdownLink.test.ts index 0d30ec948d5..62cfa9e9c8a 100644 --- a/extensions/markdown-language-features/src/test/markdownLink.test.ts +++ b/extensions/markdown-language-features/src/test/markdownLink.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; import { InMemoryDocument } from '../client/inMemoryDocument'; -import { findValidUriInText, shouldSmartPaste } from '../languageFeatures/copyFiles/pasteUrlProvider'; +import { PasteUrlAsMarkdownLink, findValidUriInText, shouldInsertMarkdownLinkByDefault } from '../languageFeatures/copyFiles/pasteUrlProvider'; import { createInsertUriListEdit } from '../languageFeatures/copyFiles/shared'; function makeTestDoc(contents: string) { @@ -134,83 +134,89 @@ suite('createEditAddingLinksForUriList', () => { }); - suite('checkSmartPaste', () => { + suite('shouldInsertMarkdownLinkByDefault', () => { - test('Should evaluate pasteAsMarkdownLink as true for selected plain text', () => { + test('Smart should enabled for selected plain text', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('hello world'), new vscode.Range(0, 0, 0, 12)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('hello world'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 12)]), true); }); - test('Should evaluate pasteAsMarkdownLink as false for a valid selected link', () => { + test('Smart should enabled for empty selection', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('https://www.microsoft.com'), new vscode.Range(0, 0, 0, 25)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('xyz'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 0, 0, 0)]), + true); + }); + + test('SmartWithSelection should disable for empty selection', () => { + assert.strictEqual( + shouldInsertMarkdownLinkByDefault(makeTestDoc('xyz'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 0)]), false); }); - test('Should evaluate pasteAsMarkdownLink as false for a valid selected link with trailing whitespace', () => { + test('Smart should disable for selected link', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc(' https://www.microsoft.com '), new vscode.Range(0, 0, 0, 30)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('https://www.microsoft.com'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 25)]), + false); + }); + + test('Smart should disable for selected link with trailing whitespace', () => { + assert.strictEqual( + shouldInsertMarkdownLinkByDefault(makeTestDoc(' https://www.microsoft.com '), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 30)]), false); }); test('Should evaluate pasteAsMarkdownLink as true for a link pasted in square brackets', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('[abc]'), new vscode.Range(0, 1, 0, 4)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('[abc]'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 4)]), true); }); - test('Should evaluate pasteAsMarkdownLink as false for no selection', () => { - assert.strictEqual( - shouldSmartPaste(makeTestDoc('xyz'), new vscode.Range(0, 0, 0, 0)), - false); - }); - test('Should evaluate pasteAsMarkdownLink as false for selected whitespace and new lines', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc(' \r\n\r\n'), new vscode.Range(0, 0, 0, 7)), + shouldInsertMarkdownLinkByDefault(makeTestDoc(' \r\n\r\n'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 7)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a backtick code block', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('```\r\n\r\n```'), new vscode.Range(0, 5, 0, 5)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('```\r\n\r\n```'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 5)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a tilde code block', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('~~~\r\n\r\n~~~'), new vscode.Range(0, 5, 0, 5)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('~~~\r\n\r\n~~~'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 5)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a math block', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('$$$\r\n\r\n$$$'), new vscode.Range(0, 5, 0, 5)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('$$$\r\n\r\n$$$'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 5)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown link', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('[a](bcdef)'), new vscode.Range(0, 4, 0, 6)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('[a](bcdef)'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 4, 0, 6)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown image link', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('![a](bcdef)'), new vscode.Range(0, 5, 0, 10)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('![a](bcdef)'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 5, 0, 10)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within inline code', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('``'), new vscode.Range(0, 1, 0, 1)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('``'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 1)]), false); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within inline math', () => { assert.strictEqual( - shouldSmartPaste(makeTestDoc('$$'), new vscode.Range(0, 1, 0, 1)), + shouldInsertMarkdownLinkByDefault(makeTestDoc('$$'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 1)]), false); }); });