Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/@types/vscode.proposed.chatParticipantAdditions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ declare module 'vscode' {
isComplete?: boolean;
toolSpecificData?: ChatTerminalToolInvocationData;
fromSubAgent?: boolean;
presentation?: 'hidden' | 'hiddenAfterComplete' | undefined;

constructor(toolName: string, toolCallId: string, isError?: boolean);
}
Expand Down
5 changes: 5 additions & 0 deletions src/@types/vscode.proposed.chatSessionsProvider.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ declare module 'vscode' {
*/
description?: string | MarkdownString;

/**
* An optional badge that provides additional context about the chat session.
*/
badge?: string | MarkdownString;

/**
* An optional status indicating the current state of the session.
*/
Expand Down
34 changes: 33 additions & 1 deletion src/github/prComment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export class TemporaryComment extends CommentBase {
const SUGGESTION_EXPRESSION = /```suggestion(\u0020*(\r\n|\n))((?<suggestion>[\s\S]*?)(\r\n|\n))?```/;
const IMG_EXPRESSION = /<img .*src=['"](?<src>.+?)['"].*?>/g;
const UUID_EXPRESSION = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}/;
export const COMMIT_SHA_EXPRESSION = /(?<![`\/\w])([0-9a-f]{7})([0-9a-f]{33})?(?![`\/\w])/g;

export class GHPRComment extends CommentBase {
private static ID = 'GHPRComment';
Expand Down Expand Up @@ -421,6 +422,36 @@ ${lineContents}
return replaceImages(body, html, this.githubRepository?.remote.host);
}

private replaceCommitShas(body: string): string {
const githubRepository = this.githubRepository;
if (!githubRepository) {
return body;
}

// Match commit SHAs that are:
// - Either 7 or 40 hex characters
// - Not already part of a URL or markdown link
// - Not inside code blocks (backticks)
return body.replace(COMMIT_SHA_EXPRESSION, (match, shortSha, remaining, offset) => {
// Don't replace if inside code blocks
const beforeMatch = body.substring(0, offset);
const backtickCount = (beforeMatch.match(/`/g)?.length ?? 0);
if (backtickCount % 2 === 1) {
return match;
}

// Don't replace if already part of a markdown link
if (beforeMatch.endsWith('[') || body.substring(offset + match.length).startsWith(']')) {
return match;
}

const owner = githubRepository.remote.owner;
const repo = githubRepository.remote.repositoryName;
const commitUrl = `https://${githubRepository.remote.host}/${owner}/${repo}/commit/${match}`;
return `[${shortSha}](${commitUrl})`;
});
}

private replaceNewlines(body: string) {
return body.replace(/(?<!\s)(\r\n|\n)/g, ' \n');
}
Expand Down Expand Up @@ -463,7 +494,8 @@ ${lineContents}
return `${substring.startsWith('@') ? '' : substring.charAt(0)}[@${username}](${url})`;
});

const permalinkReplaced = await this.replacePermalink(linkified);
const commitShasReplaced = this.replaceCommitShas(linkified);
const permalinkReplaced = await this.replacePermalink(commitShasReplaced);
await emojiPromise;
return this.postpendSpecialAuthorComment(emojify(this.replaceImg(this.replaceSuggestion(permalinkReplaced))));
}
Expand Down
51 changes: 50 additions & 1 deletion src/test/github/prComment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,56 @@
*--------------------------------------------------------------------------------------------*/

import { default as assert } from 'assert';
import { replaceImages } from '../../github/prComment';
import { COMMIT_SHA_EXPRESSION, replaceImages } from '../../github/prComment';

describe('commit SHA replacement', function () {
it('should match 7-character commit SHAs', function () {
const text = 'Fixed in commit 5cf56bc and also in abc1234';
const matches = Array.from(text.matchAll(COMMIT_SHA_EXPRESSION));
assert.strictEqual(matches.length, 2);
assert.strictEqual(matches[0][1], '5cf56bc');
assert.strictEqual(matches[1][1], 'abc1234');
});

it('should match 40-character commit SHAs', function () {
const text = 'Fixed in commit 5cf56bc1234567890abcdef1234567890abcdef0';
const matches = Array.from(text.matchAll(COMMIT_SHA_EXPRESSION));
assert.strictEqual(matches.length, 1);
assert.strictEqual(matches[0][0], '5cf56bc1234567890abcdef1234567890abcdef0');
});

it('should not match SHAs in URLs', function () {
const text = 'https://github.com/owner/repo/commit/5cf56bc';
const matches = Array.from(text.matchAll(COMMIT_SHA_EXPRESSION));
assert.strictEqual(matches.length, 0);
});

it('should not match SHAs in code blocks', function () {
const text = 'Fixed in commit 5cf56bc but not in `abc1234`';
const matches = Array.from(text.matchAll(COMMIT_SHA_EXPRESSION));
// The regex should only match the first SHA, not the one inside backticks
assert.strictEqual(matches.length, 1);
assert.strictEqual(matches[0][1], '5cf56bc');
});

it('should not match non-hex strings', function () {
const text = 'Not a SHA: 1234xyz or ABCDEFG';
const matches = Array.from(text.matchAll(COMMIT_SHA_EXPRESSION));
assert.strictEqual(matches.length, 0);
});

it('should not match SHAs with alphanumeric prefix', function () {
const text = 'prefix5cf56bc is not a SHA';
const matches = Array.from(text.matchAll(COMMIT_SHA_EXPRESSION));
assert.strictEqual(matches.length, 0);
});

it('should not match SHAs with alphanumeric suffix', function () {
const text = '5cf56bcsuffix is not a SHA';
const matches = Array.from(text.matchAll(COMMIT_SHA_EXPRESSION));
assert.strictEqual(matches.length, 0);
});
});

describe('replace images', function () {
it('github.com', function () {
Expand Down