Skip to content

Commit bfdb0eb

Browse files
authored
Merge pull request #7585 from aws/floralph/cursor-manager-track-autotrigger-setting
fix(amazonq): update cursor tracking based on autotrigger setting
2 parents 9bd9d99 + 699e00e commit bfdb0eb

File tree

2 files changed

+114
-3
lines changed

2 files changed

+114
-3
lines changed

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { LanguageClient } from 'vscode-languageclient'
88
import { getLogger } from 'aws-core-vscode/shared'
99
import { globals } from 'aws-core-vscode/shared'
1010
import { AmazonQInlineCompletionItemProvider } from './completion'
11+
import { CodeSuggestionsState } from 'aws-core-vscode/codewhisperer'
1112

1213
// Configuration section for cursor updates
1314
export const cursorUpdateConfigurationSection = 'aws.q.cursorUpdate'
@@ -32,11 +33,23 @@ export class CursorUpdateManager implements vscode.Disposable, ICursorUpdateReco
3233
private lastSentDocumentUri?: string
3334
private isActive = false
3435
private lastRequestTime = 0
36+
private autotriggerStateDisposable?: vscode.Disposable
3537

3638
constructor(
3739
private readonly languageClient: LanguageClient,
3840
private readonly inlineCompletionProvider?: AmazonQInlineCompletionItemProvider
39-
) {}
41+
) {
42+
// Listen for autotrigger state changes to enable/disable the timer
43+
this.autotriggerStateDisposable = CodeSuggestionsState.instance.onDidChangeState((isEnabled: boolean) => {
44+
if (isEnabled && this.isActive) {
45+
// If autotrigger is enabled and we're active, ensure timer is running
46+
this.setupUpdateTimer()
47+
} else {
48+
// If autotrigger is disabled, clear the timer but keep isActive state
49+
this.clearUpdateTimer()
50+
}
51+
})
52+
}
4053

4154
/**
4255
* Start tracking cursor positions and sending periodic updates
@@ -66,7 +79,9 @@ export class CursorUpdateManager implements vscode.Disposable, ICursorUpdateReco
6679
}
6780

6881
this.isActive = true
69-
this.setupUpdateTimer()
82+
if (CodeSuggestionsState.instance.isSuggestionsEnabled()) {
83+
this.setupUpdateTimer()
84+
}
7085
}
7186

7287
/**
@@ -130,7 +145,7 @@ export class CursorUpdateManager implements vscode.Disposable, ICursorUpdateReco
130145
}
131146

132147
/**
133-
* Send a cursor position update to the language server
148+
* Request LSP generate a completion for the current cursor position.
134149
*/
135150
private async sendCursorUpdate(): Promise<void> {
136151
// Don't send an update if a regular request was made recently
@@ -185,6 +200,12 @@ export class CursorUpdateManager implements vscode.Disposable, ICursorUpdateReco
185200
* Dispose of resources
186201
*/
187202
public dispose(): void {
203+
// Dispose of the autotrigger state change listener
204+
if (this.autotriggerStateDisposable) {
205+
this.autotriggerStateDisposable.dispose()
206+
this.autotriggerStateDisposable = undefined
207+
}
208+
188209
this.stop()
189210
}
190211
}

packages/amazonq/test/unit/app/inline/cursorUpdateManager.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { CursorUpdateManager } from '../../../../src/app/inline/cursorUpdateMana
1010
import { globals } from 'aws-core-vscode/shared'
1111
import assert from 'assert'
1212
import { AmazonQInlineCompletionItemProvider } from '../../../../src/app/inline/completion'
13+
import { CodeSuggestionsState } from 'aws-core-vscode/codewhisperer'
1314

