diff --git a/package.json b/package.json index 2c876d251..8eb200edc 100644 --- a/package.json +++ b/package.json @@ -2062,14 +2062,19 @@ "experimental" ] }, - { - "command": "github.copilot.open.walkthrough", - "title": "%github.copilot.command.openWalkthrough%", - "category": "GitHub Copilot" - }, - { - "command": "github.copilot.debug.generateInlineEditTests", - "title": "Generate Inline Edit Tests", + { + "command": "github.copilot.open.walkthrough", + "title": "%github.copilot.command.openWalkthrough%", + "category": "GitHub Copilot" + }, + { + "command": "github.copilot.open.releaseNotes", + "title": "%github.copilot.command.openReleaseNotes%", + "category": "GitHub Copilot" + }, + { + "command": "github.copilot.debug.generateInlineEditTests", + "title": "Generate Inline Edit Tests", "category": "GitHub Copilot", "enablement": "resourceScheme == 'ccreq'" }, diff --git a/package.nls.json b/package.nls.json index 2a366fca4..e98ed0b33 100644 --- a/package.nls.json +++ b/package.nls.json @@ -184,8 +184,9 @@ "github.copilot.resetAutomaticCommandExecutionPrompt": "Reset Automatic Command Execution Prompt", "github.copilot.command.generateSTest": "Generate STest From Last Chat Request", "github.copilot.command.generateConfiguration": "Generate Debug Configuration with GitHub Copilot", - "github.copilot.command.openWalkthrough": "Open Walkthrough", - "github.copilot.walkthrough.title": "GitHub Copilot", + "github.copilot.command.openWalkthrough": "Open Walkthrough", + "github.copilot.command.openReleaseNotes": "Show Release Notes", + "github.copilot.walkthrough.title": "GitHub Copilot", "github.copilot.walkthrough.description": "Your AI pair programmer to write code faster and smarter", "github.copilot.walkthrough.signIn.title": "Sign in with GitHub", "github.copilot.walkthrough.signIn.description": "To get started with Copilot, sign in with your GitHub account.\nMake sure you're using the correct GitHub account. You can also sign in later using the account menu.\n\n[Sign In](command:github.copilot.signIn)", diff --git a/src/extension/extension/vscode-node/contributions.ts b/src/extension/extension/vscode-node/contributions.ts index 9b27083e6..f43ee29a4 100644 --- a/src/extension/extension/vscode-node/contributions.ts +++ b/src/extension/extension/vscode-node/contributions.ts @@ -27,6 +27,7 @@ import { LoggingActionsContrib } from '../../log/vscode-node/loggingActions'; import { RequestLogTree } from '../../log/vscode-node/requestLogTree'; import { McpSetupCommands } from '../../mcp/vscode-node/commands'; import { NotebookFollowCommands } from '../../notebook/vscode-node/followActions'; +import { ReleaseNotesCommandContribution } from '../../releaseNotes/vscode-node/commands'; import { CopilotDebugCommandContribution } from '../../onboardDebug/vscode-node/copilotDebugCommandContribution'; import { OnboardTerminalTestsContribution } from '../../onboardDebug/vscode-node/onboardTerminalTestsContribution'; import { DebugCommandsContribution } from '../../prompt/vscode-node/debugCommands'; @@ -61,9 +62,10 @@ export const vscodeNodeContributions: IExtensionContributionFactory[] = [ asContributionFactory(ContextKeysContribution), asContributionFactory(CopilotDebugCommandContribution), asContributionFactory(DebugCommandsContribution), - asContributionFactory(LanguageModelAccess), - asContributionFactory(WalkthroughCommandContribution), - asContributionFactory(InlineEditProviderFeature), + asContributionFactory(LanguageModelAccess), + asContributionFactory(WalkthroughCommandContribution), + asContributionFactory(ReleaseNotesCommandContribution), + asContributionFactory(InlineEditProviderFeature), asContributionFactory(SettingsSchemaFeature), asContributionFactory(WorkspaceRecorderFeature), asContributionFactory(SurveyCommandContribution), diff --git a/src/extension/releaseNotes/vscode-node/commands.ts b/src/extension/releaseNotes/vscode-node/commands.ts new file mode 100644 index 000000000..74b8ec462 --- /dev/null +++ b/src/extension/releaseNotes/vscode-node/commands.ts @@ -0,0 +1,19 @@ +import * as vscode from 'vscode'; +import { Disposable } from '../../../util/vs/base/common/lifecycle'; +import { IReleaseNotesService } from '../../../platform/releaseNotes/common/releaseNotesService'; + +export class ReleaseNotesCommandContribution extends Disposable { + constructor(@IReleaseNotesService private readonly releaseNotesService: IReleaseNotesService) { + super(); + this._register(vscode.commands.registerCommand('github.copilot.open.releaseNotes', async () => { + const notes = await this.releaseNotesService.fetchLatestReleaseNotes(); + if (!notes) { + vscode.window.showInformationMessage('Unable to fetch release notes.'); + return; + } + const doc = await vscode.workspace.openTextDocument({ content: notes, language: 'markdown' }); + await vscode.window.showTextDocument(doc, { preview: false }); + })); + } +} + diff --git a/src/platform/releaseNotes/test/releaseNotesServiceImpl.spec.ts b/src/platform/releaseNotes/test/releaseNotesServiceImpl.spec.ts new file mode 100644 index 000000000..287b77acc --- /dev/null +++ b/src/platform/releaseNotes/test/releaseNotesServiceImpl.spec.ts @@ -0,0 +1,35 @@ +import assert from 'assert'; +import { suite, test } from 'vitest'; +import { ReleaseNotesService } from '../vscode/releaseNotesServiceImpl'; +import { IEnvService } from '../../env/common/envService'; +import { IFetcherService, Response } from '../../networking/common/fetcherService'; + +class MockEnvService implements IEnvService { + readonly _serviceBrand: undefined; + constructor(private version: string) { } + getEditorInfo() { return { version: this.version } as any; } +} + +class MockFetcher implements IFetcherService { + readonly _serviceBrand: undefined; + lastUrl: string | undefined; + getUserAgentLibrary(): string { return 'test'; } + async fetch(url: string, _options: any): Promise { this.lastUrl = url; return new Response(200, 'ok', { get() { return null; } }, async () => '', async () => ({}), async () => null); } + disconnectAll(): Promise { return Promise.resolve(); } + makeAbortController(): any { return { signal: {}, abort() { } }; } + isAbortError(_e: any): boolean { return false; } + isInternetDisconnectedError(_e: any): boolean { return false; } + isFetcherError(_e: any): boolean { return false; } + getUserMessageForFetcherError(_e: any): string { return ''; } +} + +suite('ReleaseNotesService', () => { + test('builds correct URL from version', async () => { + const env = new MockEnvService('1.88.0'); + const fetcher = new MockFetcher(); + const svc = new ReleaseNotesService(env, fetcher); + await svc.fetchLatestReleaseNotes(); + assert.strictEqual(fetcher.lastUrl?.includes('/v1_88.md'), true); + }); +}); +