Skip to content

Commit

Permalink
feat(audoedit): add the accept command callback
Browse files Browse the repository at this point in the history
  • Loading branch information
valerybugakov committed Jan 7, 2025
1 parent bd308c0 commit 1dfd5ba
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 55 deletions.
15 changes: 12 additions & 3 deletions vscode/src/autoedits/analytics-logger/analytics-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { upstreamHealthProvider } from '../../services/UpstreamHealthProvider'
import { captureException, shouldErrorBeReported } from '../../services/sentry/sentry'
import { splitSafeMetadata } from '../../services/telemetry-v2'
import type { AutoeditsPrompt } from '../adapters/base'
import { autoeditsOutputChannelLogger } from '../output-channel-logger'
import type { CodeToReplaceData } from '../prompt/prompt-utils'
import type { DecorationInfo } from '../renderer/decorators/base'

Expand Down Expand Up @@ -545,8 +546,6 @@ export class AutoeditAnalyticsLogger {
if (result?.updatedRequest) {
this.writeAutoeditRequestEvent('suggested', result.updatedRequest)
this.writeAutoeditRequestEvent('accepted', result.updatedRequest)

this.activeRequests.delete(result.updatedRequest.requestId)
}
}

Expand Down Expand Up @@ -576,7 +575,6 @@ export class AutoeditAnalyticsLogger {

if (result?.updatedRequest) {
this.writeAutoeditRequestEvent('discarded', result.updatedRequest)
this.activeRequests.delete(result.updatedRequest.requestId)
}
}

Expand Down Expand Up @@ -666,6 +664,17 @@ export class AutoeditAnalyticsLogger {
action: AutoeditEventAction,
params?: TelemetryEventParameters<{ [key: string]: number }, BillingProduct, BillingCategory>
): void {
autoeditsOutputChannelLogger.logDebug(
'writeAutoeditEvent',
`${action} id: "${params?.interactionID ?? 'n/a'}"`,
{
verbose: {
latency: params?.metadata?.latency,
prediction: params?.privateMetadata?.prediction,
codeToRewrite: params?.privateMetadata?.codeToRewrite,
},
}
)
telemetryRecorder.recordEvent('cody.autoedit', action, params)
}

Expand Down
1 change: 1 addition & 0 deletions vscode/src/autoedits/autoedits-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ export class AutoeditsProvider implements vscode.InlineCompletionItemProvider, v

