diff --git a/src/utils/__tests__/focusPanel.test.ts b/src/utils/__tests__/focusPanel.test.ts new file mode 100644 index 000000000000..7b067a1dd91d --- /dev/null +++ b/src/utils/__tests__/focusPanel.test.ts @@ -0,0 +1,127 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import * as vscode from "vscode" +import { focusPanel } from "../focusPanel" +import { ClineProvider } from "../../core/webview/ClineProvider" + +// Mock vscode module +vi.mock("vscode", () => ({ + commands: { + executeCommand: vi.fn(), + }, + window: { + activeTextEditor: undefined, + visibleTextEditors: [], + }, + ViewColumn: { + Active: 1, + }, +})) + +// Mock ClineProvider +vi.mock("../../core/webview/ClineProvider", () => ({ + ClineProvider: { + sideBarId: "roo-code.SidebarProvider", + getVisibleInstance: vi.fn(), + }, +})) + +// Mock Package +vi.mock("../../shared/package", () => ({ + Package: { + name: "roo-code", + }, +})) + +describe("focusPanel", () => { + const mockExecuteCommand = vi.mocked(vscode.commands.executeCommand) + const mockGetVisibleInstance = vi.mocked(ClineProvider.getVisibleInstance) + + beforeEach(() => { + vi.clearAllMocks() + // Reset window state + ;(vscode.window as any).activeTextEditor = undefined + ;(vscode.window as any).visibleTextEditors = [] + }) + + describe("when panels exist", () => { + it("should reveal tab panel when it exists but is not active", async () => { + const mockTabPanel = { + active: false, + reveal: vi.fn(), + } as any + + await focusPanel(mockTabPanel, undefined) + + expect(mockTabPanel.reveal).toHaveBeenCalledWith(1, false) + expect(mockExecuteCommand).not.toHaveBeenCalled() + }) + + it("should focus sidebar panel when it exists", async () => { + const mockSidebarPanel = {} as any + + await focusPanel(undefined, mockSidebarPanel) + + expect(mockExecuteCommand).toHaveBeenCalledWith("roo-code.SidebarProvider.focus") + }) + + it("should prefer tab panel over sidebar panel when both exist", async () => { + const mockTabPanel = { + active: false, + reveal: vi.fn(), + } as any + const mockSidebarPanel = {} as any + + await focusPanel(mockTabPanel, mockSidebarPanel) + + expect(mockTabPanel.reveal).toHaveBeenCalledWith(1, false) + expect(mockExecuteCommand).not.toHaveBeenCalled() + }) + }) + + describe("when no panels exist", () => { + it("should open sidebar when there is a visible Roo Code instance", async () => { + mockGetVisibleInstance.mockReturnValue({} as any) + + await focusPanel(undefined, undefined) + + expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.view.extension.roo-code-ActivityBar") + }) + + it("should open sidebar when there is an active editor (user is working in this window)", async () => { + mockGetVisibleInstance.mockReturnValue(undefined) + ;(vscode.window as any).activeTextEditor = { document: { fileName: "test.ts" } } + + await focusPanel(undefined, undefined) + + expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.view.extension.roo-code-ActivityBar") + }) + + it("should open sidebar when there are visible editors (user is working in this window)", async () => { + mockGetVisibleInstance.mockReturnValue(undefined) + ;(vscode.window as any).visibleTextEditors = [{ document: { fileName: "test.ts" } }] + + await focusPanel(undefined, undefined) + + expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.view.extension.roo-code-ActivityBar") + }) + + it("should NOT open sidebar when no visible instance and no editors (multi-window scenario)", async () => { + mockGetVisibleInstance.mockReturnValue(undefined) + // No active editor and no visible editors (default state) + + await focusPanel(undefined, undefined) + + expect(mockExecuteCommand).not.toHaveBeenCalled() + }) + + it("should open sidebar when detection fails (fallback to existing behavior)", async () => { + mockGetVisibleInstance.mockImplementation(() => { + throw new Error("Test error") + }) + + await focusPanel(undefined, undefined) + + expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.view.extension.roo-code-ActivityBar") + }) + }) +}) \ No newline at end of file diff --git a/src/utils/focusPanel.ts b/src/utils/focusPanel.ts index 343a743c025f..77f8398220fc 100644 --- a/src/utils/focusPanel.ts +++ b/src/utils/focusPanel.ts @@ -15,8 +15,14 @@ export async function focusPanel( const panel = tabPanel || sidebarPanel if (!panel) { - // If no panel is open, open the sidebar - await vscode.commands.executeCommand(`workbench.view.extension.${Package.name}-ActivityBar`) + // Check if we should open the sidebar - avoid opening in multi-window scenarios + // where the user might be working in a different VS Code window + const shouldOpenSidebar = await shouldAllowSidebarActivation() + + if (shouldOpenSidebar) { + // If no panel is open, open the sidebar + await vscode.commands.executeCommand(`workbench.view.extension.${Package.name}-ActivityBar`) + } } else if (panel === tabPanel && !panel.active) { // For tab panels, use reveal to focus panel.reveal(vscode.ViewColumn.Active, false) @@ -25,3 +31,39 @@ export async function focusPanel( await vscode.commands.executeCommand(`${ClineProvider.sideBarId}.focus`) } } + +/** + * Determines if we should allow automatic sidebar activation + * This helps prevent unwanted sidebar opening in multi-window VS Code setups + * @returns Promise - true if sidebar activation is allowed + */ +async function shouldAllowSidebarActivation(): Promise { + try { + // Check if there's a visible Roo Code instance already + const visibleProvider = ClineProvider.getVisibleInstance() + if (visibleProvider) { + // If there's already a visible provider, it's safe to open the sidebar + return true + } + + // Check if the current window has focus and is the active window + // This helps prevent opening sidebar when user is working in another window + const activeEditor = vscode.window.activeTextEditor + const visibleEditors = vscode.window.visibleTextEditors + + // If there are active editors in this window, it's likely the user's current working window + if (activeEditor || visibleEditors.length > 0) { + return true + } + + // If no editors are visible and no Roo Code instance is visible, + // be conservative and don't auto-open the sidebar to avoid disrupting + // the user's workflow in other windows + return false + } catch (error) { + // If there's any error in detection, err on the side of caution + // and allow sidebar activation to maintain existing functionality + console.warn("Error in shouldAllowSidebarActivation:", error) + return true + } +}