Repository: microsoft/vscode (TypeScript)
Date: 2022-08-11 19:01:45+00:00
Task type: Feature

Issue:
Support triple-backtick code blocks with more than just a language identifier in the info string
In the [CommonMark spec](https://spec.commonmark.org/0.28/#fenced-code-blocks:~:text=The%20first%20word%20of%20the%20info%20string) it says:

> The line with the opening code fence may optionally contain some text following the code fence; this is trimmed of leading and trailing spaces and called the [info string](https://spec.commonmark.org/0.28/#info-string). The [info string](https://spec.commonmark.org/0.28/#info-string) may not contain any backtick characters.
> 
> The first word of the [info string](https://spec.commonmark.org/0.28/#info-string) is typically used to specify the language of the code sample, and rendered in the class attribute of the code tag. However, this spec does not mandate any particular treatment of the [info string](https://spec.commonmark.org/0.28/#info-string).

It then gives [an example of such use](https://spec.commonmark.org/0.28/#example-112):

```
~~~~    ruby startline=3 $%@#$
def foo(x)
  return 3
end
~~~~~~~
```

Although the spec does not mandate it, it would be nice if VS Code supported this format. Currently, adding more than just the language identifier after the backticks results in the code note rendering as code:

```ts
/**
 * ```ts
 * const a = new MyClass();
 * ```
 *
 * ```ts foo
 * const a = new MyClass();
 * ```
 */
class MyClass { }
```

<img width="473" alt="Screenshot 2022-08-10 at 15 32 18" src="https://user-images.githubusercontent.com/1078012/183928250-1a662e71-b3b5-4de3-b085-e01238078b42.png">



Issue discussion:
@DanTup This already is supported for Markdown highlighting and in markdown previews:

![Screen Shot 2022-08-10 at 9 32 02 AM](https://user-images.githubusercontent.com/12821956/183964296-0fd4262e-61f7-41c8-8975-9e61beec28d5.png)

Is this  about support in `MarkdownString` too?
@mjbvz I don't know what's being used internally by the TS integration above, but I think it should work anywhere that VS Code is already handling these code blocks (my example above was hovers, but things like code completion documentation and other places that support code blocks should be the same - a lot of this content comes from external sources so there's benefit in VS Code being as compatible with what they may use as possible).

Solution patch:
diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts
--- a/src/vs/base/browser/markdownRenderer.ts
+++ b/src/vs/base/browser/markdownRenderer.ts
@@ -143,7 +143,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
 	if (options.codeBlockRenderer) {
 		renderer.code = (code, lang) => {
 			const id = defaultGenerator.nextId();
-			const value = options.codeBlockRenderer!(lang ?? '', code);
+			const value = options.codeBlockRenderer!(postProcessCodeBlockLanguageId(lang), code);
 			codeBlocks.push(value.then(element => [id, element]));
 			return `<div class="code" data-code="${id}">${escape(code)}</div>`;
 		};
@@ -294,6 +294,18 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
 	};
 }
 
+function postProcessCodeBlockLanguageId(lang: string | undefined): string {
+	if (!lang) {
+		return '';
+	}
+
+	const parts = lang.split(/[\s+|:|,|\{|\?]/, 1);
+	if (parts.length) {
+		return parts[0];
+	}
+	return lang;
+}
+
 function resolveWithBaseUri(baseUri: URI, href: string): string {
 	const hasScheme = /^\w[\w\d+.-]*:/.test(href);
 	if (hasScheme) {


Test patch:
diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts
--- a/src/vs/base/test/browser/markdownRenderer.test.ts
+++ b/src/vs/base/test/browser/markdownRenderer.test.ts
@@ -68,7 +68,7 @@ suite('MarkdownRenderer', () => {
 	});
 
 	suite('Code block renderer', () => {
-		const simpleCodeBlockRenderer = (code: string): Promise<HTMLElement> => {
+		const simpleCodeBlockRenderer = (lang: string, code: string): Promise<HTMLElement> => {
 			const element = document.createElement('code');
 			element.textContent = code;
 			return Promise.resolve(element);
@@ -115,6 +115,19 @@ suite('MarkdownRenderer', () => {
 				}, 50);
 			});
 		});
+
+		test('Code blocks should use leading language id (#157793)', async () => {
+			const markdown = { value: '```js some other stuff\n1 + 1;\n```' };
+			const lang = await new Promise<string>(resolve => {
+				renderMarkdown(markdown, {
+					codeBlockRenderer: async (lang, value) => {
+						resolve(lang);
+						return simpleCodeBlockRenderer(lang, value);
+					}
+				});
+			});
+			assert.strictEqual(lang, 'js');
+		});
 	});
 
 	suite('ThemeIcons Support On', () => {