Skip to content

Commit 62e9817

Browse files
committed
address pr comments
1 parent ddacc2e commit 62e9817

File tree

3 files changed

+227
-213
lines changed

3 files changed

+227
-213
lines changed

packages/amazonq/src/lsp/chat/diffAnimation/diffAnimationHandler.ts

Lines changed: 0 additions & 1 deletion
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 * as vscode from 'vscode'
76
import { getLogger } from 'aws-core-vscode/shared'
87
import { StreamingDiffController } from './streamingDiffController'

packages/amazonq/src/lsp/chat/diffAnimation/streamingDiffController.ts

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,23 @@ import { FsWriteParams } from './types'
1010

1111
export const diffViewUriScheme = 'amazonq-diff'
1212

13+
type StreamingSession = {
14+
filePath: string
15+
tempFilePath: string
16+
originalContent: string
17+
activeDiffEditor: vscode.TextEditor
18+
fadedOverlayController: DecorationController
19+
activeLineController: DecorationController
20+
streamedLines: string[]
21+
disposed: boolean
22+
fsWriteParams?: FsWriteParams
23+
}
24+
1325
/**
1426
* Streaming Diff Controller using temporary files for animations
1527
*/
1628
export class StreamingDiffController implements vscode.Disposable {
17-
private activeStreamingSessions = new Map<
18-
string,
19-
{
20-
filePath: string
21-
tempFilePath: string
22-
originalContent: string
23-
activeDiffEditor: vscode.TextEditor
24-
fadedOverlayController: DecorationController
25-
activeLineController: DecorationController
26-
streamedLines: string[]
27-
disposed: boolean
28-
fsWriteParams?: FsWriteParams
29-
}
30-
>()
29+
private activeStreamingSessions = new Map<string, StreamingSession>()
3130

3231
private fsReplaceSessionsByFile = new Map<
3332
string,
@@ -257,11 +256,7 @@ export class StreamingDiffController implements vscode.Disposable {
257256
}
258257
}
259258

