11import * as vscode from "vscode"
2- import { TextDocumentShowOptions , ViewColumn } from "vscode"
2+ import { TextDocument , TextDocumentShowOptions , ViewColumn } from "vscode"
33import * as path from "path"
44import * as fs from "fs/promises"
55import { createDirectoriesForFile } from "../../utils/fs"
@@ -27,39 +27,152 @@ export class DiffViewProvider {
2727 private streamedLines : string [ ] = [ ]
2828 private preDiagnostics : [ vscode . Uri , vscode . Diagnostic [ ] ] [ ] = [ ]
2929 private rooOpenedTabs : Set < string > = new Set ( )
30- private preserveFocus : boolean = false
31- private autoFocus : boolean = true
30+ private preserveFocus : boolean | undefined = undefined
31+ private autoApproval : boolean | undefined = undefined
32+ private autoFocus : boolean | undefined = undefined
3233 private autoCloseTabs : boolean = false
3334 // have to set the default view column to -1 since we need to set it in the initialize method and during initialization the enum ViewColumn is undefined
3435 private viewColumn : ViewColumn = - 1 // ViewColumn.Active
36+ private userInteractionListeners : vscode . Disposable [ ] = [ ]
37+ private suppressInteractionFlag : boolean = false
3538
3639 constructor ( private cwd : string ) { }
3740
38- async initialize ( viewColumn : ViewColumn ) {
41+ private async initialize ( viewColumn : ViewColumn ) {
3942 const provider = ClineProvider . getVisibleInstance ( )
40- const autoFocus = vscode . workspace . getConfiguration ( "roo-cline" ) . get < boolean > ( "diffViewAutoFocus" , true )
41-
42- const autoApproval =
43- ( provider ?. getValue ( "autoApprovalEnabled" ) && provider ?. getValue ( "alwaysAllowWrite" ) ) ?? false
4443 // If autoApproval is enabled, we want to preserve focus if autoFocus is disabled
4544 // AutoApproval is enabled when the user has set "alwaysAllowWrite" and "autoApprovalEnabled" to true
4645 // AutoFocus is enabled when the user has set "diffView.autoFocus" to true, this is the default.
4746 // If autoFocus is disabled, we want to preserve focus on the diff editor we are working on.
48- this . preserveFocus = autoApproval && ! autoFocus
49- this . autoFocus = autoFocus
47+ // we have to check for null values for the first initialization
48+ if ( this . autoApproval === undefined ) {
49+ this . autoApproval =
50+ ( provider ?. getValue ( "autoApprovalEnabled" ) && provider ?. getValue ( "alwaysAllowWrite" ) ) ?? false
51+ }
52+ if ( this . autoFocus === undefined ) {
53+ this . autoFocus = vscode . workspace . getConfiguration ( "roo-cline" ) . get < boolean > ( "diffViewAutoFocus" , true )
54+ }
55+ this . preserveFocus = this . autoApproval && ! this . autoFocus
5056 this . autoCloseTabs = vscode . workspace . getConfiguration ( "roo-cline" ) . get < boolean > ( "autoCloseRooTabs" , false )
5157 this . viewColumn = viewColumn
5258 // Track currently visible editors and active editor for focus restoration and tab cleanup
5359 this . rooOpenedTabs . clear ( )
5460 }
5561
62+ private async showTextDocumentSafe ( {
63+ uri,
64+ textDocument,
65+ options,
66+ } : {
67+ uri ?: vscode . Uri
68+ textDocument ?: TextDocument
69+ options ?: TextDocumentShowOptions
70+ } ) {
71+ this . suppressInteractionFlag = true
72+ // If the uri is already open, we want to focus it
73+ if ( uri ) {
74+ const editor = await vscode . window . showTextDocument ( uri , options )
75+ this . suppressInteractionFlag = false
76+ return editor
77+ }
78+ // If the textDocument is already open, we want to focus it
79+ if ( textDocument ) {
80+ const editor = await vscode . window . showTextDocument ( textDocument , options )
81+ this . suppressInteractionFlag = false
82+ return editor
83+ }
84+ // If the textDocument is not open and not able to be opened, we just reset the suppressInteractionFlag
85+ this . suppressInteractionFlag = false
86+ return null
87+ }
88+
89+ /**
90+ * Resets the auto-focus listeners to prevent memory leaks.
91+ * This is called when the diff editor is closed or when the user interacts with other editors.
92+ */
93+ private resetAutoFocusListeners ( ) {
94+ this . userInteractionListeners . forEach ( ( listener ) => listener . dispose ( ) )
95+ this . userInteractionListeners = [ ]
96+ }
97+
98+ /**
99+ * Disables auto-focus on the diff editor after user interaction.
100+ * This is to prevent the diff editor from stealing focus when the user interacts with other editors or tabs.
101+ */
102+ private disableAutoFocusAfterUserInteraction ( ) {
103+ this . resetAutoFocusListeners ( )
104+ // first reset listeners if they exist
105+ this . userInteractionListeners . forEach ( ( listener ) => listener . dispose ( ) )
106+ this . userInteractionListeners = [ ]
107+ // if auto approval is disabled or auto focus is disabled, we don't need to add listeners
108+ if ( ! this . autoApproval || ! this . autoFocus ) {
109+ return
110+ }
111+ // then add new listeners
112+ const changeTextEditorSelectionListener = vscode . window . onDidChangeTextEditorSelection ( ( _e ) => {
113+ // If the change was done programmatically, or if there is actually no editor or the user did not allow auto approval, we don't want to suppress focus
114+ if ( this . suppressInteractionFlag ) {
115+ // If the user is interacting with the diff editor, we don't want to suppress focus
116+ // If the user is interacting with another editor, we want to suppress focus
117+ return
118+ }
119+ // Consider this a "user interaction"
120+ this . preserveFocus = true
121+ this . autoFocus = false
122+ // remove the listeners since we don't need them anymore
123+ this . resetAutoFocusListeners ( )
124+ } , this )
125+ const changeActiveTextEditorListener = vscode . window . onDidChangeActiveTextEditor ( ( editor ) => {
126+ // If the change was done programmatically, or if there is actually no editor or the user did not allow auto approval, we don't want to suppress focus
127+ if ( this . suppressInteractionFlag || ! editor ) {
128+ // If the user is interacting with the diff editor, we don't want to suppress focus
129+ // If the user is interacting with another editor, we want to suppress focus
130+ return
131+ }
132+ // Consider this a "user interaction"
133+ this . preserveFocus = true
134+ this . autoFocus = false
135+ // remove the listeners since we don't need them anymore
136+ this . resetAutoFocusListeners ( )
137+ } , this )
138+ const changeTabListener = vscode . window . tabGroups . onDidChangeTabs ( ( _e ) => {
139+ // Some tab was added/removed/changed
140+ // If the change was done programmatically, or the user did not allow auto approval, we don't want to suppress focus
141+ if ( this . suppressInteractionFlag ) {
142+ return
143+ }
144+ this . preserveFocus = true
145+ this . autoFocus = false
146+ // remove the listeners since we don't need them anymore
147+ this . resetAutoFocusListeners ( )
148+ } , this )
149+ const changeTabGroupListener = vscode . window . tabGroups . onDidChangeTabGroups ( ( _e ) => {
150+ // Tab group layout changed (e.g., split view)
151+ // If the change was done programmatically, or the user did not allow auto approval, we don't want to suppress focus
152+ if ( this . suppressInteractionFlag ) {
153+ return
154+ }
155+ this . preserveFocus = true
156+ this . autoFocus = false
157+ // remove the listeners since we don't need them anymore
158+ this . resetAutoFocusListeners ( )
159+ } , this )
160+ this . userInteractionListeners . push (
161+ changeTextEditorSelectionListener ,
162+ changeActiveTextEditorListener ,
163+ changeTabListener ,
164+ changeTabGroupListener ,
165+ )
166+ }
167+
56168 /**
57169 * Opens a diff editor for the given relative path, optionally in a specific viewColumn.
58170 * @param relPath The relative file path to open.
59171 * @param viewColumn (Optional) The VSCode editor group to open the diff in.
60172 */
61173 async open ( relPath : string , viewColumn : ViewColumn ) : Promise < void > {
62174 await this . initialize ( viewColumn )
175+ this . disableAutoFocusAfterUserInteraction ( )
63176 // Set the edit type based on the file existence
64177 this . relPath = relPath
65178 const fileExists = this . editType === "modify"
@@ -117,7 +230,7 @@ export class DiffViewProvider {
117230 * Opens a file editor and tracks it as opened by Roo if not already open.
118231 */
119232 private async showAndTrackEditor ( uri : vscode . Uri , options : vscode . TextDocumentShowOptions = { } ) {
120- const editor = await vscode . window . showTextDocument ( uri , options )
233+ const editor = await this . showTextDocumentSafe ( { uri, options } )
121234 if ( this . autoCloseTabs && ! this . documentWasOpen ) {
122235 this . rooOpenedTabs . add ( uri . toString ( ) )
123236 }
@@ -193,26 +306,11 @@ export class DiffViewProvider {
193306 if ( ! this . relPath || ! this . newContent || ! this . activeDiffEditor ) {
194307 return { newProblemsMessage : undefined , userEdits : undefined , finalContent : undefined }
195308 }
196- const absolutePath = path . resolve ( this . cwd , this . relPath )
197309 const updatedDocument = this . activeDiffEditor . document
198310 const editedContent = updatedDocument . getText ( )
199311 if ( updatedDocument . isDirty ) {
200312 await updatedDocument . save ( )
201313 }
202- const previousEditor = vscode . window . activeTextEditor
203- const textDocumentShowOptions : TextDocumentShowOptions = {
204- preview : false ,
205- preserveFocus : this . preserveFocus ,
206- viewColumn : this . viewColumn ,
207- }
208- await vscode . window . showTextDocument ( vscode . Uri . file ( absolutePath ) , textDocumentShowOptions )
209- if ( ! this . autoFocus && previousEditor ) {
210- await vscode . window . showTextDocument ( previousEditor . document , {
211- preview : false ,
212- preserveFocus : false ,
213- selection : previousEditor . selection ,
214- } )
215- }
216314 await this . closeAllRooOpenedViews ( )
217315 /*
218316 Getting diagnostics before and after the file edit is a better approach than
@@ -293,8 +391,11 @@ export class DiffViewProvider {
293391 await updatedDocument . save ( )
294392 console . log ( `File ${ absolutePath } has been reverted to its original content.` )
295393 if ( this . documentWasOpen ) {
296- await vscode . window . showTextDocument ( vscode . Uri . file ( absolutePath ) , {
297- preview : false ,
394+ await this . showTextDocumentSafe ( {
395+ uri : vscode . Uri . file ( absolutePath ) ,
396+ options : {
397+ preview : false ,
398+ } ,
298399 } )
299400 }
300401 await this . closeAllRooOpenedViews ( )
@@ -386,9 +487,13 @@ export class DiffViewProvider {
386487 preserveFocus : this . preserveFocus ,
387488 viewColumn : this . viewColumn ,
388489 }
490+ // set interaction flag to true to prevent autoFocus from being triggered
491+ this . suppressInteractionFlag = true
389492 vscode . commands
390493 . executeCommand ( "vscode.diff" , leftUri , rightUri , title , textDocumentShowOptions )
391494 . then ( ( ) => {
495+ // set interaction flag to false to allow autoFocus to be triggered
496+ this . suppressInteractionFlag = false
392497 if ( this . autoCloseTabs && ! this . documentWasOpen ) {
393498 // If the diff tab is not already open, add it to the set
394499 this . rooOpenedTabs . add ( rightUri . toString ( ) )
@@ -402,11 +507,14 @@ export class DiffViewProvider {
402507 return
403508 }
404509 // if there is, we need to focus it
405- vscode . window . showTextDocument ( previousEditor . document , {
406- preview : false ,
407- // we need to force focus here now, because we want to restore the previous selection
408- preserveFocus : false ,
409- selection : previousEditor . selection ,
510+ this . showTextDocumentSafe ( {
511+ textDocument : previousEditor . document ,
512+ options : {
513+ preview : false ,
514+ preserveFocus : false ,
515+ selection : previousEditor . selection ,
516+ viewColumn : previousEditor . viewColumn ,
517+ } ,
410518 } )
411519 } )
412520 . then ( ( ) => {
@@ -479,5 +587,8 @@ export class DiffViewProvider {
479587 this . activeLineController = undefined
480588 this . streamedLines = [ ]
481589 this . preDiagnostics = [ ]
590+ this . rooOpenedTabs . clear ( )
591+ this . autoCloseTabs = false
592+ this . resetAutoFocusListeners ( )
482593 }
483594}
0 commit comments