Skip to content

Commit

Permalink
Command: register missing commands when unifiedPrompts is enabled (#5768
Browse files Browse the repository at this point in the history
)

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

<!-- Required. See
https://docs-legacy.sourcegraph.com/dev/background-information/testing_principles.
-->

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

<!-- OPTIONAL; info at
https://www.notion.so/sourcegraph/Writing-a-changelog-entry-dd997f411d524caabf0d8d38a24a878c
-->

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.
  • Loading branch information
abeatrix authored Oct 1, 2024
1 parent 9dd8283 commit 7473517
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 47 deletions.
1 change: 1 addition & 0 deletions lib/shared/src/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions vscode/src/commands/CommandsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
114 changes: 114 additions & 0 deletions vscode/src/commands/execute/test-chat.ts
Original file line number Diff line number Diff line change
@@ -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<CodyCommandArgs>
): Promise<ExecuteChatArguments> {
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 <selected> 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 <selected> 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 <selected> 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('<selected>', 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<CodyCommandArgs>
): Promise<ChatCommandResult | undefined> {
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)),
}
})
}
108 changes: 61 additions & 47 deletions vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -385,7 +386,6 @@ async function registerCodyCommands(
return undefined
})
}

const executeCommandUnsafe = async (
id: DefaultCodyCommands | PromptString,
args?: Partial<CodyCommandArgs>
Expand All @@ -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(
Expand All @@ -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({})
Expand All @@ -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(
Expand Down

0 comments on commit 7473517

Please sign in to comment.