diff --git a/packages/amazonq/src/app/inline/activation.ts b/packages/amazonq/src/app/inline/activation.ts index d786047b2aa..f1a45c9158c 100644 --- a/packages/amazonq/src/app/inline/activation.ts +++ b/packages/amazonq/src/app/inline/activation.ts @@ -6,68 +6,27 @@ import vscode from 'vscode' import { AuthUtil, - CodeSuggestionsState, CodeWhispererCodeCoverageTracker, CodeWhispererConstants, - CodeWhispererSettings, - ConfigurationEntry, - DefaultCodeWhispererClient, - invokeRecommendation, isInlineCompletionEnabled, - KeyStrokeHandler, - RecommendationHandler, runtimeLanguageContext, TelemetryHelper, UserWrittenCodeTracker, vsCodeState, } from 'aws-core-vscode/codewhisperer' -import { Commands, getLogger, globals, sleep } from 'aws-core-vscode/shared' +import { globals, sleep } from 'aws-core-vscode/shared' export async function activate() { - const codewhispererSettings = CodeWhispererSettings.instance - const client = new DefaultCodeWhispererClient() - if (isInlineCompletionEnabled()) { await setSubscriptionsforInlineCompletion() await AuthUtil.instance.setVscodeContextProps() } - function getAutoTriggerStatus(): boolean { - return CodeSuggestionsState.instance.isSuggestionsEnabled() - } - - async function getConfigEntry(): Promise { - const isShowMethodsEnabled: boolean = - vscode.workspace.getConfiguration('editor').get('suggest.showMethods') || false - const isAutomatedTriggerEnabled: boolean = getAutoTriggerStatus() - const isManualTriggerEnabled: boolean = true - const isSuggestionsWithCodeReferencesEnabled = codewhispererSettings.isSuggestionsWithCodeReferencesEnabled() - - // TODO:remove isManualTriggerEnabled - return { - isShowMethodsEnabled, - isManualTriggerEnabled, - isAutomatedTriggerEnabled, - isSuggestionsWithCodeReferencesEnabled, - } - } - async function setSubscriptionsforInlineCompletion() { - RecommendationHandler.instance.subscribeSuggestionCommands() - /** * Automated trigger */ globals.context.subscriptions.push( - vscode.window.onDidChangeActiveTextEditor(async (editor) => { - await RecommendationHandler.instance.onEditorChange() - }), - vscode.window.onDidChangeWindowState(async (e) => { - await RecommendationHandler.instance.onFocusChange() - }), - vscode.window.onDidChangeTextEditorSelection(async (e) => { - await RecommendationHandler.instance.onCursorChange(e) - }), vscode.workspace.onDidChangeTextDocument(async (e) => { const editor = vscode.window.activeTextEditor if (!editor) { @@ -105,19 +64,6 @@ export async function activate() { * Then this event can be processed by our code. */ await sleep(CodeWhispererConstants.vsCodeCursorUpdateDelay) - if (!RecommendationHandler.instance.isSuggestionVisible()) { - await KeyStrokeHandler.instance.processKeyStroke(e, editor, client, await getConfigEntry()) - } - }), - // manual trigger - Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => { - invokeRecommendation( - vscode.window.activeTextEditor as vscode.TextEditor, - client, - await getConfigEntry() - ).catch((e) => { - getLogger().error('invokeRecommendation failed: %s', (e as Error).message) - }) }) ) } diff --git a/packages/amazonq/test/e2e/inline/inline.test.ts b/packages/amazonq/test/e2e/inline/inline.test.ts index 43a9f67ab73..72442b5cc6d 100644 --- a/packages/amazonq/test/e2e/inline/inline.test.ts +++ b/packages/amazonq/test/e2e/inline/inline.test.ts @@ -14,7 +14,7 @@ import { toTextEditor, using, } from 'aws-core-vscode/test' -import { RecommendationHandler, RecommendationService, session } from 'aws-core-vscode/codewhisperer' +import { session } from 'aws-core-vscode/codewhisperer' import { Commands, globals, sleep, waitUntil, collectionUtil } from 'aws-core-vscode/shared' import { loginToIdC } from '../amazonq/utils/setup' @@ -54,7 +54,6 @@ describe('Amazon Q Inline', async function () { const events = getUserTriggerDecision() console.table({ 'telemetry events': JSON.stringify(events), - 'recommendation service status': RecommendationService.instance.isRunning, }) } @@ -76,24 +75,10 @@ describe('Amazon Q Inline', async function () { if (!suggestionShown) { throw new Error(`Suggestion did not show. Suggestion States: ${JSON.stringify(session.suggestionStates)}`) } - const suggestionVisible = await waitUntil( - async () => RecommendationHandler.instance.isSuggestionVisible(), - waitOptions - ) - if (!suggestionVisible) { - throw new Error( - `Suggestions failed to become visible. Suggestion States: ${JSON.stringify(session.suggestionStates)}` - ) - } console.table({ 'suggestions states': JSON.stringify(session.suggestionStates), - 'valid recommendation': RecommendationHandler.instance.isValidResponse(), - 'recommendation service status': RecommendationService.instance.isRunning, recommendations: session.recommendations, }) - if (!RecommendationHandler.instance.isValidResponse()) { - throw new Error('Did not find a valid response') - } } /** @@ -234,7 +219,7 @@ describe('Amazon Q Inline', async function () { if (name === 'automatic') { // It should never get triggered since its not a supported file type - assert.deepStrictEqual(RecommendationService.instance.isRunning, false) + // assert.deepStrictEqual(RecommendationService.instance.isRunning, false) } else { await getTestWindow().waitForMessage('currently not supported by Amazon Q inline suggestions') } diff --git a/packages/amazonq/test/unit/codewhisperer/commands/invokeRecommendation.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/invokeRecommendation.test.ts deleted file mode 100644 index 68cebe37bb1..00000000000 --- a/packages/amazonq/test/unit/codewhisperer/commands/invokeRecommendation.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test' -import { - ConfigurationEntry, - invokeRecommendation, - InlineCompletionService, - isInlineCompletionEnabled, - DefaultCodeWhispererClient, -} from 'aws-core-vscode/codewhisperer' - -describe('invokeRecommendation', function () { - describe('invokeRecommendation', function () { - let getRecommendationStub: sinon.SinonStub - let mockClient: DefaultCodeWhispererClient - - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - getRecommendationStub = sinon.stub(InlineCompletionService.instance, 'getPaginatedRecommendation') - }) - - afterEach(function () { - sinon.restore() - }) - - it('Should call getPaginatedRecommendation with OnDemand as trigger type when inline completion is enabled', async function () { - const mockEditor = createMockTextEditor() - const config: ConfigurationEntry = { - isShowMethodsEnabled: true, - isManualTriggerEnabled: true, - isAutomatedTriggerEnabled: true, - isSuggestionsWithCodeReferencesEnabled: true, - } - await invokeRecommendation(mockEditor, mockClient, config) - assert.strictEqual(getRecommendationStub.called, isInlineCompletionEnabled()) - }) - }) -}) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts deleted file mode 100644 index 0471aaa3601..00000000000 --- a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import { onAcceptance, AcceptedSuggestionEntry, session, CodeWhispererTracker } from 'aws-core-vscode/codewhisperer' -import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test' - -describe('onAcceptance', function () { - describe('onAcceptance', function () { - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - session.reset() - }) - - afterEach(function () { - sinon.restore() - session.reset() - }) - - it('Should enqueue an event object to tracker', async function () { - const mockEditor = createMockTextEditor() - const trackerSpy = sinon.spy(CodeWhispererTracker.prototype, 'enqueue') - const fakeReferences = [ - { - message: '', - licenseName: 'MIT', - repository: 'http://github.com/fake', - recommendationContentSpan: { - start: 0, - end: 10, - }, - }, - ] - await onAcceptance({ - editor: mockEditor, - range: new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 26)), - effectiveRange: new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 26)), - acceptIndex: 0, - recommendation: "print('Hello World!')", - requestId: '', - sessionId: '', - triggerType: 'OnDemand', - completionType: 'Line', - language: 'python', - references: fakeReferences, - }) - const actualArg = trackerSpy.getCall(0).args[0] as AcceptedSuggestionEntry - assert.ok(trackerSpy.calledOnce) - assert.strictEqual(actualArg.originalString, 'def two_sum(nums, target):') - assert.strictEqual(actualArg.requestId, '') - assert.strictEqual(actualArg.sessionId, '') - assert.strictEqual(actualArg.triggerType, 'OnDemand') - assert.strictEqual(actualArg.completionType, 'Line') - assert.strictEqual(actualArg.language, 'python') - assert.deepStrictEqual(actualArg.startPosition, new vscode.Position(1, 0)) - assert.deepStrictEqual(actualArg.endPosition, new vscode.Position(1, 26)) - assert.strictEqual(actualArg.index, 0) - }) - }) -}) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts deleted file mode 100644 index ed3bc99fa34..00000000000 --- a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test' -import { onInlineAcceptance, RecommendationHandler, session } from 'aws-core-vscode/codewhisperer' - -describe('onInlineAcceptance', function () { - describe('onInlineAcceptance', function () { - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - session.reset() - }) - - afterEach(function () { - sinon.restore() - session.reset() - }) - - it('Should dispose inline completion provider', async function () { - const mockEditor = createMockTextEditor() - const spy = sinon.spy(RecommendationHandler.instance, 'disposeInlineCompletion') - await onInlineAcceptance({ - editor: mockEditor, - range: new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 21)), - effectiveRange: new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 21)), - acceptIndex: 0, - recommendation: "print('Hello World!')", - requestId: '', - sessionId: '', - triggerType: 'OnDemand', - completionType: 'Line', - language: 'python', - references: undefined, - }) - assert.ok(spy.calledWith()) - }) - }) -}) diff --git a/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts deleted file mode 100644 index 956999d64ad..00000000000 --- a/packages/amazonq/test/unit/codewhisperer/service/completionProvider.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' - -import { - getCompletionItems, - getCompletionItem, - getLabel, - Recommendation, - RecommendationHandler, - session, -} from 'aws-core-vscode/codewhisperer' -import { createMockDocument, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' - -describe('completionProviderService', function () { - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - }) - - describe('getLabel', function () { - it('should return correct label given recommendation longer than Constants.LABEL_LENGTH', function () { - const mockLongRecommendation = ` - const metaDataFile = path.join(__dirname, 'nls.metadata.json'); - const locale = getUserDefinedLocale(argvConfig);` - const expected = '\n const m..' - assert.strictEqual(getLabel(mockLongRecommendation), expected) - }) - - it('should return correct label given short recommendation', function () { - const mockShortRecommendation = 'function onReady()' - const expected = 'function onReady()..' - assert.strictEqual(getLabel(mockShortRecommendation), expected) - }) - }) - - describe('getCompletionItem', function () { - it('should return targetCompletionItem given input', function () { - session.startPos = new vscode.Position(0, 0) - RecommendationHandler.instance.requestId = 'mock_requestId_getCompletionItem' - session.sessionId = 'mock_sessionId_getCompletionItem' - const mockPosition = new vscode.Position(0, 1) - const mockRecommendationDetail: Recommendation = { - content: "\n\t\tconsole.log('Hello world!');\n\t}", - } - const mockRecommendationIndex = 1 - const mockDocument = createMockDocument('', 'test.ts', 'typescript') - const expected: vscode.CompletionItem = { - label: "\n\t\tconsole.log('Hell..", - kind: 1, - detail: 'CodeWhisperer', - documentation: new vscode.MarkdownString().appendCodeblock( - "\n\t\tconsole.log('Hello world!');\n\t}", - 'typescript' - ), - sortText: '0000000002', - preselect: true, - insertText: new vscode.SnippetString("\n\t\tconsole.log('Hello world!');\n\t}"), - keepWhitespace: true, - command: { - command: 'aws.amazonq.accept', - title: 'On acceptance', - arguments: [ - new vscode.Range(0, 0, 0, 0), - 1, - "\n\t\tconsole.log('Hello world!');\n\t}", - 'mock_requestId_getCompletionItem', - 'mock_sessionId_getCompletionItem', - 'OnDemand', - 'Line', - 'typescript', - undefined, - ], - }, - } - const actual = getCompletionItem( - mockDocument, - mockPosition, - mockRecommendationDetail, - mockRecommendationIndex - ) - assert.deepStrictEqual(actual.command, expected.command) - assert.strictEqual(actual.sortText, expected.sortText) - assert.strictEqual(actual.label, expected.label) - assert.strictEqual(actual.kind, expected.kind) - assert.strictEqual(actual.preselect, expected.preselect) - assert.strictEqual(actual.keepWhitespace, expected.keepWhitespace) - assert.strictEqual(JSON.stringify(actual.documentation), JSON.stringify(expected.documentation)) - assert.strictEqual(JSON.stringify(actual.insertText), JSON.stringify(expected.insertText)) - }) - }) - - describe('getCompletionItems', function () { - it('should return completion items for each non-empty recommendation', async function () { - session.recommendations = [ - { content: "\n\t\tconsole.log('Hello world!');\n\t}" }, - { content: '\nvar a = 10' }, - ] - const mockPosition = new vscode.Position(0, 0) - const mockDocument = createMockDocument('', 'test.ts', 'typescript') - const actual = getCompletionItems(mockDocument, mockPosition) - assert.strictEqual(actual.length, 2) - }) - - it('should return empty completion items when recommendation is empty', async function () { - session.recommendations = [] - const mockPosition = new vscode.Position(14, 83) - const mockDocument = createMockDocument() - const actual = getCompletionItems(mockDocument, mockPosition) - const expected: vscode.CompletionItem[] = [] - assert.deepStrictEqual(actual, expected) - }) - }) -}) diff --git a/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts b/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts deleted file mode 100644 index 18fd7d2f21b..00000000000 --- a/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts +++ /dev/null @@ -1,255 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import assert from 'assert' -import * as sinon from 'sinon' -import { - CodeWhispererStatusBar, - InlineCompletionService, - ReferenceInlineProvider, - RecommendationHandler, - CodeSuggestionsState, - ConfigurationEntry, - CWInlineCompletionItemProvider, - session, - AuthUtil, - listCodeWhispererCommandsId, - DefaultCodeWhispererClient, -} from 'aws-core-vscode/codewhisperer' -import { createMockTextEditor, resetCodeWhispererGlobalVariables, createMockDocument } from 'aws-core-vscode/test' - -describe('inlineCompletionService', function () { - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - }) - - describe('getPaginatedRecommendation', function () { - const config: ConfigurationEntry = { - isShowMethodsEnabled: true, - isManualTriggerEnabled: true, - isAutomatedTriggerEnabled: true, - isSuggestionsWithCodeReferencesEnabled: true, - } - - let mockClient: DefaultCodeWhispererClient - - beforeEach(async function () { - mockClient = new DefaultCodeWhispererClient() - await resetCodeWhispererGlobalVariables() - }) - - afterEach(function () { - sinon.restore() - }) - - it('should call checkAndResetCancellationTokens before showing inline and next token to be null', async function () { - const mockEditor = createMockTextEditor() - sinon.stub(RecommendationHandler.instance, 'getRecommendations').resolves({ - result: 'Succeeded', - errorMessage: undefined, - recommendationCount: 1, - }) - const checkAndResetCancellationTokensStub = sinon.stub( - RecommendationHandler.instance, - 'checkAndResetCancellationTokens' - ) - session.recommendations = [{ content: "\n\t\tconsole.log('Hello world!');\n\t}" }, { content: '' }] - await InlineCompletionService.instance.getPaginatedRecommendation( - mockClient, - mockEditor, - 'OnDemand', - config - ) - assert.ok(checkAndResetCancellationTokensStub.called) - assert.strictEqual(RecommendationHandler.instance.hasNextToken(), false) - }) - }) - - describe('clearInlineCompletionStates', function () { - it('should remove inline reference and recommendations', async function () { - const fakeReferences = [ - { - message: '', - licenseName: 'MIT', - repository: 'http://github.com/fake', - recommendationContentSpan: { - start: 0, - end: 10, - }, - }, - ] - ReferenceInlineProvider.instance.setInlineReference(1, 'test', fakeReferences) - session.recommendations = [{ content: "\n\t\tconsole.log('Hello world!');\n\t}" }, { content: '' }] - session.language = 'python' - - assert.ok(session.recommendations.length > 0) - await RecommendationHandler.instance.clearInlineCompletionStates() - assert.strictEqual(ReferenceInlineProvider.instance.refs.length, 0) - assert.strictEqual(session.recommendations.length, 0) - }) - }) - - describe('truncateOverlapWithRightContext', function () { - const fileName = 'test.py' - const language = 'python' - const rightContext = 'return target\n' - const doc = `import math\ndef two_sum(nums, target):\n` - const provider = new CWInlineCompletionItemProvider(0, 0, [], '', new vscode.Position(0, 0), '') - - it('removes overlap with right context from suggestion', async function () { - const mockSuggestion = 'return target\n' - const mockEditor = createMockTextEditor(`${doc}${rightContext}`, fileName, language) - const cursorPosition = new vscode.Position(2, 0) - const result = provider.truncateOverlapWithRightContext(mockEditor.document, mockSuggestion, cursorPosition) - assert.strictEqual(result, '') - }) - - it('only removes the overlap part from suggestion', async function () { - const mockSuggestion = 'print(nums)\nreturn target\n' - const mockEditor = createMockTextEditor(`${doc}${rightContext}`, fileName, language) - const cursorPosition = new vscode.Position(2, 0) - const result = provider.truncateOverlapWithRightContext(mockEditor.document, mockSuggestion, cursorPosition) - assert.strictEqual(result, 'print(nums)\n') - }) - - it('only removes the last overlap pattern from suggestion', async function () { - const mockSuggestion = 'return target\nprint(nums)\nreturn target\n' - const mockEditor = createMockTextEditor(`${doc}${rightContext}`, fileName, language) - const cursorPosition = new vscode.Position(2, 0) - const result = provider.truncateOverlapWithRightContext(mockEditor.document, mockSuggestion, cursorPosition) - assert.strictEqual(result, 'return target\nprint(nums)\n') - }) - - it('returns empty string if the remaining suggestion only contains white space', async function () { - const mockSuggestion = 'return target\n ' - const mockEditor = createMockTextEditor(`${doc}${rightContext}`, fileName, language) - const cursorPosition = new vscode.Position(2, 0) - const result = provider.truncateOverlapWithRightContext(mockEditor.document, mockSuggestion, cursorPosition) - assert.strictEqual(result, '') - }) - - it('returns the original suggestion if no match found', async function () { - const mockSuggestion = 'import numpy\n' - const mockEditor = createMockTextEditor(`${doc}${rightContext}`, fileName, language) - const cursorPosition = new vscode.Position(2, 0) - const result = provider.truncateOverlapWithRightContext(mockEditor.document, mockSuggestion, cursorPosition) - assert.strictEqual(result, 'import numpy\n') - }) - - it('ignores the space at the end of recommendation', async function () { - const mockSuggestion = 'return target\n\n\n\n\n' - const mockEditor = createMockTextEditor(`${doc}${rightContext}`, fileName, language) - const cursorPosition = new vscode.Position(2, 0) - const result = provider.truncateOverlapWithRightContext(mockEditor.document, mockSuggestion, cursorPosition) - assert.strictEqual(result, '') - }) - }) -}) - -describe('CWInlineCompletionProvider', function () { - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - }) - - describe('provideInlineCompletionItems', function () { - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - }) - - afterEach(function () { - sinon.restore() - }) - - it('should return undefined if position is before RecommendationHandler start pos', async function () { - const position = new vscode.Position(0, 0) - const document = createMockDocument() - const fakeContext = { triggerKind: 0, selectedCompletionInfo: undefined } - const token = new vscode.CancellationTokenSource().token - const provider = new CWInlineCompletionItemProvider(0, 0, [], '', new vscode.Position(1, 1), '') - const result = await provider.provideInlineCompletionItems(document, position, fakeContext, token) - - assert.ok(result === undefined) - }) - }) -}) - -describe('codewhisperer status bar', function () { - let sandbox: sinon.SinonSandbox - let statusBar: TestStatusBar - let service: InlineCompletionService - - class TestStatusBar extends CodeWhispererStatusBar { - constructor() { - super() - } - - getStatusBar() { - return this.statusBar - } - } - - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - sandbox = sinon.createSandbox() - statusBar = new TestStatusBar() - service = new InlineCompletionService(statusBar) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('shows correct status bar when auth is not connected', async function () { - sandbox.stub(AuthUtil.instance, 'isConnectionValid').returns(false) - sandbox.stub(AuthUtil.instance, 'isConnectionExpired').returns(false) - - await service.refreshStatusBar() - - const actualStatusBar = statusBar.getStatusBar() - assert.strictEqual(actualStatusBar.text, '$(chrome-close) Amazon Q') - assert.strictEqual(actualStatusBar.command, listCodeWhispererCommandsId) - assert.deepStrictEqual(actualStatusBar.backgroundColor, new vscode.ThemeColor('statusBarItem.errorBackground')) - }) - - it('shows correct status bar when auth is connected', async function () { - sandbox.stub(AuthUtil.instance, 'isConnectionValid').returns(true) - sandbox.stub(CodeSuggestionsState.instance, 'isSuggestionsEnabled').returns(true) - - await service.refreshStatusBar() - - const actualStatusBar = statusBar.getStatusBar() - assert.strictEqual(actualStatusBar.text, '$(debug-start) Amazon Q') - assert.strictEqual(actualStatusBar.command, listCodeWhispererCommandsId) - assert.deepStrictEqual(actualStatusBar.backgroundColor, undefined) - }) - - it('shows correct status bar when auth is connected but paused', async function () { - sandbox.stub(AuthUtil.instance, 'isConnectionValid').returns(true) - sandbox.stub(CodeSuggestionsState.instance, 'isSuggestionsEnabled').returns(false) - - await service.refreshStatusBar() - - const actualStatusBar = statusBar.getStatusBar() - assert.strictEqual(actualStatusBar.text, '$(debug-pause) Amazon Q') - assert.strictEqual(actualStatusBar.command, listCodeWhispererCommandsId) - assert.deepStrictEqual(actualStatusBar.backgroundColor, undefined) - }) - - it('shows correct status bar when auth is expired', async function () { - sandbox.stub(AuthUtil.instance, 'isConnectionValid').returns(false) - sandbox.stub(AuthUtil.instance, 'isConnectionExpired').returns(true) - - await service.refreshStatusBar() - - const actualStatusBar = statusBar.getStatusBar() - assert.strictEqual(actualStatusBar.text, '$(debug-disconnect) Amazon Q') - assert.strictEqual(actualStatusBar.command, listCodeWhispererCommandsId) - assert.deepStrictEqual( - actualStatusBar.backgroundColor, - new vscode.ThemeColor('statusBarItem.warningBackground') - ) - }) -}) diff --git a/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts b/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts deleted file mode 100644 index 4b6a5291f22..00000000000 --- a/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import * as codewhispererSdkClient from 'aws-core-vscode/codewhisperer' -import { - createMockTextEditor, - createTextDocumentChangeEvent, - resetCodeWhispererGlobalVariables, -} from 'aws-core-vscode/test' -import * as EditorContext from 'aws-core-vscode/codewhisperer' -import { - ConfigurationEntry, - DocumentChangedSource, - KeyStrokeHandler, - DefaultDocumentChangedType, - RecommendationService, - ClassifierTrigger, - isInlineCompletionEnabled, - RecommendationHandler, - InlineCompletionService, -} from 'aws-core-vscode/codewhisperer' - -describe('keyStrokeHandler', function () { - const config: ConfigurationEntry = { - isShowMethodsEnabled: true, - isManualTriggerEnabled: true, - isAutomatedTriggerEnabled: true, - isSuggestionsWithCodeReferencesEnabled: true, - } - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - }) - describe('processKeyStroke', async function () { - let invokeSpy: sinon.SinonStub - let startTimerSpy: sinon.SinonStub - let mockClient: codewhispererSdkClient.DefaultCodeWhispererClient - beforeEach(async function () { - invokeSpy = sinon.stub(KeyStrokeHandler.instance, 'invokeAutomatedTrigger') - startTimerSpy = sinon.stub(KeyStrokeHandler.instance, 'startIdleTimeTriggerTimer') - sinon.spy(RecommendationHandler.instance, 'getRecommendations') - mockClient = new codewhispererSdkClient.DefaultCodeWhispererClient() - await resetCodeWhispererGlobalVariables() - sinon.stub(mockClient, 'listRecommendations') - sinon.stub(mockClient, 'generateRecommendations') - }) - afterEach(function () { - sinon.restore() - }) - - it('Whatever the input is, should skip when automatic trigger is turned off, should not call invokeAutomatedTrigger', async function () { - const mockEditor = createMockTextEditor() - const mockEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEvent( - mockEditor.document, - new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 1)), - ' ' - ) - const cfg: ConfigurationEntry = { - isShowMethodsEnabled: true, - isManualTriggerEnabled: true, - isAutomatedTriggerEnabled: false, - isSuggestionsWithCodeReferencesEnabled: true, - } - const keyStrokeHandler = new KeyStrokeHandler() - await keyStrokeHandler.processKeyStroke(mockEvent, mockEditor, mockClient, cfg) - assert.ok(!invokeSpy.called) - assert.ok(!startTimerSpy.called) - }) - - it('Should not call invokeAutomatedTrigger when changed text across multiple lines', async function () { - await testShouldInvoke('\nprint(n', false) - }) - - it('Should not call invokeAutomatedTrigger when doing delete or undo (empty changed text)', async function () { - await testShouldInvoke('', false) - }) - - it('Should call invokeAutomatedTrigger with Enter when inputing \n', async function () { - await testShouldInvoke('\n', true) - }) - - it('Should call invokeAutomatedTrigger with Enter when inputing \r\n', async function () { - await testShouldInvoke('\r\n', true) - }) - - it('Should call invokeAutomatedTrigger with SpecialCharacter when inputing {', async function () { - await testShouldInvoke('{', true) - }) - - it('Should not call invokeAutomatedTrigger for non-special characters for classifier language if classifier says no', async function () { - sinon.stub(ClassifierTrigger.instance, 'shouldTriggerFromClassifier').returns(false) - await testShouldInvoke('a', false) - }) - - it('Should call invokeAutomatedTrigger for non-special characters for classifier language if classifier says yes', async function () { - sinon.stub(ClassifierTrigger.instance, 'shouldTriggerFromClassifier').returns(true) - await testShouldInvoke('a', true) - }) - - it('Should skip invoking if there is immediate right context on the same line and not a single }', async function () { - const casesForSuppressTokenFilling = [ - { - rightContext: 'add', - shouldInvoke: false, - }, - { - rightContext: '}', - shouldInvoke: true, - }, - { - rightContext: '} ', - shouldInvoke: true, - }, - { - rightContext: ')', - shouldInvoke: true, - }, - { - rightContext: ') ', - shouldInvoke: true, - }, - { - rightContext: ' add', - shouldInvoke: true, - }, - { - rightContext: ' ', - shouldInvoke: true, - }, - { - rightContext: '\naddTwo', - shouldInvoke: true, - }, - ] - - for (const o of casesForSuppressTokenFilling) { - await testShouldInvoke('{', o.shouldInvoke, o.rightContext) - } - }) - - async function testShouldInvoke(input: string, shouldTrigger: boolean, rightContext: string = '') { - const mockEditor = createMockTextEditor(rightContext, 'test.js', 'javascript', 0, 0) - const mockEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEvent( - mockEditor.document, - new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 1)), - input - ) - await KeyStrokeHandler.instance.processKeyStroke(mockEvent, mockEditor, mockClient, config) - assert.strictEqual( - invokeSpy.called, - shouldTrigger, - `invokeAutomatedTrigger ${shouldTrigger ? 'NOT' : 'WAS'} called for rightContext: "${rightContext}"` - ) - } - }) - - describe('invokeAutomatedTrigger', function () { - let mockClient: codewhispererSdkClient.DefaultCodeWhispererClient - beforeEach(async function () { - sinon.restore() - mockClient = new codewhispererSdkClient.DefaultCodeWhispererClient() - await resetCodeWhispererGlobalVariables() - sinon.stub(mockClient, 'listRecommendations') - sinon.stub(mockClient, 'generateRecommendations') - }) - afterEach(function () { - sinon.restore() - }) - - it('should call getPaginatedRecommendation when inline completion is enabled', async function () { - const mockEditor = createMockTextEditor() - const keyStrokeHandler = new KeyStrokeHandler() - const mockEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEvent( - mockEditor.document, - new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 1)), - ' ' - ) - const getRecommendationsStub = sinon.stub(InlineCompletionService.instance, 'getPaginatedRecommendation') - await keyStrokeHandler.invokeAutomatedTrigger('Enter', mockEditor, mockClient, config, mockEvent) - assert.strictEqual(getRecommendationsStub.called, isInlineCompletionEnabled()) - }) - }) - - describe('shouldTriggerIdleTime', function () { - it('should return false when inline is enabled and inline completion is in progress ', function () { - const keyStrokeHandler = new KeyStrokeHandler() - sinon.stub(RecommendationService.instance, 'isRunning').get(() => true) - const result = keyStrokeHandler.shouldTriggerIdleTime() - assert.strictEqual(result, !isInlineCompletionEnabled()) - }) - }) - - describe('test checkChangeSource', function () { - const tabStr = ' '.repeat(EditorContext.getTabSize()) - - const cases: [string, DocumentChangedSource][] = [ - ['\n ', DocumentChangedSource.EnterKey], - ['\n', DocumentChangedSource.EnterKey], - ['(', DocumentChangedSource.SpecialCharsKey], - ['()', DocumentChangedSource.SpecialCharsKey], - ['{}', DocumentChangedSource.SpecialCharsKey], - ['(a, b):', DocumentChangedSource.Unknown], - [':', DocumentChangedSource.SpecialCharsKey], - ['a', DocumentChangedSource.RegularKey], - [tabStr, DocumentChangedSource.TabKey], - [' ', DocumentChangedSource.Reformatting], - ['def add(a,b):\n return a + b\n', DocumentChangedSource.Unknown], - ['function suggestedByIntelliSense():', DocumentChangedSource.Unknown], - ] - - for (const tuple of cases) { - const input = tuple[0] - const expected = tuple[1] - it(`test input ${input} should return ${expected}`, function () { - const actual = new DefaultDocumentChangedType( - createFakeDocumentChangeEvent(tuple[0]) - ).checkChangeSource() - assert.strictEqual(actual, expected) - }) - } - - function createFakeDocumentChangeEvent(str: string): ReadonlyArray { - return [ - { - range: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 5)), - rangeOffset: 0, - rangeLength: 0, - text: str, - }, - ] - } - }) -}) diff --git a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts b/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts deleted file mode 100644 index 86dfc5e514c..00000000000 --- a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts +++ /dev/null @@ -1,271 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import { - ReferenceInlineProvider, - session, - AuthUtil, - DefaultCodeWhispererClient, - RecommendationsList, - ConfigurationEntry, - RecommendationHandler, - CodeWhispererCodeCoverageTracker, - supplementalContextUtil, -} from 'aws-core-vscode/codewhisperer' -import { - assertTelemetryCurried, - stub, - createMockTextEditor, - resetCodeWhispererGlobalVariables, -} from 'aws-core-vscode/test' -// import * as supplementalContextUtil from 'aws-core-vscode/codewhisperer' - -describe('recommendationHandler', function () { - const config: ConfigurationEntry = { - isShowMethodsEnabled: true, - isManualTriggerEnabled: true, - isAutomatedTriggerEnabled: true, - isSuggestionsWithCodeReferencesEnabled: true, - } - beforeEach(async function () { - await resetCodeWhispererGlobalVariables() - }) - - describe('getRecommendations', async function () { - const mockClient = stub(DefaultCodeWhispererClient) - const mockEditor = createMockTextEditor() - const testStartUrl = 'testStartUrl' - - beforeEach(async function () { - sinon.restore() - await resetCodeWhispererGlobalVariables() - mockClient.listRecommendations.resolves({}) - mockClient.generateRecommendations.resolves({}) - RecommendationHandler.instance.clearRecommendations() - sinon.stub(AuthUtil.instance, 'startUrl').value(testStartUrl) - }) - - afterEach(function () { - sinon.restore() - }) - - it('should assign correct recommendations given input', async function () { - assert.strictEqual(CodeWhispererCodeCoverageTracker.instances.size, 0) - assert.strictEqual( - CodeWhispererCodeCoverageTracker.getTracker(mockEditor.document.languageId)?.serviceInvocationCount, - 0 - ) - - const mockServerResult = { - recommendations: [{ content: "print('Hello World!')" }, { content: '' }], - $response: { - requestId: 'test_request', - httpResponse: { - headers: { - 'x-amzn-sessionid': 'test_request', - }, - }, - }, - } - const handler = new RecommendationHandler() - sinon.stub(handler, 'getServerResponse').resolves(mockServerResult) - await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter', false) - const actual = session.recommendations - const expected: RecommendationsList = [{ content: "print('Hello World!')" }, { content: '' }] - assert.deepStrictEqual(actual, expected) - assert.strictEqual( - CodeWhispererCodeCoverageTracker.getTracker(mockEditor.document.languageId)?.serviceInvocationCount, - 1 - ) - }) - - it('should assign request id correctly', async function () { - const mockServerResult = { - recommendations: [{ content: "print('Hello World!')" }, { content: '' }], - $response: { - requestId: 'test_request', - httpResponse: { - headers: { - 'x-amzn-sessionid': 'test_request', - }, - }, - }, - } - const handler = new RecommendationHandler() - sinon.stub(handler, 'getServerResponse').resolves(mockServerResult) - sinon.stub(handler, 'isCancellationRequested').returns(false) - await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter', false) - assert.strictEqual(handler.requestId, 'test_request') - assert.strictEqual(session.sessionId, 'test_request') - assert.strictEqual(session.triggerType, 'AutoTrigger') - }) - - it('should call telemetry function that records a CodeWhisperer service invocation', async function () { - const mockServerResult = { - recommendations: [{ content: "print('Hello World!')" }, { content: '' }], - $response: { - requestId: 'test_request', - httpResponse: { - headers: { - 'x-amzn-sessionid': 'test_request', - }, - }, - }, - } - const handler = new RecommendationHandler() - sinon.stub(handler, 'getServerResponse').resolves(mockServerResult) - sinon.stub(supplementalContextUtil, 'fetchSupplementalContext').resolves({ - isUtg: false, - isProcessTimeout: false, - supplementalContextItems: [], - contentsLength: 100, - latency: 0, - strategy: 'empty', - }) - sinon.stub(performance, 'now').returns(0.0) - session.startPos = new vscode.Position(1, 0) - session.startCursorOffset = 2 - await handler.getRecommendations(mockClient, mockEditor, 'AutoTrigger', config, 'Enter') - const assertTelemetry = assertTelemetryCurried('codewhisperer_serviceInvocation') - assertTelemetry({ - codewhispererRequestId: 'test_request', - codewhispererSessionId: 'test_request', - codewhispererLastSuggestionIndex: 1, - codewhispererTriggerType: 'AutoTrigger', - codewhispererAutomatedTriggerType: 'Enter', - codewhispererImportRecommendationEnabled: true, - result: 'Succeeded', - codewhispererLineNumber: 1, - codewhispererCursorOffset: 38, - codewhispererLanguage: 'python', - credentialStartUrl: testStartUrl, - codewhispererSupplementalContextIsUtg: false, - codewhispererSupplementalContextTimeout: false, - codewhispererSupplementalContextLatency: 0, - codewhispererSupplementalContextLength: 100, - }) - }) - }) - - describe('isValidResponse', function () { - afterEach(function () { - sinon.restore() - }) - it('should return true if any response is not empty', function () { - const handler = new RecommendationHandler() - session.recommendations = [ - { - content: - '\n // Use the console to output debug info…n of the command with the "command" variable', - }, - { content: '' }, - ] - assert.ok(handler.isValidResponse()) - }) - - it('should return false if response is empty', function () { - const handler = new RecommendationHandler() - session.recommendations = [] - assert.ok(!handler.isValidResponse()) - }) - - it('should return false if all response has no string length', function () { - const handler = new RecommendationHandler() - session.recommendations = [{ content: '' }, { content: '' }] - assert.ok(!handler.isValidResponse()) - }) - }) - - describe('setCompletionType/getCompletionType', function () { - beforeEach(function () { - sinon.restore() - }) - - it('should set the completion type to block given a multi-line suggestion', function () { - session.setCompletionType(0, { content: 'test\n\n \t\r\nanother test' }) - assert.strictEqual(session.getCompletionType(0), 'Block') - - session.setCompletionType(0, { content: 'test\ntest\n' }) - assert.strictEqual(session.getCompletionType(0), 'Block') - - session.setCompletionType(0, { content: '\n \t\r\ntest\ntest' }) - assert.strictEqual(session.getCompletionType(0), 'Block') - }) - - it('should set the completion type to line given a single-line suggestion', function () { - session.setCompletionType(0, { content: 'test' }) - assert.strictEqual(session.getCompletionType(0), 'Line') - - session.setCompletionType(0, { content: 'test\r\t ' }) - assert.strictEqual(session.getCompletionType(0), 'Line') - }) - - it('should set the completion type to line given a multi-line completion but only one-lien of non-blank sequence', function () { - session.setCompletionType(0, { content: 'test\n\t' }) - assert.strictEqual(session.getCompletionType(0), 'Line') - - session.setCompletionType(0, { content: 'test\n ' }) - assert.strictEqual(session.getCompletionType(0), 'Line') - - session.setCompletionType(0, { content: 'test\n\r' }) - assert.strictEqual(session.getCompletionType(0), 'Line') - - session.setCompletionType(0, { content: '\n\n\n\ntest' }) - assert.strictEqual(session.getCompletionType(0), 'Line') - }) - }) - - describe('on event change', async function () { - beforeEach(function () { - const fakeReferences = [ - { - message: '', - licenseName: 'MIT', - repository: 'http://github.com/fake', - recommendationContentSpan: { - start: 0, - end: 10, - }, - }, - ] - ReferenceInlineProvider.instance.setInlineReference(1, 'test', fakeReferences) - session.sessionId = '' - RecommendationHandler.instance.requestId = '' - }) - - it('should remove inline reference onEditorChange', async function () { - session.sessionId = 'aSessionId' - RecommendationHandler.instance.requestId = 'aRequestId' - await RecommendationHandler.instance.onEditorChange() - assert.strictEqual(ReferenceInlineProvider.instance.refs.length, 0) - }) - it('should remove inline reference onFocusChange', async function () { - session.sessionId = 'aSessionId' - RecommendationHandler.instance.requestId = 'aRequestId' - await RecommendationHandler.instance.onFocusChange() - assert.strictEqual(ReferenceInlineProvider.instance.refs.length, 0) - }) - it('should not remove inline reference on cursor change from typing', async function () { - await RecommendationHandler.instance.onCursorChange({ - textEditor: createMockTextEditor(), - selections: [], - kind: vscode.TextEditorSelectionChangeKind.Keyboard, - }) - assert.strictEqual(ReferenceInlineProvider.instance.refs.length, 1) - }) - - it('should remove inline reference on cursor change from mouse movement', async function () { - await RecommendationHandler.instance.onCursorChange({ - textEditor: vscode.window.activeTextEditor!, - selections: [], - kind: vscode.TextEditorSelectionChangeKind.Mouse, - }) - assert.strictEqual(ReferenceInlineProvider.instance.refs.length, 0) - }) - }) -}) diff --git a/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts b/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts index 0f1429f130b..1f11661f002 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts @@ -19,9 +19,7 @@ import { DefaultCodeWhispererClient, ListRecommendationsResponse, Recommendation, - invokeRecommendation, ConfigurationEntry, - RecommendationHandler, session, vsCodeCursorUpdateDelay, AuthUtil, @@ -113,7 +111,6 @@ describe.skip('CodeWhisperer telemetry', async function () { }) async function resetStates() { - await RecommendationHandler.instance.clearInlineCompletionStates() await resetCodeWhispererGlobalVariables() } @@ -424,7 +421,6 @@ describe.skip('CodeWhisperer telemetry', async function () { assert.strictEqual(session.sessionId, 'session_id_1') assert.deepStrictEqual(session.requestIdList, ['request_id_1', 'request_id_1', 'request_id_1_2']) - await RecommendationHandler.instance.onEditorChange() assertSessionClean() await backspace(editor) // todo: without this, the following manual trigger will not be displayed in the test, investigate and fix it @@ -500,7 +496,6 @@ describe.skip('CodeWhisperer telemetry', async function () { await manualTrigger(editor, client, config) await assertTextEditorContains('') - await RecommendationHandler.instance.onFocusChange() assertTelemetry('codewhisperer_userTriggerDecision', [ session1UserTriggerEvent({ codewhispererSuggestionState: 'Reject' }), ]) @@ -513,7 +508,6 @@ async function manualTrigger( client: DefaultCodeWhispererClient, config: ConfigurationEntry ) { - await invokeRecommendation(editor, client, config) await waitUntilSuggestionSeen() } diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index e52e08bb98b..ec49efcedaa 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -16,7 +16,6 @@ import { CodeScanIssue, CodeIssueGroupingStrategyState, } from './models/model' -import { acceptSuggestion } from './commands/onInlineAcceptance' import { CodeWhispererSettings } from './util/codewhispererSettings' import { ExtContext } from '../shared/extensions' import { CodeWhispererTracker } from './tracker/codewhispererTracker' @@ -64,20 +63,16 @@ import { updateSecurityDiagnosticCollection, } from './service/diagnosticsProvider' import { SecurityPanelViewProvider, openEditorAtRange } from './views/securityPanelViewProvider' -import { RecommendationHandler } from './service/recommendationHandler' import { Commands, registerCommandErrorHandler, registerDeclaredCommands } from '../shared/vscode/commands2' -import { InlineCompletionService, refreshStatusBar } from './service/inlineCompletionService' -import { isInlineCompletionEnabled } from './util/commonUtil' +import { refreshStatusBar } from './service/inlineCompletionService' import { AuthUtil } from './util/authUtil' import { ImportAdderProvider } from './service/importAdderProvider' -import { TelemetryHelper } from './util/telemetryHelper' import { openUrl } from '../shared/utilities/vsCodeUtils' import { notifyNewCustomizations, onProfileChangedListener } from './util/customizationUtil' import { CodeWhispererCommandBackend, CodeWhispererCommandDeclarations } from './commands/gettingStartedPageCommands' import { SecurityIssueHoverProvider } from './service/securityIssueHoverProvider' import { SecurityIssueCodeActionProvider } from './service/securityIssueCodeActionProvider' import { listCodeWhispererCommands } from './ui/statusBarMenu' -import { Container } from './service/serviceContainer' import { debounceStartSecurityScan } from './commands/startSecurityScan' import { securityScanLanguageContext } from './util/securityScanLanguageContext' import { registerWebviewErrorHandler } from '../webviews/server' @@ -137,7 +132,6 @@ export async function activate(context: ExtContext): Promise { const client = new codewhispererClient.DefaultCodeWhispererClient() // Service initialization - const container = Container.instance ReferenceInlineProvider.instance ImportAdderProvider.instance @@ -215,20 +209,21 @@ export async function activate(context: ExtContext): Promise { await openSettings('amazonQ') } }), - Commands.register('aws.amazonq.refreshAnnotation', async (forceProceed: boolean) => { - telemetry.record({ - traceId: TelemetryHelper.instance.traceId, - }) - - const editor = vscode.window.activeTextEditor - if (editor) { - if (forceProceed) { - await container.lineAnnotationController.refresh(editor, 'codewhisperer', true) - } else { - await container.lineAnnotationController.refresh(editor, 'codewhisperer') - } - } - }), + // TODO port this to lsp + // Commands.register('aws.amazonq.refreshAnnotation', async (forceProceed: boolean) => { + // telemetry.record({ + // traceId: TelemetryHelper.instance.traceId, + // }) + + // const editor = vscode.window.activeTextEditor + // if (editor) { + // if (forceProceed) { + // await container.lineAnnotationController.refresh(editor, 'codewhisperer', true) + // } else { + // await container.lineAnnotationController.refresh(editor, 'codewhisperer') + // } + // } + // }), // show introduction showIntroduction.register(), // toggle code suggestions @@ -300,22 +295,10 @@ export async function activate(context: ExtContext): Promise { // notify new customizations notifyNewCustomizationsCmd.register(), selectRegionProfileCommand.register(), - /** - * On recommendation acceptance - */ - acceptSuggestion.register(context), // direct CodeWhisperer connection setup with customization connectWithCustomization.register(), - // on text document close. - vscode.workspace.onDidCloseTextDocument((e) => { - if (isInlineCompletionEnabled() && e.uri.fsPath !== InlineCompletionService.instance.filePath()) { - return - } - RecommendationHandler.instance.reportUserDecisions(-1) - }), - vscode.languages.registerHoverProvider( [...CodeWhispererConstants.platformLanguageIds], ReferenceHoverProvider.instance @@ -473,7 +456,6 @@ export async function activate(context: ExtContext): Promise { }) await Commands.tryExecute('aws.amazonq.refreshConnectionCallback') - container.ready() function setSubscriptionsForCodeIssues() { context.extensionContext.subscriptions.push( @@ -511,7 +493,6 @@ export async function activate(context: ExtContext): Promise { } export async function shutdown() { - RecommendationHandler.instance.reportUserDecisions(-1) await CodeWhispererTracker.getTracker().shutdown() } diff --git a/packages/core/src/codewhisperer/commands/invokeRecommendation.ts b/packages/core/src/codewhisperer/commands/invokeRecommendation.ts deleted file mode 100644 index 37fcb965774..00000000000 --- a/packages/core/src/codewhisperer/commands/invokeRecommendation.ts +++ /dev/null @@ -1,45 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { vsCodeState, ConfigurationEntry } from '../models/model' -import { resetIntelliSenseState } from '../util/globalStateUtil' -import { DefaultCodeWhispererClient } from '../client/codewhisperer' -import { RecommendationHandler } from '../service/recommendationHandler' -import { session } from '../util/codeWhispererSession' -import { RecommendationService } from '../service/recommendationService' - -/** - * This function is for manual trigger CodeWhisperer - */ - -export async function invokeRecommendation( - editor: vscode.TextEditor, - client: DefaultCodeWhispererClient, - config: ConfigurationEntry -) { - if (!editor || !config.isManualTriggerEnabled) { - return - } - - /** - * Skip when output channel gains focus and invoke - */ - if (editor.document.languageId === 'Log') { - return - } - /** - * When using intelliSense, if invocation position changed, reject previous active recommendations - */ - if (vsCodeState.isIntelliSenseActive && editor.selection.active !== session.startPos) { - resetIntelliSenseState( - config.isManualTriggerEnabled, - config.isAutomatedTriggerEnabled, - RecommendationHandler.instance.isValidResponse() - ) - } - - await RecommendationService.instance.generateRecommendation(client, editor, 'OnDemand', config, undefined) -} diff --git a/packages/core/src/codewhisperer/commands/onAcceptance.ts b/packages/core/src/codewhisperer/commands/onAcceptance.ts deleted file mode 100644 index e13c197cefd..00000000000 --- a/packages/core/src/codewhisperer/commands/onAcceptance.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { vsCodeState, OnRecommendationAcceptanceEntry } from '../models/model' -import { runtimeLanguageContext } from '../util/runtimeLanguageContext' -import { CodeWhispererTracker } from '../tracker/codewhispererTracker' -import { CodeWhispererCodeCoverageTracker } from '../tracker/codewhispererCodeCoverageTracker' -import { getLogger } from '../../shared/logger/logger' -import { handleExtraBrackets } from '../util/closingBracketUtil' -import { RecommendationHandler } from '../service/recommendationHandler' -import { ReferenceLogViewProvider } from '../service/referenceLogViewProvider' -import { ReferenceHoverProvider } from '../service/referenceHoverProvider' -import path from 'path' - -/** - * This function is called when user accepts a intelliSense suggestion or an inline suggestion - */ -export async function onAcceptance(acceptanceEntry: OnRecommendationAcceptanceEntry) { - RecommendationHandler.instance.cancelPaginatedRequest() - /** - * Format document - */ - if (acceptanceEntry.editor) { - const languageContext = runtimeLanguageContext.getLanguageContext( - acceptanceEntry.editor.document.languageId, - path.extname(acceptanceEntry.editor.document.fileName) - ) - const start = acceptanceEntry.range.start - const end = acceptanceEntry.range.end - - // codewhisperer will be doing editing while formatting. - // formatting should not trigger consoals auto trigger - vsCodeState.isCodeWhispererEditing = true - /** - * Mitigation to right context handling mainly for auto closing bracket use case - */ - try { - await handleExtraBrackets(acceptanceEntry.editor, end, start) - } catch (error) { - getLogger().error(`${error} in handleAutoClosingBrackets`) - } - // move cursor to end of suggestion before doing code format - // after formatting, the end position will still be editor.selection.active - acceptanceEntry.editor.selection = new vscode.Selection(end, end) - - vsCodeState.isCodeWhispererEditing = false - CodeWhispererTracker.getTracker().enqueue({ - time: new Date(), - fileUrl: acceptanceEntry.editor.document.uri, - originalString: acceptanceEntry.editor.document.getText(new vscode.Range(start, end)), - startPosition: start, - endPosition: end, - requestId: acceptanceEntry.requestId, - sessionId: acceptanceEntry.sessionId, - index: acceptanceEntry.acceptIndex, - triggerType: acceptanceEntry.triggerType, - completionType: acceptanceEntry.completionType, - language: languageContext.language, - }) - const insertedCoderange = new vscode.Range(start, end) - CodeWhispererCodeCoverageTracker.getTracker(languageContext.language)?.countAcceptedTokens( - insertedCoderange, - acceptanceEntry.editor.document.getText(insertedCoderange), - acceptanceEntry.editor.document.fileName - ) - if (acceptanceEntry.references !== undefined) { - const referenceLog = ReferenceLogViewProvider.getReferenceLog( - acceptanceEntry.recommendation, - acceptanceEntry.references, - acceptanceEntry.editor - ) - ReferenceLogViewProvider.instance.addReferenceLog(referenceLog) - ReferenceHoverProvider.instance.addCodeReferences( - acceptanceEntry.recommendation, - acceptanceEntry.references - ) - } - } - - // at the end of recommendation acceptance, report user decisions and clear recommendations. - RecommendationHandler.instance.reportUserDecisions(acceptanceEntry.acceptIndex) -} diff --git a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts b/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts deleted file mode 100644 index 50af478ba57..00000000000 --- a/packages/core/src/codewhisperer/commands/onInlineAcceptance.ts +++ /dev/null @@ -1,146 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as CodeWhispererConstants from '../models/constants' -import { vsCodeState, OnRecommendationAcceptanceEntry } from '../models/model' -import { runtimeLanguageContext } from '../util/runtimeLanguageContext' -import { CodeWhispererTracker } from '../tracker/codewhispererTracker' -import { CodeWhispererCodeCoverageTracker } from '../tracker/codewhispererCodeCoverageTracker' -import { getLogger } from '../../shared/logger/logger' -import { RecommendationHandler } from '../service/recommendationHandler' -import { sleep } from '../../shared/utilities/timeoutUtils' -import { handleExtraBrackets } from '../util/closingBracketUtil' -import { Commands } from '../../shared/vscode/commands2' -import { isInlineCompletionEnabled } from '../util/commonUtil' -import { ExtContext } from '../../shared/extensions' -import { onAcceptance } from './onAcceptance' -import * as codewhispererClient from '../client/codewhisperer' -import { - CodewhispererCompletionType, - CodewhispererLanguage, - CodewhispererTriggerType, -} from '../../shared/telemetry/telemetry.gen' -import { ReferenceLogViewProvider } from '../service/referenceLogViewProvider' -import { ReferenceHoverProvider } from '../service/referenceHoverProvider' -import { ImportAdderProvider } from '../service/importAdderProvider' -import { session } from '../util/codeWhispererSession' -import path from 'path' -import { RecommendationService } from '../service/recommendationService' -import { Container } from '../service/serviceContainer' -import { telemetry } from '../../shared/telemetry/telemetry' -import { TelemetryHelper } from '../util/telemetryHelper' -import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker' - -export const acceptSuggestion = Commands.declare( - 'aws.amazonq.accept', - (context: ExtContext) => - async ( - range: vscode.Range, - effectiveRange: vscode.Range, - acceptIndex: number, - recommendation: string, - requestId: string, - sessionId: string, - triggerType: CodewhispererTriggerType, - completionType: CodewhispererCompletionType, - language: CodewhispererLanguage, - references: codewhispererClient.References - ) => { - telemetry.record({ - traceId: TelemetryHelper.instance.traceId, - }) - - RecommendationService.instance.incrementAcceptedCount() - const editor = vscode.window.activeTextEditor - await Container.instance.lineAnnotationController.refresh(editor, 'codewhisperer') - const onAcceptanceFunc = isInlineCompletionEnabled() ? onInlineAcceptance : onAcceptance - await onAcceptanceFunc({ - editor, - range, - effectiveRange, - acceptIndex, - recommendation, - requestId, - sessionId, - triggerType, - completionType, - language, - references, - }) - } -) -/** - * This function is called when user accepts a intelliSense suggestion or an inline suggestion - */ -export async function onInlineAcceptance(acceptanceEntry: OnRecommendationAcceptanceEntry) { - RecommendationHandler.instance.cancelPaginatedRequest() - RecommendationHandler.instance.disposeInlineCompletion() - - if (acceptanceEntry.editor) { - await sleep(CodeWhispererConstants.vsCodeCursorUpdateDelay) - const languageContext = runtimeLanguageContext.getLanguageContext( - acceptanceEntry.editor.document.languageId, - path.extname(acceptanceEntry.editor.document.fileName) - ) - const start = acceptanceEntry.range.start - const end = acceptanceEntry.editor.selection.active - - vsCodeState.isCodeWhispererEditing = true - /** - * Mitigation to right context handling mainly for auto closing bracket use case - */ - try { - // Do not handle extra bracket if there is a right context merge - if (acceptanceEntry.recommendation === session.recommendations[acceptanceEntry.acceptIndex].content) { - await handleExtraBrackets(acceptanceEntry.editor, end, acceptanceEntry.effectiveRange.start) - } - await ImportAdderProvider.instance.onAcceptRecommendation( - acceptanceEntry.editor, - session.recommendations[acceptanceEntry.acceptIndex], - start.line - ) - } catch (error) { - getLogger().error(`${error} in handling extra brackets or imports`) - } finally { - vsCodeState.isCodeWhispererEditing = false - } - - CodeWhispererTracker.getTracker().enqueue({ - time: new Date(), - fileUrl: acceptanceEntry.editor.document.uri, - originalString: acceptanceEntry.editor.document.getText(new vscode.Range(start, end)), - startPosition: start, - endPosition: end, - requestId: acceptanceEntry.requestId, - sessionId: acceptanceEntry.sessionId, - index: acceptanceEntry.acceptIndex, - triggerType: acceptanceEntry.triggerType, - completionType: acceptanceEntry.completionType, - language: languageContext.language, - }) - const insertedCoderange = new vscode.Range(start, end) - CodeWhispererCodeCoverageTracker.getTracker(languageContext.language)?.countAcceptedTokens( - insertedCoderange, - acceptanceEntry.editor.document.getText(insertedCoderange), - acceptanceEntry.editor.document.fileName - ) - UserWrittenCodeTracker.instance.onQFinishesEdits() - if (acceptanceEntry.references !== undefined) { - const referenceLog = ReferenceLogViewProvider.getReferenceLog( - acceptanceEntry.recommendation, - acceptanceEntry.references, - acceptanceEntry.editor - ) - ReferenceLogViewProvider.instance.addReferenceLog(referenceLog) - ReferenceHoverProvider.instance.addCodeReferences( - acceptanceEntry.recommendation, - acceptanceEntry.references - ) - } - - RecommendationHandler.instance.reportUserDecisions(acceptanceEntry.acceptIndex) - } -} diff --git a/packages/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts index 98b7f9239b1..47c8cfe2b95 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -9,13 +9,6 @@ export * from './models/model' export * from './models/constants' export * from './commands/basicCommands' export * from './commands/types' -export { - AutotriggerState, - EndState, - ManualtriggerState, - PressTabState, - TryMoreExState, -} from './views/lineAnnotationController' export type { TransformationProgressUpdate, TransformationStep, @@ -53,22 +46,15 @@ export { IssueItem, SeverityItem, } from './service/securityIssueTreeViewProvider' -export { invokeRecommendation } from './commands/invokeRecommendation' -export { onAcceptance } from './commands/onAcceptance' export { CodeWhispererTracker } from './tracker/codewhispererTracker' -export { RecommendationHandler } from './service/recommendationHandler' export { CodeWhispererUserGroupSettings } from './util/userGroupUtil' export { session } from './util/codeWhispererSession' -export { onInlineAcceptance } from './commands/onInlineAcceptance' export { stopTransformByQ } from './commands/startTransformByQ' -export { getCompletionItems, getCompletionItem, getLabel } from './service/completionProvider' export { featureDefinitions, FeatureConfigProvider } from '../shared/featureConfig' export { ReferenceInlineProvider } from './service/referenceInlineProvider' export { ReferenceHoverProvider } from './service/referenceHoverProvider' export { CWInlineCompletionItemProvider } from './service/inlineCompletionItemProvider' -export { RecommendationService } from './service/recommendationService' export { ClassifierTrigger } from './service/classifierTrigger' -export { DocumentChangedSource, KeyStrokeHandler, DefaultDocumentChangedType } from './service/keyStrokeHandler' export { ReferenceLogViewProvider } from './service/referenceLogViewProvider' export { ImportAdderProvider } from './service/importAdderProvider' export { LicenseUtil } from './util/licenseUtil' diff --git a/packages/core/src/codewhisperer/service/completionProvider.ts b/packages/core/src/codewhisperer/service/completionProvider.ts deleted file mode 100644 index 226d04dec2b..00000000000 --- a/packages/core/src/codewhisperer/service/completionProvider.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as CodeWhispererConstants from '../models/constants' -import { runtimeLanguageContext } from '../util/runtimeLanguageContext' -import { Recommendation } from '../client/codewhisperer' -import { LicenseUtil } from '../util/licenseUtil' -import { RecommendationHandler } from './recommendationHandler' -import { session } from '../util/codeWhispererSession' -import path from 'path' -/** - * completion provider for intelliSense popup - */ -export function getCompletionItems(document: vscode.TextDocument, position: vscode.Position) { - const completionItems: vscode.CompletionItem[] = [] - for (const [index, recommendation] of session.recommendations.entries()) { - completionItems.push(getCompletionItem(document, position, recommendation, index)) - session.setSuggestionState(index, 'Showed') - } - return completionItems -} - -export function getCompletionItem( - document: vscode.TextDocument, - position: vscode.Position, - recommendationDetail: Recommendation, - recommendationIndex: number -) { - const start = session.startPos - const range = new vscode.Range(start, start) - const recommendation = recommendationDetail.content - const completionItem = new vscode.CompletionItem(recommendation) - completionItem.insertText = new vscode.SnippetString(recommendation) - completionItem.documentation = new vscode.MarkdownString().appendCodeblock(recommendation, document.languageId) - completionItem.kind = vscode.CompletionItemKind.Method - completionItem.detail = CodeWhispererConstants.completionDetail - completionItem.keepWhitespace = true - completionItem.label = getLabel(recommendation) - completionItem.preselect = true - completionItem.sortText = String(recommendationIndex + 1).padStart(10, '0') - completionItem.range = new vscode.Range(start, position) - const languageContext = runtimeLanguageContext.getLanguageContext( - document.languageId, - path.extname(document.fileName) - ) - let references: typeof recommendationDetail.references - if (recommendationDetail.references !== undefined && recommendationDetail.references.length > 0) { - references = recommendationDetail.references - const licenses = [ - ...new Set(references.map((r) => `[${r.licenseName}](${LicenseUtil.getLicenseHtml(r.licenseName)})`)), - ].join(', ') - completionItem.documentation.appendMarkdown(CodeWhispererConstants.suggestionDetailReferenceText(licenses)) - } - completionItem.command = { - command: 'aws.amazonq.accept', - title: 'On acceptance', - arguments: [ - range, - recommendationIndex, - recommendation, - RecommendationHandler.instance.requestId, - session.sessionId, - session.triggerType, - session.getCompletionType(recommendationIndex), - languageContext.language, - references, - ], - } - return completionItem -} - -export function getLabel(recommendation: string): string { - return recommendation.slice(0, CodeWhispererConstants.labelLength) + '..' -} diff --git a/packages/core/src/codewhisperer/service/inlineCompletionService.ts b/packages/core/src/codewhisperer/service/inlineCompletionService.ts index cc9887adb1f..18c8f0595aa 100644 --- a/packages/core/src/codewhisperer/service/inlineCompletionService.ts +++ b/packages/core/src/codewhisperer/service/inlineCompletionService.ts @@ -3,36 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ import * as vscode from 'vscode' -import { CodeSuggestionsState, ConfigurationEntry, GetRecommendationsResponse, vsCodeState } from '../models/model' -import * as CodeWhispererConstants from '../models/constants' -import { DefaultCodeWhispererClient } from '../client/codewhisperer' -import { RecommendationHandler } from './recommendationHandler' -import { CodewhispererAutomatedTriggerType, CodewhispererTriggerType } from '../../shared/telemetry/telemetry' -import { showTimedMessage } from '../../shared/utilities/messages' -import { getLogger } from '../../shared/logger/logger' -import { TelemetryHelper } from '../util/telemetryHelper' +import { CodeSuggestionsState } from '../models/model' import { AuthUtil } from '../util/authUtil' -import { shared } from '../../shared/utilities/functionUtils' -import { ClassifierTrigger } from './classifierTrigger' import { getSelectedCustomization } from '../util/customizationUtil' import { codicon, getIcon } from '../../shared/icons' -import { session } from '../util/codeWhispererSession' -import { noSuggestions } from '../models/constants' import { Commands } from '../../shared/vscode/commands2' import { listCodeWhispererCommandsId } from '../ui/statusBarMenu' export class InlineCompletionService { - private maxPage = 100 private statusBar: CodeWhispererStatusBar - private _showRecommendationTimer?: NodeJS.Timer constructor(statusBar: CodeWhispererStatusBar = CodeWhispererStatusBar.instance) { this.statusBar = statusBar - RecommendationHandler.instance.onDidReceiveRecommendation((e) => { - this.startShowRecommendationTimer() - }) - CodeSuggestionsState.instance.onDidChangeState(() => { return this.refreshStatusBar() }) @@ -44,126 +27,6 @@ export class InlineCompletionService { return (this.#instance ??= new this()) } - filePath(): string | undefined { - return RecommendationHandler.instance.documentUri?.fsPath - } - - private sharedTryShowRecommendation = shared( - RecommendationHandler.instance.tryShowRecommendation.bind(RecommendationHandler.instance) - ) - - private startShowRecommendationTimer() { - if (this._showRecommendationTimer) { - clearInterval(this._showRecommendationTimer) - this._showRecommendationTimer = undefined - } - this._showRecommendationTimer = setInterval(() => { - const delay = performance.now() - vsCodeState.lastUserModificationTime - if (delay < CodeWhispererConstants.inlineSuggestionShowDelay) { - return - } - this.sharedTryShowRecommendation() - .catch((e) => { - getLogger().error('tryShowRecommendation failed: %s', (e as Error).message) - }) - .finally(() => { - if (this._showRecommendationTimer) { - clearInterval(this._showRecommendationTimer) - this._showRecommendationTimer = undefined - } - }) - }, CodeWhispererConstants.showRecommendationTimerPollPeriod) - } - - async getPaginatedRecommendation( - client: DefaultCodeWhispererClient, - editor: vscode.TextEditor, - triggerType: CodewhispererTriggerType, - config: ConfigurationEntry, - autoTriggerType?: CodewhispererAutomatedTriggerType, - event?: vscode.TextDocumentChangeEvent - ): Promise { - if (vsCodeState.isCodeWhispererEditing || RecommendationHandler.instance.isSuggestionVisible()) { - return { - result: 'Failed', - errorMessage: 'Amazon Q is already running', - recommendationCount: 0, - } - } - - // Call report user decisions once to report recommendations leftover from last invocation. - RecommendationHandler.instance.reportUserDecisions(-1) - TelemetryHelper.instance.setInvokeSuggestionStartTime() - ClassifierTrigger.instance.recordClassifierResultForAutoTrigger(editor, autoTriggerType, event) - - const triggerChar = event?.contentChanges[0]?.text - if (autoTriggerType === 'SpecialCharacters' && triggerChar) { - TelemetryHelper.instance.setTriggerCharForUserTriggerDecision(triggerChar) - } - const isAutoTrigger = triggerType === 'AutoTrigger' - if (AuthUtil.instance.isConnectionExpired()) { - await AuthUtil.instance.notifyReauthenticate(isAutoTrigger) - return { - result: 'Failed', - errorMessage: 'auth', - recommendationCount: 0, - } - } - - await this.setState('loading') - - RecommendationHandler.instance.checkAndResetCancellationTokens() - RecommendationHandler.instance.documentUri = editor.document.uri - let response: GetRecommendationsResponse = { - result: 'Failed', - errorMessage: undefined, - recommendationCount: 0, - } - try { - let page = 0 - while (page < this.maxPage) { - response = await RecommendationHandler.instance.getRecommendations( - client, - editor, - triggerType, - config, - autoTriggerType, - true, - page - ) - if (RecommendationHandler.instance.checkAndResetCancellationTokens()) { - RecommendationHandler.instance.reportUserDecisions(-1) - await vscode.commands.executeCommand('aws.amazonq.refreshStatusBar') - if (triggerType === 'OnDemand' && session.recommendations.length === 0) { - void showTimedMessage(response.errorMessage ? response.errorMessage : noSuggestions, 2000) - } - return { - result: 'Failed', - errorMessage: 'cancelled', - recommendationCount: 0, - } - } - if (!RecommendationHandler.instance.hasNextToken()) { - break - } - page++ - } - } catch (error) { - getLogger().error(`Error ${error} in getPaginatedRecommendation`) - } - await vscode.commands.executeCommand('aws.amazonq.refreshStatusBar') - if (triggerType === 'OnDemand' && session.recommendations.length === 0) { - void showTimedMessage(response.errorMessage ? response.errorMessage : noSuggestions, 2000) - } - TelemetryHelper.instance.tryRecordClientComponentLatency() - - return { - result: 'Succeeded', - errorMessage: undefined, - recommendationCount: session.recommendations.length, - } - } - /** Updates the status bar to represent the latest CW state */ refreshStatusBar() { if (AuthUtil.instance.isConnectionValid()) { diff --git a/packages/core/src/codewhisperer/service/keyStrokeHandler.ts b/packages/core/src/codewhisperer/service/keyStrokeHandler.ts deleted file mode 100644 index 49ef633a98f..00000000000 --- a/packages/core/src/codewhisperer/service/keyStrokeHandler.ts +++ /dev/null @@ -1,267 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { DefaultCodeWhispererClient } from '../client/codewhisperer' -import * as CodeWhispererConstants from '../models/constants' -import { ConfigurationEntry } from '../models/model' -import { getLogger } from '../../shared/logger/logger' -import { RecommendationHandler } from './recommendationHandler' -import { CodewhispererAutomatedTriggerType } from '../../shared/telemetry/telemetry' -import { getTabSizeSetting } from '../../shared/utilities/editorUtilities' -import { isInlineCompletionEnabled } from '../util/commonUtil' -import { ClassifierTrigger } from './classifierTrigger' -import { extractContextForCodeWhisperer } from '../util/editorContext' -import { RecommendationService } from './recommendationService' - -/** - * This class is for CodeWhisperer auto trigger - */ -export class KeyStrokeHandler { - /** - * Special character which automated triggers codewhisperer - */ - public specialChar: string - /** - * Key stroke count for automated trigger - */ - - private idleTriggerTimer?: NodeJS.Timer - - public lastInvocationTime?: number - - constructor() { - this.specialChar = '' - } - - static #instance: KeyStrokeHandler - - public static get instance() { - return (this.#instance ??= new this()) - } - - public startIdleTimeTriggerTimer( - event: vscode.TextDocumentChangeEvent, - editor: vscode.TextEditor, - client: DefaultCodeWhispererClient, - config: ConfigurationEntry - ) { - if (this.idleTriggerTimer) { - clearInterval(this.idleTriggerTimer) - this.idleTriggerTimer = undefined - } - if (!this.shouldTriggerIdleTime()) { - return - } - this.idleTriggerTimer = setInterval(() => { - const duration = (performance.now() - RecommendationHandler.instance.lastInvocationTime) / 1000 - if (duration < CodeWhispererConstants.invocationTimeIntervalThreshold) { - return - } - - this.invokeAutomatedTrigger('IdleTime', editor, client, config, event) - .catch((e) => { - getLogger().error('invokeAutomatedTrigger failed: %s', (e as Error).message) - }) - .finally(() => { - if (this.idleTriggerTimer) { - clearInterval(this.idleTriggerTimer) - this.idleTriggerTimer = undefined - } - }) - }, CodeWhispererConstants.idleTimerPollPeriod) - } - - public shouldTriggerIdleTime(): boolean { - if (isInlineCompletionEnabled() && RecommendationService.instance.isRunning) { - return false - } - return true - } - - async processKeyStroke( - event: vscode.TextDocumentChangeEvent, - editor: vscode.TextEditor, - client: DefaultCodeWhispererClient, - config: ConfigurationEntry - ): Promise { - try { - if (!config.isAutomatedTriggerEnabled) { - return - } - - // Skip when output channel gains focus and invoke - if (editor.document.languageId === 'Log') { - return - } - - const { rightFileContent } = extractContextForCodeWhisperer(editor) - const rightContextLines = rightFileContent.split(/\r?\n/) - const rightContextAtCurrentLine = rightContextLines[0] - // we do not want to trigger when there is immediate right context on the same line - // with "}" being an exception because of IDE auto-complete - if ( - rightContextAtCurrentLine.length && - !rightContextAtCurrentLine.startsWith(' ') && - rightContextAtCurrentLine.trim() !== '}' && - rightContextAtCurrentLine.trim() !== ')' - ) { - return - } - - let triggerType: CodewhispererAutomatedTriggerType | undefined - const changedSource = new DefaultDocumentChangedType(event.contentChanges).checkChangeSource() - - switch (changedSource) { - case DocumentChangedSource.EnterKey: { - triggerType = 'Enter' - break - } - case DocumentChangedSource.SpecialCharsKey: { - triggerType = 'SpecialCharacters' - break - } - case DocumentChangedSource.RegularKey: { - triggerType = ClassifierTrigger.instance.shouldTriggerFromClassifier(event, editor, triggerType) - ? 'Classifier' - : undefined - break - } - default: { - break - } - } - - if (triggerType) { - await this.invokeAutomatedTrigger(triggerType, editor, client, config, event) - } - } catch (error) { - getLogger().verbose(`Automated Trigger Exception : ${error}`) - } - } - - async invokeAutomatedTrigger( - autoTriggerType: CodewhispererAutomatedTriggerType, - editor: vscode.TextEditor, - client: DefaultCodeWhispererClient, - config: ConfigurationEntry, - event: vscode.TextDocumentChangeEvent - ): Promise { - if (!editor) { - return - } - - // RecommendationHandler.instance.reportUserDecisionOfRecommendation(editor, -1) - await RecommendationService.instance.generateRecommendation( - client, - editor, - 'AutoTrigger', - config, - autoTriggerType - ) - } -} - -export abstract class DocumentChangedType { - constructor(protected readonly contentChanges: ReadonlyArray) { - this.contentChanges = contentChanges - } - - abstract checkChangeSource(): DocumentChangedSource - - // Enter key should always start with ONE '\n' or '\r\n' and potentially following spaces due to IDE reformat - protected isEnterKey(str: string): boolean { - if (str.length === 0) { - return false - } - return ( - (str.startsWith('\r\n') && str.substring(2).trim() === '') || - (str[0] === '\n' && str.substring(1).trim() === '') - ) - } - - // Tab should consist of space char only ' ' and the length % tabSize should be 0 - protected isTabKey(str: string): boolean { - const tabSize = getTabSizeSetting() - if (str.length % tabSize === 0 && str.trim() === '') { - return true - } - return false - } - - protected isUserTypingSpecialChar(str: string): boolean { - return ['(', '()', '[', '[]', '{', '{}', ':'].includes(str) - } - - protected isSingleLine(str: string): boolean { - let newLineCounts = 0 - for (const ch of str) { - if (ch === '\n') { - newLineCounts += 1 - } - } - - // since pressing Enter key possibly will generate string like '\n ' due to indention - if (this.isEnterKey(str)) { - return true - } - if (newLineCounts >= 1) { - return false - } - return true - } -} - -export class DefaultDocumentChangedType extends DocumentChangedType { - constructor(contentChanges: ReadonlyArray) { - super(contentChanges) - } - - checkChangeSource(): DocumentChangedSource { - if (this.contentChanges.length === 0) { - return DocumentChangedSource.Unknown - } - - // event.contentChanges.length will be 2 when user press Enter key multiple times - if (this.contentChanges.length > 2) { - return DocumentChangedSource.Reformatting - } - - // Case when event.contentChanges.length === 1 - const changedText = this.contentChanges[0].text - - if (this.isSingleLine(changedText)) { - if (changedText === '') { - return DocumentChangedSource.Deletion - } else if (this.isEnterKey(changedText)) { - return DocumentChangedSource.EnterKey - } else if (this.isTabKey(changedText)) { - return DocumentChangedSource.TabKey - } else if (this.isUserTypingSpecialChar(changedText)) { - return DocumentChangedSource.SpecialCharsKey - } else if (changedText.length === 1) { - return DocumentChangedSource.RegularKey - } else if (new RegExp('^[ ]+$').test(changedText)) { - // single line && single place reformat should consist of space chars only - return DocumentChangedSource.Reformatting - } else { - return DocumentChangedSource.Unknown - } - } - - // Won't trigger cwspr on multi-line changes - return DocumentChangedSource.Unknown - } -} - -export enum DocumentChangedSource { - SpecialCharsKey = 'SpecialCharsKey', - RegularKey = 'RegularKey', - TabKey = 'TabKey', - EnterKey = 'EnterKey', - Reformatting = 'Reformatting', - Deletion = 'Deletion', - Unknown = 'Unknown', -} diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts deleted file mode 100644 index 8ab491b32e0..00000000000 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ /dev/null @@ -1,724 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { extensionVersion } from '../../shared/vscode/env' -import { RecommendationsList, DefaultCodeWhispererClient, CognitoCredentialsError } from '../client/codewhisperer' -import * as EditorContext from '../util/editorContext' -import * as CodeWhispererConstants from '../models/constants' -import { ConfigurationEntry, GetRecommendationsResponse, vsCodeState } from '../models/model' -import { runtimeLanguageContext } from '../util/runtimeLanguageContext' -import { AWSError } from 'aws-sdk' -import { isAwsError } from '../../shared/errors' -import { TelemetryHelper } from '../util/telemetryHelper' -import { getLogger } from '../../shared/logger/logger' -import { hasVendedIamCredentials } from '../../auth/auth' -import { - asyncCallWithTimeout, - isInlineCompletionEnabled, - isVscHavingRegressionInlineCompletionApi, -} from '../util/commonUtil' -import { showTimedMessage } from '../../shared/utilities/messages' -import { - CodewhispererAutomatedTriggerType, - CodewhispererCompletionType, - CodewhispererGettingStartedTask, - CodewhispererTriggerType, - telemetry, -} from '../../shared/telemetry/telemetry' -import { CodeWhispererCodeCoverageTracker } from '../tracker/codewhispererCodeCoverageTracker' -import { invalidCustomizationMessage } from '../models/constants' -import { getSelectedCustomization, switchToBaseCustomizationAndNotify } from '../util/customizationUtil' -import { session } from '../util/codeWhispererSession' -import { Commands } from '../../shared/vscode/commands2' -import globals from '../../shared/extensionGlobals' -import { noSuggestions, updateInlineLockKey } from '../models/constants' -import AsyncLock from 'async-lock' -import { AuthUtil } from '../util/authUtil' -import { CWInlineCompletionItemProvider } from './inlineCompletionItemProvider' -import { application } from '../util/codeWhispererApplication' -import { openUrl } from '../../shared/utilities/vsCodeUtils' -import { indent } from '../../shared/utilities/textUtilities' -import path from 'path' -import { isIamConnection } from '../../auth/connection' -import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker' - -/** - * This class is for getRecommendation/listRecommendation API calls and its states - * It does not contain UI/UX related logic - */ - -/** - * Commands as a level of indirection so that declare doesn't intercept any registrations for the - * language server implementation. - * - * Otherwise you'll get: - * "Unable to launch amazonq language server: Command "aws.amazonq.rejectCodeSuggestion" has already been declared by the Toolkit" - */ -function createCommands() { - // below commands override VS Code inline completion commands - const prevCommand = Commands.declare('editor.action.inlineSuggest.showPrevious', () => async () => { - await RecommendationHandler.instance.showRecommendation(-1) - }) - const nextCommand = Commands.declare('editor.action.inlineSuggest.showNext', () => async () => { - await RecommendationHandler.instance.showRecommendation(1) - }) - - const rejectCommand = Commands.declare('aws.amazonq.rejectCodeSuggestion', () => async () => { - telemetry.record({ - traceId: TelemetryHelper.instance.traceId, - }) - - await vscode.commands.executeCommand('editor.action.inlineSuggest.hide') - RecommendationHandler.instance.reportUserDecisions(-1) - await Commands.tryExecute('aws.amazonq.refreshAnnotation') - }) - - return { - prevCommand, - nextCommand, - rejectCommand, - } -} - -const lock = new AsyncLock({ maxPending: 1 }) - -export class RecommendationHandler { - public lastInvocationTime: number - // TODO: remove this requestId - public requestId: string - private nextToken: string - private cancellationToken: vscode.CancellationTokenSource - private _onDidReceiveRecommendation: vscode.EventEmitter = new vscode.EventEmitter() - public readonly onDidReceiveRecommendation: vscode.Event = this._onDidReceiveRecommendation.event - private inlineCompletionProvider?: CWInlineCompletionItemProvider - private inlineCompletionProviderDisposable?: vscode.Disposable - private reject: vscode.Disposable - private next: vscode.Disposable - private prev: vscode.Disposable - private _timer?: NodeJS.Timer - documentUri: vscode.Uri | undefined = undefined - - constructor() { - this.requestId = '' - this.nextToken = '' - this.lastInvocationTime = performance.now() - CodeWhispererConstants.invocationTimeIntervalThreshold * 1000 - this.cancellationToken = new vscode.CancellationTokenSource() - this.prev = new vscode.Disposable(() => {}) - this.next = new vscode.Disposable(() => {}) - this.reject = new vscode.Disposable(() => {}) - } - - static #instance: RecommendationHandler - - public static get instance() { - return (this.#instance ??= new this()) - } - - isValidResponse(): boolean { - return session.recommendations.some((r) => r.content.trim() !== '') - } - - async getServerResponse( - triggerType: CodewhispererTriggerType, - isManualTriggerOn: boolean, - promise: Promise - ): Promise { - const timeoutMessage = hasVendedIamCredentials() - ? 'Generate recommendation timeout.' - : 'List recommendation timeout' - if (isManualTriggerOn && triggerType === 'OnDemand' && hasVendedIamCredentials()) { - return vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: CodeWhispererConstants.pendingResponse, - cancellable: false, - }, - async () => { - return await asyncCallWithTimeout( - promise, - timeoutMessage, - CodeWhispererConstants.promiseTimeoutLimit * 1000 - ) - } - ) - } - return await asyncCallWithTimeout(promise, timeoutMessage, CodeWhispererConstants.promiseTimeoutLimit * 1000) - } - - async getTaskTypeFromEditorFileName(filePath: string): Promise { - if (filePath.includes('CodeWhisperer_generate_suggestion')) { - return 'autoTrigger' - } else if (filePath.includes('CodeWhisperer_manual_invoke')) { - return 'manualTrigger' - } else if (filePath.includes('CodeWhisperer_use_comments')) { - return 'commentAsPrompt' - } else if (filePath.includes('CodeWhisperer_navigate_suggestions')) { - return 'navigation' - } else if (filePath.includes('Generate_unit_tests')) { - return 'unitTest' - } else { - return undefined - } - } - - async getRecommendations( - client: DefaultCodeWhispererClient, - editor: vscode.TextEditor, - triggerType: CodewhispererTriggerType, - config: ConfigurationEntry, - autoTriggerType?: CodewhispererAutomatedTriggerType, - pagination: boolean = true, - page: number = 0, - generate: boolean = isIamConnection(AuthUtil.instance.conn) - ): Promise { - let invocationResult: 'Succeeded' | 'Failed' = 'Failed' - let errorMessage: string | undefined = undefined - let errorCode: string | undefined = undefined - - if (!editor) { - return Promise.resolve({ - result: invocationResult, - errorMessage: errorMessage, - recommendationCount: 0, - }) - } - let recommendations: RecommendationsList = [] - let requestId = '' - let sessionId = '' - let reason = '' - let startTime = 0 - let latency = 0 - let nextToken = '' - let shouldRecordServiceInvocation = true - session.language = runtimeLanguageContext.getLanguageContext( - editor.document.languageId, - path.extname(editor.document.fileName) - ).language - session.taskType = await this.getTaskTypeFromEditorFileName(editor.document.fileName) - - if (pagination && !generate) { - if (page === 0) { - session.requestContext = await EditorContext.buildListRecommendationRequest( - editor as vscode.TextEditor, - this.nextToken, - config.isSuggestionsWithCodeReferencesEnabled - ) - } else { - session.requestContext = { - request: { - ...session.requestContext.request, - // Putting nextToken assignment in the end so it overwrites the existing nextToken - nextToken: this.nextToken, - }, - supplementalMetadata: session.requestContext.supplementalMetadata, - } - } - } else { - session.requestContext = await EditorContext.buildGenerateRecommendationRequest(editor as vscode.TextEditor) - } - const request = session.requestContext.request - // record preprocessing end time - TelemetryHelper.instance.setPreprocessEndTime() - - // set start pos for non pagination call or first pagination call - if (!pagination || (pagination && page === 0)) { - session.startPos = editor.selection.active - session.startCursorOffset = editor.document.offsetAt(session.startPos) - session.leftContextOfCurrentLine = EditorContext.getLeftContext(editor, session.startPos.line) - session.triggerType = triggerType - session.autoTriggerType = autoTriggerType - - /** - * Validate request - */ - if (!EditorContext.validateRequest(request)) { - getLogger().verbose('Invalid Request: %O', request) - const languageName = request.fileContext.programmingLanguage.languageName - if (!runtimeLanguageContext.isLanguageSupported(languageName)) { - errorMessage = `${languageName} is currently not supported by Amazon Q inline suggestions` - } - return Promise.resolve({ - result: invocationResult, - errorMessage: errorMessage, - recommendationCount: 0, - }) - } - } - - try { - startTime = performance.now() - this.lastInvocationTime = startTime - const mappedReq = runtimeLanguageContext.mapToRuntimeLanguage(request) - const codewhispererPromise = - pagination && !generate - ? client.listRecommendations(mappedReq) - : client.generateRecommendations(mappedReq) - const resp = await this.getServerResponse(triggerType, config.isManualTriggerEnabled, codewhispererPromise) - TelemetryHelper.instance.setSdkApiCallEndTime() - latency = startTime !== 0 ? performance.now() - startTime : 0 - if ('recommendations' in resp) { - recommendations = (resp && resp.recommendations) || [] - } else { - recommendations = (resp && resp.completions) || [] - } - invocationResult = 'Succeeded' - requestId = resp?.$response && resp?.$response?.requestId - nextToken = resp?.nextToken ? resp?.nextToken : '' - sessionId = resp?.$response?.httpResponse?.headers['x-amzn-sessionid'] - TelemetryHelper.instance.setFirstResponseRequestId(requestId) - if (page === 0) { - session.setTimeToFirstRecommendation(performance.now()) - } - if (nextToken === '') { - TelemetryHelper.instance.setAllPaginationEndTime() - } - } catch (error) { - if (error instanceof CognitoCredentialsError) { - shouldRecordServiceInvocation = false - } - if (latency === 0) { - latency = startTime !== 0 ? performance.now() - startTime : 0 - } - getLogger().error('amazonq inline-suggest: Invocation Exception : %s', (error as Error).message) - if (isAwsError(error)) { - errorMessage = error.message - requestId = error.requestId || '' - errorCode = error.code - reason = `CodeWhisperer Invocation Exception: ${error?.code ?? error?.name ?? 'unknown'}` - await this.onThrottlingException(error, triggerType) - - if (error?.code === 'AccessDeniedException' && errorMessage?.includes('no identity-based policy')) { - getLogger().error('amazonq inline-suggest: AccessDeniedException : %s', (error as Error).message) - void vscode.window - .showErrorMessage(`CodeWhisperer: ${error?.message}`, CodeWhispererConstants.settingsLearnMore) - .then(async (resp) => { - if (resp === CodeWhispererConstants.settingsLearnMore) { - void openUrl(vscode.Uri.parse(CodeWhispererConstants.learnMoreUri)) - } - }) - await vscode.commands.executeCommand('aws.amazonq.enableCodeSuggestions', false) - } - } else { - errorMessage = error instanceof Error ? error.message : String(error) - reason = error ? String(error) : 'unknown' - } - } finally { - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone - - let msg = indent( - `codewhisperer: request-id: ${requestId}, - timestamp(epoch): ${Date.now()}, - timezone: ${timezone}, - datetime: ${new Date().toLocaleString([], { timeZone: timezone })}, - vscode version: '${vscode.version}', - extension version: '${extensionVersion}', - filename: '${EditorContext.getFileName(editor)}', - left context of line: '${session.leftContextOfCurrentLine}', - line number: ${session.startPos.line}, - character location: ${session.startPos.character}, - latency: ${latency} ms. - Recommendations:`, - 4, - true - ).trimStart() - for (const [index, item] of recommendations.entries()) { - msg += `\n ${index.toString().padStart(2, '0')}: ${indent(item.content, 8, true).trim()}` - session.requestIdList.push(requestId) - } - getLogger('nextEditPrediction').debug(`codeWhisper request ${requestId}`) - if (invocationResult === 'Succeeded') { - CodeWhispererCodeCoverageTracker.getTracker(session.language)?.incrementServiceInvocationCount() - UserWrittenCodeTracker.instance.onQFeatureInvoked() - } else { - if ( - (errorMessage?.includes(invalidCustomizationMessage) && errorCode === 'AccessDeniedException') || - errorCode === 'ResourceNotFoundException' - ) { - getLogger() - .debug(`The selected customization is no longer available. Retrying with the default model. - Failed request id: ${requestId}`) - await switchToBaseCustomizationAndNotify() - await this.getRecommendations( - client, - editor, - triggerType, - config, - autoTriggerType, - pagination, - page, - true - ) - } - } - - if (shouldRecordServiceInvocation) { - TelemetryHelper.instance.recordServiceInvocationTelemetry( - requestId, - sessionId, - session.recommendations.length + recommendations.length - 1, - invocationResult, - latency, - session.language, - session.taskType, - reason, - session.requestContext.supplementalMetadata - ) - } - } - - if (this.isCancellationRequested()) { - return Promise.resolve({ - result: invocationResult, - errorMessage: errorMessage, - recommendationCount: session.recommendations.length, - }) - } - - const typedPrefix = editor.document - .getText(new vscode.Range(session.startPos, editor.selection.active)) - .replace('\r\n', '\n') - if (recommendations.length > 0) { - TelemetryHelper.instance.setTypeAheadLength(typedPrefix.length) - // mark suggestions that does not match typeahead when arrival as Discard - // these suggestions can be marked as Showed if typeahead can be removed with new inline API - for (const [i, r] of recommendations.entries()) { - const recommendationIndex = i + session.recommendations.length - if ( - !r.content.startsWith(typedPrefix) && - session.getSuggestionState(recommendationIndex) === undefined - ) { - session.setSuggestionState(recommendationIndex, 'Discard') - } - session.setCompletionType(recommendationIndex, r) - } - session.recommendations = pagination ? session.recommendations.concat(recommendations) : recommendations - if (isInlineCompletionEnabled() && this.hasAtLeastOneValidSuggestion(typedPrefix)) { - this._onDidReceiveRecommendation.fire() - } - } - - this.requestId = requestId - session.sessionId = sessionId - this.nextToken = nextToken - - // send Empty userDecision event if user receives no recommendations in this session at all. - if (invocationResult === 'Succeeded' && nextToken === '') { - // case 1: empty list of suggestion [] - if (session.recommendations.length === 0) { - session.requestIdList.push(requestId) - // Received an empty list of recommendations - TelemetryHelper.instance.recordUserDecisionTelemetryForEmptyList( - session.requestIdList, - sessionId, - page, - runtimeLanguageContext.getLanguageContext( - editor.document.languageId, - path.extname(editor.document.fileName) - ).language, - session.requestContext.supplementalMetadata - ) - } - // case 2: non empty list of suggestion but with (a) empty content or (b) non-matching typeahead - else if (!this.hasAtLeastOneValidSuggestion(typedPrefix)) { - this.reportUserDecisions(-1) - } - } - return Promise.resolve({ - result: invocationResult, - errorMessage: errorMessage, - recommendationCount: session.recommendations.length, - }) - } - - hasAtLeastOneValidSuggestion(typedPrefix: string): boolean { - return session.recommendations.some((r) => r.content.trim() !== '' && r.content.startsWith(typedPrefix)) - } - - cancelPaginatedRequest() { - this.nextToken = '' - this.cancellationToken.cancel() - } - - isCancellationRequested() { - return this.cancellationToken.token.isCancellationRequested - } - - checkAndResetCancellationTokens() { - if (this.isCancellationRequested()) { - this.cancellationToken.dispose() - this.cancellationToken = new vscode.CancellationTokenSource() - this.nextToken = '' - return true - } - return false - } - /** - * Clear recommendation state - */ - clearRecommendations() { - session.requestIdList = [] - session.recommendations = [] - session.suggestionStates = new Map() - session.completionTypes = new Map() - this.requestId = '' - session.sessionId = '' - this.nextToken = '' - session.requestContext.supplementalMetadata = undefined - } - - async clearInlineCompletionStates() { - try { - vsCodeState.isCodeWhispererEditing = false - application()._clearCodeWhispererUIListener.fire() - this.cancelPaginatedRequest() - this.clearRecommendations() - this.disposeInlineCompletion() - await vscode.commands.executeCommand('aws.amazonq.refreshStatusBar') - // fix a regression that requires user to hit Esc twice to clear inline ghost text - // because disposing a provider does not clear the UX - if (isVscHavingRegressionInlineCompletionApi()) { - await vscode.commands.executeCommand('editor.action.inlineSuggest.hide') - } - } finally { - this.clearRejectionTimer() - } - } - - reportDiscardedUserDecisions() { - for (const [i, _] of session.recommendations.entries()) { - session.setSuggestionState(i, 'Discard') - } - this.reportUserDecisions(-1) - } - - /** - * Emits telemetry reflecting user decision for current recommendation. - */ - reportUserDecisions(acceptIndex: number) { - if (session.sessionId === '' || this.requestId === '') { - return - } - TelemetryHelper.instance.recordUserDecisionTelemetry( - session.requestIdList, - session.sessionId, - session.recommendations, - acceptIndex, - session.recommendations.length, - session.completionTypes, - session.suggestionStates, - session.requestContext.supplementalMetadata - ) - if (isInlineCompletionEnabled()) { - this.clearInlineCompletionStates().catch((e) => { - getLogger().error('clearInlineCompletionStates failed: %s', (e as Error).message) - }) - } - } - - hasNextToken(): boolean { - return this.nextToken !== '' - } - - canShowRecommendationInIntelliSense( - editor: vscode.TextEditor, - showPrompt: boolean = false, - response: GetRecommendationsResponse - ): boolean { - const reject = () => { - this.reportUserDecisions(-1) - } - if (!this.isValidResponse()) { - if (showPrompt) { - void showTimedMessage(response.errorMessage ? response.errorMessage : noSuggestions, 3000) - } - reject() - return false - } - // do not show recommendation if cursor is before invocation position - // also mark as Discard - if (editor.selection.active.isBefore(session.startPos)) { - for (const [i, _] of session.recommendations.entries()) { - session.setSuggestionState(i, 'Discard') - } - reject() - return false - } - - // do not show recommendation if typeahead does not match - // also mark as Discard - const typedPrefix = editor.document.getText( - new vscode.Range( - session.startPos.line, - session.startPos.character, - editor.selection.active.line, - editor.selection.active.character - ) - ) - if (!session.recommendations[0].content.startsWith(typedPrefix.trimStart())) { - for (const [i, _] of session.recommendations.entries()) { - session.setSuggestionState(i, 'Discard') - } - reject() - return false - } - return true - } - - async onThrottlingException(awsError: AWSError, triggerType: CodewhispererTriggerType) { - if ( - awsError.code === 'ThrottlingException' && - awsError.message.includes(CodeWhispererConstants.throttlingMessage) - ) { - if (triggerType === 'OnDemand') { - void vscode.window.showErrorMessage(CodeWhispererConstants.freeTierLimitReached) - } - vsCodeState.isFreeTierLimitReached = true - } - } - - public disposeInlineCompletion() { - this.inlineCompletionProviderDisposable?.dispose() - this.inlineCompletionProvider = undefined - } - - private disposeCommandOverrides() { - this.prev.dispose() - this.reject.dispose() - this.next.dispose() - } - - // These commands override the vs code inline completion commands - // They are subscribed when suggestion starts and disposed when suggestion is accepted/rejected - // to avoid impacting other plugins or user who uses this API - private registerCommandOverrides() { - const { prevCommand, nextCommand, rejectCommand } = createCommands() - this.prev = prevCommand.register() - this.next = nextCommand.register() - this.reject = rejectCommand.register() - } - - subscribeSuggestionCommands() { - this.disposeCommandOverrides() - this.registerCommandOverrides() - globals.context.subscriptions.push(this.prev) - globals.context.subscriptions.push(this.next) - globals.context.subscriptions.push(this.reject) - } - - async showRecommendation(indexShift: number, noSuggestionVisible: boolean = false) { - await lock.acquire(updateInlineLockKey, async () => { - if (!vscode.window.state.focused) { - this.reportDiscardedUserDecisions() - return - } - const inlineCompletionProvider = new CWInlineCompletionItemProvider( - this.inlineCompletionProvider?.getActiveItemIndex, - indexShift, - session.recommendations, - this.requestId, - session.startPos, - this.nextToken - ) - this.inlineCompletionProviderDisposable?.dispose() - // when suggestion is active, registering a new provider will let VS Code invoke inline API automatically - this.inlineCompletionProviderDisposable = vscode.languages.registerInlineCompletionItemProvider( - Object.assign([], CodeWhispererConstants.platformLanguageIds), - inlineCompletionProvider - ) - this.inlineCompletionProvider = inlineCompletionProvider - - if (isVscHavingRegressionInlineCompletionApi() && !noSuggestionVisible) { - // fix a regression in new VS Code when disposing and re-registering - // a new provider does not auto refresh the inline suggestion widget - // by manually refresh it - await vscode.commands.executeCommand('editor.action.inlineSuggest.hide') - await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger') - } - if (noSuggestionVisible) { - await vscode.commands.executeCommand(`editor.action.inlineSuggest.trigger`) - this.sendPerceivedLatencyTelemetry() - } - }) - } - - async onEditorChange() { - this.reportUserDecisions(-1) - } - - async onFocusChange() { - this.reportUserDecisions(-1) - } - - async onCursorChange(e: vscode.TextEditorSelectionChangeEvent) { - // we do not want to reset the states for keyboard events because they can be typeahead - if ( - e.kind !== vscode.TextEditorSelectionChangeKind.Keyboard && - vscode.window.activeTextEditor === e.textEditor - ) { - application()._clearCodeWhispererUIListener.fire() - // when cursor change due to mouse movement we need to reset the active item index for inline - if (e.kind === vscode.TextEditorSelectionChangeKind.Mouse) { - this.inlineCompletionProvider?.clearActiveItemIndex() - } - } - } - - isSuggestionVisible(): boolean { - return this.inlineCompletionProvider?.getActiveItemIndex !== undefined - } - - async tryShowRecommendation() { - const editor = vscode.window.activeTextEditor - if (editor === undefined) { - return - } - if (this.isSuggestionVisible()) { - // do not force refresh the tooltip to avoid suggestion "flashing" - return - } - if ( - editor.selection.active.isBefore(session.startPos) || - editor.document.uri.fsPath !== this.documentUri?.fsPath - ) { - for (const [i, _] of session.recommendations.entries()) { - session.setSuggestionState(i, 'Discard') - } - this.reportUserDecisions(-1) - } else if (session.recommendations.length > 0) { - await this.showRecommendation(0, true) - } - } - - private clearRejectionTimer() { - if (this._timer !== undefined) { - clearInterval(this._timer) - this._timer = undefined - } - } - - private sendPerceivedLatencyTelemetry() { - if (vscode.window.activeTextEditor) { - const languageContext = runtimeLanguageContext.getLanguageContext( - vscode.window.activeTextEditor.document.languageId, - vscode.window.activeTextEditor.document.fileName.substring( - vscode.window.activeTextEditor.document.fileName.lastIndexOf('.') + 1 - ) - ) - telemetry.codewhisperer_perceivedLatency.emit({ - codewhispererRequestId: this.requestId, - codewhispererSessionId: session.sessionId, - codewhispererTriggerType: session.triggerType, - codewhispererCompletionType: session.getCompletionType(0), - codewhispererCustomizationArn: getSelectedCustomization().arn, - codewhispererLanguage: languageContext.language, - duration: performance.now() - this.lastInvocationTime, - passive: true, - credentialStartUrl: AuthUtil.instance.startUrl, - result: 'Succeeded', - }) - } - } -} diff --git a/packages/core/src/codewhisperer/service/recommendationService.ts b/packages/core/src/codewhisperer/service/recommendationService.ts deleted file mode 100644 index de78b435913..00000000000 --- a/packages/core/src/codewhisperer/service/recommendationService.ts +++ /dev/null @@ -1,122 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { ConfigurationEntry, GetRecommendationsResponse } from '../models/model' -import { isInlineCompletionEnabled } from '../util/commonUtil' -import { - CodewhispererAutomatedTriggerType, - CodewhispererTriggerType, - telemetry, -} from '../../shared/telemetry/telemetry' -import { InlineCompletionService } from '../service/inlineCompletionService' -import { ClassifierTrigger } from './classifierTrigger' -import { DefaultCodeWhispererClient } from '../client/codewhisperer' -import { randomUUID } from '../../shared/crypto' -import { TelemetryHelper } from '../util/telemetryHelper' -import { AuthUtil } from '../util/authUtil' - -export interface SuggestionActionEvent { - readonly editor: vscode.TextEditor | undefined - readonly isRunning: boolean - readonly triggerType: CodewhispererTriggerType - readonly response: GetRecommendationsResponse | undefined -} - -export class RecommendationService { - static #instance: RecommendationService - - private _isRunning: boolean = false - get isRunning() { - return this._isRunning - } - - private _onSuggestionActionEvent = new vscode.EventEmitter() - get suggestionActionEvent(): vscode.Event { - return this._onSuggestionActionEvent.event - } - - private _acceptedSuggestionCount: number = 0 - get acceptedSuggestionCount() { - return this._acceptedSuggestionCount - } - - private _totalValidTriggerCount: number = 0 - get totalValidTriggerCount() { - return this._totalValidTriggerCount - } - - public static get instance() { - return (this.#instance ??= new RecommendationService()) - } - - incrementAcceptedCount() { - this._acceptedSuggestionCount++ - } - - incrementValidTriggerCount() { - this._totalValidTriggerCount++ - } - - async generateRecommendation( - client: DefaultCodeWhispererClient, - editor: vscode.TextEditor, - triggerType: CodewhispererTriggerType, - config: ConfigurationEntry, - autoTriggerType?: CodewhispererAutomatedTriggerType, - event?: vscode.TextDocumentChangeEvent - ) { - // TODO: should move all downstream auth check(inlineCompletionService, recommendationHandler etc) to here(upstream) instead of spreading everywhere - if (AuthUtil.instance.isConnected() && AuthUtil.instance.requireProfileSelection()) { - return - } - - if (this._isRunning) { - return - } - - /** - * Use an existing trace ID if invoked through a command (e.g., manual invocation), - * otherwise generate a new trace ID - */ - const traceId = telemetry.attributes?.traceId ?? randomUUID() - TelemetryHelper.instance.setTraceId(traceId) - await telemetry.withTraceId(async () => { - if (isInlineCompletionEnabled()) { - if (triggerType === 'OnDemand') { - ClassifierTrigger.instance.recordClassifierResultForManualTrigger(editor) - } - - this._isRunning = true - let response: GetRecommendationsResponse | undefined = undefined - - try { - this._onSuggestionActionEvent.fire({ - editor: editor, - isRunning: true, - triggerType: triggerType, - response: undefined, - }) - - response = await InlineCompletionService.instance.getPaginatedRecommendation( - client, - editor, - triggerType, - config, - autoTriggerType, - event - ) - } finally { - this._isRunning = false - this._onSuggestionActionEvent.fire({ - editor: editor, - isRunning: false, - triggerType: triggerType, - response: response, - }) - } - } - }, traceId) - } -} diff --git a/packages/core/src/codewhisperer/views/activeStateController.ts b/packages/core/src/codewhisperer/views/activeStateController.ts index b3c991a9d38..6994ef8af9a 100644 --- a/packages/core/src/codewhisperer/views/activeStateController.ts +++ b/packages/core/src/codewhisperer/views/activeStateController.ts @@ -6,13 +6,9 @@ import * as vscode from 'vscode' import { LineSelection, LinesChangeEvent } from '../tracker/lineTracker' import { isTextEditor } from '../../shared/utilities/editorUtilities' -import { RecommendationService, SuggestionActionEvent } from '../service/recommendationService' import { subscribeOnce } from '../../shared/utilities/vsCodeUtils' import { Container } from '../service/serviceContainer' -import { RecommendationHandler } from '../service/recommendationHandler' import { cancellableDebounce } from '../../shared/utilities/functionUtils' -import { telemetry } from '../../shared/telemetry/telemetry' -import { TelemetryHelper } from '../util/telemetryHelper' export class ActiveStateController implements vscode.Disposable { private readonly _disposable: vscode.Disposable @@ -34,14 +30,14 @@ export class ActiveStateController implements vscode.Disposable { constructor(private readonly container: Container) { this._disposable = vscode.Disposable.from( - RecommendationService.instance.suggestionActionEvent(async (e) => { - await telemetry.withTraceId(async () => { - await this.onSuggestionActionEvent(e) - }, TelemetryHelper.instance.traceId) - }), - RecommendationHandler.instance.onDidReceiveRecommendation(async (_) => { - await this.onDidReceiveRecommendation() - }), + // RecommendationService.instance.suggestionActionEvent(async (e) => { + // await telemetry.withTraceId(async () => { + // await this.onSuggestionActionEvent(e) + // }, TelemetryHelper.instance.traceId) + // }), + // RecommendationHandler.instance.onDidReceiveRecommendation(async (_) => { + // await this.onDidReceiveRecommendation() + // }), this.container.lineTracker.onDidChangeActiveLines(async (e) => { await this.onActiveLinesChanged(e) }), @@ -70,32 +66,32 @@ export class ActiveStateController implements vscode.Disposable { await this._refresh(vscode.window.activeTextEditor) } - private async onSuggestionActionEvent(e: SuggestionActionEvent) { - if (!this._isReady) { - return - } - - this.clear(e.editor) // do we need this? - if (e.triggerType === 'OnDemand' && e.isRunning) { - // if user triggers on demand, immediately update the UI and cancel the previous debounced update if there is one - this.refreshDebounced.cancel() - await this._refresh(this._editor) - } else { - await this.refreshDebounced.promise(e.editor) - } - } - - private async onDidReceiveRecommendation() { - if (!this._isReady) { - return - } - - if (this._editor && this._editor === vscode.window.activeTextEditor) { - // receives recommendation, immediately update the UI and cacnel the debounced update if there is one - this.refreshDebounced.cancel() - await this._refresh(this._editor, false) - } - } + // private async onSuggestionActionEvent(e: SuggestionActionEvent) { + // if (!this._isReady) { + // return + // } + + // this.clear(e.editor) // do we need this? + // if (e.triggerType === 'OnDemand' && e.isRunning) { + // // if user triggers on demand, immediately update the UI and cancel the previous debounced update if there is one + // this.refreshDebounced.cancel() + // await this._refresh(this._editor) + // } else { + // await this.refreshDebounced.promise(e.editor) + // } + // } + + // private async onDidReceiveRecommendation() { + // if (!this._isReady) { + // return + // } + + // if (this._editor && this._editor === vscode.window.activeTextEditor) { + // // receives recommendation, immediately update the UI and cacnel the debounced update if there is one + // this.refreshDebounced.cancel() + // await this._refresh(this._editor, false) + // } + // } private async onActiveLinesChanged(e: LinesChangeEvent) { if (!this._isReady) { @@ -147,7 +143,7 @@ export class ActiveStateController implements vscode.Disposable { if (shouldDisplay !== undefined) { await this.updateDecorations(editor, selections, shouldDisplay) } else { - await this.updateDecorations(editor, selections, RecommendationService.instance.isRunning) + await this.updateDecorations(editor, selections, true) } } diff --git a/packages/core/src/codewhisperer/views/lineAnnotationController.ts b/packages/core/src/codewhisperer/views/lineAnnotationController.ts index 8b1d38ed7ae..ae853a25739 100644 --- a/packages/core/src/codewhisperer/views/lineAnnotationController.ts +++ b/packages/core/src/codewhisperer/views/lineAnnotationController.ts @@ -9,18 +9,17 @@ import { LineSelection, LinesChangeEvent } from '../tracker/lineTracker' import { isTextEditor } from '../../shared/utilities/editorUtilities' import { cancellableDebounce } from '../../shared/utilities/functionUtils' import { subscribeOnce } from '../../shared/utilities/vsCodeUtils' -import { RecommendationService } from '../service/recommendationService' +// import { RecommendationService } from '../service/recommendationService' import { AnnotationChangeSource, inlinehintKey } from '../models/constants' import globals from '../../shared/extensionGlobals' import { Container } from '../service/serviceContainer' import { telemetry } from '../../shared/telemetry/telemetry' import { getLogger } from '../../shared/logger/logger' import { Commands } from '../../shared/vscode/commands2' -import { session } from '../util/codeWhispererSession' -import { RecommendationHandler } from '../service/recommendationHandler' +// import { session } from '../util/codeWhispererSession' +// import { RecommendationHandler } from '../service/recommendationHandler' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { setContext } from '../../shared/vscode/setContext' -import { TelemetryHelper } from '../util/telemetryHelper' const case3TimeWindow = 30000 // 30 seconds @@ -75,13 +74,14 @@ export class AutotriggerState implements AnnotationState { static acceptedCount = 0 updateState(changeSource: AnnotationChangeSource, force: boolean): AnnotationState | undefined { - if (AutotriggerState.acceptedCount < RecommendationService.instance.acceptedSuggestionCount) { - return new ManualtriggerState() - } else if (session.recommendations.length > 0 && RecommendationHandler.instance.isSuggestionVisible()) { - return new PressTabState() - } else { - return this - } + // if (AutotriggerState.acceptedCount < RecommendationService.instance.acceptedSuggestionCount) { + // return new ManualtriggerState() + // } else if (session.recommendations.length > 0 && RecommendationHandler.instance.isSuggestionVisible()) { + // return new PressTabState() + // } else { + // return this + // } + return undefined } isNextState(state: AnnotationState | undefined): boolean { @@ -268,28 +268,28 @@ export class LineAnnotationController implements vscode.Disposable { subscribeOnce(this.container.lineTracker.onReady)(async (_) => { await this.onReady() }), - RecommendationService.instance.suggestionActionEvent(async (e) => { - await telemetry.withTraceId(async () => { - if (!this._isReady) { - return - } - - if (this._currentState instanceof ManualtriggerState) { - if (e.triggerType === 'OnDemand' && this._currentState.hasManualTrigger === false) { - this._currentState.hasManualTrigger = true - } - if ( - e.response?.recommendationCount !== undefined && - e.response?.recommendationCount > 0 && - this._currentState.hasValidResponse === false - ) { - this._currentState.hasValidResponse = true - } - } - - await this.refresh(e.editor, 'codewhisperer') - }, TelemetryHelper.instance.traceId) - }), + // RecommendationService.instance.suggestionActionEvent(async (e) => { + // await telemetry.withTraceId(async () => { + // if (!this._isReady) { + // return + // } + + // if (this._currentState instanceof ManualtriggerState) { + // if (e.triggerType === 'OnDemand' && this._currentState.hasManualTrigger === false) { + // this._currentState.hasManualTrigger = true + // } + // if ( + // e.response?.recommendationCount !== undefined && + // e.response?.recommendationCount > 0 && + // this._currentState.hasValidResponse === false + // ) { + // this._currentState.hasValidResponse = true + // } + // } + + // await this.refresh(e.editor, 'codewhisperer') + // }, TelemetryHelper.instance.traceId) + // }), this.container.lineTracker.onDidChangeActiveLines(async (e) => { await this.onActiveLinesChanged(e) }), @@ -484,7 +484,7 @@ export class LineAnnotationController implements vscode.Disposable { source: AnnotationChangeSource, force?: boolean ): Partial | undefined { - const isCWRunning = RecommendationService.instance.isRunning + const isCWRunning = true const textOptions: vscode.ThemableDecorationAttachmentRenderOptions = { contentText: '', @@ -517,9 +517,9 @@ export class LineAnnotationController implements vscode.Disposable { this._currentState = updatedState // take snapshot of accepted session so that we can compre if there is delta -> users accept 1 suggestion after seeing this state - AutotriggerState.acceptedCount = RecommendationService.instance.acceptedSuggestionCount + AutotriggerState.acceptedCount = 0 // take snapshot of total trigger count so that we can compare if there is delta -> users accept/reject suggestions after seeing this state - TryMoreExState.triggerCount = RecommendationService.instance.totalValidTriggerCount + TryMoreExState.triggerCount = 0 textOptions.contentText = this._currentState.text() diff --git a/packages/core/src/test/codewhisperer/testUtil.ts b/packages/core/src/test/codewhisperer/testUtil.ts index f3b82fd3850..b0c66d6552f 100644 --- a/packages/core/src/test/codewhisperer/testUtil.ts +++ b/packages/core/src/test/codewhisperer/testUtil.ts @@ -23,7 +23,6 @@ import { HttpResponse, Service } from 'aws-sdk' import userApiConfig = require('./../../codewhisperer/client/user-service-2.json') import CodeWhispererUserClient = require('../../codewhisperer/client/codewhispereruserclient') import { codeWhispererClient } from '../../codewhisperer/client/codewhisperer' -import { RecommendationHandler } from '../../codewhisperer/service/recommendationHandler' import * as model from '../../codewhisperer/models/model' import { stub } from '../utilities/stubber' import { Dirent } from 'fs' // eslint-disable-line no-restricted-imports @@ -36,7 +35,6 @@ export async function resetCodeWhispererGlobalVariables() { session.reset() await globals.globalState.clear() await CodeSuggestionsState.instance.setSuggestionsEnabled(true) - await RecommendationHandler.instance.clearInlineCompletionStates() } export function createMockDocument( diff --git a/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts b/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts deleted file mode 100644 index 0038795ad89..00000000000 --- a/packages/core/src/testE2E/codewhisperer/referenceTracker.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as codewhispererClient from '../../codewhisperer/client/codewhisperer' -import { ConfigurationEntry } from '../../codewhisperer/models/model' -import { setValidConnection, skipTestIfNoValidConn } from '../util/connection' -import { RecommendationHandler } from '../../codewhisperer/service/recommendationHandler' -import { createMockTextEditor, resetCodeWhispererGlobalVariables } from '../../test/codewhisperer/testUtil' -import { invokeRecommendation } from '../../codewhisperer/commands/invokeRecommendation' -import { session } from '../../codewhisperer/util/codeWhispererSession' - -/* -New model deployment may impact references returned. - -These tests: - 1) are not required for github approval flow - 2) will be auto-skipped until fix for manual runs is posted. -*/ - -const leftContext = `InAuto.GetContent( - InAuto.servers.auto, "vendors.json", - function (data) { - let block = ''; - for(let i = 0; i < data.length; i++) { - block += '' + cars[i].title + ''; - } - $('#cars').html(block); - });` - -describe('CodeWhisperer service invocation', async function () { - let validConnection: boolean - const client = new codewhispererClient.DefaultCodeWhispererClient() - const configWithRefs: ConfigurationEntry = { - isShowMethodsEnabled: true, - isManualTriggerEnabled: true, - isAutomatedTriggerEnabled: true, - isSuggestionsWithCodeReferencesEnabled: true, - } - const configWithNoRefs: ConfigurationEntry = { - isShowMethodsEnabled: true, - isManualTriggerEnabled: true, - isAutomatedTriggerEnabled: true, - isSuggestionsWithCodeReferencesEnabled: false, - } - - before(async function () { - validConnection = await setValidConnection() - }) - - beforeEach(function () { - void resetCodeWhispererGlobalVariables() - RecommendationHandler.instance.clearRecommendations() - // TODO: remove this line (this.skip()) when these tests no longer auto-skipped - this.skip() - // valid connection required to run tests - skipTestIfNoValidConn(validConnection, this) - }) - - it('trigger known to return recs with references returns rec with reference', async function () { - // check that handler is empty before invocation - const requestIdBefore = RecommendationHandler.instance.requestId - const sessionIdBefore = session.sessionId - const validRecsBefore = RecommendationHandler.instance.isValidResponse() - - assert.ok(requestIdBefore.length === 0) - assert.ok(sessionIdBefore.length === 0) - assert.ok(!validRecsBefore) - - const doc = leftContext + rightContext - const filename = 'test.js' - const language = 'javascript' - const line = 5 - const character = 39 - const mockEditor = createMockTextEditor(doc, filename, language, line, character) - - await invokeRecommendation(mockEditor, client, configWithRefs) - - const requestId = RecommendationHandler.instance.requestId - const sessionId = session.sessionId - const validRecs = RecommendationHandler.instance.isValidResponse() - const references = session.recommendations[0].references - - assert.ok(requestId.length > 0) - assert.ok(sessionId.length > 0) - assert.ok(validRecs) - assert.ok(references !== undefined) - // TODO: uncomment this assert when this test is no longer auto-skipped - // assert.ok(references.length > 0) - }) - - // This test will fail if user is logged in with IAM identity center - it('trigger known to return rec with references does not return rec with references when reference tracker setting is off', async function () { - // check that handler is empty before invocation - const requestIdBefore = RecommendationHandler.instance.requestId - const sessionIdBefore = session.sessionId - const validRecsBefore = RecommendationHandler.instance.isValidResponse() - - assert.ok(requestIdBefore.length === 0) - assert.ok(sessionIdBefore.length === 0) - assert.ok(!validRecsBefore) - - const doc = leftContext + rightContext - const filename = 'test.js' - const language = 'javascript' - const line = 5 - const character = 39 - const mockEditor = createMockTextEditor(doc, filename, language, line, character) - - await invokeRecommendation(mockEditor, client, configWithNoRefs) - - const requestId = RecommendationHandler.instance.requestId - const sessionId = session.sessionId - const validRecs = RecommendationHandler.instance.isValidResponse() - - assert.ok(requestId.length > 0) - assert.ok(sessionId.length > 0) - // no recs returned because example request returns 1 rec with reference, so no recs returned when references off - assert.ok(!validRecs) - }) -}) diff --git a/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts b/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts deleted file mode 100644 index d4265d13982..00000000000 --- a/packages/core/src/testE2E/codewhisperer/serviceInvocations.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as path from 'path' -import { setValidConnection, skipTestIfNoValidConn } from '../util/connection' -import { ConfigurationEntry } from '../../codewhisperer/models/model' -import * as codewhispererClient from '../../codewhisperer/client/codewhisperer' -import { RecommendationHandler } from '../../codewhisperer/service/recommendationHandler' -import { - createMockTextEditor, - createTextDocumentChangeEvent, - resetCodeWhispererGlobalVariables, -} from '../../test/codewhisperer/testUtil' -import { KeyStrokeHandler } from '../../codewhisperer/service/keyStrokeHandler' -import { sleep } from '../../shared/utilities/timeoutUtils' -import { invokeRecommendation } from '../../codewhisperer/commands/invokeRecommendation' -import { getTestWorkspaceFolder } from '../../testInteg/integrationTestsUtilities' -import { session } from '../../codewhisperer/util/codeWhispererSession' - -describe('CodeWhisperer service invocation', async function () { - let validConnection: boolean - const client = new codewhispererClient.DefaultCodeWhispererClient() - const config: ConfigurationEntry = { - isShowMethodsEnabled: true, - isManualTriggerEnabled: true, - isAutomatedTriggerEnabled: true, - isSuggestionsWithCodeReferencesEnabled: true, - } - - before(async function () { - validConnection = await setValidConnection() - }) - - beforeEach(function () { - void resetCodeWhispererGlobalVariables() - RecommendationHandler.instance.clearRecommendations() - // valid connection required to run tests - skipTestIfNoValidConn(validConnection, this) - }) - - it('manual trigger returns valid recommendation response', async function () { - // check that handler is empty before invocation - const requestIdBefore = RecommendationHandler.instance.requestId - const sessionIdBefore = session.sessionId - const validRecsBefore = RecommendationHandler.instance.isValidResponse() - - assert.ok(requestIdBefore.length === 0) - assert.ok(sessionIdBefore.length === 0) - assert.ok(!validRecsBefore) - - const mockEditor = createMockTextEditor() - await invokeRecommendation(mockEditor, client, config) - - const requestId = RecommendationHandler.instance.requestId - const sessionId = session.sessionId - const validRecs = RecommendationHandler.instance.isValidResponse() - - assert.ok(requestId.length > 0) - assert.ok(sessionId.length > 0) - assert.ok(validRecs) - }) - - it('auto trigger returns valid recommendation response', async function () { - // check that handler is empty before invocation - const requestIdBefore = RecommendationHandler.instance.requestId - const sessionIdBefore = session.sessionId - const validRecsBefore = RecommendationHandler.instance.isValidResponse() - - assert.ok(requestIdBefore.length === 0) - assert.ok(sessionIdBefore.length === 0) - assert.ok(!validRecsBefore) - - const mockEditor = createMockTextEditor() - - const mockEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEvent( - mockEditor.document, - new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 1)), - '\n' - ) - - await KeyStrokeHandler.instance.processKeyStroke(mockEvent, mockEditor, client, config) - // wait for 5 seconds to allow time for response to be generated - await sleep(5000) - - const requestId = RecommendationHandler.instance.requestId - const sessionId = session.sessionId - const validRecs = RecommendationHandler.instance.isValidResponse() - - assert.ok(requestId.length > 0) - assert.ok(sessionId.length > 0) - assert.ok(validRecs) - }) - - it('invocation in unsupported language does not generate a request', async function () { - const workspaceFolder = getTestWorkspaceFolder() - const appRoot = path.join(workspaceFolder, 'go1-plain-sam-app') - const appCodePath = path.join(appRoot, 'hello-world', 'main.go') - - // check that handler is empty before invocation - const requestIdBefore = RecommendationHandler.instance.requestId - const sessionIdBefore = session.sessionId - const validRecsBefore = RecommendationHandler.instance.isValidResponse() - - assert.ok(requestIdBefore.length === 0) - assert.ok(sessionIdBefore.length === 0) - assert.ok(!validRecsBefore) - - const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(appCodePath)) - const editor = await vscode.window.showTextDocument(doc) - await invokeRecommendation(editor, client, config) - - const requestId = RecommendationHandler.instance.requestId - const sessionId = session.sessionId - const validRecs = RecommendationHandler.instance.isValidResponse() - - assert.ok(requestId.length === 0) - assert.ok(sessionId.length === 0) - assert.ok(!validRecs) - }) -})