Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions src/utils/__tests__/focusPanel.test.ts
Original file line number Diff line number Diff line change
@@ -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")
})
})
})
46 changes: 44 additions & 2 deletions src/utils/focusPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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<boolean> - true if sidebar activation is allowed
*/
async function shouldAllowSidebarActivation(): Promise<boolean> {
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
}
}