From 7473517baeb10f13bacdee539ef06bd8a988a507 Mon Sep 17 00:00:00 2001 From: Beatrix <68532117+abeatrix@users.noreply.github.com> Date: Tue, 1 Oct 2024 08:06:30 -0700 Subject: [PATCH] Command: register missing commands when unifiedPrompts is enabled (#5768) CLOSE https://linear.app/sourcegraph/issue/QA-81 Currently, some commands unrelated to the chat commands are not registered when unified prompts feature is enabled, which I believe is unintentional. This PR add some documentations around some cody commands, and refactor the logic to make it clear what the new unified prompts commands are during the command registeration. This PR also fixes the issue where the generate unit test command was unintentionally removed when the unified prompts feature is enabled. ## Test plan Manual Testing: Sign into S2 where the feature flag is enabled for all users. ![image](https://github.com/user-attachments/assets/0322c772-4ce3-40f1-ac23-491449544111) 1. In the Cody Commands menu, search for the following commands to make sure they are working: - Auto Edit - Generate Unit Test 2. Open the Terminal panel and highlight some text, right click to select "Ask Cody to Explain" to verify a Chat panel is opened with the selected text appended. ![image](https://github.com/user-attachments/assets/b8f55170-0a32-43cb-ac42-f1df5d3aefab) Before this change, none of these commands were working because they were not registered when the unified prompts feature flag is enabled. ## Changelog Command: Fixed an issue where some commands (`Auto-Edit` and `Cody Explain Terminal Output`) were not registered when the unified prompt feature flag is enabled. --- lib/shared/src/commands/types.ts | 1 + vscode/src/commands/CommandsController.ts | 1 + vscode/src/commands/execute/test-chat.ts | 114 ++++++++++++++++++++++ vscode/src/main.ts | 108 +++++++++++--------- 4 files changed, 177 insertions(+), 47 deletions(-) create mode 100644 vscode/src/commands/execute/test-chat.ts diff --git a/lib/shared/src/commands/types.ts b/lib/shared/src/commands/types.ts index 3259d7c51b95..e11bd0038612 100644 --- a/lib/shared/src/commands/types.ts +++ b/lib/shared/src/commands/types.ts @@ -4,6 +4,7 @@ export type DefaultCodyCommands = DefaultChatCommands | DefaultEditCommands // Default Cody Commands that runs as a Chat request export enum DefaultChatCommands { Doc = 'doc', // Generate documentation in Chat + Test = 'test', // Generate documentation in Chat Explain = 'explain', // Explain code Smell = 'smell', // Generate code smell report in Chat Custom = 'custom-chat', // Run custom command in Chat diff --git a/vscode/src/commands/CommandsController.ts b/vscode/src/commands/CommandsController.ts index b22489e2ac64..94a876dd7b35 100644 --- a/vscode/src/commands/CommandsController.ts +++ b/vscode/src/commands/CommandsController.ts @@ -127,6 +127,7 @@ function convertDefaultCommandsToPromptString(input: DefaultCodyCommands | Promp return ps`explain` case DefaultChatCommands.Smell: return ps`smell` + case DefaultChatCommands.Test: case DefaultEditCommands.Test: return ps`test` case DefaultChatCommands.Doc: diff --git a/vscode/src/commands/execute/test-chat.ts b/vscode/src/commands/execute/test-chat.ts new file mode 100644 index 000000000000..4116c8200f09 --- /dev/null +++ b/vscode/src/commands/execute/test-chat.ts @@ -0,0 +1,114 @@ +import { type ContextItem, DefaultChatCommands, logDebug, logError, ps } from '@sourcegraph/cody-shared' +import { wrapInActiveSpan } from '@sourcegraph/cody-shared' +import { telemetryRecorder } from '@sourcegraph/cody-shared' +import type { ChatCommandResult } from '../../CommandResult' +import { getEditor } from '../../editor/active-editor' +import { getContextFileFromCursor } from '../context/selection' +import type { CodyCommandArgs } from '../types' +import { type ExecuteChatArguments, executeChat } from './ask' + +import type { Span } from '@opentelemetry/api' +import { isUriIgnoredByContextFilterWithNotification } from '../../cody-ignore/context-filter' +import { getContextFilesForUnitTestCommand } from '../context/unit-test-file' +import { selectedCodePromptWithExtraFiles } from './index' + +/** + * Generates the prompt and context files with arguments for the '/test' command in Chat. + * + * Context: Test files, current selection, and current file + */ +async function unitTestCommand( + span: Span, + args?: Partial +): Promise { + let prompt = ps`Review the shared code context and configurations to identify the test framework and libraries in use. Then, generate a suite of multiple unit tests for the functions in using the detected test framework and libraries. Be sure to import the function being tested. Follow the same patterns as any shared context. Only add packages, imports, dependencies, and assertions if they are used in the shared code. Pay attention to the file path of each shared context to see if test for already exists. If one exists, focus on generating new unit tests for uncovered cases. If none are detected, import common unit test libraries for {languageName}. Focus on validating key functionality with simple and complete assertions. Only include mocks if one is detected in the shared code. Before writing the tests, identify which test libraries and frameworks to import, e.g. 'No new imports needed - using existing libs' or 'Importing test framework that matches shared context usage' or 'Importing the defined framework', etc. Then briefly summarize test coverage and any limitations. At the end, enclose the full completed code for the new unit tests, including all necessary imports, in a single markdown codeblock. No fragments or TODO. The new tests should validate expected functionality and cover edge cases for with all required imports, including importing the function being tested. Do not repeat existing tests.` + + if (args?.additionalInstruction) { + prompt = ps`${prompt} ${args.additionalInstruction}` + } + + const editor = getEditor()?.active + const document = editor?.document + const contextItems: ContextItem[] = [] + + if (document) { + try { + const cursorContext = await getContextFileFromCursor() + if (cursorContext === null) { + throw new Error( + 'Selection content is empty. Please select some code to generate tests for.' + ) + } + + const sharedContext = await getContextFilesForUnitTestCommand(document.uri).catch(error => { + logError('executeNewTestCommand', 'failed to fetch context', { verbose: error }) + return [] + }) + + prompt = prompt.replaceAll('', selectedCodePromptWithExtraFiles(cursorContext, [])) + + if (sharedContext.length > 0) { + prompt = prompt.replaceAll( + 'the shared code', + selectedCodePromptWithExtraFiles(sharedContext[0], sharedContext.slice(1)) + ) + } + + contextItems.push(cursorContext) + contextItems.push(...sharedContext) + } catch (error) { + logError('testCommand', 'failed to fetch context', { verbose: error }) + } + } + + return { + text: prompt, + contextItems, + source: args?.source, + submitType: 'user-newchat', + command: DefaultChatCommands.Test, + } +} + +/** + * Executes the /test command for generating unit tests in Chat for selected code. + * + * NOTE: Currently used by agent until inline test command is added to agent. + */ +export async function executeTestChatCommand( + args?: Partial +): Promise { + return wrapInActiveSpan('command.test-chat', async span => { + span.setAttribute('sampled', true) + + const editor = getEditor() + if ( + editor.active && + (await isUriIgnoredByContextFilterWithNotification(editor.active.document.uri, 'test')) + ) { + return + } + + logDebug('executeTestEditCommand', 'executing', { args }) + telemetryRecorder.recordEvent('cody.command.test', 'executed', { + metadata: { + useCodebaseContex: 0, + }, + interactionID: args?.requestID, + privateMetadata: { + requestID: args?.requestID, + source: args?.source, + traceId: span.spanContext().traceId, + }, + billingMetadata: { + product: 'cody', + category: 'core', + }, + }) + + return { + type: 'chat', + session: await executeChat(await unitTestCommand(span, args)), + } + }) +} diff --git a/vscode/src/main.ts b/vscode/src/main.ts index 5305d651a56c..102574fe1a6c 100644 --- a/vscode/src/main.ts +++ b/vscode/src/main.ts @@ -63,6 +63,7 @@ import { } from './commands/execute' import { executeAutoEditCommand } from './commands/execute/auto-edit' import { executeDocChatCommand } from './commands/execute/doc' +import { executeTestChatCommand } from './commands/execute/test-chat' import { CodySourceControl } from './commands/scm/source-control' import type { CodyCommandArgs } from './commands/types' import { newCodyCommandArgs } from './commands/utils/get-commands' @@ -385,7 +386,6 @@ async function registerCodyCommands( return undefined }) } - const executeCommandUnsafe = async ( id: DefaultCodyCommands | PromptString, args?: Partial @@ -402,6 +402,11 @@ async function registerCodyCommands( return await executeCodyCommand(id, newCodyCommandArgs(args)) } + // Register the execution command from above. + disposables.push( + vscode.commands.registerCommand('cody.action.command', (id, a) => executeCommand(id, a)) + ) + // Initialize supercompletion provider if experimental feature is enabled disposables.push( enableFeature( @@ -410,67 +415,69 @@ async function registerCodyCommands( ) ) + // Experimental Command: Auto Edit + disposables.push( + vscode.commands.registerCommand('cody.command.auto-edit', a => executeAutoEditCommand(a)) + ) + disposables.push( subscriptionDisposable( featureFlagProvider .evaluatedFeatureFlag(FeatureFlag.CodyUnifiedPrompts) .pipe( createDisposables(codyUnifiedPromptsFlag => { + // Commands that are available only if unified prompts feature is enabled. const unifiedPromptsEnabled = codyUnifiedPromptsFlag && !clientCapabilities().isCodyWeb + vscode.commands.executeCommand( 'setContext', 'cody.menu.custom-commands.enable', !unifiedPromptsEnabled ) + // NOTE: Soon to be deprecated and replaced by unified prompts. + const chatCommands = [ + // Register prompt-like command if unified prompts feature is available. + vscode.commands.registerCommand('cody.command.explain-code', a => + executeExplainCommand(a) + ), + vscode.commands.registerCommand('cody.command.smell-code', a => + executeSmellCommand(a) + ), + ] + + // NOTE: Soon to be deprecated and replaced by unified prompts. + const editCommands = [ + vscode.commands.registerCommand('cody.command.document-code', a => + executeDocCommand(a) + ), + ] + + const unitTestCommand = [ + vscode.commands.registerCommand('cody.command.unit-tests', a => + unifiedPromptsEnabled + ? executeTestChatCommand(a) + : executeTestEditCommand(a) + ), + ] + + // Prompt-like commands. + const unifiedPromptsCommands = [ + vscode.commands.registerCommand('cody.command.prompt-document-code', a => + executeDocChatCommand(a) + ), + ] + + // Register prompt-like command if unified prompts feature is available. return unifiedPromptsEnabled ? [ - // Register prompt-like command if unified prompts feature is available. - vscode.commands.registerCommand('cody.action.command', (id, a) => - executeCommand(id, a) - ), - vscode.commands.registerCommand('cody.command.explain-code', a => - executeExplainCommand(a) - ), - vscode.commands.registerCommand('cody.command.smell-code', a => - executeSmellCommand(a) - ), - vscode.commands.registerCommand('cody.command.document-code', a => - executeDocCommand(a) - ), - vscode.commands.registerCommand( - 'cody.command.prompt-document-code', - a => executeDocChatCommand(a) - ), - ] - : [ - // Otherwise register old-style commands. - vscode.commands.registerCommand('cody.action.command', (id, a) => - executeCommand(id, a) - ), - vscode.commands.registerCommand('cody.command.explain-code', a => - executeExplainCommand(a) - ), - vscode.commands.registerCommand('cody.command.smell-code', a => - executeSmellCommand(a) - ), - vscode.commands.registerCommand('cody.command.document-code', a => - executeDocCommand(a) - ), - vscode.commands.registerCommand('cody.command.unit-tests', a => - executeTestEditCommand(a) - ), - vscode.commands.registerCommand('cody.command.tests-cases', a => - executeTestCaseEditCommand(a) - ), - vscode.commands.registerCommand('cody.command.explain-output', a => - executeExplainOutput(a) - ), - vscode.commands.registerCommand('cody.command.auto-edit', a => - executeAutoEditCommand(a) - ), + ...chatCommands, + ...editCommands, + ...unitTestCommand, + ...unifiedPromptsCommands, ] + : [...chatCommands, ...editCommands, ...unitTestCommand] }) ) .subscribe({}) @@ -482,10 +489,17 @@ async function registerCodyCommands( * Features that are currently available only in VS Code. */ function registerVSCodeOnlyFeatures(chatClient: ChatClient, disposable: vscode.Disposable[]): void { - // Source Control Panel for generating commit message command. + // Generating commit message command in the VS Code Source Control Panel. disposable.push(new CodySourceControl(chatClient)) - // Command for executing CLI commands in the VS Code terminal. + // Command for executing CLI commands in the Terminal panel used by Smart Apply. disposable.push(new CodyTerminal()) + + disposable.push( + // Command that sends the selected output from the Terminal panel to Cody Chat for explanation. + vscode.commands.registerCommand('cody.command.explain-output', a => executeExplainOutput(a)), + // Internal Experimental: Command to generate additional test cases through Code Lenses in test files. + vscode.commands.registerCommand('cody.command.tests-cases', a => executeTestCaseEditCommand(a)) + ) } function enableFeature(