diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts
index c0ecbe9a8509b..3d3dae404b9f4 100644
--- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts
+++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts
@@ -118,12 +118,12 @@ export function createUriListSnippet(
if (insertAsVideo) {
insertedAudioVideoCount++;
- snippet.appendText(`');
} else if (insertAsAudio) {
insertedAudioVideoCount++;
- snippet.appendText(`');
} else {
@@ -139,7 +139,7 @@ export function createUriListSnippet(
const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : undefined;
snippet.appendPlaceholder(placeholderText, placeholderIndex);
- snippet.appendText(`](${mdPath})`);
+ snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
}
if (i < uris.length - 1 && uris.length > 1) {
@@ -246,11 +246,53 @@ function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
// so that drive-letters are resolved cast insensitively. However we then want to
// convert back to a posix path to insert in to the document.
const relativePath = path.relative(dir.fsPath, file.fsPath);
- return encodeURI(path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep)));
+ return path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep));
}
- return encodeURI(path.posix.relative(dir.path, file.path));
+ return path.posix.relative(dir.path, file.path);
}
return file.toString(false);
}
+
+function escapeHtmlAttribute(attr: string): string {
+ return encodeURI(attr).replaceAll('"', '"');
+}
+
+function escapeMarkdownLinkPath(mdPath: string): string {
+ if (needsBracketLink(mdPath)) {
+ return '<' + mdPath.replace('<', '\\<').replace('>', '\\>') + '>';
+ }
+
+ return encodeURI(mdPath);
+}
+
+function needsBracketLink(mdPath: string) {
+ // Links with whitespace or control characters must be enclosed in brackets
+ if (mdPath.startsWith('<') || /\s|[\u007F\u0000-\u001f]/.test(mdPath)) {
+ return true;
+ }
+
+ // Check if the link has mis-matched parens
+ if (!/[\(\)]/.test(mdPath)) {
+ return false;
+ }
+
+ let previousChar = '';
+ let nestingCount = 0;
+ for (const char of mdPath) {
+ if (char === '(' && previousChar !== '\\') {
+ nestingCount++;
+ } else if (char === ')' && previousChar !== '\\') {
+ nestingCount--;
+ }
+
+ if (nestingCount < 0) {
+ return true;
+ }
+ previousChar = char;
+ }
+
+ return nestingCount > 0;
+}
+