const { inlineCompletionItems, updatedDecorationInfo, updatedPrediction } =
this.rendererManager.tryMakeInlineCompletions({
requestId,
prediction,
codeToReplaceData,
document,
Expand Down
36 changes: 12 additions & 24 deletions vscode/src/autoedits/renderer/inline-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { isFileURI } from '@sourcegraph/cody-shared'

import { completionMatchesSuffix } from '../../completions/is-completion-visible'
import { getNewLineChar } from '../../completions/text-processing'
import { autoeditAnalyticsLogger } from '../analytics-logger'
import { autoeditsOutputChannelLogger } from '../output-channel-logger'
import { areSameUriDocs } from '../utils'

import type {
AddedLineInfo,
Expand All @@ -31,26 +29,6 @@ export class AutoEditsInlineRendererManager
extends AutoEditsDefaultRendererManager
implements AutoEditsRendererManager
{
protected async acceptEdit(): Promise<void> {
const editor = vscode.window.activeTextEditor
const { activeRequest } = this
if (
!editor ||
!activeRequest ||
!areSameUriDocs(editor.document, this.activeRequest?.document)
) {
return this.rejectEdit()
}

await vscode.commands.executeCommand('editor.action.inlineSuggest.hide')
await editor.edit(editBuilder => {
editBuilder.replace(activeRequest.codeToReplaceData.range, activeRequest.prediction)
})

await this.handleDidHideSuggestion()
autoeditAnalyticsLogger.markAsAccepted(activeRequest.requestId)
}

protected async onDidChangeTextEditorSelection(
event: vscode.TextEditorSelectionChangeEvent
): Promise<void> {
Expand All @@ -59,11 +37,12 @@ export class AutoEditsInlineRendererManager
// rendered as inline completion ghost text, which is hidden by default
// whenever the cursor moves.
if (isFileURI(event.textEditor.document.uri)) {
this.rejectEdit()
this.rejectActiveEdit()
}
}

tryMakeInlineCompletions({
requestId,
prediction,
document,
position,
Expand Down Expand Up @@ -94,7 +73,16 @@ export class AutoEditsInlineRendererManager
new vscode.Range(
document.lineAt(position).range.start,
document.lineAt(position).range.end
)
),
{
title: 'Autoedit accepted',
command: 'cody.supersuggest.accept',
arguments: [
{
requestId,
},
],
}
),
]

Expand Down
89 changes: 61 additions & 28 deletions vscode/src/autoedits/renderer/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import type { AutoEditsDecorator, DecorationInfo } from './decorators/base'

export interface TryMakeInlineCompletionsArgs {
requestId: AutoeditRequestID
prediction: string
codeToReplaceData: CodeToReplaceData
document: vscode.TextDocument
Expand Down Expand Up @@ -83,8 +84,8 @@ export class AutoEditsDefaultRendererManager implements AutoEditsRendererManager

constructor(protected createDecorator: (editor: vscode.TextEditor) => AutoEditsDecorator) {
this.disposables.push(
vscode.commands.registerCommand('cody.supersuggest.accept', () => this.acceptEdit()),
vscode.commands.registerCommand('cody.supersuggest.dismiss', () => this.rejectEdit()),
vscode.commands.registerCommand('cody.supersuggest.accept', () => this.acceptActiveEdit()),
vscode.commands.registerCommand('cody.supersuggest.dismiss', () => this.rejectActiveEdit()),
vscode.workspace.onDidChangeTextDocument(event => this.onDidChangeTextDocument(event)),
vscode.window.onDidChangeTextEditorSelection(event =>
this.onDidChangeTextEditorSelection(event)
Expand All @@ -97,33 +98,46 @@ export class AutoEditsDefaultRendererManager implements AutoEditsRendererManager
}

protected onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent): void {
// Only dismiss if we have an active suggestion and the changed document matches
// else, we will falsely discard the suggestion on unrelated changes such as changes in output panel.
if (areSameUriDocs(event.document, this.activeRequest?.document)) {
this.rejectEdit()
if (
// Only dismiss if there are inline decorations, as inline completion items rely on
// a native acceptance/rejection mechanism that we can't interfere with.
this.hasInlineDecorationOnly() &&
// Only dismiss if we have an active suggestion and the changed document matches
// else, we will falsely discard the suggestion on unrelated changes such as changes in output panel.
areSameUriDocs(event.document, this.activeRequest?.document)
) {
this.rejectActiveEdit()
}
}

protected onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined): void {
if (!editor || !areSameUriDocs(editor.document, this.activeRequest?.document)) {
this.rejectEdit()
this.rejectActiveEdit()
}
}

protected onDidCloseTextDocument(document: vscode.TextDocument): void {
if (areSameUriDocs(document, this.activeRequest?.document)) {
this.rejectEdit()
this.rejectActiveEdit()
}
}

protected onDidChangeTextEditorSelection(event: vscode.TextEditorSelectionChangeEvent): void {
if (
// Only dismiss if there are inline decorations, as inline completion items rely on
// a native acceptance/rejection mechanism that we can't interfere with.
//
// For instance, the acceptance command is triggered only after the document changes.
// This means we can't depend on document or selection changes to handle cases where
// inline completion items are accepted because they get rejected before the
// acceptance callback is fired by VS Code.
this.hasInlineDecorationOnly() &&
this.activeRequest &&
areSameUriDocs(event.textEditor.document, this.activeRequest?.document)
) {
const currentSelectionRange = event.selections.at(-1)
if (!currentSelectionRange?.intersection(this.activeRequest.codeToReplaceData.range)) {
this.rejectEdit()
this.rejectActiveEdit()
}
}
}
Expand All @@ -142,8 +156,12 @@ export class AutoEditsDefaultRendererManager implements AutoEditsRendererManager
return this.activeRequestId !== null
}

public hasInlineDecorationOnly(): boolean {
return !this.activeRequest?.inlineCompletionItems
}

public async handleDidShowSuggestion(requestId: AutoeditRequestID): Promise<void> {
await this.rejectEdit()
await this.rejectActiveEdit()

this.activeRequestId = requestId
this.decorator = this.createDecorator(vscode.window.activeTextEditor!)
Expand All @@ -163,43 +181,48 @@ export class AutoEditsDefaultRendererManager implements AutoEditsRendererManager
}, AUTOEDIT_VISIBLE_DELAY_MS)
}

protected async handleDidHideSuggestion(): Promise<void> {
if (this.activeRequest && this.decorator) {
protected async handleDidHideSuggestion(decorator: AutoEditsDecorator | null): Promise<void> {
if (decorator) {
decorator.dispose()
// Hide inline decorations
this.decorator.dispose()
await vscode.commands.executeCommand('setContext', 'cody.supersuggest.active', false)

// Hide inline completion provider item ghost text
await vscode.commands.executeCommand('editor.action.inlineSuggest.hide')
}

// Hide inline completion provider item ghost text
await vscode.commands.executeCommand('editor.action.inlineSuggest.hide')

this.activeRequestId = null
this.decorator = null
}

protected async acceptEdit(): Promise<void> {
protected async acceptActiveEdit(): Promise<void> {
const editor = vscode.window.activeTextEditor
const { activeRequest } = this
const { activeRequest, decorator } = this
if (
!editor ||
!activeRequest ||
!areSameUriDocs(editor.document, this.activeRequest?.document)
) {
return this.rejectEdit()
return this.rejectActiveEdit()
}

await this.handleDidHideSuggestion(decorator)
autoeditAnalyticsLogger.markAsAccepted(activeRequest.requestId)

await editor.edit(editBuilder => {
editBuilder.replace(activeRequest.codeToReplaceData.range, activeRequest.prediction)
})

autoeditAnalyticsLogger.markAsAccepted(activeRequest.requestId)
await this.handleDidHideSuggestion()
}

protected async rejectEdit(): Promise<void> {
if (this.activeRequest) {
autoeditAnalyticsLogger.markAsRejected(this.activeRequest.requestId)
await this.handleDidHideSuggestion()
protected async rejectActiveEdit(): Promise<void> {
const { activeRequest, decorator } = this

if (decorator) {
await this.handleDidHideSuggestion(decorator)
}

if (activeRequest) {
autoeditAnalyticsLogger.markAsRejected(activeRequest.requestId)
}
}

Expand All @@ -209,6 +232,7 @@ export class AutoEditsDefaultRendererManager implements AutoEditsRendererManager
}

tryMakeInlineCompletions({
requestId,
prediction,
codeToReplaceData,
document,
Expand Down Expand Up @@ -247,7 +271,16 @@ export class AutoEditsDefaultRendererManager implements AutoEditsRendererManager
new vscode.Range(
document.lineAt(position).range.start,
document.lineAt(position).range.end
)
),
{
title: 'Autoedit accepted',
command: 'cody.supersuggest.accept',
arguments: [
{
requestId,
},
],
}
)
autoeditsOutputChannelLogger.logDebug(
'tryMakeInlineCompletions',
Expand All @@ -273,7 +306,7 @@ export class AutoEditsDefaultRendererManager implements AutoEditsRendererManager
}

public dispose(): void {
this.rejectEdit()
this.rejectActiveEdit()
for (const disposable of this.disposables) {
disposable.dispose()
}
Expand Down

0 comments on commit 1dfd5ba

Please sign in to comment.