Skip to content

Commit a591dcb

Browse files
committed
fix: prevent sidebar opening in multi-window VS Code setups (#5485)
- Add logic to detect multi-window scenarios in focusPanel utility - Prevent automatic sidebar activation when user is working in different window - Add comprehensive tests for focusPanel functionality - Maintain backward compatibility with existing behavior Fixes #5485
1 parent cb4652e commit a591dcb

File tree

2 files changed

+171
-2
lines changed

2 files changed

+171
-2
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest"
2+
import * as vscode from "vscode"
3+
import { focusPanel } from "../focusPanel"
4+
import { ClineProvider } from "../../core/webview/ClineProvider"
5+
6+
// Mock vscode module
7+
vi.mock("vscode", () => ({
8+
commands: {
9+
executeCommand: vi.fn(),
10+
},
11+
window: {
12+
activeTextEditor: undefined,
13+
visibleTextEditors: [],
14+
},
15+
ViewColumn: {
16+
Active: 1,
17+
},
18+
}))
19+
20+
// Mock ClineProvider
21+
vi.mock("../../core/webview/ClineProvider", () => ({
22+
ClineProvider: {
23+
sideBarId: "roo-code.SidebarProvider",
24+
getVisibleInstance: vi.fn(),
25+
},
26+
}))
27+
28+
// Mock Package
29+
vi.mock("../../shared/package", () => ({
30+
Package: {
31+
name: "roo-code",
32+
},
33+
}))
34+
35+
describe("focusPanel", () => {
36+
const mockExecuteCommand = vi.mocked(vscode.commands.executeCommand)
37+
const mockGetVisibleInstance = vi.mocked(ClineProvider.getVisibleInstance)
38+
39+
beforeEach(() => {
40+
vi.clearAllMocks()
41+
// Reset window state
42+
;(vscode.window as any).activeTextEditor = undefined
43+
;(vscode.window as any).visibleTextEditors = []
44+
})
45+
46+
describe("when panels exist", () => {
47+
it("should reveal tab panel when it exists but is not active", async () => {
48+
const mockTabPanel = {
49+
active: false,
50+
reveal: vi.fn(),
51+
} as any
52+
53+
await focusPanel(mockTabPanel, undefined)
54+
55+
expect(mockTabPanel.reveal).toHaveBeenCalledWith(1, false)
56+
expect(mockExecuteCommand).not.toHaveBeenCalled()
57+
})
58+
59+
it("should focus sidebar panel when it exists", async () => {
60+
const mockSidebarPanel = {} as any
61+
62+
await focusPanel(undefined, mockSidebarPanel)
63+
64+
expect(mockExecuteCommand).toHaveBeenCalledWith("roo-code.SidebarProvider.focus")
65+
})
66+
67+
it("should prefer tab panel over sidebar panel when both exist", async () => {
68+
const mockTabPanel = {
69+
active: false,
70+
reveal: vi.fn(),
71+
} as any
72+
const mockSidebarPanel = {} as any
73+
74+
await focusPanel(mockTabPanel, mockSidebarPanel)
75+
76+
expect(mockTabPanel.reveal).toHaveBeenCalledWith(1, false)
77+
expect(mockExecuteCommand).not.toHaveBeenCalled()
78+
})
79+
})
80+
81+
describe("when no panels exist", () => {
82+
it("should open sidebar when there is a visible Roo Code instance", async () => {
83+
mockGetVisibleInstance.mockReturnValue({} as any)
84+
85+
await focusPanel(undefined, undefined)
86+
87+
expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.view.extension.roo-code-ActivityBar")
88+
})
89+
90+
it("should open sidebar when there is an active editor (user is working in this window)", async () => {
91+
mockGetVisibleInstance.mockReturnValue(undefined)
92+
;(vscode.window as any).activeTextEditor = { document: { fileName: "test.ts" } }
93+
94+
await focusPanel(undefined, undefined)
95+
96+
expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.view.extension.roo-code-ActivityBar")
97+
})
98+
99+
it("should open sidebar when there are visible editors (user is working in this window)", async () => {
100+
mockGetVisibleInstance.mockReturnValue(undefined)
101+
;(vscode.window as any).visibleTextEditors = [{ document: { fileName: "test.ts" } }]
102+
103+
await focusPanel(undefined, undefined)
104+
105+
expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.view.extension.roo-code-ActivityBar")
106+
})
107+
108+
it("should NOT open sidebar when no visible instance and no editors (multi-window scenario)", async () => {
109+
mockGetVisibleInstance.mockReturnValue(undefined)
110+
// No active editor and no visible editors (default state)
111+
112+
await focusPanel(undefined, undefined)
113+
114+
expect(mockExecuteCommand).not.toHaveBeenCalled()
115+
})
116+
117+
it("should open sidebar when detection fails (fallback to existing behavior)", async () => {
118+
mockGetVisibleInstance.mockImplementation(() => {
119+
throw new Error("Test error")
120+
})
121+
122+
await focusPanel(undefined, undefined)
123+
124+
expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.view.extension.roo-code-ActivityBar")
125+
})
126+
})
127+
})

src/utils/focusPanel.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@ export async function focusPanel(
1515
const panel = tabPanel || sidebarPanel
1616

1717
if (!panel) {
18-
// If no panel is open, open the sidebar
19-
await vscode.commands.executeCommand(`workbench.view.extension.${Package.name}-ActivityBar`)
18+
// Check if we should open the sidebar - avoid opening in multi-window scenarios
19+
// where the user might be working in a different VS Code window
20+
const shouldOpenSidebar = await shouldAllowSidebarActivation()
21+
22+
if (shouldOpenSidebar) {
23+
// If no panel is open, open the sidebar
24+
await vscode.commands.executeCommand(`workbench.view.extension.${Package.name}-ActivityBar`)
25+
}
2026
} else if (panel === tabPanel && !panel.active) {
2127
// For tab panels, use reveal to focus
2228
panel.reveal(vscode.ViewColumn.Active, false)
@@ -25,3 +31,39 @@ export async function focusPanel(
2531
await vscode.commands.executeCommand(`${ClineProvider.sideBarId}.focus`)
2632
}
2733
}
34+
35+
/**
36+
* Determines if we should allow automatic sidebar activation
37+
* This helps prevent unwanted sidebar opening in multi-window VS Code setups
38+
* @returns Promise<boolean> - true if sidebar activation is allowed
39+
*/
40+
async function shouldAllowSidebarActivation(): Promise<boolean> {
41+
try {
42+
// Check if there's a visible Roo Code instance already
43+
const visibleProvider = ClineProvider.getVisibleInstance()
44+
if (visibleProvider) {
45+
// If there's already a visible provider, it's safe to open the sidebar
46+
return true
47+
}
48+
49+
// Check if the current window has focus and is the active window
50+
// This helps prevent opening sidebar when user is working in another window
51+
const activeEditor = vscode.window.activeTextEditor
52+
const visibleEditors = vscode.window.visibleTextEditors
53+
54+
// If there are active editors in this window, it's likely the user's current working window
55+
if (activeEditor || visibleEditors.length > 0) {
56+
return true
57+
}
58+
59+
// If no editors are visible and no Roo Code instance is visible,
60+
// be conservative and don't auto-open the sidebar to avoid disrupting
61+
// the user's workflow in other windows
62+
return false
63+
} catch (error) {
64+
// If there's any error in detection, err on the side of caution
65+
// and allow sidebar activation to maintain existing functionality
66+
console.warn("Error in shouldAllowSidebarActivation:", error)
67+
return true
68+
}
69+
}

0 commit comments

Comments
 (0)