Skip to content

Commit c837759

Browse files
authored
fix(codewhisperer): re-enable suggestion navigation in vscode 1.78+ #3471
Problem: 1. Suggestion navigation is no longer working when using VS Code 1.78+ 2. Users need to hit Esc twice to clear/reject a code suggestion. Root cause: The dispose method of a inlineCompletionProvider used to clear the inline Completion widget, which allows us to update the widget whenever we want to by doing a dispose and doing another register with a new inlineCompletionProvider. However, after VS Code 1.78+, disposing the provider no longer clears the active inline completion widget, which is a regression because disposing a object should clear the states it created. Solution: Manually refresh the suggestion ghost text after each dispose of `inlineCompletionProvider` only for VS Code 1.78+.
1 parent bd1b892 commit c837759

File tree

5 files changed

+72
-38
lines changed

5 files changed

+72
-38
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "CodeWhisperer: cannot navigate to next or previous code suggestion in VS Code 1.78+"
4+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3175,7 +3175,7 @@
31753175
"command": "aws.codeWhisperer.rejectCodeSuggestion",
31763176
"key": "escape",
31773177
"mac": "escape",
3178-
"when": "editorTextFocus && CODEWHISPERER_SERVICE_ACTIVE"
3178+
"when": "inlineSuggestionVisible && !editorReadonly && CODEWHISPERER_ENABLED"
31793179
},
31803180
{
31813181
"command": "aws.codeWhisperer.rejectCodeSuggestion",

src/codewhisperer/models/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const supportedLanguages = [
9797
'sql',
9898
] as const
9999

100-
export type SupportedLanguage = typeof supportedLanguages[number]
100+
export type SupportedLanguage = (typeof supportedLanguages)[number]
101101

102102
/**
103103
* Prompt
@@ -282,3 +282,5 @@ export const stopScanMessage =
282282
export const showScannedFilesMessage = 'Show Scanned Files'
283283

284284
export const isClassifierEnabledKey = 'CODEWHISPERER_CLASSIFIER_TRIGGER_ENABLED'
285+
286+
export const updateInlineLockKey = 'CODEWHISPERER_INLINE_UPDATE_LOCK_KEY'

src/codewhisperer/service/inlineCompletionService.ts

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ import { getLogger } from '../../shared/logger/logger'
1818
import { TelemetryHelper } from '../util/telemetryHelper'
1919
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
2020
import { Commands } from '../../shared/vscode/commands2'
21-
import { getPrefixSuffixOverlap } from '../util/commonUtil'
21+
import { getPrefixSuffixOverlap, isVscHavingRegressionInlineCompletionApi } from '../util/commonUtil'
2222
import globals from '../../shared/extensionGlobals'
2323
import { AuthUtil } from '../util/authUtil'
2424
import { shared } from '../../shared/utilities/functionUtils'
2525
import { ImportAdderProvider } from './importAdderProvider'
26+
import * as AsyncLock from 'async-lock'
27+
import { updateInlineLockKey } from '../models/constants'
2628

2729
const performance = globalThis.performance ?? require('perf_hooks').performance
30+
const lock = new AsyncLock({ maxPending: 1 })
2831

2932
export class CWInlineCompletionItemProvider implements vscode.InlineCompletionItemProvider {
3033
private activeItemIndex: number | undefined
@@ -219,8 +222,8 @@ const nextCommand = Commands.declare(
219222
}
220223
)
221224

222-
const hideCommand = Commands.declare(
223-
'editor.action.inlineSuggest.hide',
225+
const rejectCommand = Commands.declare(
226+
'aws.codeWhisperer.rejectCodeSuggestion',
224227
(service: InlineCompletionService) => async () => {
225228
await service.clearInlineCompletionStates(vscode.window.activeTextEditor)
226229
}
@@ -234,15 +237,15 @@ export class InlineCompletionService {
234237
private _timer?: NodeJS.Timer
235238
private _showRecommendationTimer?: NodeJS.Timer
236239
private documentUri: vscode.Uri | undefined = undefined
237-
private hide: vscode.Disposable
240+
private reject: vscode.Disposable
238241
private next: vscode.Disposable
239242
private prev: vscode.Disposable
240243
private _isPaginationRunning = false
241244

242245
constructor() {
243246
this.prev = prevCommand.register(this)
244247
this.next = nextCommand.register(this)
245-
this.hide = hideCommand.register(this)
248+
this.reject = rejectCommand.register(this)
246249
RecommendationHandler.instance.onDidReceiveRecommendation(e => {
247250
this.startShowRecommendationTimer()
248251
})
@@ -287,20 +290,20 @@ export class InlineCompletionService {
287290
private registerCommandOverrides() {
288291
this.prev = prevCommand.register(this)
289292
this.next = nextCommand.register(this)
290-
this.hide = hideCommand.register(this)
293+
this.reject = rejectCommand.register(this)
291294
}
292295

293296
subscribeSuggestionCommands() {
294297
this.disposeCommandOverrides()
295298
this.registerCommandOverrides()
296299
globals.context.subscriptions.push(this.prev)
297300
globals.context.subscriptions.push(this.next)
298-
globals.context.subscriptions.push(this.hide)
301+
globals.context.subscriptions.push(this.reject)
299302
}
300303

301304
private disposeCommandOverrides() {
302305
this.prev.dispose()
303-
this.hide.dispose()
306+
this.reject.dispose()
304307
this.next.dispose()
305308
}
306309

@@ -347,6 +350,11 @@ export class InlineCompletionService {
347350
this.disposeInlineCompletion()
348351
vscode.commands.executeCommand('aws.codeWhisperer.refreshStatusBar')
349352
this.disposeCommandOverrides()
353+
// fix a regression that requires user to hit Esc twice to clear inline ghost text
354+
// because disposing a provider does not clear the UX
355+
if (isVscHavingRegressionInlineCompletionApi()) {
356+
await vscode.commands.executeCommand('editor.action.inlineSuggest.hide')
357+
}
350358
} finally {
351359
this.clearRejectionTimer()
352360
}
@@ -360,7 +368,7 @@ export class InlineCompletionService {
360368
if (this.isSuggestionVisible()) {
361369
// to force refresh the visual cue so that the total recommendation count can be updated
362370
const index = this.inlineCompletionProvider?.getActiveItemIndex
363-
await this.showRecommendation(index ? index : 0, true)
371+
await this.showRecommendation(index ? index : 0, false)
364372
return
365373
}
366374
if (
@@ -440,34 +448,49 @@ export class InlineCompletionService {
440448
TelemetryHelper.instance.tryRecordClientComponentLatency(editor.document.languageId)
441449
}
442450

443-
async showRecommendation(indexShift: number, isFirstRecommendation: boolean = false) {
444-
this.inlineCompletionProvider = new CWInlineCompletionItemProvider(
445-
this.inlineCompletionProvider?.getActiveItemIndex,
446-
indexShift
447-
)
448-
this.inlineCompletionProviderDisposable?.dispose()
449-
// when suggestion is active, registering a new provider will let VS Code invoke inline API automatically
450-
this.inlineCompletionProviderDisposable = vscode.languages.registerInlineCompletionItemProvider(
451-
Object.assign([], CodeWhispererConstants.supportedLanguages),
452-
this.inlineCompletionProvider
453-
)
454-
if (isFirstRecommendation) {
455-
await vscode.commands.executeCommand(`editor.action.inlineSuggest.trigger`)
456-
if (vscode.window.activeTextEditor) {
457-
const languageContext = runtimeLanguageContext.getLanguageContext(
458-
vscode.window.activeTextEditor.document.languageId
459-
)
460-
telemetry.codewhisperer_perceivedLatency.emit({
461-
codewhispererRequestId: RecommendationHandler.instance.requestId,
462-
codewhispererSessionId: RecommendationHandler.instance.sessionId,
463-
codewhispererTriggerType: TelemetryHelper.instance.triggerType,
464-
codewhispererCompletionType: TelemetryHelper.instance.completionType,
465-
codewhispererLanguage: languageContext.language,
466-
duration: performance.now() - RecommendationHandler.instance.lastInvocationTime,
467-
passive: true,
468-
credentialStartUrl: TelemetryHelper.instance.startUrl,
469-
})
451+
async showRecommendation(indexShift: number, noSuggestionVisible: boolean = false) {
452+
await lock.acquire(updateInlineLockKey, async () => {
453+
const inlineCompletionProvider = new CWInlineCompletionItemProvider(
454+
this.inlineCompletionProvider?.getActiveItemIndex,
455+
indexShift
456+
)
457+
this.inlineCompletionProviderDisposable?.dispose()
458+
// when suggestion is active, registering a new provider will let VS Code invoke inline API automatically
459+
this.inlineCompletionProviderDisposable = vscode.languages.registerInlineCompletionItemProvider(
460+
Object.assign([], CodeWhispererConstants.supportedLanguages),
461+
inlineCompletionProvider
462+
)
463+
this.inlineCompletionProvider = inlineCompletionProvider
464+
465+
if (isVscHavingRegressionInlineCompletionApi() && !noSuggestionVisible) {
466+
// fix a regression in new VS Code when disposing and re-registering
467+
// a new provider does not auto refresh the inline suggestion widget
468+
// by manually refresh it
469+
await vscode.commands.executeCommand('editor.action.inlineSuggest.hide')
470+
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
471+
}
472+
if (noSuggestionVisible) {
473+
await vscode.commands.executeCommand(`editor.action.inlineSuggest.trigger`)
474+
this.sendPerceivedLatencyTelemetry()
470475
}
476+
})
477+
}
478+
479+
private sendPerceivedLatencyTelemetry() {
480+
if (vscode.window.activeTextEditor) {
481+
const languageContext = runtimeLanguageContext.getLanguageContext(
482+
vscode.window.activeTextEditor.document.languageId
483+
)
484+
telemetry.codewhisperer_perceivedLatency.emit({
485+
codewhispererRequestId: RecommendationHandler.instance.requestId,
486+
codewhispererSessionId: RecommendationHandler.instance.sessionId,
487+
codewhispererTriggerType: TelemetryHelper.instance.triggerType,
488+
codewhispererCompletionType: TelemetryHelper.instance.completionType,
489+
codewhispererLanguage: languageContext.language,
490+
duration: performance.now() - RecommendationHandler.instance.lastInvocationTime,
491+
passive: true,
492+
credentialStartUrl: TelemetryHelper.instance.startUrl,
493+
})
471494
}
472495
}
473496

src/codewhisperer/util/commonUtil.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ export function isInlineCompletionEnabled() {
2828
return semver.gte(vscode.version, '1.68.0') && getInlineSuggestEnabled() && !isCloud9()
2929
}
3030

31+
// This is the VS Code version that started to have regressions in inline completion API
32+
export function isVscHavingRegressionInlineCompletionApi() {
33+
return semver.gte(vscode.version, '1.78.0') && getInlineSuggestEnabled() && !isCloud9()
34+
}
35+
3136
export function getFileExt(languageId: string) {
3237
switch (languageId) {
3338
case 'java':

0 commit comments

Comments
 (0)