Skip to content

Commit d50d38e

Browse files
authored
fix(amazonq): Bring back #3129, add 200ms delay before rendering suggestion if user is actively typing (#7675)
## Problem #3129 this change was not migrated when we move inline completion to Flare. ## Solution Replicate PR #3129 in the new code path. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 90f5459 commit d50d38e

File tree

5 files changed

+97
-24
lines changed

5 files changed

+97
-24
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": "Slightly delay rendering inline completion when user is typing"
4+
}

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

Lines changed: 25 additions & 18 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-
import * as vscode from 'vscode'
5+
66
import {
77
CancellationToken,
88
InlineCompletionContext,
@@ -46,9 +46,7 @@ import { Experiments, getLogger, sleep } from 'aws-core-vscode/shared'
4646
import { debounce, messageUtils } from 'aws-core-vscode/utils'
4747
import { showEdits } from './EditRendering/imageRenderer'
4848
import { ICursorUpdateRecorder } from './cursorUpdateManager'
49-
50-
let lastDocumentDeleteEvent: vscode.TextDocumentChangeEvent | undefined = undefined
51-
let lastDocumentDeleteTime = 0
49+
import { DocumentEventListener } from './documentEventListener'
5250

5351
export class InlineCompletionManager implements Disposable {
5452
private disposable: Disposable
@@ -60,7 +58,7 @@ export class InlineCompletionManager implements Disposable {
6058

6159
private inlineTutorialAnnotation: InlineTutorialAnnotation
6260
private readonly logSessionResultMessageName = 'aws/logInlineCompletionSessionResults'
63-
private documentChangeListener: Disposable
61+
private documentEventListener: DocumentEventListener
6462

6563
constructor(
6664
languageClient: LanguageClient,
@@ -74,24 +72,19 @@ export class InlineCompletionManager implements Disposable {
7472
this.lineTracker = lineTracker
7573
this.recommendationService = new RecommendationService(this.sessionManager, cursorUpdateRecorder)
7674
this.inlineTutorialAnnotation = inlineTutorialAnnotation
75+
this.documentEventListener = new DocumentEventListener()
7776
this.inlineCompletionProvider = new AmazonQInlineCompletionItemProvider(
7877
languageClient,
7978
this.recommendationService,
8079
this.sessionManager,
81-
this.inlineTutorialAnnotation
80+
this.inlineTutorialAnnotation,
81+
this.documentEventListener
8282
)
8383

84-
this.documentChangeListener = vscode.workspace.onDidChangeTextDocument((e) => {
85-
if (e.contentChanges.length === 1 && e.contentChanges[0].text === '') {
86-
lastDocumentDeleteEvent = e
87-
lastDocumentDeleteTime = performance.now()
88-
}
89-
})
9084
this.disposable = languages.registerInlineCompletionItemProvider(
9185
CodeWhispererConstants.platformLanguageIds,
9286
this.inlineCompletionProvider
9387
)
94-
9588
this.lineTracker.ready()
9689
}
9790

@@ -104,8 +97,8 @@ export class InlineCompletionManager implements Disposable {
10497
this.disposable.dispose()
10598
this.lineTracker.dispose()
10699
}
107-
if (this.documentChangeListener) {
108-
this.documentChangeListener.dispose()
100+
if (this.documentEventListener) {
101+
this.documentEventListener.dispose()
109102
}
110103
}
111104

@@ -211,7 +204,8 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
211204
private readonly languageClient: LanguageClient,
212205
private readonly recommendationService: RecommendationService,
213206
private readonly sessionManager: SessionManager,
214-
private readonly inlineTutorialAnnotation: InlineTutorialAnnotation
207+
private readonly inlineTutorialAnnotation: InlineTutorialAnnotation,
208+
private readonly documentEventListener: DocumentEventListener
215209
) {}
216210

217211
private readonly logSessionResultMessageName = 'aws/logInlineCompletionSessionResults'
@@ -251,8 +245,7 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
251245
await sleep(1)
252246
// prevent user deletion invoking auto trigger
253247
// this is a best effort estimate of deletion
254-
const timeDiff = Math.abs(performance.now() - lastDocumentDeleteTime)
255-
if (timeDiff < 500 && lastDocumentDeleteEvent && lastDocumentDeleteEvent.document.uri === document.uri) {
248+
if (this.documentEventListener.isLastEventDeletion(document.uri.fsPath)) {
256249
getLogger().debug('Skip auto trigger when deleting code')
257250
return []
258251
}
@@ -393,6 +386,20 @@ ${itemLog}
393386
return []
394387
}
395388

389+
// delay the suggestion rendeing if user is actively typing
390+
// see https://github.com/aws/aws-toolkit-vscode/commit/a537602a96f498f372ed61ec9d82cf8577a9d854
391+
for (let i = 0; i < 30; i++) {
392+
const lastDocumentChange = this.documentEventListener.getLastDocumentChangeEvent(document.uri.fsPath)
393+
if (
394+
lastDocumentChange &&
395+
performance.now() - lastDocumentChange.timestamp < CodeWhispererConstants.inlineSuggestionShowDelay
396+
) {
397+
await sleep(CodeWhispererConstants.showRecommendationTimerPollPeriod)
398+
} else {
399+
break
400+
}
401+
}
402+
396403
// the user typed characters from invoking suggestion cursor position to receiving suggestion position
397404
const typeahead = document.getText(new Range(position, editor.selection.active))
398405

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import * as vscode from 'vscode'
6+
7+
export interface DocumentChangeEvent {
8+
event: vscode.TextDocumentChangeEvent
9+
timestamp: number
10+
}
11+
12+
export class DocumentEventListener {
13+
private lastDocumentChangeEventMap: Map<string, DocumentChangeEvent> = new Map()
14+
private documentChangeListener: vscode.Disposable
15+
private _maxDocument = 1000
16+
17+
constructor() {
18+
this.documentChangeListener = vscode.workspace.onDidChangeTextDocument((e) => {
19+
if (e.contentChanges.length > 0) {
20+
if (this.lastDocumentChangeEventMap.size > this._maxDocument) {
21+
this.lastDocumentChangeEventMap.clear()
22+
}
23+
this.lastDocumentChangeEventMap.set(e.document.uri.fsPath, { event: e, timestamp: performance.now() })
24+
}
25+
})
26+
}
27+
28+
public isLastEventDeletion(filepath: string): boolean {
29+
const result = this.lastDocumentChangeEventMap.get(filepath)
30+
if (result) {
31+
const event = result.event
32+
const eventTime = result.timestamp
33+
const isDelete =
34+
(event && event.contentChanges.length === 1 && event.contentChanges[0].text === '') || false
35+
const timeDiff = Math.abs(performance.now() - eventTime)
36+
return timeDiff < 500 && isDelete
37+
}
38+
return false
39+
}
40+
41+
public getLastDocumentChangeEvent(filepath: string): DocumentChangeEvent | undefined {
42+
return this.lastDocumentChangeEventMap.get(filepath)
43+
}
44+
45+
public dispose(): void {
46+
if (this.documentChangeListener) {
47+
this.documentChangeListener.dispose()
48+
}
49+
}
50+
}

packages/amazonq/test/unit/amazonq/apps/inline/completion.test.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
} from 'aws-core-vscode/codewhisperer'
2929
import { LineTracker } from '../../../../../src/app/inline/stateTracker/lineTracker'
3030
import { InlineTutorialAnnotation } from '../../../../../src/app/inline/tutorials/inlineTutorialAnnotation'
31+
import { DocumentEventListener } from '../../../../../src/app/inline/documentEventListener'
3132

3233
describe('InlineCompletionManager', () => {
3334
let manager: InlineCompletionManager
@@ -243,11 +244,13 @@ describe('InlineCompletionManager', () => {
243244
let getAllRecommendationsStub: sinon.SinonStub
244245
let recommendationService: RecommendationService
245246
let inlineTutorialAnnotation: InlineTutorialAnnotation
247+
let documentEventListener: DocumentEventListener
246248

247249
beforeEach(() => {
248250
const lineTracker = new LineTracker()
249251
inlineTutorialAnnotation = new InlineTutorialAnnotation(lineTracker, mockSessionManager)
250252
recommendationService = new RecommendationService(mockSessionManager)
253+
documentEventListener = new DocumentEventListener()
251254
vsCodeState.isRecommendationsActive = false
252255
mockSessionManager = {
253256
getActiveSession: getActiveSessionStub,
@@ -271,7 +274,8 @@ describe('InlineCompletionManager', () => {
271274
languageClient,
272275
recommendationService,
273276
mockSessionManager,
274-
inlineTutorialAnnotation
277+
inlineTutorialAnnotation,
278+
documentEventListener
275279
)
276280
const items = await provider.provideInlineCompletionItems(
277281
mockDocument,
@@ -287,7 +291,8 @@ describe('InlineCompletionManager', () => {
287291
languageClient,
288292
recommendationService,
289293
mockSessionManager,
290-
inlineTutorialAnnotation
294+
inlineTutorialAnnotation,
295+
documentEventListener
291296
)
292297
await provider.provideInlineCompletionItems(mockDocument, mockPosition, mockContext, mockToken)
293298
}),
@@ -296,7 +301,8 @@ describe('InlineCompletionManager', () => {
296301
languageClient,
297302
recommendationService,
298303
mockSessionManager,
299-
inlineTutorialAnnotation
304+
inlineTutorialAnnotation,
305+
documentEventListener
300306
)
301307
getActiveRecommendationStub.returns([
302308
{
@@ -326,7 +332,8 @@ describe('InlineCompletionManager', () => {
326332
languageClient,
327333
recommendationService,
328334
mockSessionManager,
329-
inlineTutorialAnnotation
335+
inlineTutorialAnnotation,
336+
documentEventListener
330337
)
331338
const expectedText = `${mockSuggestions[1].insertText}this is my text`
332339
getActiveRecommendationStub.returns([
@@ -352,7 +359,8 @@ describe('InlineCompletionManager', () => {
352359
languageClient,
353360
recommendationService,
354361
mockSessionManager,
355-
inlineTutorialAnnotation
362+
inlineTutorialAnnotation,
363+
documentEventListener
356364
)
357365
getActiveRecommendationStub.returns([])
358366
const messageShown = new Promise((resolve) =>
@@ -385,7 +393,8 @@ describe('InlineCompletionManager', () => {
385393
languageClient,
386394
recommendationService,
387395
mockSessionManager,
388-
inlineTutorialAnnotation
396+
inlineTutorialAnnotation,
397+
documentEventListener
389398
)
390399
const p1 = provider.provideInlineCompletionItems(mockDocument, mockPosition, mockContext, mockToken)
391400
const p2 = provider.provideInlineCompletionItems(mockDocument, mockPosition, mockContext, mockToken)

packages/core/src/codewhisperer/models/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ export const identityPoolID = 'us-east-1:70717e99-906f-4add-908c-bd9074a2f5b9'
184184
*/
185185
export const inlineCompletionsDebounceDelay = 200
186186

187+
// add 200ms more delay on top of inline default 30-50ms
188+
export const inlineSuggestionShowDelay = 200
189+
187190
export const referenceLog = 'Code Reference Log'
188191

189192
export const suggestionDetailReferenceText = (licenses: string) =>

0 commit comments

Comments
 (0)