Skip to content

Commit 824635e

Browse files
committed
Merge branch 'feature/ui-e2e-tests' of https://github.com/aws/aws-toolkit-vscode into feature/ui-e2e-tests
2 parents ed3c220 + b5ff858 commit 824635e

File tree

23 files changed

+473
-173
lines changed

23 files changed

+473
-173
lines changed

package-lock.json

Lines changed: 14 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"skippedTestReport": "ts-node ./scripts/skippedTestReport.ts ./packages/amazonq/test/e2e/"
4242
},
4343
"devDependencies": {
44-
"@aws-toolkits/telemetry": "^1.0.326",
44+
"@aws-toolkits/telemetry": "^1.0.328",
4545
"@playwright/browser-chromium": "^1.43.1",
4646
"@stylistic/eslint-plugin": "^2.11.0",
4747
"@types/he": "^1.2.3",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Render first response before receiving all paginated inline completion results"
4+
}

packages/amazonq/package.json

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,21 @@
551551
]
552552
},
553553
"commands": [
554+
{
555+
"command": "aws.amazonq.stopCmdExecution",
556+
"title": "Stop Amazon Q Command Execution",
557+
"category": "%AWS.amazonq.title%"
558+
},
559+
{
560+
"command": "aws.amazonq.runCmdExecution",
561+
"title": "Run Amazon Q Command Execution",
562+
"category": "%AWS.amazonq.title%"
563+
},
564+
{
565+
"command": "aws.amazonq.rejectCmdExecution",
566+
"title": "Reject Amazon Q Command Execution",
567+
"category": "%AWS.amazonq.title%"
568+
},
554569
{
555570
"command": "_aws.amazonq.notifications.dismiss",
556571
"title": "%AWS.generic.dismiss%",
@@ -840,6 +855,24 @@
840855
}
841856
],
842857
"keybindings": [
858+
{
859+
"command": "aws.amazonq.stopCmdExecution",
860+
"key": "ctrl+shift+backspace",
861+
"mac": "cmd+shift+backspace",
862+
"when": "aws.amazonq.amazonqChatLSP.isRunning"
863+
},
864+
{
865+
"command": "aws.amazonq.runCmdExecution",
866+
"key": "ctrl+shift+enter",
867+
"mac": "cmd+shift+enter",
868+
"when": "aws.amazonq.amazonqChatLSP.isRunning"
869+
},
870+
{
871+
"command": "aws.amazonq.rejectCmdExecution",
872+
"key": "ctrl+shift+r",
873+
"mac": "cmd+shift+r",
874+
"when": "aws.amazonq.amazonqChatLSP.isRunning"
875+
},
843876
{
844877
"command": "_aws.amazonq.focusChat.keybinding",
845878
"win": "win+alt+i",
@@ -914,12 +947,12 @@
914947
},
915948
{
916949
"key": "right",
917-
"command": "editor.action.inlineSuggest.showNext",
950+
"command": "aws.amazonq.showNext",
918951
"when": "inlineSuggestionVisible && !editorReadonly && aws.codewhisperer.connected"
919952
},
920953
{
921954
"key": "left",
922-
"command": "editor.action.inlineSuggest.showPrevious",
955+
"command": "aws.amazonq.showPrev",
923956
"when": "inlineSuggestionVisible && !editorReadonly && aws.codewhisperer.connected"
924957
},
925958
{

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

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5-
65
import {
76
InlineCompletionListWithReferences,
87
InlineCompletionWithReferencesParams,
@@ -80,7 +79,7 @@ export class RecommendationService {
8079
nextToken: request.partialResultToken,
8180
},
8281
})
83-
let result: InlineCompletionListWithReferences = await languageClient.sendRequest(
82+
const result: InlineCompletionListWithReferences = await languageClient.sendRequest(
8483
inlineCompletionWithReferencesRequestType.method,
8584
request,
8685
token
@@ -120,18 +119,10 @@ export class RecommendationService {
120119
getLogger().info(
121120
'Suggestion type is COMPLETIONS. Start fetching for more items if partialResultToken exists.'
122121
)
123-
try {
124-
while (result.partialResultToken) {
125-
const paginatedRequest = { ...request, partialResultToken: result.partialResultToken }
126-
result = await languageClient.sendRequest(
127-
inlineCompletionWithReferencesRequestType.method,
128-
paginatedRequest,
129-
token
130-
)
131-
this.sessionManager.updateSessionSuggestions(result.items)
132-
}
133-
} catch (error) {
134-
languageClient.warn(`Error when getting suggestions: ${error}`)
122+
if (result.partialResultToken) {
123+
this.processRemainingRequests(languageClient, request, result, token).catch((error) => {
124+
languageClient.warn(`Error when getting suggestions: ${error}`)
125+
})
135126
}
136127
} else {
137128
// Skip fetching for more items if the suggesion is EDITS. If it is EDITS suggestion, only fetching for more
@@ -140,11 +131,6 @@ export class RecommendationService {
140131
getLogger().info('Suggestion type is EDITS. Skip fetching for more items.')
141132
this.sessionManager.updateActiveEditsStreakToken(result.partialResultToken)
142133
}
143-
144-
// Close session and finalize telemetry regardless of pagination path
145-
this.sessionManager.closeSession()
146-
TelemetryHelper.instance.setAllPaginationEndTime()
147-
options.emitTelemetry && TelemetryHelper.instance.tryRecordClientComponentLatency()
148134
} catch (error: any) {
149135
getLogger().error('Error getting recommendations: %O', error)
150136
// bearer token expired
@@ -167,4 +153,31 @@ export class RecommendationService {
167153
}
168154
}
169155
}
156+
157+
private async processRemainingRequests(
158+
languageClient: LanguageClient,
159+
initialRequest: InlineCompletionWithReferencesParams,
160+
firstResult: InlineCompletionListWithReferences,
161+
token: CancellationToken
162+
): Promise<void> {
163+
let nextToken = firstResult.partialResultToken
164+
while (nextToken) {
165+
const request = { ...initialRequest, partialResultToken: nextToken }
166+
167+
const result: InlineCompletionListWithReferences = await languageClient.sendRequest(
168+
inlineCompletionWithReferencesRequestType.method,
169+
request,
170+
token
171+
)
172+
this.sessionManager.updateSessionSuggestions(result.items)
173+
nextToken = result.partialResultToken
174+
}
175+
176+
this.sessionManager.closeSession()
177+
178+
// refresh inline completion items to render paginated responses
179+
// All pagination requests completed
180+
TelemetryHelper.instance.setAllPaginationEndTime()
181+
TelemetryHelper.instance.tryRecordClientComponentLatency()
182+
}
170183
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface CodeWhispererSession {
2424
export class SessionManager {
2525
private activeSession?: CodeWhispererSession
2626
private _acceptedSuggestionCount: number = 0
27+
private _refreshedSessions = new Set<string>()
2728

2829
constructor() {}
2930

@@ -86,4 +87,20 @@ export class SessionManager {
8687
public clear() {
8788
this.activeSession = undefined
8889
}
90+
91+
// re-render the session ghost text to display paginated responses once per completed session
92+
public async maybeRefreshSessionUx() {
93+
if (
94+
this.activeSession &&
95+
!this.activeSession.isRequestInProgress &&
96+
!this._refreshedSessions.has(this.activeSession.sessionId)
97+
) {
98+
await vscode.commands.executeCommand('editor.action.inlineSuggest.hide')
99+
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
100+
if (this._refreshedSessions.size > 1000) {
101+
this._refreshedSessions.clear()
102+
}
103+
this._refreshedSessions.add(this.activeSession.sessionId)
104+
}
105+
}
89106
}

packages/amazonq/src/lsp/chat/commands.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ export function registerCommands(provider: AmazonQChatViewProvider) {
7878
params: {},
7979
})
8080
})
81-
})
81+
}),
82+
registerShellCommandShortCut('aws.amazonq.runCmdExecution', 'run-shell-command', provider),
83+
registerShellCommandShortCut('aws.amazonq.rejectCmdExecution', 'reject-shell-command', provider),
84+
registerShellCommandShortCut('aws.amazonq.stopCmdExecution', 'stop-shell-command', provider)
8285
)
8386
}
8487