1415
describe('CursorUpdateManager', () => {
1516
let cursorUpdateManager: CursorUpdateManager
@@ -236,4 +237,93 @@ describe('CursorUpdateManager', () => {
236237
// Verify the provider was called again
237238
assert.strictEqual(provideStub.callCount, 1, 'Update should be sent when position has changed')
238239
})
240+
241+
describe('autotrigger state handling', () => {
242+
class TestCodeSuggestionsState extends CodeSuggestionsState {
243+
private _isEnabled: boolean
244+
private _onDidChangeStateEmitter = new vscode.EventEmitter<boolean>()
245+
246+
public constructor(initialState: boolean = true) {
247+
super(initialState)
248+
this._isEnabled = initialState
249+
}
250+
251+
public override onDidChangeState = this._onDidChangeStateEmitter.event
252+
253+
public override isSuggestionsEnabled(): boolean {
254+
return this._isEnabled
255+
}
256+
257+
public override async setSuggestionsEnabled(enabled: boolean): Promise<void> {
258+
if (this._isEnabled !== enabled) {
259+
this._isEnabled = enabled
260+
this._onDidChangeStateEmitter.fire(enabled)
261+
}
262+
}
263+
}
264+
265+
let testCodeSuggestionsState: TestCodeSuggestionsState
266+
let instanceStub: sinon.SinonStub
267+
let testCursorUpdateManager: CursorUpdateManager
268+
269+
beforeEach(() => {
270+
// Create test instance
271+
testCodeSuggestionsState = new TestCodeSuggestionsState(true)
272+
273+
// Stub the static getter to return our test instance
274+
instanceStub = sinon.stub(CodeSuggestionsState, 'instance').get(() => testCodeSuggestionsState)
275+
276+
// Create the manager AFTER the stub is in place so it uses the test instance
277+
const mockInlineCompletionProvider = {
278+
provideInlineCompletionItems: sinon.stub().resolves([]),
279+
} as unknown as AmazonQInlineCompletionItemProvider
280+
testCursorUpdateManager = new CursorUpdateManager(languageClient, mockInlineCompletionProvider)
281+
})
282+
283+
afterEach(() => {
284+
// Dispose the test manager
285+
testCursorUpdateManager.dispose()
286+
// Restore the original getter
287+
instanceStub.restore()
288+
})
289+
290+
it('should not start timer when autotrigger is disabled', async () => {
291+
// Test the new behavior: timer doesn't start when autotrigger is disabled
292+
await testCodeSuggestionsState.setSuggestionsEnabled(false)
293+
sendRequestStub.resolves({})
294+
295+
await testCursorUpdateManager.start()
296+
297+
// Manager should be active but timer should not be started
298+
assert.strictEqual((testCursorUpdateManager as any).isActive, true)
299+
assert.ok(!setIntervalStub.called, 'Timer should NOT be started when autotrigger is disabled')
300+
})
301+
302+
it('should start/stop timer when autotrigger state changes', async () => {
303+
// Start with autotrigger enabled
304+
await testCodeSuggestionsState.setSuggestionsEnabled(true)
305+
sendRequestStub.resolves({})
306+
await testCursorUpdateManager.start()
307+
308+
// Reset stubs to test state changes
309+
setIntervalStub.resetHistory()
310+
clearIntervalStub.resetHistory()
311+
312+
// Simulate autotrigger being disabled
313+
await testCodeSuggestionsState.setSuggestionsEnabled(false)
314+
assert.ok(clearIntervalStub.called, 'Timer should be stopped when autotrigger is disabled')
315+
316+
// Simulate autotrigger being enabled again
317+
await testCodeSuggestionsState.setSuggestionsEnabled(true)
318+
assert.ok(setIntervalStub.called, 'Timer should be started when autotrigger is re-enabled')
319+
})
320+
321+
it('should dispose autotrigger state listener on dispose', () => {
322+
testCursorUpdateManager.dispose()
323+
// The dispose method should clean up the state listener
324+
// We can't easily test the disposal without more complex mocking,
325+
// but we can at least verify dispose doesn't throw
326+
assert.ok(true, 'Dispose should complete without errors')
327+
})
328+
})
239329
})

0 commit comments

Comments
 (0)