Skip to content

Commit

Permalink
Add new smartWithSelection option for markdown links (microsoft#202183
Browse files Browse the repository at this point in the history
)

This becomes the new default while `smart` always smartly pastes, even with no selection
  • Loading branch information
mjbvz authored Jan 10, 2024
1 parent 115155d commit 0bb69da
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 45 deletions.
4 changes: 3 additions & 1 deletion extensions/markdown-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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%"
]
},
Expand Down
3 changes: 2 additions & 1 deletion extensions/markdown-language-features/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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#`.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PasteUrlAsFormattedLink>('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsFormattedLink.Smart);
.get<PasteUrlAsMarkdownLink>('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsMarkdownLink.SmartWithSelection);
}

/**
Expand All @@ -37,17 +38,17 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
token: vscode.CancellationToken,
): Promise<vscode.DocumentPasteEdit | undefined> {
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;
}
Expand All @@ -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;
}
}
Expand All @@ -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) {
return false;
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;
}
}
}

const rangeText = document.getText(selectedRange);
// Disable for whitespace only selections
if (rangeText.trim().length === 0) {
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 when the selection is already a link
if (findValidUriInText(rangeText)) {
return false;
Expand Down
50 changes: 28 additions & 22 deletions extensions/markdown-language-features/src/test/markdownLink.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)),
false);
shouldInsertMarkdownLinkByDefault(makeTestDoc('xyz'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 0, 0, 0)]),
true);
});

test('Should evaluate pasteAsMarkdownLink as false for a valid selected link with trailing whitespace', () => {
test('SmartWithSelection should disable for empty selection', () => {
assert.strictEqual(
shouldSmartPaste(makeTestDoc(' https://www.microsoft.com '), new vscode.Range(0, 0, 0, 30)),
shouldInsertMarkdownLinkByDefault(makeTestDoc('xyz'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 0)]),
false);
});

test('Should evaluate pasteAsMarkdownLink as true for a link pasted in square brackets', () => {
test('Smart should disable for selected link', () => {
assert.strictEqual(
shouldSmartPaste(makeTestDoc('[abc]'), new vscode.Range(0, 1, 0, 4)),
true);
shouldInsertMarkdownLinkByDefault(makeTestDoc('https://www.microsoft.com'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 25)]),
false);
});

test('Should evaluate pasteAsMarkdownLink as false for no selection', () => {
test('Smart should disable for selected link with trailing whitespace', () => {
assert.strictEqual(
shouldSmartPaste(makeTestDoc('xyz'), new vscode.Range(0, 0, 0, 0)),
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(
shouldInsertMarkdownLinkByDefault(makeTestDoc('[abc]'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 1, 0, 4)]),
true);
});

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);
});
});
Expand Down

0 comments on commit 0bb69da

Please sign in to comment.