Skip to content

Commit a834171

Browse files
committed
feat(diff): enhance focus behavior and tab management after diff operations
1 parent e189a0f commit a834171

File tree

1 file changed

+193
-37
lines changed

1 file changed

+193
-37
lines changed

src/integrations/editor/DiffViewProvider.ts

Lines changed: 193 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export class DiffViewProvider {
4545
private viewColumn: ViewColumn = -1 // ViewColumn.Active
4646
private userInteractionListeners: vscode.Disposable[] = []
4747
private suppressInteractionFlag: boolean = false
48+
private preDiffActiveEditor?: vscode.TextEditor // Store active editor before diff operation
4849

4950
constructor(private cwd: string) {}
5051

@@ -185,6 +186,9 @@ export class DiffViewProvider {
185186
* @param viewColumn (Optional) The VSCode editor group to open the diff in.
186187
*/
187188
async open(relPath: string, viewColumn: ViewColumn): Promise<void> {
189+
// Store the pre-diff active editor for potential focus restoration
190+
this.preDiffActiveEditor = vscode.window.activeTextEditor
191+
188192
this.viewColumn = viewColumn
189193
this.disableAutoFocusAfterUserInteraction()
190194
// Set the edit type based on the file existence
@@ -253,6 +257,51 @@ export class DiffViewProvider {
253257
this.streamedLines = []
254258
}
255259

260+
/**
261+
* Prepares the optimal view column and placement for the diff view.
262+
* For existing open files: Places diff beside the original tab in the same group.
263+
* For new/unopened files: Places at the end of the currently active editor group.
264+
*/
265+
private async prepareDiffViewPlacement(absolutePath: string): Promise<void> {
266+
if (!this.documentWasOpen) {
267+
return Promise.resolve()
268+
}
269+
// For existing files that are currently open, find the original tab
270+
const originalTab = this.findTabForFile(absolutePath)
271+
if (originalTab) {
272+
// Find the tab group containing the original tab
273+
const tabGroup = vscode.window.tabGroups.all.find((group) => group.tabs.some((tab) => tab === originalTab))
274+
275+
if (tabGroup) {
276+
const viewColumn = this.viewColumn !== ViewColumn.Active ? tabGroup.viewColumn : this.viewColumn
277+
// Ensure the original tab is active within its group to place diff beside it
278+
await this.showTextDocumentSafe({
279+
uri: vscode.Uri.file(absolutePath),
280+
options: {
281+
viewColumn: viewColumn,
282+
preserveFocus: true,
283+
preview: false,
284+
},
285+
})
286+
// Update viewColumn to match the original file's group
287+
this.viewColumn = viewColumn
288+
}
289+
}
290+
// For new files or unopened files, keep the original viewColumn (active group)
291+
// No additional preparation needed as it will default to end of active group
292+
}
293+
294+
/**
295+
* Finds the VS Code tab for a given file path.
296+
*/
297+
private findTabForFile(absolutePath: string): vscode.Tab | undefined {
298+
return vscode.window.tabGroups.all
299+
.flatMap((group) => group.tabs)
300+
.find(
301+
(tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath),
302+
)
303+
}
304+
256305
/**
257306
* Opens a file editor and tracks it as opened by Roo if not already open.
258307
*/
@@ -384,6 +433,9 @@ export class DiffViewProvider {
384433

385434
await this.closeAllRooOpenedViews()
386435

436+
// Implement post-diff focus behavior
437+
await this.handlePostDiffFocus()
438+
387439
// If no auto-close settings are enabled and the document was not open before,
388440
// open the file after the diff is complete.
389441

@@ -502,11 +554,98 @@ export class DiffViewProvider {
502554
await this.closeAllRooOpenedViews()
503555
}
504556

557+
// Implement post-diff focus behavior
558+
await this.handlePostDiffFocus()
559+
505560
// Edit is done.
506561
this.resetWithListeners()
507562
}
508563

509-
private filterTabsToClose(tab: vscode.Tab, settings: DiffSettings): boolean {
564+
/**
565+
* Handles post-diff focus behavior.
566+
* Currently defaults to focusing the edited file (Behavior A).
567+
* Future implementation will support configurable focus behavior.
568+
*/
569+
private async handlePostDiffFocus(): Promise<void> {
570+
if (!this.relPath) {
571+
return
572+
}
573+
574+
if (this.autoCloseAllRooTabs) {
575+
// Focus on the pre-diff active tab
576+
await this.focusOnPreDiffActiveTab()
577+
return
578+
}
579+
// Focus on the edited file (temporary default)
580+
await this.focusOnEditedFile()
581+
}
582+
583+
/**
584+
* Focuses on the tab of the file that was just edited.
585+
*/
586+
private async focusOnEditedFile(): Promise<void> {
587+
if (!this.relPath) {
588+
return
589+
}
590+
591+
try {
592+
const absolutePath = path.resolve(this.cwd, this.relPath)
593+
const fileUri = vscode.Uri.file(absolutePath)
594+
595+
// Check if the file still exists as a tab
596+
const editedFileTab = this.findTabForFile(absolutePath)
597+
if (editedFileTab) {
598+
// Find the tab group containing the edited file
599+
const tabGroup = vscode.window.tabGroups.all.find((group) =>
600+
group.tabs.some((tab) => tab === editedFileTab),
601+
)
602+
603+
if (tabGroup) {
604+
// Make the edited file's tab active
605+
await this.showTextDocumentSafe({
606+
uri: fileUri,
607+
options: {
608+
viewColumn: tabGroup.viewColumn,
609+
preserveFocus: false,
610+
preview: false,
611+
},
612+
})
613+
}
614+
}
615+
} catch (error) {
616+
console.error("Roo Debug: Error focusing on edited file:", error)
617+
}
618+
}
619+
620+
/**
621+
* Restores focus to the tab that was active before the diff operation.
622+
* This method is prepared for future use when configurable focus behavior is implemented.
623+
*/
624+
private async focusOnPreDiffActiveTab(): Promise<void> {
625+
if (!this.preDiffActiveEditor || !this.preDiffActiveEditor.document) {
626+
return
627+
}
628+
629+
try {
630+
// Check if the pre-diff active editor is still valid and its document is still open
631+
const isDocumentStillOpen = vscode.workspace.textDocuments.some(
632+
(doc) => doc === this.preDiffActiveEditor!.document,
633+
)
634+
635+
if (isDocumentStillOpen) {
636+
// Restore focus to the pre-diff active editor
637+
await vscode.window.showTextDocument(this.preDiffActiveEditor.document.uri, {
638+
viewColumn: this.preDiffActiveEditor.viewColumn,
639+
preserveFocus: false,
640+
preview: false,
641+
})
642+
}
643+
} catch (error) {
644+
console.error("Roo Debug: Error restoring focus to pre-diff active tab:", error)
645+
}
646+
}
647+
648+
private tabToCloseFilter(tab: vscode.Tab, settings: DiffSettings): boolean {
510649
// Always close DiffView tabs opened by Roo
511650
if (tab.input instanceof vscode.TabInputTextDiff && tab.input?.original?.scheme === DIFF_VIEW_URI_SCHEME) {
512651
return true
@@ -622,7 +761,7 @@ export class DiffViewProvider {
622761

623762
const closeOps = vscode.window.tabGroups.all
624763
.flatMap((tg) => tg.tabs)
625-
.filter((tab) => this.filterTabsToClose(tab, settings))
764+
.filter((tab) => this.tabToCloseFilter(tab, settings))
626765
.map(this.closeTab)
627766

628767
await Promise.all(closeOps)
@@ -640,7 +779,6 @@ export class DiffViewProvider {
640779

641780
// right uri = the file path
642781
const rightUri = vscode.Uri.file(path.resolve(this.cwd, this.relPath))
643-
644782
// Open new diff editor.
645783
return new Promise<vscode.TextEditor>((resolve, reject) => {
646784
const fileName = path.basename(rightUri.fsPath)
@@ -657,44 +795,61 @@ export class DiffViewProvider {
657795
}
658796
// set interaction flag to true to prevent autoFocus from being triggered
659797
this.suppressInteractionFlag = true
660-
vscode.commands
661-
.executeCommand("vscode.diff", leftUri, rightUri, title, textDocumentShowOptions)
662-
.then(async () => {
663-
// set interaction flag to false to allow autoFocus to be triggered
664-
this.suppressInteractionFlag = false
665-
666-
// Get the active text editor, which should be the diff editor opened by vscode.diff
667-
const diffEditor = vscode.window.activeTextEditor
668-
669-
// Ensure we have a valid editor and it's the one we expect (the right side of the diff)
670-
if (!diffEditor || !arePathsEqual(diffEditor.document.uri.fsPath, rightUri.fsPath)) {
671-
reject(new Error("Failed to get diff editor after opening."))
672-
return
673-
}
798+
// Implement improved diff view placement logic
799+
const previousEditor = vscode.window.activeTextEditor
800+
this.prepareDiffViewPlacement(rightUri.fsPath).then(() => {
801+
vscode.commands
802+
.executeCommand("vscode.diff", leftUri, rightUri, title, textDocumentShowOptions)
803+
.then(async () => {
804+
// set interaction flag to false to allow autoFocus to be triggered
805+
this.suppressInteractionFlag = false
806+
807+
// Get the active text editor, which should be the diff editor opened by vscode.diff
808+
const diffEditor = vscode.window.activeTextEditor
809+
810+
// Ensure we have a valid editor and it's the one we expect (the right side of the diff)
811+
if (!diffEditor || !arePathsEqual(diffEditor.document.uri.fsPath, rightUri.fsPath)) {
812+
reject(new Error("Failed to get diff editor after opening."))
813+
return
814+
}
674815

675-
this.activeDiffEditor = diffEditor // Assign to activeDiffEditor
816+
this.activeDiffEditor = diffEditor // Assign to activeDiffEditor
676817

677-
// Ensure rightUri is tracked even if not explicitly shown again
678-
this.rooOpenedTabs.add(rightUri.toString())
818+
// Ensure rightUri is tracked even if not explicitly shown again
819+
this.rooOpenedTabs.add(rightUri.toString())
679820

680-
// If autoFocus is disabled, explicitly clear the selection to prevent cursor focus.
681-
if (!settings.autoFocus) {
682-
// Use dynamically read autoFocus
683-
// Add a small delay to allow VS Code to potentially set focus first,
684-
// then clear it.
685-
await new Promise((resolve) => setTimeout(resolve, 50))
686-
const beginningOfDocument = new vscode.Position(0, 0)
687-
diffEditor.selection = new vscode.Selection(beginningOfDocument, beginningOfDocument)
688-
}
821+
// If autoFocus is disabled, explicitly clear the selection to prevent cursor focus.
822+
if (!settings.autoFocus) {
823+
// Use dynamically read autoFocus
824+
// Add a small delay to allow VS Code to potentially set focus first,
825+
// then clear it.
826+
await new Promise((resolve) => setTimeout(resolve, 50))
827+
const beginningOfDocument = new vscode.Position(0, 0)
828+
diffEditor.selection = new vscode.Selection(beginningOfDocument, beginningOfDocument)
829+
}
689830

690-
// Resolve the promise with the diff editor
691-
resolve(diffEditor)
692-
})
693-
// Removed the second .then block that called getEditorFromDiffTab
694-
// This may happen on very slow machines ie project idx
695-
setTimeout(() => {
696-
reject(new Error("Failed to open diff editor, please try again..."))
697-
}, 10_000)
831+
// if this happens in a window different from the active one, we need to show the document
832+
if (previousEditor) {
833+
await this.showTextDocumentSafe({
834+
textDocument: previousEditor.document,
835+
options: {
836+
preview: false,
837+
preserveFocus: false,
838+
selection: previousEditor.selection,
839+
viewColumn: previousEditor.viewColumn,
840+
},
841+
})
842+
}
843+
844+
// Resolve the promise with the diff editor
845+
resolve(diffEditor)
846+
})
847+
// Removed the second .then block that called getEditorFromDiffTab
848+
// This may happen on very slow machines ie project idx
849+
setTimeout(() => {
850+
reject(new Error("Failed to open diff editor, please try again..."))
851+
}, 10_000)
852+
})
698853
})
699854
}
700855

@@ -768,6 +923,7 @@ export class DiffViewProvider {
768923
this.streamedLines = []
769924
this.preDiagnostics = []
770925
this.rooOpenedTabs.clear()
926+
this.preDiffActiveEditor = undefined
771927
}
772928

773929
resetWithListeners() {

0 commit comments

Comments
 (0)