Skip to content

Commit 3a2df64

Browse files
committed
Merge remote-tracking branch 'origin/master' into HEAD
2 parents 9c5f854 + 0fcd624 commit 3a2df64

File tree

86 files changed

+281
-51713
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+281
-51713
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ src.gen/*
3131
**/src/shared/telemetry/clienttelemetry.d.ts
3232
**/src/codewhisperer/client/codewhispererclient.d.ts
3333
**/src/codewhisperer/client/codewhispereruserclient.d.ts
34-
**/src/amazonqFeatureDev/client/featuredevproxyclient.d.ts
3534
**/src/auth/sso/oidcclientpkce.d.ts
3635

3736
# Generated by tests

package-lock.json

Lines changed: 0 additions & 36873 deletions
This file was deleted.
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": "Faster and more responsive inline completion UX"
4+
}

packages/amazonq/package.json

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -521,22 +521,17 @@
521521
"command": "aws.amazonq.walkthrough.show",
522522
"group": "1_help@1"
523523
},
524-
{
525-
"command": "aws.amazonq.exploreAgents",
526-
"when": "!aws.isSageMaker",
527-
"group": "1_help@2"
528-
},
529524
{
530525
"command": "aws.amazonq.github",
531-
"group": "1_help@3"
526+
"group": "1_help@2"
532527
},
533528
{
534529
"command": "aws.amazonq.aboutExtension",
535-
"group": "1_help@4"
530+
"group": "1_help@3"
536531
},
537532
{
538533
"command": "aws.amazonq.viewLogs",
539-
"group": "1_help@5"
534+
"group": "1_help@4"
540535
}
541536
],
542537
"aws.amazonq.submenu.securityIssueMoreActions": [
@@ -843,12 +838,6 @@
843838
"title": "%AWS.amazonq.openChat%",
844839
"category": "%AWS.amazonq.title%"
845840
},
846-
{
847-
"command": "aws.amazonq.exploreAgents",
848-
"title": "%AWS.amazonq.exploreAgents%",
849-
"category": "%AWS.amazonq.title%",
850-
"enablement": "aws.codewhisperer.connected && !aws.isSageMaker"
851-
},
852841
{
853842
"command": "aws.amazonq.walkthrough.show",
854843
"title": "%AWS.amazonq.welcomeWalkthrough%"
@@ -972,6 +961,10 @@
972961
"command": "aws.amazonq.showPrev",
973962
"when": "inlineSuggestionVisible && !editorReadonly && aws.codewhisperer.connected"
974963
},
964+
{
965+
"command": "aws.amazonq.checkInlineSuggestionVisibility",
966+
"when": "inlineSuggestionVisible && !editorReadonly && aws.codewhisperer.connected"
967+
},
975968
{
976969
"command": "aws.amazonq.inline.invokeChat",
977970
"win": "ctrl+i",

packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class SvgGenerationService {
3030
origionalCodeHighlightRange: Range[]
3131
}> {
3232
const textDoc = await vscode.workspace.openTextDocument(filePath)
33-
const originalCode = textDoc.getText()
33+
const originalCode = textDoc.getText().replaceAll('\r\n', '\n')
3434
if (originalCode === '') {
3535
logger.error(`udiff format error`)
3636
throw new ToolkitError('udiff format error')

packages/amazonq/src/app/inline/completion.ts

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5-
5+
import * as vscode from 'vscode'
66
import {
77
CancellationToken,
88
InlineCompletionContext,
@@ -32,7 +32,6 @@ import {
3232
ImportAdderProvider,
3333
CodeSuggestionsState,
3434
vsCodeState,
35-
inlineCompletionsDebounceDelay,
3635
noInlineSuggestionsMsg,
3736
getDiagnosticsDifferences,
3837
getDiagnosticsOfCurrentFile,
@@ -42,7 +41,7 @@ import { LineTracker } from './stateTracker/lineTracker'
4241
import { InlineTutorialAnnotation } from './tutorials/inlineTutorialAnnotation'
4342
import { TelemetryHelper } from './telemetryHelper'
4443
import { Experiments, getLogger, sleep } from 'aws-core-vscode/shared'
45-
import { debounce, messageUtils } from 'aws-core-vscode/utils'
44+
import { messageUtils } from 'aws-core-vscode/utils'
4645
import { showEdits } from './EditRendering/imageRenderer'
4746
import { ICursorUpdateRecorder } from './cursorUpdateManager'
4847
import { DocumentEventListener } from './documentEventListener'
@@ -164,6 +163,11 @@ export class InlineCompletionManager implements Disposable {
164163
const onInlineRejection = async () => {
165164
try {
166165
vsCodeState.isCodeWhispererEditing = true
166+
if (this.sessionManager.getActiveSession() === undefined) {
167+
return
168+
}
169+
const requestStartTime = this.sessionManager.getActiveSession()!.requestStartTime
170+
const totalSessionDisplayTime = performance.now() - requestStartTime
167171
await commands.executeCommand('editor.action.inlineSuggest.hide')
168172
// TODO: also log the seen state for other suggestions in session
169173
this.disposable.dispose()
@@ -185,6 +189,7 @@ export class InlineCompletionManager implements Disposable {
185189
discarded: false,
186190
},
187191
},
192+
totalSessionDisplayTime: totalSessionDisplayTime,
188193
}
189194
this.languageClient.sendNotification(this.logSessionResultMessageName, params)
190195
// clear session manager states once rejected
@@ -198,7 +203,7 @@ export class InlineCompletionManager implements Disposable {
198203
}
199204

200205
export class AmazonQInlineCompletionItemProvider implements InlineCompletionItemProvider {
201-
private logger = getLogger('nextEditPrediction')
206+
private logger = getLogger()
202207
constructor(
203208
private readonly languageClient: LanguageClient,
204209
private readonly recommendationService: RecommendationService,
@@ -208,13 +213,23 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
208213
) {}
209214

210215
private readonly logSessionResultMessageName = 'aws/logInlineCompletionSessionResults'
211-
provideInlineCompletionItems = debounce(
212-
this._provideInlineCompletionItems.bind(this),
213-
inlineCompletionsDebounceDelay,
214-
true
215-
)
216216

217-
private async _provideInlineCompletionItems(
217+
// Ideally use this API handleDidShowCompletionItem
218+
// https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts#L83
219+
// we need this because the returned items of provideInlineCompletionItems may not be actually rendered on screen
220+
// if VS Code believes the user is actively typing then it will not show such item
221+
async checkWhetherInlineCompletionWasShown() {
222+
// this line is to force VS Code to re-render the inline completion
223+
// if it decides the inline completion can be shown
224+
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
225+
// yield event loop to let backend state transition finish plus wait for vsc to render
226+
await sleep(10)
227+
// run the command to detect if inline suggestion is really shown or not
228+
await vscode.commands.executeCommand(`aws.amazonq.checkInlineSuggestionVisibility`)
229+
}
230+
231+
// this method is automatically invoked by VS Code as user types
232+
async provideInlineCompletionItems(
218233
document: TextDocument,
219234
position: Position,
220235
context: InlineCompletionContext,
@@ -299,26 +314,28 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
299314
}
300315
// re-use previous suggestions as long as new typed prefix matches
301316
if (prevItemMatchingPrefix.length > 0) {
302-
getLogger().debug(`Re-using suggestions that match user typed characters`)
317+
logstr += `- not call LSP and reuse previous suggestions that match user typed characters
318+
- duration between trigger to completion suggestion is displayed ${performance.now() - t0}`
319+
void this.checkWhetherInlineCompletionWasShown()
303320
return prevItemMatchingPrefix
304321
}
305-
getLogger().debug(`Auto rejecting suggestions from previous session`)
306-
// if no such suggestions, report the previous suggestion as Reject
322+
323+
// if no such suggestions, report the previous suggestion as Reject or Discarded
307324
const params: LogInlineCompletionSessionResultsParams = {
308325
sessionId: prevSessionId,
309326
completionSessionResult: {
310327
[prevItemId]: {
311-
seen: true,
328+
seen: prevSession.displayed,
312329
accepted: false,
313-
discarded: false,
330+
discarded: !prevSession.displayed,
314331
},
315332
},
333+
totalSessionDisplayTime: performance.now() - prevSession.requestStartTime,
316334
}
317335
this.languageClient.sendNotification(this.logSessionResultMessageName, params)
318336
this.sessionManager.clear()
319337
}
320338

321-
// TODO: this line will take ~200ms each trigger, need to root cause and maybe better to disable it for now
322339
// tell the tutorial that completions has been triggered
323340
await this.inlineTutorialAnnotation.triggered(context.triggerKind)
324341

@@ -346,12 +363,13 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
346363

347364
const t2 = performance.now()
348365

349-
logstr = logstr += `- number of suggestions: ${items.length}
366+
logstr += `- number of suggestions: ${items.length}
350367
- sessionId: ${this.sessionManager.getActiveSession()?.sessionId}
351368
- first suggestion content (next line):
352369
${itemLog}
353-
- duration since trigger to before sending Flare call: ${t1 - t0}ms
354-
- duration since trigger to receiving responses from Flare: ${t2 - t0}ms
370+
- duration between trigger to before sending LSP call: ${t1 - t0}ms
371+
- duration between trigger to after receiving LSP response: ${t2 - t0}ms
372+
- duration between before sending LSP call to after receving LSP response: ${t2 - t1}ms
355373
`
356374
const session = this.sessionManager.getActiveSession()
357375

@@ -361,16 +379,13 @@ ${itemLog}
361379
}
362380

363381
if (!session || !items.length || !editor) {
364-
getLogger().debug(
365-
`Failed to produce inline suggestion results. Received ${items.length} items from service`
366-
)
382+
logstr += `Failed to produce inline suggestion results. Received ${items.length} items from service`
367383
return []
368384
}
369385

370386
const cursorPosition = document.validatePosition(position)
371387

372388
if (position.isAfter(editor.selection.active)) {
373-
getLogger().debug(`Cursor moved behind trigger position. Discarding suggestion...`)
374389
const params: LogInlineCompletionSessionResultsParams = {
375390
sessionId: session.sessionId,
376391
completionSessionResult: {
@@ -383,6 +398,7 @@ ${itemLog}
383398
}
384399
this.languageClient.sendNotification(this.logSessionResultMessageName, params)
385400
this.sessionManager.clear()
401+
logstr += `- cursor moved behind trigger position. Discarding suggestion...`
386402
return []
387403
}
388404

@@ -410,9 +426,7 @@ ${itemLog}
410426
// Check if Next Edit Prediction feature flag is enabled
411427
if (Experiments.instance.get('amazonqLSPNEP', true)) {
412428
await showEdits(item, editor, session, this.languageClient, this)
413-
const t3 = performance.now()
414-
logstr = logstr + `- duration since trigger to NEP suggestion is displayed: ${t3 - t0}ms`
415-
this.logger.info(logstr)
429+
logstr += `- duration between trigger to edits suggestion is displayed: ${performance.now() - t0}ms`
416430
}
417431
return []
418432
}
@@ -438,9 +452,6 @@ ${itemLog}
438452

439453
// report discard if none of suggestions match typeahead
440454
if (itemsMatchingTypeahead.length === 0) {
441-
getLogger().debug(
442-
`Suggestion does not match user typeahead from insertion position. Discarding suggestion...`
443-
)
444455
const params: LogInlineCompletionSessionResultsParams = {
445456
sessionId: session.sessionId,
446457
completionSessionResult: {
@@ -453,17 +464,22 @@ ${itemLog}
453464
}
454465
this.languageClient.sendNotification(this.logSessionResultMessageName, params)
455466
this.sessionManager.clear()
467+
logstr += `- suggestion does not match user typeahead from insertion position. Discarding suggestion...`
456468
return []
457469
}
458470

459471
this.sessionManager.updateCodeReferenceAndImports()
460472
// suggestions returned here will be displayed on screen
473+
logstr += `- duration between trigger to completion suggestion is displayed: ${performance.now() - t0}ms`
474+
void this.checkWhetherInlineCompletionWasShown()
461475
return itemsMatchingTypeahead as InlineCompletionItem[]
462476
} catch (e) {
463477
getLogger('amazonqLsp').error('Failed to provide completion items: %O', e)
478+
logstr += `- failed to provide completion items ${(e as Error).message}`
464479
return []
465480
} finally {
466481
vsCodeState.isRecommendationsActive = false
482+
this.logger.info(logstr)
467483
}
468484
}
469485
}

packages/amazonq/src/app/inline/recommendationService.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import {
1212
import { CancellationToken, InlineCompletionContext, Position, TextDocument } from 'vscode'
1313
import { LanguageClient } from 'vscode-languageclient'
1414
import { SessionManager } from './sessionManager'
15-
import { AuthUtil, CodeWhispererStatusBarManager } from 'aws-core-vscode/codewhisperer'
15+
import { AuthUtil, CodeWhispererStatusBarManager, vsCodeState } from 'aws-core-vscode/codewhisperer'
1616
import { TelemetryHelper } from './telemetryHelper'
1717
import { ICursorUpdateRecorder } from './cursorUpdateManager'
18-
import { globals, getLogger } from 'aws-core-vscode/shared'
18+
import { getLogger } from 'aws-core-vscode/shared'
1919

2020
export interface GetAllRecommendationsOptions {
2121
emitTelemetry?: boolean
@@ -68,7 +68,7 @@ export class RecommendationService {
6868
if (options.editsStreakToken) {
6969
request = { ...request, partialResultToken: options.editsStreakToken }
7070
}
71-
const requestStartTime = globals.clock.Date.now()
71+
const requestStartTime = performance.now()
7272
const statusBar = CodeWhispererStatusBarManager.instance
7373

7474
// Only track telemetry if enabled
@@ -92,13 +92,15 @@ export class RecommendationService {
9292
nextToken: request.partialResultToken,
9393
},
9494
})
95+
const t0 = performance.now()
9596
const result: InlineCompletionListWithReferences = await languageClient.sendRequest(
9697
inlineCompletionWithReferencesRequestType.method,
9798
request,
9899
token
99100
)
100-
getLogger().info('Received inline completion response: %O', {
101+
getLogger().info('Received inline completion response from LSP: %O', {
101102
sessionId: result.sessionId,
103+
latency: performance.now() - t0,
102104
itemCount: result.items?.length || 0,
103105
items: result.items?.map((item) => ({
104106
itemId: item.itemId,
@@ -117,7 +119,7 @@ export class RecommendationService {
117119
}
118120
TelemetryHelper.instance.setFirstSuggestionShowTime()
119121

120-
const firstCompletionDisplayLatency = globals.clock.Date.now() - requestStartTime
122+
const firstCompletionDisplayLatency = performance.now() - requestStartTime
121123
this.sessionManager.startSession(
122124
result.sessionId,
123125
result.items,
@@ -128,6 +130,7 @@ export class RecommendationService {
128130

129131
const isInlineEdit = result.items.some((item) => item.isInlineEdit)
130132

133+
// TODO: question, is it possible that the first request returns empty suggestion but has non-empty next token?
131134
if (result.partialResultToken) {
132135
if (!isInlineEdit) {
133136
// If the suggestion is COMPLETIONS and there are more results to fetch, handle them in the background
@@ -183,6 +186,11 @@ export class RecommendationService {
183186
request,
184187
token
185188
)
189+
// when pagination is in progress, but user has already accepted or rejected an inline completion
190+
// then stop pagination
191+
if (this.sessionManager.getActiveSession() === undefined || vsCodeState.isCodeWhispererEditing) {
192+
break
193+
}
186194
this.sessionManager.updateSessionSuggestions(result.items)
187195
nextToken = result.partialResultToken
188196
}

packages/amazonq/src/app/inline/sessionManager.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export interface CodeWhispererSession {
2424
// partialResultToken for the next trigger if user accepts an EDITS suggestion
2525
editsStreakPartialResultToken?: number | string
2626
triggerOnAcceptance?: boolean
27+
// whether any suggestion in this session was displayed on screen
28+
displayed: boolean
2729
}
2830

2931
export class SessionManager {
@@ -49,6 +51,7 @@ export class SessionManager {
4951
startPosition,
5052
firstCompletionDisplayLatency,
5153
diagnosticsBeforeAccept,
54+
displayed: false,
5255
}
5356
this._currentSuggestionIndex = 0
5457
}
@@ -128,6 +131,12 @@ export class SessionManager {
128131
}
129132
}
130133

134+
public checkInlineSuggestionVisibility() {
135+
if (this.activeSession) {
136+
this.activeSession.displayed = true
137+
}
138+
}
139+
131140
private clearReferenceInlineHintsAndImportHints() {
132141
ReferenceInlineProvider.instance.removeInlineReference()
133142
ImportAdderProvider.instance.clear()

0 commit comments

Comments
 (0)