Skip to content

Commit f7a724d

Browse files
authored
Merge branch 'aws:master' into code-review-tool
2 parents 4aa521e + 6ac1207 commit f7a724d

File tree

111 files changed

+277
-20434
lines changed

Some content is hidden

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

111 files changed

+277
-20434
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
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: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@
410410
},
411411
{
412412
"command": "aws.amazonq.showLogs",
413-
"when": "view == aws.amazonq.AmazonQChatView",
413+
"when": "!aws.isSageMakerUnifiedStudio",
414414
"group": "1_amazonQ@5"
415415
},
416416
{
@@ -644,8 +644,7 @@
644644
{
645645
"command": "aws.amazonq.showLogs",
646646
"title": "%AWS.command.codewhisperer.showLogs%",
647-
"category": "%AWS.amazonq.title%",
648-
"enablement": "aws.codewhisperer.connected"
647+
"category": "%AWS.amazonq.title%"
649648
},
650649
{
651650
"command": "aws.amazonq.selectRegionProfile",
@@ -976,6 +975,10 @@
976975
"command": "aws.amazonq.showPrev",
977976
"when": "inlineSuggestionVisible && !editorReadonly && aws.codewhisperer.connected"
978977
},
978+
{
979+
"command": "aws.amazonq.checkInlineSuggestionVisibility",
980+
"when": "inlineSuggestionVisible && !editorReadonly && aws.codewhisperer.connected"
981+
},
979982
{
980983
"command": "aws.amazonq.inline.invokeChat",
981984
"win": "ctrl+i",

packages/amazonq/src/app/chat/node/activateAgents.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ export function activateAgents() {
1111
const appInitContext = DefaultAmazonQAppInitContext.instance
1212

1313
amazonqNode.cwChatAppInit(appInitContext)
14-
amazonqNode.featureDevChatAppInit(appInitContext)
1514
amazonqNode.gumbyChatAppInit(appInitContext)
16-
amazonqNode.testChatAppInit(appInitContext)
17-
amazonqNode.docChatAppInit(appInitContext)
1815
scanChatAppInit(appInitContext)
1916
}

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

Lines changed: 6 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { diffChars } from 'diff'
6+
import { diffWordsWithSpace } from 'diff'
77
import * as vscode from 'vscode'
88
import { ToolkitError, getLogger } from 'aws-core-vscode/shared'
99
import { diffUtilities } from 'aws-core-vscode/shared'
@@ -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')
@@ -413,45 +413,6 @@ export class SvgGenerationService {
413413
const originalRanges: Range[] = []
414414
const afterRanges: Range[] = []
415415

416-
/**
417-
* Merges ranges on the same line that are separated by only one character
418-
*/
419-
const mergeAdjacentRanges = (ranges: Range[]): Range[] => {
420-
const sortedRanges = [...ranges].sort((a, b) => {
421-
if (a.line !== b.line) {
422-
return a.line - b.line
423-
}
424-
return a.start - b.start
425-
})
426-
427-
const result: Range[] = []
428-
429-
// Process all ranges
430-
for (let i = 0; i < sortedRanges.length; i++) {
431-
const current = sortedRanges[i]
432-
433-
// If this is the last range or ranges are on different lines, add it directly
434-
if (i === sortedRanges.length - 1 || current.line !== sortedRanges[i + 1].line) {
435-
result.push(current)
436-
continue
437-
}
438-
439-
// Check if current range and next range can be merged
440-
const next = sortedRanges[i + 1]
441-
if (current.line === next.line && next.start - current.end <= 1) {
442-
sortedRanges[i + 1] = {
443-
line: current.line,
444-
start: current.start,
445-
end: Math.max(current.end, next.end),
446-
}
447-
} else {
448-
result.push(current)
449-
}
450-
}
451-
452-
return result
453-
}
454-
455416
// Create reverse mapping for quicker lookups
456417
const reverseMap = new Map<string, string>()
457418
for (const [original, modified] of modifiedLines.entries()) {
@@ -465,7 +426,7 @@ export class SvgGenerationService {
465426
// If line exists in modifiedLines as a key, process character diffs
466427
if (Array.from(modifiedLines.keys()).includes(line)) {
467428
const modifiedLine = modifiedLines.get(line)!
468-
const changes = diffChars(line, modifiedLine)
429+
const changes = diffWordsWithSpace(line, modifiedLine)
469430

470431
let charPos = 0
471432
for (const part of changes) {
@@ -497,7 +458,7 @@ export class SvgGenerationService {
497458

498459
if (reverseMap.has(line)) {
499460
const originalLine = reverseMap.get(line)!
500-
const changes = diffChars(originalLine, line)
461+
const changes = diffWordsWithSpace(originalLine, line)
501462

502463
let charPos = 0
503464
for (const part of changes) {
@@ -522,12 +483,9 @@ export class SvgGenerationService {
522483
}
523484
}
524485

525-
const mergedOriginalRanges = mergeAdjacentRanges(originalRanges)
526-
const mergedAfterRanges = mergeAdjacentRanges(afterRanges)
527-
528486
return {
529-
removedRanges: mergedOriginalRanges,
530-
addedRanges: mergedAfterRanges,
487+
removedRanges: originalRanges,
488+
addedRanges: afterRanges,
531489
}
532490
}
533491
}

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
}

0 commit comments

Comments
 (0)