Skip to content

Conversation

@seedlord
Copy link
Contributor

@seedlord seedlord commented Apr 24, 2025

Enhance Diff View Focus and Tab State Management

Enhances focus and tab state management when opening and closing diff views by:

  • Tracking the user's initially focused editor.
  • Storing the original tab's pinned status, view column, and position (index).
  • Adding proper focus restoration logic with fallbacks after the diff view is closed.
  • Opening the diff view in the original tab's view column, pinning it if the original was pinned, and moving it to the original index.
  • Restoring the original tab's pinned status and position after the diff view is closed.
  • Implementing correct handling for opening/focusing newly created files.

Introduces a more robust focus and state restoration system that prioritizes returning to the originally focused editor and restoring the original tab's state (pinning/position), while maintaining fallback behavior for edge cases and ensuring stability.

Context

When Roo opens a diff view to apply changes (apply_diff, write_to_file), the focus management and tab handling could be disruptive. Previously, the focus would always jump to the diff editor and, upon completion, often land on the modified file's tab, even if the user was focused elsewhere initially. Furthermore, if the original file tab was pinned, it would lose its pinned status and its position within the tab group after the diff operation. Additionally, newly created files were not reliably opened or focused after the diff was accepted. This PR aims to improve the user experience by making focus transitions more predictable, preserving the original tab's state (pinning and position), and ensuring newly created files are handled correctly.

Implementation

The core changes were made within src/integrations/editor/DiffViewProvider.ts:

  1. Store Initial State: In the open method, before the diff editor is opened:
    • The uri and viewColumn of the vscode.window.activeTextEditor (if one exists) are stored in userFocusedEditorInfo.
    • If the file to be diffed is already open in a tab, its uri, isPinned status, viewColumn, and index (position within the group) are stored in originalTabState before the tab is closed.
  2. Open and Position Diff View: In openDiffEditor:
    • The diff view is opened in the viewColumn stored in originalTabState (or a fallback).
    • Immediately after the diff editor becomes active: If originalTabState indicates the original tab was pinned, the command workbench.action.pinEditor is executed to pin the diff view itself. Then, the command moveActiveEditor is executed to move the (potentially newly pinned) diff view to the index stored in originalTabState.
  3. Restore State After Completion:
    • The logic for restoring focus/state immediately after the diff editor opens was removed due to causing instability.
    • In saveChanges and revertChanges, after closing the diff view, a new method _restoreOriginalTabState is called if originalTabState exists.
    • _restoreOriginalTabState: This method re-opens the original document in the correct viewColumn. It then uses workbench.action.pinEditor to restore the pinned status and moveActiveEditor to restore the original index (position). Finally, it handles restoring focus either to the originally focused editor (if different) or keeps focus on the restored tab.
  4. Focus Handling (_focusOriginalDocument): This method is now primarily used as a fallback when originalTabState does not exist (e.g., for new files or files not previously open).
    • It attempts to restore focus to the userFocusedEditorInfo if available.
    • It handles opening and focusing newly created files (!this.documentWasOpen).
    • The fallback logic for focusing existing, previously open files (if originalTabState wasn't set, which shouldn't happen anymore for open files) was removed as _restoreOriginalTabState covers this.
  5. "No Initial Editor Focus" Trade-off: If no text editor was focused when the diff process started, userFocusedEditorInfo remains undefined. In this case, after the diff, VS Code's default behavior takes over (typically focusing the last active editor).

These changes build upon the structural improvements made in the separate refactor/extract-focus-logic branch and incorporate the fix for preserving tab state.

Screenshots

before after
Pinned tab loses state after diff Pinned tab state (pin, position) preserved
Diff view appears at default tab position Diff view appears at original tab position

Changes are behavioral regarding editor focus and tab state.

How to Test

Setup: Ensure "workbench.editor.pinnedTabsOnSeparateRow": true is set in VS Code settings for easier observation of pinning.

