Skip to content

Commit fa8eefd

Browse files
authored
Use <...> style markdown links when needed (microsoft#183876)
Fixes microsoft#183849
1 parent f4175f4 commit fa8eefd

File tree

1 file changed

+47
-5
lines changed
  • extensions/markdown-language-features/src/languageFeatures/copyFiles

1 file changed

+47
-5
lines changed

extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,12 @@ export function createUriListSnippet(
118118

119119
if (insertAsVideo) {
120120
insertedAudioVideoCount++;
121-
snippet.appendText(`<video src="${mdPath}" controls title="`);
121+
snippet.appendText(`<video src="${escapeHtmlAttribute(mdPath)}" controls title="`);
122122
snippet.appendPlaceholder('Title');
123123
snippet.appendText('"></video>');
124124
} else if (insertAsAudio) {
125125
insertedAudioVideoCount++;
126-
snippet.appendText(`<audio src="${mdPath}" controls title="`);
126+
snippet.appendText(`<audio src="${escapeHtmlAttribute(mdPath)}" controls title="`);
127127
snippet.appendPlaceholder('Title');
128128
snippet.appendText('"></audio>');
129129
} else {
@@ -139,7 +139,7 @@ export function createUriListSnippet(
139139
const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : undefined;
140140
snippet.appendPlaceholder(placeholderText, placeholderIndex);
141141

142-
snippet.appendText(`](${mdPath})`);
142+
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
143143
}
144144

145145
if (i < uris.length - 1 && uris.length > 1) {
@@ -246,11 +246,53 @@ function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
246246
// so that drive-letters are resolved cast insensitively. However we then want to
247247
// convert back to a posix path to insert in to the document.
248248
const relativePath = path.relative(dir.fsPath, file.fsPath);
249-
return encodeURI(path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep)));
249+
return path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep));
250250
}
251251

252-
return encodeURI(path.posix.relative(dir.path, file.path));
252+
return path.posix.relative(dir.path, file.path);
253253
}
254254

255255
return file.toString(false);
256256
}
257+
258+
function escapeHtmlAttribute(attr: string): string {
259+
return encodeURI(attr).replaceAll('"', '&quot;');
260+
}
261+
262+
function escapeMarkdownLinkPath(mdPath: string): string {
263+
if (needsBracketLink(mdPath)) {
264+
return '<' + mdPath.replace('<', '\\<').replace('>', '\\>') + '>';
265+
}
266+
267+
return encodeURI(mdPath);
268+
}
269+
270+
function needsBracketLink(mdPath: string) {
271+
// Links with whitespace or control characters must be enclosed in brackets
272+
if (mdPath.startsWith('<') || /\s|[\u007F\u0000-\u001f]/.test(mdPath)) {
273+
return true;
274+
}
275+
276+
// Check if the link has mis-matched parens
277+
if (!/[\(\)]/.test(mdPath)) {
278+
return false;
279+
}
280+
281+
let previousChar = '';
282+
let nestingCount = 0;
283+
for (const char of mdPath) {
284+
if (char === '(' && previousChar !== '\\') {
285+
nestingCount++;
286+
} else if (char === ')' && previousChar !== '\\') {
287+
nestingCount--;
288+
}
289+
290+
if (nestingCount < 0) {
291+
return true;
292+
}
293+
previousChar = char;
294+
}
295+
296+
return nestingCount > 0;
297+
}
298+

0 commit comments

Comments
 (0)