Skip to content

Commit 5d8706e

Browse files
aws-toolkit-automationroger-zhanggfloralphrhamiltlaileni-aws
authored
Merge master into feature/ui-e2e-tests (#7767)
## Automatic merge failed - Resolve conflicts and push to this PR branch. - **Do not squash-merge** this PR. Use the "Create a merge commit" option to do a regular merge. ## Command line hint To perform the merge from the command line, you could do something like the following (where "origin" is the name of the remote in your local git repo): ``` git stash git fetch --all git checkout origin/feature/ui-e2e-tests git merge origin/master git commit git push origin HEAD:refs/heads/autoMerge/feature/ui-e2e-tests ``` --------- Co-authored-by: Roger Zhang <[email protected]> Co-authored-by: 🥩 Flora <[email protected]> Co-authored-by: Reed Hamilton <[email protected]> Co-authored-by: Laxman Reddy <[email protected]> Co-authored-by: Roger Zhang <[email protected]> Co-authored-by: Jacob Chung <[email protected]> Co-authored-by: aws-asolidu <[email protected]> Co-authored-by: Newton Der <[email protected]> Co-authored-by: Newton Der <[email protected]> Co-authored-by: aws-toolkit-automation <> Co-authored-by: Tyrone Smith <[email protected]> Co-authored-by: Aidan Ton <[email protected]> Co-authored-by: abhraina-aws <[email protected]> Co-authored-by: atontb <[email protected]> Co-authored-by: Nitish <[email protected]> Co-authored-by: Nitish Kumar Singh <[email protected]> Co-authored-by: Blake Lazarine <[email protected]> Co-authored-by: mkovelam <[email protected]> Co-authored-by: BlakeLazarine <[email protected]> Co-authored-by: Will Lo <[email protected]> Co-authored-by: Na Yue <[email protected]> Co-authored-by: Lei Gao <[email protected]> Co-authored-by: tsmithsz <[email protected]> Co-authored-by: Reed Hamilton <[email protected]> Co-authored-by: Boyu <[email protected]> Co-authored-by: Dung Dong <[email protected]> Co-authored-by: Bryce Ito <[email protected]> Co-authored-by: Suraj Reddy <[email protected]>
1 parent 9c5f854 commit 5d8706e

File tree

86 files changed

+282
-14862
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

+282
-14862
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: 1 addition & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
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
}

0 commit comments

Comments
 (0)