diff --git a/src/commands.ts b/src/commands.ts index 79f4303e59..923d61832e 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -129,6 +129,18 @@ export async function closeAllPrAndReviewEditors() { } } +type PullRequestQuickPickItem = vscode.QuickPickItem & { prNumber: number }; + +export function findExactPullRequestNumberMatch(value: string, items: readonly PullRequestQuickPickItem[]): PullRequestQuickPickItem | undefined { + const numberMatch = /^#?(\d+)$/.exec(value); + if (!numberMatch) { + return undefined; + } + + const prNumber = Number(numberMatch[1]); + return items.find(item => item.prNumber === prNumber); +} + function isCrossChatSessionWithPR(value: any): value is CrossChatSessionWithPR { const asCrossChatSessionWithPR = value as Partial; return !!asCrossChatSessionWithPR.pullRequestDetails; @@ -1968,6 +1980,7 @@ ${contents} let acceptDisposable: vscode.Disposable | undefined; let hideDisposable: vscode.Disposable | undefined; + let valueChangeDisposable: vscode.Disposable | undefined; try { const selectedPromise = new Promise<{ selectedItem: (vscode.QuickPickItem & { prNumber?: number }) | undefined, selectedString: string | undefined }>((resolve) => { @@ -1994,13 +2007,19 @@ ${contents} } // Sort PRs by number in descending order (most recent first) const sortedPRs = prs.sort((a, b) => b.number - a.number); - const prItems: (vscode.QuickPickItem & { prNumber: number })[] = sortedPRs.map(pr => ({ + const prItems: PullRequestQuickPickItem[] = sortedPRs.map(pr => ({ label: `#${pr.number} ${pr.title}`, description: `by @${pr.author.login}`, prNumber: pr.number })); quickPick.items = prItems; + const prioritizeExactNumberMatch = (value: string) => { + const exactNumberMatch = findExactPullRequestNumberMatch(value, prItems); + quickPick.activeItems = exactNumberMatch ? [exactNumberMatch] : []; + }; + valueChangeDisposable = quickPick.onDidChangeValue(prioritizeExactNumberMatch); + prioritizeExactNumberMatch(quickPick.value); const selected = await selectedPromise; quickPick.busy = true; @@ -2044,6 +2063,7 @@ ${contents} // Clean up event listeners and QuickPick acceptDisposable?.dispose(); hideDisposable?.dispose(); + valueChangeDisposable?.dispose(); quickPick.hide(); quickPick.dispose(); } diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 3038c33295..4be8763c4e 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -1,7 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + import { default as assert } from 'assert'; import { parseDiffHunk } from '../common/diffHunk'; +import { findExactPullRequestNumberMatch } from '../commands'; describe('Extension Tests', function () { + describe('findExactPullRequestNumberMatch', () => { + it('prioritizes an exact number over a title match without changing the label', () => { + const items = [ + { + label: '#10064 Follow up on #10063', + description: 'by @octocat', + prNumber: 10064, + }, + { + label: '#10063 Upgrade library to v5', + description: 'by @hubot', + prNumber: 10063, + }, + ]; + + assert.strictEqual(findExactPullRequestNumberMatch('10063', items), items[1]); + assert.strictEqual(findExactPullRequestNumberMatch('#10063', items), items[1]); + assert.strictEqual(findExactPullRequestNumberMatch('1006', items), undefined); + assert.strictEqual(findExactPullRequestNumberMatch('10063 title', items), undefined); + assert.strictEqual(items[1].label, '#10063 Upgrade library to v5'); + }); + }); + describe('parseDiffHunk', () => { it('should handle empty string', () => { const diffHunk = parseDiffHunk('');