Skip to content

Commit a54ea26

Browse files
committed
Merge remote-tracking branch 'upstream/master' into inline
2 parents 1470e1f + 1ca10f2 commit a54ea26

File tree

17 files changed

+195
-47
lines changed

17 files changed

+195
-47
lines changed

package-lock.json

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"date": "2025-08-28",
3+
"version": "1.92.0",
4+
"entries": [
5+
{
6+
"type": "Feature",
7+
"description": "Amazon Q supports admin control for MCP servers to restrict MCP server usage"
8+
},
9+
{
10+
"type": "Feature",
11+
"description": "Enabling dynamic model fetching capabilities in Amazon Q chat"
12+
},
13+
{
14+
"type": "Feature",
15+
"description": "Amazon Q: Support for configuring and utilizing remote MCP servers."
16+
}
17+
]
18+
}

packages/amazonq/.changes/next-release/Feature-5e9de7e7-cf7b-4ed3-824f-062b82b3f4b9.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

packages/amazonq/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 1.92.0 2025-08-28
2+
3+
- **Feature** Amazon Q supports admin control for MCP servers to restrict MCP server usage
4+
- **Feature** Enabling dynamic model fetching capabilities in Amazon Q chat
5+
- **Feature** Amazon Q: Support for configuring and utilizing remote MCP servers.
6+
17
## 1.91.0 2025-08-22
28

39
- **Bug Fix** Enable inline completion in Jupyter Notebook

packages/amazonq/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "amazon-q-vscode",
33
"displayName": "Amazon Q",
44
"description": "The most capable generative AI–powered assistant for software development.",
5-
"version": "1.92.0-SNAPSHOT",
5+
"version": "1.93.0-SNAPSHOT",
66
"extensionKind": [
77
"workspace"
88
],
@@ -925,7 +925,7 @@
925925
},
926926
{
927927
"command": "aws.amazonq.fixCode",
928-
"win": "win+alt+y",
928+
"win": "win+alt+h",
929929
"mac": "cmd+alt+y",
930930
"linux": "meta+alt+y"
931931
},
@@ -943,7 +943,7 @@
943943
},
944944
{
945945
"command": "aws.amazonq.generateUnitTests",
946-
"key": "win+alt+t",
946+
"key": "win+alt+n",
947947
"mac": "cmd+alt+t",
948948
"linux": "meta+alt+t"
949949
},