Test the following scenarios:

  1. Modify Pinned Tab (Middle Position):
    • Open several files (A, B, C, D). Pin Tab B and Tab C. Move Tab B to be the second pinned tab.
    • Focus Tab A (unpinned). Ask Roo to modify Tab B (second pinned tab).
    • Observe: Diff View for B opens, is pinned, and appears at the second position in the pinned row. Focus is on the Diff View.
    • Accept Diff: Diff View closes. Tab B is restored, pinned, at the second position. Focus returns to Tab A.
  2. Modify Pinned Tab (First Position):
    • Pin Tab A. Ensure it's the first tab.
    • Focus Tab C (unpinned). Ask Roo to modify Tab A (first pinned tab).
    • Observe: Diff View for A opens, is pinned, and appears at the first position. Focus is on the Diff View.
    • Accept Diff: Diff View closes. Tab A is restored, pinned, at the first position. Focus returns to Tab C.
  3. Modify Unpinned Tab:
    • Focus Tab A (unpinned). Ask Roo to modify Tab B (unpinned).
    • Observe: Diff View for B opens (unpinned) at its default position. Focus is on the Diff View.
    • Accept Diff: Diff View closes. Tab B is restored (unpinned) at its default position. Focus returns to Tab A.
  4. Modify Existing (Different Group):
    • Focus Tab X in Group 1. Ask Roo to modify Tab Y (pinned) in Group 2.
    • Observe: Diff Z opens in Group 2, is pinned, takes focus.
    • Accept Diff: Diff Z closes, Tab Y is restored (pinned) in Group 2. Focus returns to Tab X (in Group 1).
  5. Create New File:
    • Focus Tab X in Group 1. Ask Roo to create new file Y.
    • Observe: Diff Z opens (unpinned), takes focus.
    • Accept Diff: Diff Z closes, new Tab Y opens (unpinned) and receives focus.
  6. Modify Existing (Not Open):
    • Ensure file Y exists but is not open in any tab. Focus Tab X. Ask Roo to modify file Y.
    • Observe: Diff Z opens (unpinned), takes focus.
    • Accept Diff: Diff Z closes, Tab Y opens (unpinned) and receives focus.
  7. No Initial Editor Focus:
    • Focus the Roo chat view. Ask Roo to modify Tab Y (pinned) in Group 1.
    • Observe: Diff Z opens, is pinned, takes focus.
    • Accept Diff: Diff Z closes, Tab Y is restored (pinned). Focus returns to Tab Y (as it was likely the last active editor).

Get in Touch

Discord: seed92 (via https://discord.com/users/201009672675786752)


Important

Enhance focus management in DiffViewProvider.ts by prioritizing original editor focus restoration and handling new files, with a command update in index.ts.

  • Focus Management in DiffViewProvider.ts:
    • Store initial focus in open() method using userFocusedEditorInfo.
    • Remove immediate focus restoration logic; restore focus after diff completion in _focusOriginalDocument().
    • Prioritize restoring focus to the originally focused editor; fallback to modified file's tab if needed.
    • Handle newly created files by ensuring they are opened and focused after diff acceptance.
    • Accept VS Code's default behavior if no initial editor focus was detected.
      This description was created by Ellipsis for 6b22ba6. You can customize this summary. It will automatically update as commits are pushed.

- Maintains editor view column state when closing and reopening files during diff operations, ensuring tabs stay opened in their original position.

- Prevents closing the original editor tab when opening the diff view, preserving pinned status when applying changes via write_to_file or apply_diff.

- Updates VSCode workspace launch flag from -n to -W for compatibility.
Enhances focus management when opening and closing diff views by:
- Tracking the user's initially focused editor
- Adding proper focus restoration logic with fallbacks
- Implementing better handling for new files

Introduces a more robust focus restoration system that prioritizes returning to the originally focused editor while maintaining fallback behavior for edge cases.
@changeset-bot
Copy link

changeset-bot bot commented Apr 24, 2025

⚠️ No Changeset found

Latest commit: 66fe9d8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Apr 24, 2025
@KJ7LNW
Copy link
Contributor

KJ7LNW commented Apr 25, 2025

FYI, I started on this but haven't gotten to making a PR. Since you have, thanks for taking this on!

Notes:

viewColumn: vscode.ViewColumn.Active,

This keeps roo tabs that are pulled off in another window together w/ the editor tabs

preserveFocus: true

Useful when auto-approve write is enabled to keep from stealing focus

Here is my diff if its useful

diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts
index 0bf49485..7634a0cd 100644
--- a/src/integrations/editor/DiffViewProvider.ts
+++ b/src/integrations/editor/DiffViewProvider.ts
@@ -8,6 +8,7 @@ import { DecorationController } from "./DecorationController"
 import * as diff from "diff"
 import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
 import stripBom from "strip-bom"
+import { ClineProvider } from "../../core/webview/ClineProvider"
 
 export const DIFF_VIEW_URI_SCHEME = "cline-diff"
 
@@ -20,6 +21,7 @@ export class DiffViewProvider {
 	private relPath?: string
 	private newContent?: string
 	private activeDiffEditor?: vscode.TextEditor
+	private savedEditor?: { editor: vscode.TextEditor; selection: vscode.Selection }
 	private fadedOverlayController?: DecorationController
 	private activeLineController?: DecorationController
 	private streamedLines: string[] = []
@@ -156,9 +158,21 @@ export class DiffViewProvider {
 			await updatedDocument.save()
 		}
 
-		await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false })
-		await this.closeAllDiffViews()
+		await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), {
+			preview: false,
+			viewColumn: vscode.ViewColumn.Active,
+			preserveFocus: true,
+		})
+		// Restore the editor that was active when diff was opened
+		if (this.savedEditor) {
+			await vscode.window.showTextDocument(this.savedEditor.editor.document, {
+				selection: this.savedEditor.selection
+			})
+			// Clear saved editor after restoring
+			this.savedEditor = undefined
+		}
 
