diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 995e2a05819..4968f3cb227 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -7,8 +7,8 @@ import * as vscode from 'vscode'; import { IMdParser } from '../../markdownEngine'; import { ITextDocument } from '../../types/textDocument'; import { Mime } from '../../util/mimes'; -import { createInsertUriListEdit } from './shared'; import { Schemes } from '../../util/schemes'; +import { createInsertUriListEdit } from './shared'; export enum PasteUrlAsMarkdownLink { Always = 'always', @@ -59,7 +59,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { return; } - const edit = createInsertUriListEdit(document, ranges, uriText); + const edit = createInsertUriListEdit(document, ranges, uriText, { preserveAbsoluteUris: true }); if (!edit) { return; } @@ -212,8 +212,10 @@ const externalUriSchemes: ReadonlySet = new Set([ export function findValidUriInText(text: string): string | undefined { const trimmedUrlList = text.trim(); - // Uri must consist of a single sequence of characters without spaces - if (!/^\S+$/.test(trimmedUrlList)) { + if ( + !/^\S+$/.test(trimmedUrlList) // Uri must consist of a single sequence of characters without spaces + || !trimmedUrlList.includes(':') // And it must have colon somewhere for the scheme. We will verify the schema again later + ) { return; } @@ -225,7 +227,21 @@ export function findValidUriInText(text: string): string | undefined { return; } - if (!externalUriSchemes.has(uri.scheme.toLowerCase()) || uri.authority.length <= 1) { + // `Uri.parse` is lenient and will return a `file:` uri even for non-uri text such as `abc` + // Make sure that the resolved scheme starts the original text + if (!trimmedUrlList.toLowerCase().startsWith(uri.scheme.toLowerCase() + ':')) { + return; + } + + // Only enable for an allow list of schemes. Otherwise this can be accidentally activated for non-uri text + // such as `c:\abc` or `value:foo` + if (!externalUriSchemes.has(uri.scheme.toLowerCase())) { + return; + } + + // Some part of the uri must not be empty + // This disables the feature for text such as `http:` + if (!uri.authority && uri.path.length < 2 && !uri.query && !uri.fragment) { return; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index 7881e5938a5..8bfc9ae2ff5 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -69,6 +69,7 @@ export function createInsertUriListEdit( document: ITextDocument, ranges: readonly vscode.Range[], urlList: string, + options?: UriListSnippetOptions, ): { edits: vscode.SnippetTextEdit[]; label: string } | undefined { if (!ranges.length) { return; @@ -103,6 +104,7 @@ export function createInsertUriListEdit( const snippet = createUriListSnippet(document.uri, entries, { placeholderText: range.isEmpty ? undefined : document.getText(range), placeholderStartIndex: allRangesAreEmpty ? 1 : placeHolderStartIndex, + ...options, }); if (!snippet) { continue; @@ -134,6 +136,13 @@ interface UriListSnippetOptions { readonly insertAsMedia?: boolean; readonly separator?: string; + + /** + * Prevents uris from being made relative to the document. + * + * This is mostly useful for `file:` uris. + */ + readonly preserveAbsoluteUris?: boolean; } @@ -168,7 +177,7 @@ export function createUriListSnippet( let placeholderIndex = options?.placeholderStartIndex ?? 1; uris.forEach((uri, i) => { - const mdPath = getRelativeMdPath(documentDir, uri.uri) ?? uri.str ?? uri.uri.toString(); + 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));