@@ -123,3 +126,14 @@ export async function focusAmazonQPanel() {
123126
await Commands.tryExecute('aws.amazonq.AmazonQChatView.focus')
124127
await Commands.tryExecute('aws.amazonq.AmazonCommonAuth.focus')
125128
}
129+
130+
function registerShellCommandShortCut(commandName: string, buttonId: string, provider: AmazonQChatViewProvider) {
131+
return Commands.register(commandName, async () => {
132+
void focusAmazonQPanel().then(() => {
133+
void provider.webview?.postMessage({
134+
command: 'aws/chat/executeShellCommandShortCut',
135+
params: { id: buttonId },
136+
})
137+
})
138+
})
139+
}

packages/amazonq/src/lsp/chat/messages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
ruleClickRequestType,
6262
pinnedContextNotificationType,
6363
activeEditorChangedNotificationType,
64+
listAvailableModelsRequestType,
6465
ShowOpenDialogRequestType,
6566
ShowOpenDialogParams,
6667
openFileDialogRequestType,
@@ -369,6 +370,7 @@ export function registerMessageListeners(
369370
case listMcpServersRequestType.method:
370371
case mcpServerClickRequestType.method:
371372
case tabBarActionRequestType.method:
373+
case listAvailableModelsRequestType.method:
372374
await resolveChatResponse(message.command, message.params, languageClient, webview)
373375
break
374376
case followUpClickNotificationType.method:

packages/amazonq/src/lsp/chat/webviewProvider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Webview,
1414
} from 'vscode'
1515
import * as path from 'path'
16+
import * as os from 'os'
1617
import {
1718
globals,
1819
isSageMaker,
@@ -149,7 +150,7 @@ export class AmazonQChatViewProvider implements WebviewViewProvider {
149150
const vscodeApi = acquireVsCodeApi()
150151
const hybridChatConnector = new HybridChatAdapter(${(await AuthUtil.instance.getChatAuthState()).amazonQ === 'connected'},${featureConfigData},${welcomeCount},${disclaimerAcknowledged},${regionProfileString},${disabledCommands},${isSMUS},${isSM},vscodeApi.postMessage)
151152
const commands = [hybridChatConnector.initialQuickActions[0]]
152-
qChat = amazonQChat.createChat(vscodeApi, {disclaimerAcknowledged: ${disclaimerAcknowledged}, pairProgrammingAcknowledged: ${pairProgrammingAcknowledged}, agenticMode: true, quickActionCommands: commands, modelSelectionEnabled: ${modelSelectionEnabled}}, hybridChatConnector, ${JSON.stringify(featureConfigData)});
153+
qChat = amazonQChat.createChat(vscodeApi, {os: "${os.platform()}", disclaimerAcknowledged: ${disclaimerAcknowledged}, pairProgrammingAcknowledged: ${pairProgrammingAcknowledged}, agenticMode: true, quickActionCommands: commands, modelSelectionEnabled: ${modelSelectionEnabled}}, hybridChatConnector, ${JSON.stringify(featureConfigData)});
153154
}
154155
window.addEventListener('message', (event) => {
155156
/**

packages/amazonq/src/lsp/client.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
getClientId,
3939
extensionVersion,
4040
isSageMaker,
41+
setContext,
4142
} from 'aws-core-vscode/shared'
4243
import { processUtils } from 'aws-core-vscode/shared'
4344
import { activate } from './chat/activation'
@@ -164,6 +165,7 @@ export async function startLanguageServer(
164165
pinnedContextEnabled: true,
165166
imageContextEnabled: true,
166167
mcp: true,
168+
shortcut: true,
167169
reroute: true,
168170
modelSelection: true,
169171
workspaceFilePath: vscode.workspace.workspaceFile?.fsPath,
@@ -247,6 +249,17 @@ async function onLanguageServerReady(
247249

248250
if (Experiments.instance.get('amazonqChatLSP', true)) {
249251
await activate(client, encryptionKey, resourcePaths.ui)
252+
253+
await setContext('aws.amazonq.amazonqChatLSP.isRunning', true)
254+
getLogger().info('Amazon Q Chat LSP context flag set on client activated')
255+
256+
// Add a disposable to reset the context flag when the client stops
257+
toDispose.push({
258+
dispose: async () => {
259+
await setContext('aws.amazonq.amazonqChatLSP.isRunning', false)
260+
getLogger().info('Amazon Q Chat LSP context flag reset on client disposal')
261+
},
262+
})
250263
}
251264

252265
const refreshInterval = auth.startTokenRefreshInterval(10 * oneSecond)
@@ -265,6 +278,14 @@ async function onLanguageServerReady(
265278

266279
toDispose.push(
267280
inlineManager,
281+
Commands.register('aws.amazonq.showPrev', async () => {
282+
await sessionManager.maybeRefreshSessionUx()
283+
await vscode.commands.executeCommand('editor.action.inlineSuggest.showPrevious')
284+
}),
285+
Commands.register('aws.amazonq.showNext', async () => {
286+
await sessionManager.maybeRefreshSessionUx()
287+
await vscode.commands.executeCommand('editor.action.inlineSuggest.showNext')
288+
}),
268289
Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => {
269290
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
270291
}),

0 commit comments

Comments
 (0)