Skip to content

Commit c1005e9

Browse files
authored
Various improvements to CodeWhisperer codebase (#3823)
1. Refactor some duplicated code 2. Refactor some condition checking logic to improve readiability 3. Moved some of the states from RecommendationHandler to CodeWhisperer session since they are session scope(continued work), as well as removed some others that are not needed. 4. Fixed an issue where CodeWhisperer would try to get editor context (left/right context) for every request within a trigger session. These info only need to be retrieved once in the beginning of the session. 5. Fixed an issue where CodeWhisperer would try to use the language type of the current editor for sending telemetry; There could be cases when the current editor is no longer the original editor where invocation happens, leading to the telemetry to have the incorrect language type. 6. Fixed an issue where CodeWhisperer would keep stale suggestions as global states after the pagination has been requested to cancel. Add a check right after the service response and return immediately if the current pagination session is cancelled. 7. Right before showing the inline suggestions, do a check for the current window focus state. If it's not focused, mark all suggestions as discarded and exit this session. 8. Created a CodeWhispererApplication class to managed some CW specific application-wide listeners/variables.
1 parent c552091 commit c1005e9

18 files changed

+296
-255
lines changed

src/codewhisperer/activation.ts

Lines changed: 61 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ export async function activate(context: ExtContext): Promise<void> {
7575
*/
7676
const client = new codewhispererClient.DefaultCodeWhispererClient()
7777

78+
// Service initialization
79+
ReferenceInlineProvider.instance
80+
ImportAdderProvider.instance
81+
7882
context.extensionContext.subscriptions.push(
7983
/**
8084
* Configuration change
@@ -176,7 +180,7 @@ export async function activate(context: ExtContext): Promise<void> {
176180
if (isInlineCompletionEnabled() && e.uri.fsPath !== InlineCompletionService.instance.filePath()) {
177181
return
178182
}
179-
RecommendationHandler.instance.reportUserDecisionOfRecommendation(vscode.window.activeTextEditor, -1)
183+
RecommendationHandler.instance.reportUserDecisions(-1)
180184
}),
181185

182186
vscode.languages.registerHoverProvider(
@@ -259,49 +263,49 @@ export async function activate(context: ExtContext): Promise<void> {
259263
await RecommendationHandler.instance.onCursorChange(e)
260264
}),
261265
vscode.workspace.onDidChangeTextDocument(async e => {
266+
const editor = vscode.window.activeTextEditor
267+
if (!editor) {
268+
return
269+
}
270+
if (e.document !== editor.document) {
271+
return
272+
}
273+
if (!runtimeLanguageContext.isLanguageSupported(e.document.languageId)) {
274+
return
275+
}
276+
262277
/**
263278
* CodeWhisperer security panel dynamic handling
264279
*/
265-
if (e.document === vscode.window.activeTextEditor?.document) {
266-
disposeSecurityDiagnostic(e)
267-
}
280+
disposeSecurityDiagnostic(e)
268281

269282
CodeWhispererCodeCoverageTracker.getTracker(e.document.languageId)?.countTotalTokens(e)
270283

271284
/**
272285
* Handle this keystroke event only when
273-
* 1. It is in current non plaintext active editor
274-
* 2. It is not a backspace
275-
* 3. It is not caused by CodeWhisperer editing
276-
* 4. It is not from undo/redo.
286+
* 1. It is not a backspace
287+
* 2. It is not caused by CodeWhisperer editing
288+
* 3. It is not from undo/redo.
277289
*/
278-
if (
279-
e.document === vscode.window.activeTextEditor?.document &&
280-
runtimeLanguageContext.isLanguageSupported(e.document.languageId) &&
281-
e.contentChanges.length != 0 &&
282-
!vsCodeState.isCodeWhispererEditing
283-
) {
284-
if (vsCodeState.lastUserModificationTime) {
285-
TelemetryHelper.instance.setTimeSinceLastModification(
286-
performance.now() - vsCodeState.lastUserModificationTime
287-
)
288-
}
289-
vsCodeState.lastUserModificationTime = performance.now()
290-
/**
291-
* Important: Doing this sleep(10) is to make sure
292-
* 1. this event is processed by vs code first
293-
* 2. editor.selection.active has been successfully updated by VS Code
294-
* Then this event can be processed by our code.
295-
*/
296-
await sleep(CodeWhispererConstants.vsCodeCursorUpdateDelay)
297-
if (!RecommendationHandler.instance.isSuggestionVisible()) {
298-
await KeyStrokeHandler.instance.processKeyStroke(
299-
e,
300-
vscode.window.activeTextEditor,
301-
client,
302-
await getConfigEntry()
303-
)
304-
}
290+
if (e.contentChanges.length === 0 || vsCodeState.isCodeWhispererEditing) {
291+
return
292+
}
293+
294+
if (vsCodeState.lastUserModificationTime) {
295+
TelemetryHelper.instance.setTimeSinceLastModification(
296+
performance.now() - vsCodeState.lastUserModificationTime
297+
)
298+
}
299+
vsCodeState.lastUserModificationTime = performance.now()
300+
/**
301+
* Important: Doing this sleep(10) is to make sure
302+
* 1. this event is processed by vs code first
303+
* 2. editor.selection.active has been successfully updated by VS Code
304+
* Then this event can be processed by our code.
305+
*/
306+
await sleep(CodeWhispererConstants.vsCodeCursorUpdateDelay)
307+
if (!RecommendationHandler.instance.isSuggestionVisible()) {
308+
await KeyStrokeHandler.instance.processKeyStroke(e, editor, client, await getConfigEntry())
305309
}
306310
})
307311
)
@@ -327,39 +331,33 @@ export async function activate(context: ExtContext): Promise<void> {
327331
* Automated trigger
328332
*/
329333
vscode.workspace.onDidChangeTextDocument(async e => {
334+
const editor = vscode.window.activeTextEditor
335+
if (!editor) {
336+
return
337+
}
338+
if (e.document !== editor.document) {
339+
return
340+
}
341+
if (!runtimeLanguageContext.isLanguageSupported(e.document.languageId)) {
342+
return
343+
}
330344
/**
331345
* CodeWhisperer security panel dynamic handling
332346
*/
333-
if (e.document === vscode.window.activeTextEditor?.document) {
334-
if (isCloud9()) {
335-
securityPanelViewProvider.disposeSecurityPanelItem(e, vscode.window.activeTextEditor)
336-
} else {
337-
disposeSecurityDiagnostic(e)
338-
}
339-
}
340-
347+
securityPanelViewProvider.disposeSecurityPanelItem(e, editor)
341348
CodeWhispererCodeCoverageTracker.getTracker(e.document.languageId)?.countTotalTokens(e)
342349

343-
if (
344-
e.document === vscode.window.activeTextEditor?.document &&
345-
runtimeLanguageContext.isLanguageSupported(e.document.languageId) &&
346-
e.contentChanges.length != 0 &&
347-
!vsCodeState.isCodeWhispererEditing
348-
) {
349-
/**
350-
* Important: Doing this sleep(10) is to make sure
351-
* 1. this event is processed by vs code first
352-
* 2. editor.selection.active has been successfully updated by VS Code
353-
* Then this event can be processed by our code.
354-
*/
355-
await sleep(CodeWhispererConstants.vsCodeCursorUpdateDelay)
356-
await KeyStrokeHandler.instance.processKeyStroke(
357-
e,
358-
vscode.window.activeTextEditor,
359-
client,
360-
await getConfigEntry()
361-
)
350+
if (e.contentChanges.length != 0 && !vsCodeState.isCodeWhispererEditing) {
351+
return
362352
}
353+
/**
354+
* Important: Doing this sleep(10) is to make sure
355+
* 1. this event is processed by vs code first
356+
* 2. editor.selection.active has been successfully updated by VS Code
357+
* Then this event can be processed by our code.
358+
*/
359+
await sleep(CodeWhispererConstants.vsCodeCursorUpdateDelay)
360+
await KeyStrokeHandler.instance.processKeyStroke(e, editor, client, await getConfigEntry())
363361
}),
364362

365363
/**
@@ -389,7 +387,7 @@ export async function activate(context: ExtContext): Promise<void> {
389387
}
390388

391389
export async function shutdown() {
392-
RecommendationHandler.instance.reportUserDecisionOfRecommendation(vscode.window.activeTextEditor, -1)
390+
RecommendationHandler.instance.reportUserDecisions(-1)
393391
CodeWhispererTracker.getTracker().shutdown()
394392
}
395393

src/codewhisperer/commands/invokeRecommendation.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import * as vscode from 'vscode'
7-
import { vsCodeState, ConfigurationEntry } from '../models/model'
7+
import { vsCodeState, ConfigurationEntry, GetRecommendationsResponse } from '../models/model'
88
import { resetIntelliSenseState } from '../util/globalStateUtil'
99
import { DefaultCodeWhispererClient } from '../client/codewhisperer'
1010
import { isCloud9 } from '../../shared/extensionUtilities'
@@ -64,8 +64,9 @@ export async function invokeRecommendation(
6464
vsCodeState.isIntelliSenseActive = false
6565
RecommendationHandler.instance.isGenerateRecommendationInProgress = true
6666
try {
67+
let response: GetRecommendationsResponse
6768
if (isCloud9('classic') || isIamConnection(AuthUtil.instance.conn)) {
68-
await RecommendationHandler.instance.getRecommendations(
69+
response = await RecommendationHandler.instance.getRecommendations(
6970
client,
7071
editor,
7172
'OnDemand',
@@ -77,7 +78,7 @@ export async function invokeRecommendation(
7778
if (AuthUtil.instance.isConnectionExpired()) {
7879
await AuthUtil.instance.showReauthenticatePrompt()
7980
}
80-
await RecommendationHandler.instance.getRecommendations(
81+
response = await RecommendationHandler.instance.getRecommendations(
8182
client,
8283
editor,
8384
'OnDemand',
@@ -86,7 +87,7 @@ export async function invokeRecommendation(
8687
true
8788
)
8889
}
89-
if (RecommendationHandler.instance.canShowRecommendationInIntelliSense(editor, true)) {
90+
if (RecommendationHandler.instance.canShowRecommendationInIntelliSense(editor, true, response)) {
9091
await vscode.commands.executeCommand('editor.action.triggerSuggest').then(() => {
9192
vsCodeState.isIntelliSenseActive = true
9293
})

src/codewhisperer/commands/onAcceptance.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,5 @@ export async function onAcceptance(acceptanceEntry: OnRecommendationAcceptanceEn
8383
}
8484

8585
// at the end of recommendation acceptance, report user decisions and clear recommendations.
86-
RecommendationHandler.instance.reportUserDecisionOfRecommendation(
87-
acceptanceEntry.editor,
88-
acceptanceEntry.acceptIndex
89-
)
86+
RecommendationHandler.instance.reportUserDecisions(acceptanceEntry.acceptIndex)
9087
}

src/codewhisperer/commands/onInlineAcceptance.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ export async function onInlineAcceptance(
129129
)
130130
}
131131

132-
RecommendationHandler.instance.reportUserDecisionOfRecommendation(
133-
acceptanceEntry.editor,
134-
acceptanceEntry.acceptIndex
135-
)
132+
RecommendationHandler.instance.reportUserDecisions(acceptanceEntry.acceptIndex)
136133
}
137134
}

src/codewhisperer/models/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export const artifactTypeSource = 'SourceCode'
217217
export const codeScanFindingsSchema = 'codescan/findings/1.0'
218218

219219
// wait time for editor to update editor.selection.active (in milliseconds)
220-
export const vsCodeCursorUpdateDelay = 3
220+
export const vsCodeCursorUpdateDelay = 10
221221

222222
export const reloadWindow = 'Reload Now'
223223

src/codewhisperer/models/model.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ export const vsCodeState: VsCodeState = {
3636
lastUserModificationTime: 0,
3737
}
3838

39+
// This response struct can contain more info as needed
40+
export interface GetRecommendationsResponse {
41+
readonly result: 'Succeeded' | 'Failed'
42+
readonly errorMessage: string | undefined
43+
}
44+
3945
export interface AcceptedSuggestionEntry {
4046
readonly time: Date
4147
readonly fileUrl: vscode.Uri

src/codewhisperer/service/importAdderProvider.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { isCloud9 } from '../../shared/extensionUtilities'
88
import { Recommendation } from '../client/codewhisperer'
99
import { CodeWhispererSettings } from '../util/codewhispererSettings'
1010
import { findLineToInsertImportStatement } from '../util/importAdderUtil'
11+
import { application } from '../util/codeWhispererApplication'
1112

1213
/**
1314
* ImportAdderProvider
@@ -27,6 +28,12 @@ export class ImportAdderProvider implements vscode.CodeLensProvider {
2728

2829
static #instance: ImportAdderProvider
2930

31+
constructor() {
32+
application().clearCodeWhispererUIListener(_ => {
33+
this.clear()
34+
})
35+
}
36+
3037
public static get instance() {
3138
return (this.#instance ??= new this())
3239
}

src/codewhisperer/service/inlineCompletionItemProvider.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { TelemetryHelper } from '../util/telemetryHelper'
1010
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
1111
import { ReferenceInlineProvider } from './referenceInlineProvider'
1212
import { ImportAdderProvider } from './importAdderProvider'
13+
import { application } from '../util/codeWhispererApplication'
1314

1415
export class CWInlineCompletionItemProvider implements vscode.InlineCompletionItemProvider {
1516
private activeItemIndex: number | undefined
@@ -133,8 +134,7 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt
133134
token: vscode.CancellationToken
134135
): vscode.ProviderResult<vscode.InlineCompletionItem[] | vscode.InlineCompletionList> {
135136
if (position.line < 0 || position.isBefore(this.startPos)) {
136-
ReferenceInlineProvider.instance.removeInlineReference()
137-
ImportAdderProvider.instance.clear()
137+
application()._clearCodeWhispererUIListener.fire()
138138
this.activeItemIndex = undefined
139139
return
140140
}
@@ -164,7 +164,7 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt
164164
ImportAdderProvider.instance.onShowRecommendation(document, this.startPos.line, r)
165165
this.nextMove = 0
166166
TelemetryHelper.instance.setFirstSuggestionShowTime()
167-
TelemetryHelper.instance.tryRecordClientComponentLatency(document.languageId)
167+
TelemetryHelper.instance.tryRecordClientComponentLatency()
168168
this._onDidShow.fire()
169169
if (matchedCount >= 2 || this.nextToken !== '') {
170170
const result = [item]
@@ -175,8 +175,7 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt
175175
}
176176
return [item]
177177
}
178-
ReferenceInlineProvider.instance.removeInlineReference()
179-
ImportAdderProvider.instance.clear()
178+
application()._clearCodeWhispererUIListener.fire()
180179
this.activeItemIndex = undefined
181180
return []
182181
}

src/codewhisperer/service/inlineCompletionService.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55
import * as vscode from 'vscode'
6-
import { ConfigurationEntry, vsCodeState } from '../models/model'
6+
import { ConfigurationEntry, GetRecommendationsResponse, vsCodeState } from '../models/model'
77
import * as CodeWhispererConstants from '../models/constants'
88
import { DefaultCodeWhispererClient } from '../client/codewhisperer'
99
import { RecommendationHandler } from './recommendationHandler'
@@ -15,6 +15,7 @@ import { AuthUtil } from '../util/authUtil'
1515
import { shared } from '../../shared/utilities/functionUtils'
1616
import { ClassifierTrigger } from './classifierTrigger'
1717
import { session } from '../util/codeWhispererSession'
18+
import { noSuggestions } from '../models/constants'
1819

1920
const performance = globalThis.performance ?? require('perf_hooks').performance
2021

@@ -80,6 +81,10 @@ export class InlineCompletionService {
8081
) {
8182
return
8283
}
84+
85+
// Call report user decisions once to report recommendations leftover from last invocation.
86+
RecommendationHandler.instance.reportUserDecisions(-1)
87+
8388
this.setCodeWhispererStatusBarLoading()
8489
if (ClassifierTrigger.instance.shouldInvokeClassifier(editor.document.languageId)) {
8590
ClassifierTrigger.instance.recordClassifierResultForAutoTrigger(editor, autoTriggerType, event)
@@ -96,10 +101,14 @@ export class InlineCompletionService {
96101
TelemetryHelper.instance.setInvocationStartTime(performance.now())
97102
RecommendationHandler.instance.checkAndResetCancellationTokens()
98103
RecommendationHandler.instance.documentUri = editor.document.uri
104+
let response: GetRecommendationsResponse = {
105+
result: 'Failed',
106+
errorMessage: undefined,
107+
}
99108
try {
100109
let page = 0
101110
while (page < this.maxPage) {
102-
await RecommendationHandler.instance.getRecommendations(
111+
response = await RecommendationHandler.instance.getRecommendations(
103112
client,
104113
editor,
105114
triggerType,
@@ -109,8 +118,8 @@ export class InlineCompletionService {
109118
page
110119
)
111120
if (RecommendationHandler.instance.checkAndResetCancellationTokens()) {
112-
// RecommendationHandler.instance.reportUserDecisionOfRecommendation(editor, -1)
113-
// vscode.commands.executeCommand('aws.codeWhisperer.refreshStatusBar')
121+
RecommendationHandler.instance.reportUserDecisions(-1)
122+
vscode.commands.executeCommand('aws.codeWhisperer.refreshStatusBar')
114123
TelemetryHelper.instance.setIsRequestCancelled(true)
115124
return
116125
}
@@ -125,13 +134,9 @@ export class InlineCompletionService {
125134
}
126135
vscode.commands.executeCommand('aws.codeWhisperer.refreshStatusBar')
127136
if (triggerType === 'OnDemand' && session.recommendations.length === 0) {
128-
if (RecommendationHandler.instance.errorMessagePrompt !== '') {
129-
showTimedMessage(RecommendationHandler.instance.errorMessagePrompt, 2000)
130-
} else {
131-
showTimedMessage(CodeWhispererConstants.noSuggestions, 2000)
132-
}
137+
showTimedMessage(response.errorMessage ? response.errorMessage : noSuggestions, 2000)
133138
}
134-
TelemetryHelper.instance.tryRecordClientComponentLatency(editor.document.languageId)
139+
TelemetryHelper.instance.tryRecordClientComponentLatency()
135140
}
136141

137142
setCodeWhispererStatusBarLoading() {

0 commit comments

Comments
 (0)