packages/amazonq/src/app/inline/EditRendering/displayImage.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class EditDecorationManager {
2424
private currentImageDecoration: vscode.DecorationOptions | undefined
2525
private currentRemovedCodeDecorations: vscode.DecorationOptions[] = []
2626
private acceptHandler: (() => void) | undefined
27-
private rejectHandler: (() => void) | undefined
27+
private rejectHandler: ((isDiscard: boolean) => void) | undefined
2828

2929
constructor() {
3030
this.registerCommandHandlers()
@@ -131,7 +131,7 @@ export class EditDecorationManager {
131131
svgImage: vscode.Uri,
132132
startLine: number,
133133
onAccept: () => Promise<void>,
134-
onReject: () => Promise<void>,
134+
onReject: (isDiscard: boolean) => Promise<void>,
135135
originalCode: string,
136136
newCode: string,
137137
originalCodeHighlightRanges: Array<{ line: number; start: number; end: number }>
@@ -185,9 +185,9 @@ export class EditDecorationManager {
185185
})
186186

187187
// Register Esc key handler for rejecting suggestion
188-
vscode.commands.registerCommand('aws.amazonq.inline.rejectEdit', () => {
188+
vscode.commands.registerCommand('aws.amazonq.inline.rejectEdit', (isDiscard: boolean = false) => {
189189
if (this.rejectHandler) {
190-
this.rejectHandler()
190+
this.rejectHandler(isDiscard)
191191
}
192192
})
193193
}
@@ -416,20 +416,31 @@ export async function displaySvgDecoration(
416416
// )
417417
// }
418418
},
419-
async () => {
419+
async (isDiscard: boolean) => {
420420
// Handle reject
421-
getLogger().info('Edit suggestion rejected')
421+
if (isDiscard) {
422+
getLogger().info('Edit suggestion discarded')
423+
} else {
424+
getLogger().info('Edit suggestion rejected')
425+
}
422426
await decorationManager.clearDecorations(editor)
423427
documentChangeListener.dispose()
424428
cursorChangeListener.dispose()
429+
const suggestionState = isDiscard
430+
? {
431+
seen: false,
432+
accepted: false,
433+
discarded: true,
434+
}
435+
: {
436+
seen: true,
437+
accepted: false,
438+
discarded: false,
439+
}
425440
const params: LogInlineCompletionSessionResultsParams = {
426441
sessionId: session.sessionId,
427442
completionSessionResult: {
428-
[item.itemId]: {
429-
seen: true,
430-
accepted: false,
431-
discarded: false,
432-
},
443+
[item.itemId]: suggestionState,
433444
},
434445
totalSessionDisplayTime: Date.now() - session.requestStartTime,
435446
firstCompletionDisplayLatency: session.firstCompletionDisplayLatency,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/**
7+
* Strips common indentation from each line of code that may contain HTML tags
8+
* @param lines Array of code lines (may contain HTML tags)
9+
* @returns Array of code lines with common indentation removed
10+
*/
11+
export function stripCommonIndentation(lines: string[]): string[] {
12+
if (lines.length === 0) {
13+
return lines
14+
}
15+
const removeFirstTag = (line: string) => line.replace(/^<[^>]*>/, '')
16+
const getLeadingWhitespace = (text: string) => text.match(/^\s*/)?.[0] || ''
17+
18+
// Find minimum indentation across all lines
19+
const minIndentLength = Math.min(...lines.map((line) => getLeadingWhitespace(removeFirstTag(line)).length))
20+
21+
// Remove common indentation from each line
22+
return lines.map((line) => {
23+
const firstTagRemovedLine = removeFirstTag(line)
24+
const leadingWhitespace = getLeadingWhitespace(firstTagRemovedLine)
25+
const reducedWhitespace = leadingWhitespace.substring(minIndentLength)
26+
return line.replace(leadingWhitespace, reducedWhitespace)
27+
})
28+
}

packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { diffWordsWithSpace, diffLines } from 'diff'
77
import * as vscode from 'vscode'
88
import { ToolkitError, getLogger } from 'aws-core-vscode/shared'
99
import { diffUtilities } from 'aws-core-vscode/shared'
10+
import { stripCommonIndentation } from './stringUtils'
1011
type Range = { line: number; start: number; end: number }
1112

1213
const logger = getLogger('nextEditPrediction')
@@ -78,6 +79,7 @@ export class SvgGenerationService {
7879

7980
const highlightRanges = this.generateHighlightRanges(removedLines, addedLines, modifiedLines)
8081
const diffAddedWithHighlight = this.getHighlightEdit(addedLines, highlightRanges.addedRanges)
82+
const normalizedDiffLines = stripCommonIndentation(diffAddedWithHighlight)
8183

8284
// Create SVG window, document, and container
8385
const window = createSVGWindow()
@@ -90,7 +92,7 @@ export class SvgGenerationService {
9092

9193
// Generate CSS for syntax highlighting HTML content based on theme
9294
const styles = this.generateStyles(currentTheme)
93-
const htmlContent = this.generateHtmlContent(diffAddedWithHighlight, styles, offset)
95+
const htmlContent = this.generateHtmlContent(normalizedDiffLines, styles, offset)
9496

9597
// Create foreignObject to embed HTML
9698
const foreignObject = draw.foreignObject(width + offset, height)
@@ -162,6 +164,9 @@ export class SvgGenerationService {
162164
white-space: pre-wrap; /* Preserve whitespace */
163165
background-color: ${diffAdded};
164166
}
167+
.diff-unchanged {
168+
white-space: pre-wrap; /* Preserve indentation for unchanged lines */
169+
}
165170
`
166171
}
167172

@@ -229,7 +234,7 @@ export class SvgGenerationService {
229234

230235
// If no ranges for this line, leave it as-is with HTML escaping
231236
if (lineRanges.length === 0) {
232-
result.push(this.escapeHtml(line))
237+
result.push(`<span class="diff-unchanged">${this.escapeHtml(line)}</span>`)
233238
continue
234239
}
235240

@@ -244,7 +249,7 @@ export class SvgGenerationService {
244249
// Add text before the current range (with HTML escaping)
245250
if (range.start > currentPos) {
246251
const beforeText = line.substring(currentPos, range.start)
247-
highlightedLine += this.escapeHtml(beforeText)
252+
highlightedLine += `<span class="diff-unchanged">${this.escapeHtml(beforeText)}</span>`
248253
}
249254

250255
// Add the highlighted part (with HTML escaping)
@@ -258,7 +263,7 @@ export class SvgGenerationService {
258263
// Add any remaining text after the last range (with HTML escaping)
259264
if (currentPos < line.length) {
260265
const afterText = line.substring(currentPos)
261-
highlightedLine += this.escapeHtml(afterText)
266+
highlightedLine += `<span class="diff-unchanged">${this.escapeHtml(afterText)}</span>`
262267
}
263268

264269
result.push(highlightedLine)

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@
88
*/
99
export class EditSuggestionState {
1010
private static isEditSuggestionCurrentlyActive = false
11+
private static displayStartTime = performance.now()
1112

1213
static setEditSuggestionActive(active: boolean): void {
1314
this.isEditSuggestionCurrentlyActive = active
15+
if (active) {
16+
this.displayStartTime = performance.now()
17+
}
1418
}
1519

1620
static isEditSuggestionActive(): boolean {
1721
return this.isEditSuggestionCurrentlyActive
1822
}
23+
24+
static isEditSuggestionDisplayingOverOneSecond(): boolean {
25+
return this.isEditSuggestionActive() && performance.now() - this.displayStartTime > 1000
26+
}
1927
}

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import {
88
inlineCompletionWithReferencesRequestType,
99
TextDocumentContentChangeEvent,
1010
editCompletionRequestType,
11+
LogInlineCompletionSessionResultsParams,
1112
} from '@aws/language-server-runtimes/protocol'
12-
import { CancellationToken, InlineCompletionContext, Position, TextDocument } from 'vscode'
13+
import { CancellationToken, InlineCompletionContext, Position, TextDocument, commands } from 'vscode'
1314
import { LanguageClient } from 'vscode-languageclient'
1415
import { SessionManager } from './sessionManager'
1516
import {
@@ -24,8 +25,8 @@ import { getLogger } from 'aws-core-vscode/shared'
2425
import { DocumentEventListener } from './documentEventListener'
2526
import { getOpenFilesInWindow } from 'aws-core-vscode/utils'
2627
import { asyncCallWithTimeout } from '../../util/timeoutUtil'
27-
import { EditSuggestionState } from './editSuggestionState'
2828
import { extractFileContextInNotebooks } from './notebookUtil'
29+
import { EditSuggestionState } from './editSuggestionState'
2930

3031
export interface GetAllRecommendationsOptions {
3132
emitTelemetry?: boolean
@@ -137,7 +138,7 @@ export class RecommendationService {
137138
* Completions use PartialResultToken with single 1 call of [getAllRecommendations].
138139
* Edits leverage partialResultToken to achieve EditStreak such that clients can pull all continuous suggestions generated by the model within 1 EOS block.
139140
*/
140-
if (!isTriggerByDeletion && !request.partialResultToken && !EditSuggestionState.isEditSuggestionActive()) {
141+
if (!isTriggerByDeletion && !request.partialResultToken) {
141142
const completionPromise: Promise<InlineCompletionListWithReferences> = languageClient.sendRequest(
142143
inlineCompletionWithReferencesRequestType.method,
143144
request,
@@ -187,6 +188,39 @@ export class RecommendationService {
187188
})),
188189
})
189190

191+
if (result.items.length > 0 && result.items[0].isInlineEdit === false) {
192+
// Completion will not be rendered if an edit suggestion has been active for longer than 1 second
193+
if (EditSuggestionState.isEditSuggestionDisplayingOverOneSecond()) {
194+
const session = this.sessionManager.getActiveSession()
195+
if (!session) {
196+
return []
197+
}
198+
const params: LogInlineCompletionSessionResultsParams = {
199+
sessionId: session.sessionId,
200+
completionSessionResult: Object.fromEntries(
201+
result.items.map((item) => [
202+
item.itemId,
203+
{
204+
seen: false,
205+
accepted: false,
206+
discarded: true,
207+
},
208+
])
209+
),
210+
}
211+
languageClient.sendNotification('aws/logInlineCompletionSessionResults', params)
212+
this.sessionManager.clear()
213+
getLogger().info(
214+
'Completion discarded due to active edit suggestion displayed longer than 1 second'
215+
)
216+
return []
217+
} else if (EditSuggestionState.isEditSuggestionActive()) {
218+
// discard the current edit suggestion if its display time is less than 1 sec
219+
await commands.executeCommand('aws.amazonq.inline.rejectEdit', true)
220+
getLogger().info('Discarding active edit suggestion displaying less than 1 second')
221+
}
222+
}
223+
190224
TelemetryHelper.instance.setSdkApiCallEndTime()
191225
TelemetryHelper.instance.setSessionId(result.sessionId)
192226
if (result.items.length > 0 && result.items[0].itemId !== undefined) {

0 commit comments

Comments
 (0)