+		await this.closeAllDiffViews()
 		/*
 		Getting diagnostics before and after the file edit is a better approach than
 		automatically tracking problems in real-time. This method ensures we only
@@ -269,6 +283,15 @@ export class DiffViewProvider {
 		if (!this.relPath) {
 			throw new Error("No file path set")
 		}
+		const provider = ClineProvider.getVisibleInstance()
+		const preserveFocus = provider?.getValue("autoApprovalEnabled") && provider?.getValue("alwaysAllowWrite")
+		// Store current editor before opening diff
+		if (vscode.window.activeTextEditor) {
+			this.savedEditor = {
+				editor: vscode.window.activeTextEditor,
+				selection: vscode.window.activeTextEditor.selection
+			}
+		}
 		const uri = vscode.Uri.file(path.resolve(this.cwd, this.relPath))
 		// If this diff editor is already open (ie if a previous write file was interrupted) then we should activate that instead of opening a new diff
 		const diffTab = vscode.window.tabGroups.all
@@ -280,15 +303,25 @@ export class DiffViewProvider {
 					arePathsEqual(tab.input.modified.fsPath, uri.fsPath),
 			)
 		if (diffTab && diffTab.input instanceof vscode.TabInputTextDiff) {
-			const editor = await vscode.window.showTextDocument(diffTab.input.modified)
+			const currentEditor = vscode.window.activeTextEditor
+			const editor = await vscode.window.showTextDocument(diffTab.input.modified, {
+				viewColumn: vscode.ViewColumn.Active,
+				preserveFocus,
+			})
+			if (currentEditor) {
+				await vscode.window.showTextDocument(currentEditor.document, {
+					selection: currentEditor.selection,
+				})
+			}
 			return editor
 		}
 		// Open new diff editor
 		return new Promise<vscode.TextEditor>((resolve, reject) => {
 			const fileName = path.basename(uri.fsPath)
 			const fileExists = this.editType === "modify"
-			const disposable = vscode.window.onDidChangeActiveTextEditor((editor) => {
-				if (editor && arePathsEqual(editor.document.uri.fsPath, uri.fsPath)) {
+			const disposable = vscode.window.onDidChangeVisibleTextEditors((editors) => {
+				const editor = editors.find((e) => arePathsEqual(e.document.uri.fsPath, uri.fsPath))
+				if (editor) {
 					disposable.dispose()
 					resolve(editor)
 				}
@@ -300,6 +333,7 @@ export class DiffViewProvider {
 				}),
 				uri,
 				`${fileName}: ${fileExists ? "Original ↔ Roo's Changes" : "New File"} (Editable)`,
+				{ preserveFocus: true, viewColumn: vscode.ViewColumn.Active },
 			)
 			// This may happen on very slow machines ie project idx
 			setTimeout(() => {

@seedlord
Copy link
Contributor Author

there is another PR that addresses that focus behaviour
see #2955

@hannesrudolph hannesrudolph moved this from New to PR [Pre Approval Review] in Roo Code Roadmap Apr 28, 2025
@seedlord seedlord marked this pull request as draft April 29, 2025 02:54
@hannesrudolph hannesrudolph moved this from PR [Pre Approval Review] to PR [Draft/WIP] in Roo Code Roadmap May 10, 2025
@KJ7LNW
Copy link
Contributor

KJ7LNW commented May 10, 2025

This may duplicate #2122

@KJ7LNW
Copy link
Contributor

KJ7LNW commented May 10, 2025

#2122 #2955 and #2908 seem to be related.

Authors: I am not sure what is happening, maybe this is fine, but please make sure we avoid duplicate effort.

@seedlord seedlord closed this May 14, 2025
@github-project-automation github-project-automation bot moved this from PR [Draft/WIP] to Done in Roo Code Roadmap May 14, 2025
@seedlord seedlord deleted the feat/improve-tab-focus branch May 19, 2025 20:07
@hannesrudolph hannesrudolph moved this from New to Done in Roo Code Roadmap May 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants