33 * SPDX-License-Identifier: Apache-2.0
44 */
55
6- import { getLogger , setContext } from 'aws-core-vscode/shared'
6+ import { getContext , getLogger , setContext } from 'aws-core-vscode/shared'
77import * as vscode from 'vscode'
8- import { diffLines } from 'diff'
8+ import { applyPatch , diffLines } from 'diff'
99import { LanguageClient } from 'vscode-languageclient'
1010import { CodeWhispererSession } from '../sessionManager'
1111import { LogInlineCompletionSessionResultsParams } from '@aws/language-server-runtimes/protocol'
1212import { InlineCompletionItemWithReferences } from '@aws/language-server-runtimes/protocol'
1313import path from 'path'
1414import { imageVerticalOffset } from './svgGenerator'
15- import { AmazonQInlineCompletionItemProvider } from '../completion'
15+ import { EditSuggestionState } from '../editSuggestionState'
16+ import type { AmazonQInlineCompletionItemProvider } from '../completion'
1617import { vsCodeState } from 'aws-core-vscode/codewhisperer'
1718
19+ const autoRejectEditCursorDistance = 25
20+
1821export class EditDecorationManager {
1922 private imageDecorationType : vscode . TextEditorDecorationType
2023 private removedCodeDecorationType : vscode . TextEditorDecorationType
@@ -136,6 +139,7 @@ export class EditDecorationManager {
136139 await this . clearDecorations ( editor )
137140
138141 await setContext ( 'aws.amazonq.editSuggestionActive' as any , true )
142+ EditSuggestionState . setEditSuggestionActive ( true )
139143
140144 this . acceptHandler = onAccept
141145 this . rejectHandler = onReject
@@ -166,6 +170,7 @@ export class EditDecorationManager {
166170 this . acceptHandler = undefined
167171 this . rejectHandler = undefined
168172 await setContext ( 'aws.amazonq.editSuggestionActive' as any , false )
173+ EditSuggestionState . setEditSuggestionActive ( false )
169174 }
170175
171176 /**
@@ -270,6 +275,28 @@ function getEndOfEditPosition(originalCode: string, newCode: string): vscode.Pos
270275 return editor ? editor . selection . active : new vscode . Position ( 0 , 0 )
271276}
272277
278+ /**
279+ * Helper function to create discard telemetry params
280+ */
281+ function createDiscardTelemetryParams (
282+ session : CodeWhispererSession ,
283+ item : InlineCompletionItemWithReferences
284+ ) : LogInlineCompletionSessionResultsParams {
285+ return {
286+ sessionId : session . sessionId ,
287+ completionSessionResult : {
288+ [ item . itemId ] : {
289+ seen : false ,
290+ accepted : false ,
291+ discarded : true ,
292+ } ,
293+ } ,
294+ totalSessionDisplayTime : Date . now ( ) - session . requestStartTime ,
295+ firstCompletionDisplayLatency : session . firstCompletionDisplayLatency ,
296+ isInlineEdit : true ,
297+ }
298+ }
299+
273300/**
274301 * Helper function to display SVG decorations
275302 */
@@ -286,6 +313,54 @@ export async function displaySvgDecoration(
286313) {
287314 const originalCode = editor . document . getText ( )
288315
316+ // Check if a completion suggestion is currently active - if so, discard edit suggestion
317+ if ( inlineCompletionProvider && ( await inlineCompletionProvider . isCompletionActive ( ) ) ) {
318+ // Emit DISCARD telemetry for edit suggestion that can't be shown due to active completion
319+ const params = createDiscardTelemetryParams ( session , item )
320+ languageClient . sendNotification ( 'aws/logInlineCompletionSessionResults' , params )
321+ getLogger ( ) . info ( 'Edit suggestion discarded due to active completion suggestion' )
322+ return
323+ }
324+
325+ const isPatchValid = applyPatch ( editor . document . getText ( ) , item . insertText as string )
326+ if ( ! isPatchValid ) {
327+ const params = createDiscardTelemetryParams ( session , item )
328+ // TODO: this session is closed on flare side hence discarded is not emitted in flare
329+ languageClient . sendNotification ( 'aws/logInlineCompletionSessionResults' , params )
330+ return
331+ }
332+ const documentChangeListener = vscode . workspace . onDidChangeTextDocument ( ( e ) => {
333+ if ( e . contentChanges . length <= 0 ) {
334+ return
335+ }
336+ if ( e . document !== editor . document ) {
337+ return
338+ }
339+ if ( vsCodeState . isCodeWhispererEditing ) {
340+ return
341+ }
342+ if ( getContext ( 'aws.amazonq.editSuggestionActive' ) === false ) {
343+ return
344+ }
345+
346+ const isPatchValid = applyPatch ( e . document . getText ( ) , item . insertText as string )
347+ if ( ! isPatchValid ) {
348+ void vscode . commands . executeCommand ( 'aws.amazonq.inline.rejectEdit' )
349+ }
350+ } )
351+ const cursorChangeListener = vscode . window . onDidChangeTextEditorSelection ( ( e ) => {
352+ if ( ! EditSuggestionState . isEditSuggestionActive ( ) ) {
353+ return
354+ }
355+ if ( e . textEditor !== editor ) {
356+ return
357+ }
358+ const currentPosition = e . selections [ 0 ] . active
359+ const distance = Math . abs ( currentPosition . line - startLine )
360+ if ( distance > autoRejectEditCursorDistance ) {
361+ void vscode . commands . executeCommand ( 'aws.amazonq.inline.rejectEdit' )
362+ }
363+ } )
289364 await decorationManager . displayEditSuggestion (
290365 editor ,
291366 svgImage ,
@@ -310,6 +385,8 @@ export async function displaySvgDecoration(
310385 editor . selection = new vscode . Selection ( endPosition , endPosition )
311386
312387 await decorationManager . clearDecorations ( editor )
388+ documentChangeListener . dispose ( )
389+ cursorChangeListener . dispose ( )
313390 const params : LogInlineCompletionSessionResultsParams = {
314391 sessionId : session . sessionId ,
315392 completionSessionResult : {
@@ -343,6 +420,8 @@ export async function displaySvgDecoration(
343420 // Handle reject
344421 getLogger ( ) . info ( 'Edit suggestion rejected' )
345422 await decorationManager . clearDecorations ( editor )
423+ documentChangeListener . dispose ( )
424+ cursorChangeListener . dispose ( )
346425 const params : LogInlineCompletionSessionResultsParams = {
347426 sessionId : session . sessionId ,
348427 completionSessionResult : {
0 commit comments