Skip to content

Commit

Permalink
fix(codelens): update codelens visibility based on Tips and Amazon Q …
Browse files Browse the repository at this point in the history
…Chat

- disable tips for filetypes not supported by amazon q
- disable codelens if amazon q chat is visible
- disable codelens if lineAnnotationController is visible
  • Loading branch information
ivikash committed May 24, 2024
1 parent 020f6f7 commit ff225c1
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 15 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/amazonq/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export async function activate(context: ExtensionContext) {
appInitContext.onDidChangeAmazonQVisibility
)

await TryChatCodeLensProvider.register()
await TryChatCodeLensProvider.register(appInitContext.onDidChangeAmazonQVisibility.event)

context.subscriptions.push(
window.registerWebviewViewProvider(AmazonQChatViewProvider.viewType, provider, {
Expand Down
16 changes: 11 additions & 5 deletions packages/core/src/codewhisperer/views/lineAnnotationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { getLogger } from '../../shared/logger/logger'
import { Commands } from '../../shared/vscode/commands2'
import { session } from '../util/codeWhispererSession'
import { RecommendationHandler } from '../service/recommendationHandler'
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'

const case3TimeWindow = 30000 // 30 seconds

Expand Down Expand Up @@ -61,7 +62,7 @@ interface AnnotationState {
* User accepts 1 suggestion
*
*/
class AutotriggerState implements AnnotationState {
export class AutotriggerState implements AnnotationState {
static id = 'codewhisperer_learnmore_case_1'
id = AutotriggerState.id

Expand Down Expand Up @@ -93,7 +94,7 @@ class AutotriggerState implements AnnotationState {
* Exit criteria:
* User accepts 1 suggestion
*/
class PressTabState implements AnnotationState {
export class PressTabState implements AnnotationState {
static id = 'codewhisperer_learnmore_case_1a'
id = PressTabState.id

Expand All @@ -119,7 +120,7 @@ class PressTabState implements AnnotationState {
* Exit criteria:
* User inokes manual trigger shortcut
*/
class ManualtriggerState implements AnnotationState {
export class ManualtriggerState implements AnnotationState {
static id = 'codewhisperer_learnmore_case_2'
id = ManualtriggerState.id

Expand Down Expand Up @@ -161,7 +162,7 @@ class ManualtriggerState implements AnnotationState {
* Exit criteria:
* User accepts or rejects the suggestion
*/
class TryMoreExState implements AnnotationState {
export class TryMoreExState implements AnnotationState {
static id = 'codewhisperer_learnmore_case_3'
id = TryMoreExState.id

Expand All @@ -183,7 +184,7 @@ class TryMoreExState implements AnnotationState {
static learnmoeCount: number = 0
}

class EndState implements AnnotationState {
export class EndState implements AnnotationState {
static id = 'codewhisperer_learnmore_end'
id = EndState.id

Expand Down Expand Up @@ -379,6 +380,11 @@ export class LineAnnotationController implements vscode.Disposable {
return
}

// Disable Tips when language is not supported by Amazon Q.
if (!runtimeLanguageContext.isLanguageSupported(editor.document.languageId)) {
return
}

await this.updateDecorations(editor, selections, source, force)
}

Expand Down
28 changes: 25 additions & 3 deletions packages/core/src/codewhispererChat/editor/codelens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Commands, placeholder } from '../../shared/vscode/commands2'
import { platform } from 'os'
import { focusAmazonQPanel } from '../commands/registerCommands'
import { AuthStates, AuthUtil } from '../../codewhisperer/util/authUtil'
import { inlinehintKey } from '../../codewhisperer/models/constants'
import { EndState } from '../../codewhisperer/views/lineAnnotationController'

/** When the user clicks the CodeLens that prompts user to try Amazon Q chat */
export const tryChatCodeLensCommand = Commands.declare(`_aws.amazonq.tryChatCodeLens`, () => async () => {
Expand Down Expand Up @@ -36,15 +38,25 @@ export class TryChatCodeLensProvider implements vscode.CodeLensProvider {
private static providerDisposable: vscode.Disposable | undefined = undefined
private disposables: vscode.Disposable[] = []

constructor(private readonly cursorPositionIfValid = () => TryChatCodeLensProvider._resolveCursorPosition()) {
private isAmazonQVisible: boolean = false

constructor(
isAmazonQVisibleEvent: vscode.Event<boolean>,
private readonly cursorPositionIfValid = () => TryChatCodeLensProvider._resolveCursorPosition()
) {
// when we want to recalculate the codelens
this.disposables.push(
vscode.window.onDidChangeActiveTextEditor(() => this._onDidChangeCodeLenses.fire()),
vscode.window.onDidChangeTextEditorSelection(() => this._onDidChangeCodeLenses.fire())
)

isAmazonQVisibleEvent(visible => {
this.isAmazonQVisible = visible
this._onDidChangeCodeLenses.fire()
})
}

static async register(): Promise<boolean> {
static async register(isAmazonQVisible: vscode.Event<boolean>): Promise<boolean> {
const shouldShow = globals.context.globalState.get(this.showCodeLensId, true)
if (!shouldShow) {
return false
Expand All @@ -54,7 +66,7 @@ export class TryChatCodeLensProvider implements vscode.CodeLensProvider {
throw new ToolkitError(`${this.name} can only be registered once.`)
}

const provider = new TryChatCodeLensProvider()
const provider = new TryChatCodeLensProvider(isAmazonQVisible)
this.providerDisposable = vscode.languages.registerCodeLensProvider({ scheme: 'file' }, provider)
globals.context.subscriptions.push(provider)
return true
Expand All @@ -67,6 +79,16 @@ export class TryChatCodeLensProvider implements vscode.CodeLensProvider {
return new Promise(resolve => {
token.onCancellationRequested(() => resolve([]))

/**
* Disable Codelens if
* - lineAnnotationController is visible (i.e not in end state)
* - Amazon Q chat is visible
*/
const isLineAnnotationVisible = globals.context.globalState.get<string>(inlinehintKey)
if ((isLineAnnotationVisible && isLineAnnotationVisible !== EndState.id) || this.isAmazonQVisible) {
return resolve([])
}

if (AuthUtil.instance.getChatAuthStateSync().amazonQ !== AuthStates.connected) {
return resolve([])
}
Expand Down
88 changes: 82 additions & 6 deletions packages/core/src/test/codewhispererChat/editor/codelens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,41 @@ import globals from '../../../shared/extensionGlobals'
import { focusAmazonQPanel } from '../../../codewhispererChat/commands/registerCommands'
import sinon from 'sinon'
import { AuthState, AuthStates, AuthUtil, FeatureAuthState } from '../../../codewhisperer/util/authUtil'
import { inlinehintKey } from '../../../codewhisperer/models/constants'
import {
AutotriggerState,
EndState,
ManualtriggerState,
PressTabState,
TryMoreExState,
} from '../../../codewhisperer/views/lineAnnotationController'

describe('TryChatCodeLensProvider', () => {
let instance: TryChatCodeLensProvider = new TryChatCodeLensProvider()
let instance: TryChatCodeLensProvider
let cancellationTokenSource: vscode.CancellationTokenSource
let clock: InstalledClock
const codeLensPosition = new vscode.Position(1, 2)

let isAmazonQVisibleEventEmitter: vscode.EventEmitter<boolean>
let isAmazonQVisibleEvent: vscode.Event<boolean>

before(async function () {
// HACK: We need to register these commands since the `core` `activate()` function
// does not run Amazon Q `activate()` functions anymore. Due to this we need to register the Commands
// that originally would have been registered by the `core` `activate()` at some point
tryRegister(tryChatCodeLensCommand)
tryRegister(focusAmazonQPanel)
await TryChatCodeLensProvider.register()
})

beforeEach(function () {
instance = new TryChatCodeLensProvider(() => codeLensPosition)
beforeEach(async function () {
isAmazonQVisibleEventEmitter = new vscode.EventEmitter<boolean>()
isAmazonQVisibleEvent = isAmazonQVisibleEventEmitter.event
instance = new TryChatCodeLensProvider(isAmazonQVisibleEvent, () => codeLensPosition)
clock = installFakeClock()
})

afterEach(function () {
isAmazonQVisibleEventEmitter.dispose()
instance.dispose()
cancellationTokenSource?.dispose()
clock.uninstall()
Expand Down Expand Up @@ -78,16 +91,17 @@ describe('TryChatCodeLensProvider', () => {
})

it('does not register the provider if we do not want to show the code lens', async function () {
await TryChatCodeLensProvider.register(isAmazonQVisibleEvent)
// indicate we do not want to show it
await globals.context.globalState.update(TryChatCodeLensProvider.showCodeLensId, false)
// ensure we do not show it
assert.deepStrictEqual(await TryChatCodeLensProvider.register(), false)
assert.deepStrictEqual(await TryChatCodeLensProvider.register(isAmazonQVisibleEvent), false)

// indicate we want to show it
await globals.context.globalState.update(TryChatCodeLensProvider.showCodeLensId, true)
// The general toolkit activation will have already registered this provider, so it throws when we try again
// But if it throws it implies it tried to register it.
await assert.rejects(TryChatCodeLensProvider.register(), {
await assert.rejects(TryChatCodeLensProvider.register(isAmazonQVisibleEvent), {
message: `${TryChatCodeLensProvider.name} can only be registered once.`,
})
})
Expand All @@ -107,6 +121,68 @@ describe('TryChatCodeLensProvider', () => {
}
})

it('does show codelens if lineAnnotationController (tips) is in end state', async function () {
stubConnection('connected')
// indicate lineAnnotationController is not visible and in end state
await globals.context.globalState.update(inlinehintKey, EndState.id)

let codeLensCount = 0
const modifierKey = resolveModifierKey()
while (codeLensCount < 10) {
cancellationTokenSource = new vscode.CancellationTokenSource()
const resultPromise = instance.provideCodeLenses({} as any, cancellationTokenSource.token)
clock.tick(TryChatCodeLensProvider.debounceMillis) // skip debounce

assert.deepStrictEqual(await resultPromise, [
{
range: new vscode.Range(codeLensPosition, codeLensPosition),
command: {
title: `Amazon Q: open chat with (${modifierKey} + i) - showing ${
TryChatCodeLensProvider.maxCount - codeLensCount
} more times`,
command: tryChatCodeLensCommand.id,
},
isResolved: true,
},
])

codeLensCount++
}
const emptyResult = await instance.provideCodeLenses({} as any, new vscode.CancellationTokenSource().token)
assert.deepStrictEqual(emptyResult, [])
})

it('does NOT show codelens if lineAnnotationController (tips) is visible', async function () {
stubConnection('connected')
// indicate lineAnnotationController is visible and not in end state
const lineAnnotationControllerStates: string[] = [
AutotriggerState.id,
PressTabState.id,
ManualtriggerState.id,
TryMoreExState.id,
]

lineAnnotationControllerStates.forEach((id: string) => {
it(`id - ${id}`, async () => {
await globals.context.globalState.update(inlinehintKey, id)

const emptyResult = await instance.provideCodeLenses(
{} as any,
new vscode.CancellationTokenSource().token
)

assert.deepStrictEqual(emptyResult, [])
})
})
})

it('does NOT show codelens if amazon Q chat is open', async function () {
stubConnection('connected')
isAmazonQVisibleEventEmitter.fire(true)
const emptyResult = await instance.provideCodeLenses({} as any, new vscode.CancellationTokenSource().token)
assert.deepStrictEqual(emptyResult, [])
})

it('outputs expected telemetry', async function () {
await tryChatCodeLensCommand.execute()
assertTelemetry('vscode_executeCommand', { command: focusAmazonQPanel.id, source: 'codeLens' })
Expand Down

0 comments on commit ff225c1

Please sign in to comment.