diff --git a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/codeWhispererServer.ts b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/codeWhispererServer.ts index 3e8461a576..9933ce18f8 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/codeWhispererServer.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/codeWhispererServer.ts @@ -5,7 +5,6 @@ import { InlineCompletionWithReferencesParams, Server, } from '@aws/language-server-runtimes/server-interface' -import { getSupportedLanguageId } from '../../shared/languageDetection' import { SessionManager } from './session/sessionManager' import { CodePercentageTracker } from './tracker/codePercentageTracker' import { safeGet } from '../../shared/utils' @@ -22,7 +21,7 @@ import { RecentEditTracker, RecentEditTrackerDefaultConfig } from './tracker/cod import { CursorTracker } from './tracker/cursorTracker' import { RejectedEditTracker, DEFAULT_REJECTED_EDIT_TRACKER_CONFIG } from './tracker/rejectedEditTracker' import { StreakTracker } from './tracker/streakTracker' -import { DocumentChangedListener } from './documentChangedListener' +import { DocumentEventHandler } from './handler/documentEventHandler' import { EditCompletionHandler } from './handler/editCompletionHandler' import { InlineCompletionHandler } from './handler/inlineCompletionHandler' import { SessionResultsHandler } from './handler/sessionResultsHandler' @@ -31,7 +30,6 @@ import { isUsingIAMAuth } from '../../shared/utils' export const CodewhispererServerFactory = (serviceManager: (credentialsProvider?: any) => AmazonQBaseServiceManager): Server => ({ credentialsProvider, lsp, workspace, telemetry, logging, runtime, sdkInitializator }) => { - let lastUserModificationTime: number let timeSinceLastUserModification: number = 0 const completionSessionManager = SessionManager.getInstance('COMPLETIONS') @@ -61,7 +59,7 @@ export const CodewhispererServerFactory = const streakTracker = StreakTracker.getInstance() let editsEnabled = false - const documentChangedListener = new DocumentChangedListener() + let documentEventHandler: DocumentEventHandler const onInlineCompletionHandler = async ( params: InlineCompletionWithReferencesParams, @@ -146,6 +144,18 @@ export const CodewhispererServerFactory = await amazonQServiceManager.addDidChangeConfigurationListener(updateConfiguration) + documentEventHandler = new DocumentEventHandler( + workspace, + logging, + codePercentageTracker, + userWrittenCodeTracker, + recentEditTracker, + cursorTracker, + completionSessionManager, + amazonQServiceManager, + () => editsEnabled + ) + editCompletionHandler = new EditCompletionHandler( logging, clientParams, @@ -155,7 +165,7 @@ export const CodewhispererServerFactory = cursorTracker, recentEditTracker, rejectedEditTracker, - documentChangedListener, + documentEventHandler, telemetry, telemetryService, credentialsProvider @@ -179,6 +189,8 @@ export const CodewhispererServerFactory = lsp ) + documentEventHandler.setEditCompletionHandler(editCompletionHandler) + sessionResultsHandler = new SessionResultsHandler( logging, telemetry, @@ -209,78 +221,16 @@ export const CodewhispererServerFactory = lsp.onInitialized(onInitializedHandler) lsp.onDidChangeTextDocument(async p => { - const textDocument = await workspace.getTextDocument(p.textDocument.uri) - const languageId = getSupportedLanguageId(textDocument) - - if (!textDocument || !languageId) { - return - } - - p.contentChanges.forEach(change => { - codePercentageTracker.countTotalTokens(languageId, change.text, false) - - const { sendUserWrittenCodeMetrics } = amazonQServiceManager.getConfiguration() - if (!sendUserWrittenCodeMetrics) { - return - } - // exclude cases that the document change is from Q suggestions - const currentSession = completionSessionManager.getCurrentSession() - if ( - !currentSession?.suggestions.some( - suggestion => suggestion?.insertText && suggestion.insertText === change.text - ) - ) { - userWrittenCodeTracker?.countUserWrittenTokens(languageId, change.text) - } - }) - - // Record last user modification time for any document - if (lastUserModificationTime) { - timeSinceLastUserModification = Date.now() - lastUserModificationTime - } - lastUserModificationTime = Date.now() - - documentChangedListener.onDocumentChanged(p) - editCompletionHandler.documentChanged() - - // Process document changes with RecentEditTracker. - if (editsEnabled && recentEditTracker) { - await recentEditTracker.handleDocumentChange({ - uri: p.textDocument.uri, - languageId: textDocument.languageId, - version: textDocument.version, - text: textDocument.getText(), - }) - } + await documentEventHandler.onDidChangeTextDocument(p) + timeSinceLastUserModification = documentEventHandler.timeSinceLastUserModification }) lsp.onDidOpenTextDocument(p => { - logging.log(`Document opened: ${p.textDocument.uri}`) - - // Track document opening with RecentEditTracker - if (recentEditTracker) { - logging.log(`[SERVER] Tracking document open with RecentEditTracker: ${p.textDocument.uri}`) - recentEditTracker.handleDocumentOpen({ - uri: p.textDocument.uri, - languageId: p.textDocument.languageId, - version: p.textDocument.version, - text: p.textDocument.text, - }) - } + documentEventHandler.onDidOpenTextDocument(p) }) lsp.onDidCloseTextDocument(p => { - logging.log(`Document closed: ${p.textDocument.uri}`) - - // Track document closing with RecentEditTracker - if (recentEditTracker) { - logging.log(`[SERVER] Tracking document close with RecentEditTracker: ${p.textDocument.uri}`) - recentEditTracker.handleDocumentClose(p.textDocument.uri) - } - - if (cursorTracker) { - cursorTracker.clearHistory(p.textDocument.uri) - } + documentEventHandler.onDidCloseTextDocument(p) }) logging.log('Amazon Q Inline Suggestion server has been initialised') diff --git a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/documentChangedListener.ts b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/documentChangedListener.ts deleted file mode 100644 index 302eab1159..0000000000 --- a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/documentChangedListener.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DidChangeTextDocumentParams } from '@aws/language-server-runtimes/protocol' - -export class DocumentChangedListener { - private _lastUserModificationTime: number = 0 - private _timeSinceLastUserModification: number = 0 - get timeSinceLastUserModification(): number { - return this._timeSinceLastUserModification - } - - constructor() {} - - onDocumentChanged(e: DidChangeTextDocumentParams) { - // Record last user modification time for any document - if (this._lastUserModificationTime) { - this._timeSinceLastUserModification = new Date().getTime() - this._lastUserModificationTime - } - this._lastUserModificationTime = new Date().getTime() - } -} diff --git a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/documentEventHandler.test.ts b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/documentEventHandler.test.ts new file mode 100644 index 0000000000..de05cb2a26 --- /dev/null +++ b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/documentEventHandler.test.ts @@ -0,0 +1,129 @@ +import { DocumentEventHandler } from './documentEventHandler' + +describe('DocumentEventHandler', () => { + let handler: DocumentEventHandler + let mockWorkspace: any + let mockLogging: any + let mockCodePercentageTracker: any + let mockUserWrittenCodeTracker: any + let mockRecentEditTracker: any + let mockCursorTracker: any + let mockCompletionSessionManager: any + let mockAmazonQServiceManager: any + let mockGetEditsEnabled: jest.Mock + + beforeEach(() => { + mockWorkspace = { getTextDocument: jest.fn() } + mockLogging = { log: jest.fn() } + mockCodePercentageTracker = { countTotalTokens: jest.fn() } + mockUserWrittenCodeTracker = { countUserWrittenTokens: jest.fn() } + mockRecentEditTracker = { + handleDocumentChange: jest.fn(), + handleDocumentOpen: jest.fn(), + handleDocumentClose: jest.fn(), + } + mockCursorTracker = { clearHistory: jest.fn() } + mockCompletionSessionManager = { getCurrentSession: jest.fn() } + mockAmazonQServiceManager = { getConfiguration: jest.fn() } + mockGetEditsEnabled = jest.fn() + + handler = new DocumentEventHandler( + mockWorkspace, + mockLogging, + mockCodePercentageTracker, + mockUserWrittenCodeTracker, + mockRecentEditTracker, + mockCursorTracker, + mockCompletionSessionManager, + mockAmazonQServiceManager, + mockGetEditsEnabled + ) + }) + + describe('onDidChangeTextDocument', () => { + it('should return early if no text document', async () => { + mockWorkspace.getTextDocument.mockResolvedValue(null) + const params = { textDocument: { uri: 'test.ts' }, contentChanges: [] } + + await handler.onDidChangeTextDocument(params as any) + + expect(mockCodePercentageTracker.countTotalTokens).not.toHaveBeenCalled() + }) + + it('should count tokens and update modification time', async () => { + const mockTextDocument = { languageId: 'typescript', getText: jest.fn() } + mockWorkspace.getTextDocument.mockResolvedValue(mockTextDocument) + mockAmazonQServiceManager.getConfiguration.mockReturnValue({ sendUserWrittenCodeMetrics: true }) + mockCompletionSessionManager.getCurrentSession.mockReturnValue(null) + + const params = { + textDocument: { uri: 'test.ts' }, + contentChanges: [{ text: 'console.log()' }], + } + + await handler.onDidChangeTextDocument(params as any) + + expect(mockCodePercentageTracker.countTotalTokens).toHaveBeenCalledWith( + 'typescript', + 'console.log()', + false + ) + expect(handler.timeSinceLastUserModification).toBeGreaterThanOrEqual(0) + }) + }) + + describe('onDidOpenTextDocument', () => { + it('should log and track document open', () => { + const params = { + textDocument: { + uri: 'test.ts', + languageId: 'typescript', + version: 1, + text: 'content', + }, + } + + handler.onDidOpenTextDocument(params as any) + + expect(mockLogging.log).toHaveBeenCalledWith('Document opened: test.ts') + expect(mockRecentEditTracker.handleDocumentOpen).toHaveBeenCalledWith({ + uri: 'test.ts', + languageId: 'typescript', + version: 1, + text: 'content', + }) + }) + }) + + describe('onDidCloseTextDocument', () => { + it('should log and clear cursor history', () => { + const params = { textDocument: { uri: 'test.ts' } } + + handler.onDidCloseTextDocument(params as any) + + expect(mockLogging.log).toHaveBeenCalledWith('Document closed: test.ts') + expect(mockRecentEditTracker.handleDocumentClose).toHaveBeenCalledWith('test.ts') + expect(mockCursorTracker.clearHistory).toHaveBeenCalledWith('test.ts') + }) + }) + + describe('setEditCompletionHandler', () => { + it('should set and call edit completion handler', async () => { + const mockHandler = { documentChanged: jest.fn() } + handler.setEditCompletionHandler(mockHandler) + + const mockTextDocument = { languageId: 'typescript', getText: jest.fn() } + mockWorkspace.getTextDocument.mockResolvedValue(mockTextDocument) + mockAmazonQServiceManager.getConfiguration.mockReturnValue({ sendUserWrittenCodeMetrics: false }) + + const params = { + textDocument: { uri: 'test.ts' }, + contentChanges: [{ text: 'test' }], + } + + await handler.onDidChangeTextDocument(params as any) + + expect(mockHandler.documentChanged).toHaveBeenCalled() + }) + }) +}) diff --git a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/documentEventHandler.ts b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/documentEventHandler.ts new file mode 100644 index 0000000000..cdb72db8c4 --- /dev/null +++ b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/documentEventHandler.ts @@ -0,0 +1,115 @@ +import { + DidChangeTextDocumentParams, + DidOpenTextDocumentParams, + DidCloseTextDocumentParams, +} from '@aws/language-server-runtimes/protocol' +import { getSupportedLanguageId } from '../../../shared/languageDetection' +import { CodePercentageTracker } from '../tracker/codePercentageTracker' +import { UserWrittenCodeTracker } from '../../../shared/userWrittenCodeTracker' +import { RecentEditTracker } from '../tracker/codeEditTracker' +import { CursorTracker } from '../tracker/cursorTracker' +import { SessionManager } from '../session/sessionManager' +import { AmazonQBaseServiceManager } from '../../../shared/amazonQServiceManager/BaseAmazonQServiceManager' +import { Logging, Workspace } from '@aws/language-server-runtimes/server-interface' + +export class DocumentEventHandler { + private _lastUserModificationTime: number = 0 + private _timeSinceLastUserModification: number = 0 + private editCompletionHandler: any + + get timeSinceLastUserModification(): number { + return this._timeSinceLastUserModification + } + + constructor( + private readonly workspace: Workspace, + private readonly logging: Logging, + private readonly codePercentageTracker: CodePercentageTracker, + private readonly userWrittenCodeTracker: UserWrittenCodeTracker | undefined, + private readonly recentEditTracker: RecentEditTracker, + private readonly cursorTracker: CursorTracker, + private readonly completionSessionManager: SessionManager, + private readonly amazonQServiceManager: AmazonQBaseServiceManager, + private readonly getEditsEnabled: () => boolean + ) {} + + setEditCompletionHandler(handler: any) { + this.editCompletionHandler = handler + } + + async onDidChangeTextDocument(p: DidChangeTextDocumentParams) { + const textDocument = await this.workspace.getTextDocument(p.textDocument.uri) + const languageId = getSupportedLanguageId(textDocument) + + if (!textDocument || !languageId) { + return + } + + p.contentChanges.forEach(change => { + this.codePercentageTracker.countTotalTokens(languageId, change.text, false) + + const { sendUserWrittenCodeMetrics } = this.amazonQServiceManager.getConfiguration() + if (!sendUserWrittenCodeMetrics) { + return + } + // exclude cases that the document change is from Q suggestions + const currentSession = this.completionSessionManager.getCurrentSession() + if ( + !currentSession?.suggestions.some( + suggestion => suggestion?.insertText && suggestion.insertText === change.text + ) + ) { + this.userWrittenCodeTracker?.countUserWrittenTokens(languageId, change.text) + } + }) + + // Record last user modification time for any document + if (this._lastUserModificationTime) { + this._timeSinceLastUserModification = Date.now() - this._lastUserModificationTime + } + this._lastUserModificationTime = Date.now() + + if (this.editCompletionHandler) { + this.editCompletionHandler.documentChanged() + } + + // Process document changes with RecentEditTracker. + if (this.getEditsEnabled() && this.recentEditTracker) { + await this.recentEditTracker.handleDocumentChange({ + uri: p.textDocument.uri, + languageId: textDocument.languageId, + version: textDocument.version, + text: textDocument.getText(), + }) + } + } + + onDidOpenTextDocument(p: DidOpenTextDocumentParams) { + this.logging.log(`Document opened: ${p.textDocument.uri}`) + + // Track document opening with RecentEditTracker + if (this.recentEditTracker) { + this.logging.log(`[SERVER] Tracking document open with RecentEditTracker: ${p.textDocument.uri}`) + this.recentEditTracker.handleDocumentOpen({ + uri: p.textDocument.uri, + languageId: p.textDocument.languageId, + version: p.textDocument.version, + text: p.textDocument.text, + }) + } + } + + onDidCloseTextDocument(p: DidCloseTextDocumentParams) { + this.logging.log(`Document closed: ${p.textDocument.uri}`) + + // Track document closing with RecentEditTracker + if (this.recentEditTracker) { + this.logging.log(`[SERVER] Tracking document close with RecentEditTracker: ${p.textDocument.uri}`) + this.recentEditTracker.handleDocumentClose(p.textDocument.uri) + } + + if (this.cursorTracker) { + this.cursorTracker.clearHistory(p.textDocument.uri) + } + } +} diff --git a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/editCompletionHandler.test.ts b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/editCompletionHandler.test.ts new file mode 100644 index 0000000000..f125e8c9d2 --- /dev/null +++ b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/editCompletionHandler.test.ts @@ -0,0 +1,186 @@ +import { EditCompletionHandler } from './editCompletionHandler' +import { InlineCompletionTriggerKind } from '@aws/language-server-runtimes/protocol' +import { EMPTY_RESULT } from '../contants/constants' + +describe('EditCompletionHandler', () => { + let handler: EditCompletionHandler + let mockLogging: any + let mockWorkspace: any + let mockAmazonQServiceManager: any + let mockSessionManager: any + let mockCursorTracker: any + let mockRecentEditsTracker: any + let mockRejectedEditTracker: any + let mockDocumentEventHandler: any + let mockTelemetry: any + let mockTelemetryService: any + let mockCredentialsProvider: any + let mockCodeWhispererService: any + + beforeEach(() => { + mockLogging = { info: jest.fn(), warn: jest.fn(), log: jest.fn(), debug: jest.fn() } + mockWorkspace = { getTextDocument: jest.fn(), getWorkspaceFolder: jest.fn() } + mockCodeWhispererService = { + generateSuggestions: jest.fn(), + constructSupplementalContext: jest.fn(), + customizationArn: undefined, + } + mockAmazonQServiceManager = { getCodewhispererService: jest.fn(() => mockCodeWhispererService) } + mockSessionManager = { + getCurrentSession: jest.fn(), + createSession: jest.fn(), + discardSession: jest.fn(), + activateSession: jest.fn(), + closeSession: jest.fn(), + } + mockCursorTracker = { trackPosition: jest.fn() } + mockRecentEditsTracker = {} + mockRejectedEditTracker = { isSimilarToRejected: jest.fn(() => false) } + mockDocumentEventHandler = { timeSinceLastUserModification: 1000 } + mockTelemetry = {} + mockTelemetryService = {} + mockCredentialsProvider = { getConnectionMetadata: jest.fn() } + + const clientMetadata = { + processId: 123, + rootUri: null, + capabilities: {}, + initializationOptions: { + aws: { + awsClientCapabilities: { + textDocument: { + inlineCompletionWithReferences: { + inlineEditSupport: true, + }, + }, + }, + }, + }, + } + + handler = new EditCompletionHandler( + mockLogging, + clientMetadata, + mockWorkspace, + mockAmazonQServiceManager, + mockSessionManager, + mockCursorTracker, + mockRecentEditsTracker, + mockRejectedEditTracker, + mockDocumentEventHandler, + mockTelemetry, + mockTelemetryService, + mockCredentialsProvider + ) + }) + + describe('onEditCompletion', () => { + it('should return empty result when in progress', async () => { + handler['isInProgress'] = true + const params = { + textDocument: { uri: 'test.ts' }, + position: { line: 0, character: 0 }, + context: { triggerKind: InlineCompletionTriggerKind.Automatic }, + } + + const result = await handler.onEditCompletion(params as any, {} as any) + + expect(result).toBe(EMPTY_RESULT) + expect(mockLogging.info).toHaveBeenCalledWith('editCompletionHandler is WIP, skip the request') + }) + + it('should return empty result when text document not found', async () => { + mockWorkspace.getTextDocument.mockResolvedValue(null) + const params = { + textDocument: { uri: 'test.ts' }, + position: { line: 0, character: 0 }, + context: { triggerKind: InlineCompletionTriggerKind.Automatic }, + } + + const result = await handler.onEditCompletion(params as any, {} as any) + + expect(result).toBe(EMPTY_RESULT) + expect(mockLogging.warn).toHaveBeenCalledWith('textDocument [test.ts] not found') + }) + + it('should return empty result when service is not token service', async () => { + const mockTextDocument = { languageId: 'typescript' } + mockWorkspace.getTextDocument.mockResolvedValue(mockTextDocument) + mockAmazonQServiceManager.getCodewhispererService.mockReturnValue({}) + + const params = { + textDocument: { uri: 'test.ts' }, + position: { line: 0, character: 0 }, + context: { triggerKind: InlineCompletionTriggerKind.Automatic }, + } + + const result = await handler.onEditCompletion(params as any, {} as any) + + expect(result).toBe(EMPTY_RESULT) + }) + }) + + describe('documentChanged', () => { + it('should set hasDocumentChangedSinceInvocation when waiting', () => { + handler['debounceTimeout'] = setTimeout(() => {}, 1000) as any + handler['isWaiting'] = true + + handler.documentChanged() + + expect(handler['hasDocumentChangedSinceInvocation']).toBe(true) + }) + + it('should refresh timeout when not waiting', () => { + const mockTimeout = { refresh: jest.fn() } + handler['debounceTimeout'] = mockTimeout as any + handler['isWaiting'] = false + + handler.documentChanged() + + expect(mockTimeout.refresh).toHaveBeenCalled() + }) + + it('should do nothing when no timeout exists', () => { + handler['debounceTimeout'] = undefined + + expect(() => handler.documentChanged()).not.toThrow() + }) + }) + + describe('processSuggestionResponse', () => { + it('should filter out similar rejected suggestions', async () => { + mockRejectedEditTracker.isSimilarToRejected.mockReturnValue(true) + const mockSession = { + setSuggestionState: jest.fn(), + id: 'session-1', + } + const suggestionResponse = { + suggestions: [{ itemId: 'item-1', content: 'test content' }], + responseContext: { requestId: 'req-1', nextToken: null }, + } + + const result = await handler.processSuggestionResponse(suggestionResponse as any, mockSession as any, true) + + expect(mockSession.setSuggestionState).toHaveBeenCalledWith('item-1', 'Reject') + expect(result.items).toHaveLength(0) + }) + + it('should return suggestions when not rejected', async () => { + const mockSession = { + setSuggestionState: jest.fn(), + id: 'session-1', + setTimeToFirstRecommendation: jest.fn(), + } + const suggestionResponse = { + suggestions: [{ itemId: 'item-1', content: 'test content' }], + responseContext: { requestId: 'req-1', nextToken: null }, + } + + const result = await handler.processSuggestionResponse(suggestionResponse as any, mockSession as any, true) + + expect(result.items).toHaveLength(1) + expect(result.items[0].insertText).toBe('test content') + expect(result.items[0].isInlineEdit).toBe(true) + }) + }) +}) diff --git a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/editCompletionHandler.ts b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/editCompletionHandler.ts index bbfd441699..de6629da8d 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/editCompletionHandler.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/handler/editCompletionHandler.ts @@ -35,7 +35,7 @@ import { AmazonQBaseServiceManager } from '../../../shared/amazonQServiceManager import { RejectedEditTracker } from '../tracker/rejectedEditTracker' import { getErrorMessage, hasConnectionExpired } from '../../../shared/utils' import { AmazonQError, AmazonQServiceConnectionExpiredError } from '../../../shared/amazonQServiceManager/errors' -import { DocumentChangedListener } from '../documentChangedListener' +import { DocumentEventHandler } from './documentEventHandler' import { EMPTY_RESULT, EDIT_DEBOUNCE_INTERVAL_MS } from '../contants/constants' import { StreakTracker } from '../tracker/streakTracker' @@ -57,7 +57,7 @@ export class EditCompletionHandler { readonly cursorTracker: CursorTracker, readonly recentEditsTracker: RecentEditTracker, readonly rejectedEditTracker: RejectedEditTracker, - readonly documentChangedListener: DocumentChangedListener, + readonly documentEventHandler: DocumentEventHandler, readonly telemetry: Telemetry, readonly telemetryService: TelemetryService, readonly credentialsProvider: CredentialsProvider @@ -291,7 +291,7 @@ export class EditCompletionHandler { this.telemetry, this.telemetryService, currentSession, - this.documentChangedListener.timeSinceLastUserModification, + this.documentEventHandler.timeSinceLastUserModification, 0, 0, [], @@ -363,7 +363,7 @@ export class EditCompletionHandler { this.telemetry, this.telemetryService, session, - this.documentChangedListener.timeSinceLastUserModification, + this.documentEventHandler.timeSinceLastUserModification, 0, 0, [], @@ -381,7 +381,7 @@ export class EditCompletionHandler { await emitEmptyUserTriggerDecisionTelemetry( this.telemetryService, session, - this.documentChangedListener.timeSinceLastUserModification, + this.documentEventHandler.timeSinceLastUserModification, this.editsEnabled ? this.streakTracker.getAndUpdateStreakLength(false) : 0 ) return EMPTY_RESULT