@@ -16,6 +16,7 @@ import { Task } from "../../core/task/Task"
1616import { DecorationController } from "./DecorationController"
1717import { ClineProvider } from "../../core/webview/ClineProvider"
1818import { UserInteractionProvider } from "./UserInteractionProvider"
19+ import { PostDiffViewBehaviorUtils } from "./PostDiffViewBehaviorUtils"
1920
2021export const DIFF_VIEW_URI_SCHEME = "cline-diff"
2122
@@ -43,27 +44,36 @@ export class DiffViewProvider {
4344 private streamedLines : string [ ] = [ ]
4445 private preDiagnostics : [ vscode . Uri , vscode . Diagnostic [ ] ] [ ] = [ ]
4546 private rooOpenedTabs : Set < string > = new Set ( )
46- private preserveFocus : boolean | undefined = undefined
4747 private autoApproval : boolean | undefined = undefined
4848 private autoFocus : boolean | undefined = undefined
49- private autoCloseTabs : boolean = false
5049 private autoCloseAllRooTabs : boolean = false // Added new setting
5150 // 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
5251 private viewColumn : ViewColumn = - 1 // ViewColumn.Active
5352 private userInteractionProvider : UserInteractionProvider
5453 private suppressInteractionFlag : boolean = false
5554 private preDiffActiveEditor ?: vscode . TextEditor // Store active editor before diff operation
55+ private postDiffBehaviorUtils : PostDiffViewBehaviorUtils
5656
5757 constructor ( private cwd : string ) {
5858 this . userInteractionProvider = new UserInteractionProvider ( {
5959 onUserInteraction : ( ) => {
60- this . preserveFocus = true
6160 this . autoFocus = false
6261 } ,
6362 getSuppressFlag : ( ) => this . suppressInteractionFlag ,
6463 autoApproval : false ,
6564 autoFocus : true ,
6665 } )
66+
67+ // Initialize PostDiffviewBehaviorUtils with initial context
68+ this . postDiffBehaviorUtils = new PostDiffViewBehaviorUtils ( {
69+ relPath : this . relPath ,
70+ editType : this . editType ,
71+ documentWasOpen : this . documentWasOpen ,
72+ cwd : this . cwd ,
73+ rooOpenedTabs : this . rooOpenedTabs ,
74+ preDiffActiveEditor : this . preDiffActiveEditor ,
75+ autoCloseAllRooTabs : this . autoCloseAllRooTabs ,
76+ } )
6777 }
6878
6979 public async initialize ( ) {
@@ -79,11 +89,14 @@ export class DiffViewProvider {
7989 }
8090 const settings = await this . _readDiffSettings ( )
8191 this . autoFocus = settings . autoFocus
82- this . autoCloseTabs = settings . autoCloseRooTabs
8392 this . autoCloseAllRooTabs = settings . autoCloseAllRooTabs
84- this . preserveFocus = this . autoApproval && ! this . autoFocus
8593 // Track currently visible editors and active editor for focus restoration and tab cleanup
8694 this . rooOpenedTabs . clear ( )
95+
96+ // Update PostDiffviewBehaviorUtils context with latest values
97+ this . postDiffBehaviorUtils . updateContext ( {
98+ autoCloseAllRooTabs : this . autoCloseAllRooTabs ,
99+ } )
87100 }
88101
89102 private async _readDiffSettings ( ) : Promise < DiffSettings > {
@@ -143,6 +156,14 @@ export class DiffViewProvider {
143156 this . preDiffActiveEditor = vscode . window . activeTextEditor
144157
145158 this . viewColumn = viewColumn
159+
160+ // Update PostDiffviewBehaviorUtils context with current state
161+ this . postDiffBehaviorUtils . updateContext ( {
162+ relPath : relPath ,
163+ editType : this . editType ,
164+ documentWasOpen : this . documentWasOpen ,
165+ preDiffActiveEditor : this . preDiffActiveEditor ,
166+ } )
146167 // Update the user interaction provider with current settings
147168 this . userInteractionProvider . updateOptions ( {
148169 autoApproval : this . autoApproval ?? false ,
@@ -203,6 +224,10 @@ export class DiffViewProvider {
203224 tab . input instanceof vscode . TabInputText && arePathsEqual ( tab . input . uri . fsPath , absolutePath ) ,
204225 ) . length > 0
205226
227+ this . postDiffBehaviorUtils . updateContext ( {
228+ documentWasOpen : this . documentWasOpen ,
229+ } )
230+
206231 this . activeDiffEditor = await this . openDiffEditor ( )
207232 this . fadedOverlayController = new DecorationController ( "fadedOverlay" , this . activeDiffEditor )
208233 this . activeLineController = new DecorationController ( "activeLine" , this . activeDiffEditor )
@@ -417,10 +442,10 @@ export class DiffViewProvider {
417442 }
418443 }
419444
420- await this . closeAllRooOpenedViews ( )
445+ await this . postDiffBehaviorUtils . closeAllRooOpenedViews ( await this . _readDiffSettings ( ) )
421446
422447 // Implement post-diff focus behavior
423- await this . handlePostDiffFocus ( )
448+ await this . postDiffBehaviorUtils . handlePostDiffFocus ( )
424449
425450 // If no auto-close settings are enabled and the document was not open before,
426451 // open the file after the diff is complete.
@@ -583,7 +608,7 @@ export class DiffViewProvider {
583608 await updatedDocument . save ( )
584609 }
585610
586- await this . closeAllRooOpenedViews ( )
611+ await this . postDiffBehaviorUtils . closeAllRooOpenedViews ( await this . _readDiffSettings ( ) )
587612 await fs . unlink ( absolutePath )
588613
589614 // Remove only the directories we created, in reverse order.
@@ -615,222 +640,16 @@ export class DiffViewProvider {
615640 await this . showTextDocumentSafe ( { uri : vscode . Uri . file ( absolutePath ) , options : { preview : false } } )
616641 }
617642
618- await this . closeAllRooOpenedViews ( )
643+ await this . postDiffBehaviorUtils . closeAllRooOpenedViews ( await this . _readDiffSettings ( ) )
619644 }
620645
621646 // Implement post-diff focus behavior
622- await this . handlePostDiffFocus ( )
647+ await this . postDiffBehaviorUtils . handlePostDiffFocus ( )
623648
624649 // Edit is done.
625650 this . resetWithListeners ( )
626651 }
627652
628- /**
629- * Handles post-diff focus behavior.
630- * Currently defaults to focusing the edited file (Behavior A).
631- * Future implementation will support configurable focus behavior.
632- */
633- private async handlePostDiffFocus ( ) : Promise < void > {
634- if ( ! this . relPath ) {
635- return
636- }
637-
638- if ( this . autoCloseAllRooTabs ) {
639- // Focus on the pre-diff active tab
640- await this . focusOnPreDiffActiveTab ( )
641- return
642- }
643- // Focus on the edited file (temporary default)
644- await this . focusOnEditedFile ( )
645- }
646-
647- /**
648- * Focuses on the tab of the file that was just edited.
649- */
650- private async focusOnEditedFile ( ) : Promise < void > {
651- if ( ! this . relPath ) {
652- return
653- }
654-
655- try {
656- const absolutePath = path . resolve ( this . cwd , this . relPath )
657- const fileUri = vscode . Uri . file ( absolutePath )
658-
659- // Check if the file still exists as a tab
660- const editedFileTab = this . findTabForFile ( absolutePath )
661- if ( editedFileTab ) {
662- // Find the tab group containing the edited file
663- const tabGroup = vscode . window . tabGroups . all . find ( ( group ) =>
664- group . tabs . some ( ( tab ) => tab === editedFileTab ) ,
665- )
666-
667- if ( tabGroup ) {
668- // Make the edited file's tab active
669- await this . showTextDocumentSafe ( {
670- uri : fileUri ,
671- options : {
672- viewColumn : tabGroup . viewColumn ,
673- preserveFocus : false ,
674- preview : false ,
675- } ,
676- } )
677- }
678- }
679- } catch ( error ) {
680- console . error ( "Roo Debug: Error focusing on edited file:" , error )
681- }
682- }
683-
684- /**
685- * Restores focus to the tab that was active before the diff operation.
686- * This method is prepared for future use when configurable focus behavior is implemented.
687- */
688- private async focusOnPreDiffActiveTab ( ) : Promise < void > {
689- if ( ! this . preDiffActiveEditor || ! this . preDiffActiveEditor . document ) {
690- return
691- }
692-
693- try {
694- // Check if the pre-diff active editor is still valid and its document is still open
695- const isDocumentStillOpen = vscode . workspace . textDocuments . some (
696- ( doc ) => doc === this . preDiffActiveEditor ! . document ,
697- )
698-
699- if ( isDocumentStillOpen ) {
700- // Restore focus to the pre-diff active editor
701- await vscode . window . showTextDocument ( this . preDiffActiveEditor . document . uri , {
702- viewColumn : this . preDiffActiveEditor . viewColumn ,
703- preserveFocus : false ,
704- preview : false ,
705- } )
706- }
707- } catch ( error ) {
708- console . error ( "Roo Debug: Error restoring focus to pre-diff active tab:" , error )
709- }
710- }
711-
712- private tabToCloseFilter ( tab : vscode . Tab , settings : DiffSettings ) : boolean {
713- // Always close DiffView tabs opened by Roo
714- if ( tab . input instanceof vscode . TabInputTextDiff && tab . input ?. original ?. scheme === DIFF_VIEW_URI_SCHEME ) {
715- return true
716- }
717-
718- let isRooOpenedTextTab = false
719- if ( tab . input instanceof vscode . TabInputText ) {
720- const currentTabUri = ( tab . input as vscode . TabInputText ) . uri
721- for ( const openedUriString of this . rooOpenedTabs ) {
722- try {
723- const previouslyOpenedUri = vscode . Uri . parse ( openedUriString , true ) // true for strict parsing
724- if ( currentTabUri . scheme === "file" && previouslyOpenedUri . scheme === "file" ) {
725- if ( arePathsEqual ( currentTabUri . fsPath , previouslyOpenedUri . fsPath ) ) {
726- isRooOpenedTextTab = true
727- break
728- }
729- } else {
730- if ( currentTabUri . toString ( ) === previouslyOpenedUri . toString ( ) ) {
731- isRooOpenedTextTab = true
732- break
733- }
734- }
735- } catch ( e ) {
736- // Log parsing error if necessary, or ignore if a URI in rooOpenedTabs is malformed
737- console . error ( `Roo Debug: Error parsing URI from rooOpenedTabs: ${ openedUriString } ` , e )
738- }
739- }
740- }
741-
742- if ( ! isRooOpenedTextTab ) {
743- return false // Not a text tab or not identified as opened by Roo
744- }
745-
746- // Haken 2 (settings.autoCloseAllRooTabs) - takes precedence
747- if ( settings . autoCloseAllRooTabs ) {
748- // This implies Haken 1 is also effectively on
749- return true // Close all Roo-opened text tabs
750- }
751-
752- // Only Haken 1 (settings.autoCloseRooTabs) is on, Haken 2 is off
753- if ( settings . autoCloseRooTabs ) {
754- const tabUriFsPath = ( tab . input as vscode . TabInputText ) . uri . fsPath
755- const absolutePathDiffedFile = this . relPath ? path . resolve ( this . cwd , this . relPath ) : null
756-
757- // Guard against null absolutePathDiffedFile if relPath is somehow not set
758- if ( ! absolutePathDiffedFile ) {
759- // If we don't know the main diffed file, but Haken 1 is on,
760- // it's safer to close any tab Roo opened to avoid leaving extras.
761- return true
762- }
763-
764- const isMainDiffedFileTab = arePathsEqual ( tabUriFsPath , absolutePathDiffedFile )
765-
766- if ( this . editType === "create" && isMainDiffedFileTab ) {
767- return true // Case: New file, Haken 1 is on -> Close its tab.
768- }
769-
770- if ( this . editType === "modify" && isMainDiffedFileTab ) {
771- return ! this . documentWasOpen
772- }
773-
774- // If the tab is for a file OTHER than the main diffedFile, but was opened by Roo
775- if ( ! isMainDiffedFileTab ) {
776- // This covers scenarios where Roo might open auxiliary files (though less common for single diff).
777- // If Haken 1 is on, these should also be closed.
778- return true
779- }
780- }
781- return false // Default: do not close if no above condition met
782- }
783-
784- private async closeTab ( tab : vscode . Tab ) {
785- // If a tab has made it through the filter, it means one of the auto-close settings
786- // (autoCloseTabs or autoCloseAllRooTabs) is active and the conditions for closing
787- // this specific tab are met. Therefore, we should always bypass the dirty check.
788- // const bypassDirtyCheck = true; // This is implicitly true now.
789-
790- // Attempt to find the freshest reference to the tab before closing,
791- // as the original 'tab' object from the initial flatMap might be stale.
792- const tabInputToClose = tab . input
793- const freshTabToClose = vscode . window . tabGroups . all
794- . flatMap ( ( group ) => group . tabs )
795- . find ( ( t ) => t . input === tabInputToClose )
796-
797- if ( freshTabToClose ) {
798- try {
799- await vscode . window . tabGroups . close ( freshTabToClose , true ) // true to bypass dirty check implicitly
800- } catch ( closeError ) {
801- console . error ( `Roo Debug CloseLoop: Error closing tab "${ freshTabToClose . label } ":` , closeError )
802- }
803- } else {
804- // This case should ideally not happen if the tab was in the filtered list.
805- // It might indicate the tab was closed by another means or its input changed.
806- console . warn (
807- `Roo Debug CloseLoop: Tab "${ tab . label } " (input: ${ JSON . stringify ( tab . input ) } ) intended for closure was not found in the current tab list.` ,
808- )
809- // Fallback: Try to close the original tab reference if the fresh one isn't found,
810- // though this is less likely to succeed if it's genuinely stale.
811- try {
812- console . log ( `Roo Debug CloseLoop: Attempting to close original (stale?) tab "${ tab . label } "` )
813- await vscode . window . tabGroups . close ( tab , true )
814- } catch ( fallbackCloseError ) {
815- console . error (
816- `Roo Debug CloseLoop: Error closing original tab reference for "${ tab . label } ":` ,
817- fallbackCloseError ,
818- )
819- }
820- }
821- }
822-
823- private async closeAllRooOpenedViews ( ) {
824- const settings = await this . _readDiffSettings ( ) // Dynamically read settings
825-
826- const closeOps = vscode . window . tabGroups . all
827- . flatMap ( ( tg ) => tg . tabs )
828- . filter ( ( tab ) => this . tabToCloseFilter ( tab , settings ) )
829- . map ( this . closeTab )
830-
831- await Promise . all ( closeOps )
832- }
833-
834653 /**
835654 * Opens the diff editor, optionally in a specific viewColumn.
836655 */
@@ -972,7 +791,7 @@ export class DiffViewProvider {
972791 // Ensure any diff views opened by this provider are closed to release
973792 // memory.
974793 try {
975- await this . closeAllRooOpenedViews ( )
794+ await this . postDiffBehaviorUtils . closeAllRooOpenedViews ( await this . _readDiffSettings ( ) )
976795 } catch ( error ) {
977796 console . error ( "Error closing diff views" , error )
978797 }
0 commit comments