Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { computeDecorations } from '../decorations/computeDecorations'
import { CodelensProvider } from '../codeLenses/codeLenseProvider'
import { PromptMessage, ReferenceLogController } from 'aws-core-vscode/codewhispererChat'
import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer'
import { UserWrittenCodeTracker } from 'aws-core-vscode/codewhisperer'
import {
codicon,
getIcon,
Expand Down Expand Up @@ -84,6 +85,7 @@ export class InlineChatController {
await this.updateTaskAndLenses(task)
this.referenceLogController.addReferenceLog(task.codeReferences, task.replacement ? task.replacement : '')
await this.reset()
UserWrittenCodeTracker.instance.onQFinishesEdits()
}

public async rejectAllChanges(task = this.task, userInvoked: boolean): Promise<void> {
Expand Down Expand Up @@ -199,7 +201,7 @@ export class InlineChatController {
getLogger().info('inlineQuickPick query is empty')
return
}

UserWrittenCodeTracker.instance.onQStartsMakingEdits()
this.userQuery = query
await textDocumentUtil.addEofNewline(editor)
this.task = await this.createTask(query, editor.document, editor.selection)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*!
* 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 * as vscode from 'vscode'
import { UserWrittenCodeTracker, TelemetryHelper, AuthUtil } from 'aws-core-vscode/codewhisperer'
import { createMockDocument, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test'

describe('userWrittenCodeTracker', function () {
describe('test isActive', function () {
afterEach(async function () {
await resetCodeWhispererGlobalVariables()
UserWrittenCodeTracker.instance.reset()
sinon.restore()
})

it('inactive case: telemetryEnable = true, isConnected = false', function () {
sinon.stub(TelemetryHelper.instance, 'isTelemetryEnabled').returns(true)
sinon.stub(AuthUtil.instance, 'isConnected').returns(false)
assert.strictEqual(UserWrittenCodeTracker.instance.isActive(), false)
})

it('inactive case: telemetryEnabled = false, isConnected = false', function () {
sinon.stub(TelemetryHelper.instance, 'isTelemetryEnabled').returns(false)
sinon.stub(AuthUtil.instance, 'isConnected').returns(false)
assert.strictEqual(UserWrittenCodeTracker.instance.isActive(), false)
})

it('active case: telemetryEnabled = true, isConnected = true', function () {
sinon.stub(TelemetryHelper.instance, 'isTelemetryEnabled').returns(true)
sinon.stub(AuthUtil.instance, 'isConnected').returns(true)
assert.strictEqual(UserWrittenCodeTracker.instance.isActive(), true)
})
})

describe('onDocumentChange', function () {
let tracker: UserWrittenCodeTracker | undefined

beforeEach(async function () {
await resetCodeWhispererGlobalVariables()
tracker = UserWrittenCodeTracker.instance
if (tracker) {
sinon.stub(tracker, 'isActive').returns(true)
}
})

afterEach(function () {
sinon.restore()
UserWrittenCodeTracker.instance.reset()
})

it('Should skip when content change size is more than 50', function () {
if (!tracker) {
assert.fail()
}
tracker.onQFeatureInvoked()
tracker.onTextDocumentChange({
reason: undefined,
document: createMockDocument(),
contentChanges: [
{
range: new vscode.Range(0, 0, 0, 600),
rangeOffset: 0,
rangeLength: 600,
text: 'def twoSum(nums, target):\nfor '.repeat(20),
},
],
})
assert.strictEqual(tracker.getUserWrittenCharacters('python'), 0)
assert.strictEqual(tracker.getUserWrittenLines('python'), 0)
})

it('Should not skip when content change size is less than 50', function () {
if (!tracker) {
assert.fail()
}
tracker.onQFeatureInvoked()
tracker.onTextDocumentChange({
reason: undefined,
document: createMockDocument(),
contentChanges: [
{
range: new vscode.Range(0, 0, 0, 49),
rangeOffset: 0,
rangeLength: 49,
text: 'a = 123'.repeat(7),
},
],
})
tracker.onTextDocumentChange({
reason: undefined,
document: createMockDocument('', 'test.java', 'java'),
contentChanges: [
{
range: new vscode.Range(0, 0, 1, 3),
rangeOffset: 0,
rangeLength: 11,
text: 'a = 123\nbcd',
},
],
})
assert.strictEqual(tracker.getUserWrittenCharacters('python'), 49)
assert.strictEqual(tracker.getUserWrittenLines('python'), 0)
assert.strictEqual(tracker.getUserWrittenCharacters('java'), 11)
assert.strictEqual(tracker.getUserWrittenLines('java'), 1)
assert.strictEqual(tracker.getUserWrittenLines('cpp'), 0)
})

it('Should skip when Q is editing', function () {
if (!tracker) {
assert.fail()
}
tracker.onQFeatureInvoked()
tracker.onQStartsMakingEdits()
tracker.onTextDocumentChange({
reason: undefined,
document: createMockDocument(),
contentChanges: [
{
range: new vscode.Range(0, 0, 0, 30),
rangeOffset: 0,
rangeLength: 30,
text: 'def twoSum(nums, target):\nfor',
},
],
})
tracker.onQFinishesEdits()
tracker.onTextDocumentChange({
reason: undefined,
document: createMockDocument(),
contentChanges: [
{
range: new vscode.Range(0, 0, 0, 2),
rangeOffset: 0,
rangeLength: 2,
text: '\na',
},
],
})
assert.strictEqual(tracker.getUserWrittenCharacters('python'), 2)
assert.strictEqual(tracker.getUserWrittenLines('python'), 1)
})

it('Should not reduce tokens when delete', function () {
if (!tracker) {
assert.fail()
}
const doc = createMockDocument('import math', 'test.py', 'python')

tracker.onQFeatureInvoked()
tracker.onTextDocumentChange({
reason: undefined,
document: doc,
contentChanges: [
{
range: new vscode.Range(0, 0, 0, 1),
rangeOffset: 0,
rangeLength: 0,
text: 'a',
},
],
})
tracker.onTextDocumentChange({
reason: undefined,
document: doc,
contentChanges: [
{
range: new vscode.Range(0, 0, 0, 1),
rangeOffset: 0,
rangeLength: 0,
text: 'b',
},
],
})
assert.strictEqual(tracker.getUserWrittenCharacters('python'), 2)
tracker.onTextDocumentChange({
reason: undefined,
document: doc,
contentChanges: [
{
range: new vscode.Range(0, 0, 0, 1),
rangeOffset: 1,
rangeLength: 1,
text: '',
},
],
})
assert.strictEqual(tracker.getUserWrittenCharacters('python'), 2)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getSelectionFromRange,
} from '../../../shared/utilities/textDocumentUtilities'
import { extractFileAndCodeSelectionFromMessage, fs, getErrorMsg, ToolkitError } from '../../../shared'
import { UserWrittenCodeTracker } from '../../../codewhisperer/tracker/userWrittenCodeTracker'

export class ContentProvider implements vscode.TextDocumentContentProvider {
constructor(private uri: vscode.Uri) {}
Expand All @@ -41,6 +42,7 @@ export class EditorContentController {
) {
const editor = window.activeTextEditor
if (editor) {
UserWrittenCodeTracker.instance.onQStartsMakingEdits()
const cursorStart = editor.selection.active
const indentRange = new vscode.Range(new vscode.Position(cursorStart.line, 0), cursorStart)
// use the user editor intent if the position to the left of cursor is just space or tab
Expand All @@ -66,9 +68,11 @@ export class EditorContentController {
if (appliedEdits) {
trackCodeEdit(editor, cursorStart)
}
UserWrittenCodeTracker.instance.onQFinishesEdits()
},
(e) => {
getLogger().error('TextEditor.edit failed: %s', (e as Error).message)
UserWrittenCodeTracker.instance.onQFinishesEdits()
}
)
}
Expand Down Expand Up @@ -97,6 +101,7 @@ export class EditorContentController {

if (filePath && message?.code?.trim().length > 0 && selection) {
try {
UserWrittenCodeTracker.instance.onQStartsMakingEdits()
const doc = await vscode.workspace.openTextDocument(filePath)

const code = getIndentedCode(message, doc, selection)
Expand Down Expand Up @@ -130,6 +135,8 @@ export class EditorContentController {
const wrappedError = ChatDiffError.chain(error, `Failed to Accept Diff`, { code: chatDiffCode })
getLogger().error('%s: Failed to open diff view %s', chatDiffCode, getErrorMsg(wrappedError, true))
throw wrappedError
} finally {
UserWrittenCodeTracker.instance.onQFinishesEdits()
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/amazonqFeatureDev/client/featureDev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { createCodeWhispererChatStreamingClient } from '../../shared/clients/cod
import { getClientId, getOptOutPreference, getOperatingSystem } from '../../shared/telemetry/util'
import { extensionVersion } from '../../shared/vscode/env'
import apiConfig = require('./codewhispererruntime-2022-11-11.json')
import { UserWrittenCodeTracker } from '../../codewhisperer'
import {
FeatureDevCodeAcceptanceEvent,
FeatureDevCodeGenerationEvent,
Expand Down Expand Up @@ -260,6 +261,7 @@ export class FeatureDevClient {
references?: CodeReference[]
}
}
UserWrittenCodeTracker.instance.onQFeatureInvoked()

const newFileContents: { zipFilePath: string; fileContent: string }[] = []
for (const [filePath, fileContent] of Object.entries(newFiles)) {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/amazonqTest/chat/controller/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
TestGenerationBuildStep,
testGenState,
unitTestGenerationCancelMessage,
UserWrittenCodeTracker,
} from '../../../codewhisperer'
import {
fs,
Expand Down Expand Up @@ -651,12 +652,14 @@ export class TestController {
acceptedLines = acceptedLines < 0 ? 0 : acceptedLines
acceptedChars -= originalContent.length
acceptedChars = acceptedChars < 0 ? 0 : acceptedChars
UserWrittenCodeTracker.instance.onQStartsMakingEdits()
const document = await vscode.workspace.openTextDocument(absolutePath)
await applyChanges(
document,
new vscode.Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end),
updatedContent
)
UserWrittenCodeTracker.instance.onQFinishesEdits()
} else {
await fs.writeFile(absolutePath, updatedContent)
}
Expand Down Expand Up @@ -816,6 +819,7 @@ export class TestController {
const chatRequest = triggerPayloadToChatRequest(triggerPayload)
const client = await createCodeWhispererChatStreamingClient()
const response = await client.generateAssistantResponse(chatRequest)
UserWrittenCodeTracker.instance.onQFeatureInvoked()
await this.messenger.sendAIResponse(
response,
session,
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/codewhisperer/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import { SecurityIssueTreeViewProvider } from './service/securityIssueTreeViewPr
import { setContext } from '../shared/vscode/setContext'
import { syncSecurityIssueWebview } from './views/securityIssue/securityIssueWebview'
import { detectCommentAboveLine } from '../shared/utilities/commentUtils'
import { UserWrittenCodeTracker } from './tracker/userWrittenCodeTracker'

let localize: nls.LocalizeFunc

Expand Down Expand Up @@ -555,7 +556,7 @@ export async function activate(context: ExtContext): Promise<void> {
}

CodeWhispererCodeCoverageTracker.getTracker(e.document.languageId)?.countTotalTokens(e)

UserWrittenCodeTracker.instance.onTextDocumentChange(e)
/**
* Handle this keystroke event only when
* 1. It is not a backspace
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/codewhisperer/client/user-service-2.json
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,9 @@
"timestamp": { "shape": "Timestamp" },
"unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" },
"totalNewCodeCharacterCount": { "shape": "PrimitiveInteger" },
"totalNewCodeLineCount": { "shape": "PrimitiveInteger" }
"totalNewCodeLineCount": { "shape": "PrimitiveInteger" },
"userWrittenCodeCharacterCount": { "shape": "PrimitiveInteger" },
"userWrittenCodeLineCount": { "shape": "PrimitiveInteger" }
}
},
"CodeFixAcceptanceEvent": {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/codewhisperer/commands/basicCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import { cancel, confirm } from '../../shared'
import { startCodeFixGeneration } from './startCodeFixGeneration'
import { DefaultAmazonQAppInitContext } from '../../amazonq/apps/initContext'
import path from 'path'
import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker'
import { parsePatch } from 'diff'

const MessageTimeOut = 5_000
Expand Down Expand Up @@ -451,6 +452,7 @@ export const applySecurityFix = Commands.declare(
}
let languageId = undefined
try {
UserWrittenCodeTracker.instance.onQStartsMakingEdits()
const document = await vscode.workspace.openTextDocument(targetFilePath)
languageId = document.languageId
const updatedContent = await getPatchedCode(targetFilePath, suggestedFix.code)
Expand Down Expand Up @@ -565,6 +567,7 @@ export const applySecurityFix = Commands.declare(
applyFixTelemetryEntry.result,
!!targetIssue.suggestedFixes.length
)
UserWrittenCodeTracker.instance.onQFinishesEdits()
}
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { RecommendationService } from '../service/recommendationService'
import { Container } from '../service/serviceContainer'
import { telemetry } from '../../shared/telemetry'
import { TelemetryHelper } from '../util/telemetryHelper'
import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker'

export const acceptSuggestion = Commands.declare(
'aws.amazonq.accept',
Expand Down Expand Up @@ -126,6 +127,7 @@ export async function onInlineAcceptance(acceptanceEntry: OnRecommendationAccept
acceptanceEntry.editor.document.getText(insertedCoderange),
acceptanceEntry.editor.document.fileName
)
UserWrittenCodeTracker.instance.onQFinishesEdits()
if (acceptanceEntry.references !== undefined) {
const referenceLog = ReferenceLogViewProvider.getReferenceLog(
acceptanceEntry.recommendation,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/codewhisperer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,4 @@ export * as CodeWhispererConstants from '../codewhisperer/models/constants'
export { getSelectedCustomization } from './util/customizationUtil'
export { Container } from './service/serviceContainer'
export * from './util/gitUtil'
export { UserWrittenCodeTracker } from './tracker/userWrittenCodeTracker'
Loading
Loading