Skip to content

Commit 667ab7d

Browse files
Copilotalexr00
andauthored
Fix PR description card showing escaped markdown for inline code, strong, and emphasis (#7805)
* Initial plan * Add optional simple markdown support to PlainTextRenderer for inline code Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Add strong and em formatting support to PlainTextRenderer when allowSimpleMarkdown is true Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Fixes * Fix tests * Fix tests --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent 9f5c2fe commit 667ab7d

File tree

5 files changed

+54
-3
lines changed

5 files changed

+54
-3
lines changed

src/github/copilotRemoteAgent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ export class CopilotRemoteAgentManager extends Disposable {
528528
}
529529

530530
if (pr && (_version && _version === 2)) { /* version 2 means caller knows how to render this */
531-
const plaintextBody = marked.parse(pr.body, { renderer: new PlainTextRenderer(), smartypants: true }).trim();
531+
const plaintextBody = marked.parse(pr.body, { renderer: new PlainTextRenderer(true), smartypants: true }).trim();
532532

533533
return {
534534
uri: webviewUri.toString(),

src/github/copilotRemoteAgent/chatSessionContentBuilder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class ChatSessionContentBuilder {
7676
// if this is the first response, then also add the PR card
7777
if (sessionIndex === 0) {
7878
const uri = await toOpenPullRequestWebviewUri({ owner: pullRequest.remote.owner, repo: pullRequest.remote.repositoryName, pullRequestNumber: pullRequest.number });
79-
const plaintextBody = marked.parse(pullRequest.body, { renderer: new PlainTextRenderer(), smartypants: true }).trim();
79+
const plaintextBody = marked.parse(pullRequest.body, { renderer: new PlainTextRenderer(true), smartypants: true }).trim();
8080

8181
const card = new vscode.ChatResponsePullRequestPart(uri, pullRequest.title, plaintextBody, pullRequest.author.specialDisplayName ?? pullRequest.author.login, `#${pullRequest.number}`);
8282
const cardTurn = new vscode.ChatResponseTurn2([card], {}, COPILOT_SWE_AGENT);

src/github/markdownUtils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,13 @@ export async function issueMarkdown(
237237
}
238238

239239
export class PlainTextRenderer extends marked.Renderer {
240+
private allowSimpleMarkdown: boolean;
241+
242+
constructor(allowSimpleMarkdown: boolean = false) {
243+
super();
244+
this.allowSimpleMarkdown = allowSimpleMarkdown;
245+
}
246+
240247
override code(code: string, _infostring: string | undefined): string {
241248
return code;
242249
}
@@ -286,6 +293,9 @@ export class PlainTextRenderer extends marked.Renderer {
286293
return text;
287294
}
288295
override codespan(code: string): string {
296+
if (this.allowSimpleMarkdown) {
297+
return `\`${code}\``;
298+
}
289299
return `\\\`${code}\\\``;
290300
}
291301
override br(): string {

src/lm/tools/copilotRemoteAgentTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export class CopilotRemoteAgentTool implements vscode.LanguageModelTool<CopilotR
122122
let lmResult: (vscode.LanguageModelTextPart | vscode.LanguageModelDataPart)[] = [new vscode.LanguageModelTextPart(result.llmDetails)];
123123
const pr = await targetRepo.fm.resolvePullRequest(targetRepo.owner, targetRepo.repo, result.number);
124124
if (pr) {
125-
const plaintextBody = marked.parse(pr.body, { renderer: new PlainTextRenderer(), smartypants: true }).trim();
125+
const plaintextBody = marked.parse(pr.body, { renderer: new PlainTextRenderer(true), smartypants: true }).trim();
126126
const preferredRendering = {
127127
uri: (await toOpenPullRequestWebviewUri({ owner: pr.githubRepository.remote.owner, repo: pr.githubRepository.remote.repositoryName, pullRequestNumber: pr.number })).toString(),
128128
title: pr.title,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import * as marked from 'marked';
8+
import { PlainTextRenderer } from '../../github/markdownUtils';
9+
import { describe, it } from 'mocha';
10+
11+
describe('PlainTextRenderer', () => {
12+
it('should escape inline code by default', () => {
13+
const renderer = new PlainTextRenderer();
14+
const result = marked.parse('rename the `Foo` class', { renderer, smartypants: true });
15+
assert.strictEqual(result.trim(), 'rename the \\`Foo\\` class');
16+
});
17+
18+
it('should preserve inline code when allowSimpleMarkdown is true', () => {
19+
const renderer = new PlainTextRenderer(true);
20+
const result = marked.parse('rename the `Foo` class', { renderer, smartypants: true });
21+
assert.strictEqual(result.trim(), 'rename the `Foo` class');
22+
});
23+
24+
it('should handle multiple inline code spans', () => {
25+
const renderer = new PlainTextRenderer(true);
26+
const result = marked.parse('rename the `Foo` class to `Bar`', { renderer, smartypants: true });
27+
assert.strictEqual(result.trim(), 'rename the `Foo` class to `Bar`');
28+
});
29+
30+
it('should still escape when allowSimpleMarkdown is false', () => {
31+
const renderer = new PlainTextRenderer(false);
32+
const result = marked.parse('rename the `Foo` class to `Bar`', { renderer, smartypants: true });
33+
assert.strictEqual(result.trim(), 'rename the \\`Foo\\` class to \\`Bar\\`');
34+
});
35+
36+
it('should strip all formatting by default', () => {
37+
const renderer = new PlainTextRenderer(false);
38+
const result = marked.parse('rename the `Foo` class to **`Bar`** and make it *italic*', { renderer, smartypants: true });
39+
assert.strictEqual(result.trim(), 'rename the \\`Foo\\` class to \\`Bar\\` and make it italic');
40+
});
41+
});

0 commit comments

Comments
 (0)