260-
/**
261-
* Handle fsReplace diffPair phase - individual diff pair animation (like Cline's SEARCH/REPLACE blocks)
262-
* **RACE CONDITION FIX**: Ensures the same temp file is reused for all diff pairs from the same toolUseId
263-
*/
264-
async handleFsReplaceDiffPair(session: any, partialContent: string, isFinal: boolean): Promise<void> {
259+
async handleFsReplaceDiffPair(session: StreamingSession, partialContent: string, isFinal: boolean): Promise<void> {
265260
try {
266261
const diffEditor = session.activeDiffEditor
267262
const document = diffEditor.document
@@ -278,24 +273,22 @@ export class StreamingDiffController implements vscode.Disposable {
278273
return
279274
}
280275
const currentContent = document.getText()
281-
282-
if (document.uri.fsPath !== session.tempFilePath) {
283-
try {
284-
const correctDocument = await vscode.workspace.openTextDocument(
285-
vscode.Uri.file(session.tempFilePath)
276+
if (document.uri.fsPath === session.tempFilePath) {
277+
return
278+
}
279+
try {
280+
const correctDocument = await vscode.workspace.openTextDocument(vscode.Uri.file(session.tempFilePath))
281+
if (correctDocument) {
282+
const correctEditor = vscode.window.visibleTextEditors.find(
283+
(editor) => editor.document.uri.fsPath === session.tempFilePath
286284
)
287-
if (correctDocument) {
288-
const correctEditor = vscode.window.visibleTextEditors.find(
289-
(editor) => editor.document.uri.fsPath === session.tempFilePath
290-
)
291-
if (correctEditor) {
292-
session.activeDiffEditor = correctEditor
293-
}
285+
if (correctEditor) {
286+
session.activeDiffEditor = correctEditor
294287
}
295-
} catch (error) {
296-
getLogger().error(`[StreamingDiffController] ❌ Failed to correct document path: ${error}`)
297-
return
298288
}
289+
} catch (error) {
290+
getLogger().error(`[StreamingDiffController] ❌ Failed to correct document path: ${error}`)
291+
return
299292
}
300293

301294
// Find the location of oldStr in the current content
@@ -324,8 +317,11 @@ export class StreamingDiffController implements vscode.Disposable {
324317
for (let lineNum = startLineNumber; lineNum <= newEndLineNumber; lineNum++) {
325318
session.activeLineController.setActiveLine(lineNum)
326319
session.fadedOverlayController.updateOverlayAfterLine(lineNum, document.lineCount)
320+
// Small delay to create smooth line-by-line animation effect for visual feedback
327321
await new Promise((resolve) => setTimeout(resolve, 20))
328322
}
323+
// Clear active line highlighting after a brief delay to allow user to see the final result
324+
// before moving to the next diff pair or completing the animation
329325
setTimeout(() => {
330326
session.activeLineController.clear()
331327
}, 500)
@@ -378,9 +374,6 @@ export class StreamingDiffController implements vscode.Disposable {
378374
}
379375
}
380376

381-
/**
382-
* Scroll editor to line like Cline
383-
*/
384377
private scrollEditorToLine(editor: vscode.TextEditor, line: number): void {
385378
const scrollLine = line
386379
editor.revealRange(new vscode.Range(scrollLine, 0, scrollLine, 0), vscode.TextEditorRevealType.InCenter)
@@ -436,7 +429,7 @@ export class StreamingDiffController implements vscode.Disposable {
436429
/**
437430
* Handle fsReplace completion signal from parser - triggers immediate cleanup
438431
*/
439-
private async handleFsReplaceCompletionSignal(session: any): Promise<void> {
432+
private async handleFsReplaceCompletionSignal(session: StreamingSession): Promise<void> {
440433
const filePath = session.filePath
441434
try {
442435
// Clear decorations immediately
@@ -453,6 +446,8 @@ export class StreamingDiffController implements vscode.Disposable {
453446
getLogger().error(`[StreamingDiffController] ❌ Failed to save fsReplace temp file: ${saveError}`)
454447
}
455448
}
449+
// Delay cleanup to allow final UI updates and user to see completion state
450+
// before removing visual elements and cleaning up resources
456451
setTimeout(async () => {
457452
try {
458453
await this.cleanupTempFile(session.tempFilePath)
@@ -480,10 +475,27 @@ export class StreamingDiffController implements vscode.Disposable {
480475
}
481476
}
482477

478+
/**
479+
* Clean up multiple streaming sessions by their tool use IDs
480+
*/
481+
private async cleanupSessions(toolUseIds: Set<string>): Promise<void> {
482+
for (const toolUseId of toolUseIds) {
483+
const sessionToCleanup = this.activeStreamingSessions.get(toolUseId)
484+
if (sessionToCleanup) {
485+
sessionToCleanup.disposed = true
486+
this.activeStreamingSessions.delete(toolUseId)
487+
}
488+
}
489+
}
490+
483491
/**
484492
* Handle fsReplace completion - properly track and cleanup when all diff pairs for a file are done
485493
*/
486-
private async handleFsReplaceCompletion(session: any, pairIndex: number, totalPairs: number): Promise<void> {
494+
private async handleFsReplaceCompletion(
495+
session: StreamingSession,
496+
pairIndex: number,
497+
totalPairs: number
498+
): Promise<void> {
487499
const filePath = session.filePath
488500
const fsReplaceSession = this.fsReplaceSessionsByFile.get(filePath)
489501

@@ -501,13 +513,7 @@ export class StreamingDiffController implements vscode.Disposable {
501513
setTimeout(async () => {
502514
try {
503515
await this.cleanupTempFile(fsReplaceSession.tempFilePath)
504-
for (const toolUseId of fsReplaceSession.toolUseIds) {
505-
const sessionToCleanup = this.activeStreamingSessions.get(toolUseId)
506-
if (sessionToCleanup) {
507-
sessionToCleanup.disposed = true
508-
this.activeStreamingSessions.delete(toolUseId)
509-
}
510-
}
516+
await this.cleanupSessions(fsReplaceSession.toolUseIds)
511517
this.fsReplaceSessionsByFile.delete(filePath)
512518
} catch (error) {
513519
getLogger().warn(
@@ -522,18 +528,23 @@ export class StreamingDiffController implements vscode.Disposable {
522528
* Clean up all temporary files for a chat session
523529
*/
524530
async cleanupChatSession(): Promise<void> {
525-
const tempFilesToCleanup: string[] = []
526-
for (const [, session] of this.activeStreamingSessions.entries()) {
531+
const tempFilePaths: string[] = []
532+
533+
// Collect from active streaming sessions
534+
for (const session of this.activeStreamingSessions.values()) {
527535
if (session.tempFilePath) {
528-
tempFilesToCleanup.push(session.tempFilePath)
536+
tempFilePaths.push(session.tempFilePath)
529537
}
530538
}
531-
for (const [, fsReplaceSession] of this.fsReplaceSessionsByFile.entries()) {
532-
if (fsReplaceSession.tempFilePath) {
533-
tempFilesToCleanup.push(fsReplaceSession.tempFilePath)
539+
540+
// Collect from fs replace sessions
541+
for (const session of this.fsReplaceSessionsByFile.values()) {
542+
if (session.tempFilePath) {
543+
tempFilePaths.push(session.tempFilePath)
534544
}
535545
}
536-
for (const tempFilePath of tempFilesToCleanup) {
546+
547+
for (const tempFilePath of tempFilePaths) {
537548
try {
538549
await this.cleanupTempFile(tempFilePath)
539550
} catch (error) {

0 commit comments

Comments
 (0)