Skip to content

Commit 156458e

Browse files
author
Jimmycai
committed
Fix Dragging Roo to the sides leaves blank space or makes buttons unresponsive (#3729)
1 parent 39d0f68 commit 156458e

File tree

3 files changed

+137
-35
lines changed

3 files changed

+137
-35
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,16 @@ import { formatLanguage } from "../../shared/language"
3737
import { Terminal } from "../../integrations/terminal/Terminal"
3838
import { downloadTask } from "../../integrations/misc/export-markdown"
3939
import { getTheme } from "../../integrations/theme/getTheme"
40-
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
4140
import { McpHub } from "../../services/mcp/McpHub"
42-
import { McpServerManager } from "../../services/mcp/McpServerManager"
4341
import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckpointService"
4442
import { CodeIndexManager } from "../../services/code-index/manager"
4543
import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
4644
import { fileExistsAtPath } from "../../utils/fs"
4745
import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
4846
import { ContextProxy } from "../config/ContextProxy"
4947
import { ProviderSettingsManager } from "../config/ProviderSettingsManager"
50-
import { CustomModesManager } from "../config/CustomModesManager"
5148
import { buildApiHandler } from "../../api"
49+
import { ComponentManager } from "./ComponentManager"
5250
import { Task, TaskOptions } from "../task/Task"
5351
import { getNonce } from "./getNonce"
5452
import { getUri } from "./getUri"
@@ -82,17 +80,24 @@ export class ClineProvider
8280
private view?: vscode.WebviewView | vscode.WebviewPanel
8381
private clineStack: Task[] = []
8482
private codeIndexStatusSubscription?: vscode.Disposable
85-
private _workspaceTracker?: WorkspaceTracker // workSpaceTracker read-only for access outside this class
86-
public get workspaceTracker(): WorkspaceTracker | undefined {
87-
return this._workspaceTracker
83+
private componentManager: ComponentManager
84+
85+
public get workspaceTracker() {
86+
return this.componentManager.workspaceTracker
87+
}
88+
89+
private get mcpHub() {
90+
return this.componentManager.mcpHub
91+
}
92+
93+
public get customModesManager() {
94+
return this.componentManager.customModesManager
8895
}
89-
protected mcpHub?: McpHub // Change from private to protected
9096

9197
public isViewLaunched = false
9298
public settingsImportedAt?: number
9399
public readonly latestAnnouncementId = "may-21-2025-3-18" // Update for v3.18.0 announcement
94100
public readonly providerSettingsManager: ProviderSettingsManager
95-
public readonly customModesManager: CustomModesManager
96101

97102
constructor(
98103
readonly context: vscode.ExtensionContext,
@@ -116,23 +121,10 @@ export class ClineProvider
116121
// properties like mode and provider.
117122
telemetryService.setProvider(this)
118123

119-
this._workspaceTracker = new WorkspaceTracker(this)
120-
121124
this.providerSettingsManager = new ProviderSettingsManager(this.context)
122125

123-
this.customModesManager = new CustomModesManager(this.context, async () => {
124-
await this.postStateToWebview()
125-
})
126-
127-
// Initialize MCP Hub through the singleton manager
128-
McpServerManager.getInstance(this.context, this)
129-
.then((hub) => {
130-
this.mcpHub = hub
131-
this.mcpHub.registerClient()
132-
})
133-
.catch((error) => {
134-
this.log(`Failed to initialize MCP Hub: ${error}`)
135-
})
126+
// Initialize ComponentManager to handle workspaceTracker, mcpHub, and customModesManager
127+
this.componentManager = new ComponentManager(this.context, this)
136128
}
137129

138130
// Adds a new Cline instance to clineStack, marking the start of a new task.
@@ -217,8 +209,6 @@ export class ClineProvider
217209
*/
218210
async dispose() {
219211
this.log("Disposing ClineProvider...")
220-
await this.removeClineFromStack()
221-
this.log("Cleared task")
222212

223213
if (this.view && "dispose" in this.view) {
224214
this.view.dispose()
@@ -233,16 +223,17 @@ export class ClineProvider
233223
}
234224
}
235225

236-
this._workspaceTracker?.dispose()
237-
this._workspaceTracker = undefined
238-
await this.mcpHub?.unregisterClient()
239-
this.mcpHub = undefined
240-
this.customModesManager?.dispose()
226+
await this.componentManager.dispose()
227+
228+
await this.removeClineFromStack()
229+
this.log("Cleared task")
230+
241231
this.log("Disposed all disposables")
242-
ClineProvider.activeInstances.delete(this)
243232

244-
// Unregister from McpServerManager
245-
McpServerManager.unregisterProvider(this)
233+
if (this.componentManager.isDisposed) {
234+
// As resolveWebviewView is not executed during dispose, the instance can be safely removed.
235+
ClineProvider.activeInstances.delete(this)
236+
}
246237
}
247238

248239
public static getVisibleInstance(): ClineProvider | undefined {
@@ -336,6 +327,11 @@ export class ClineProvider
336327
async resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel) {
337328
this.log("Resolving webview view")
338329

330+
ClineProvider.activeInstances.add(this)
331+
if (this.componentManager.isDisposed) {
332+
this.componentManager = new ComponentManager(this.context, this)
333+
}
334+
339335
this.view = webviewView
340336

341337
// Set panel reference according to webview type
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import * as vscode from "vscode"
2+
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
3+
import { McpHub } from "../../services/mcp/McpHub"
4+
import { McpServerManager } from "../../services/mcp/McpServerManager"
5+
import { CustomModesManager } from "../config/CustomModesManager"
6+
import { ClineProvider } from "./ClineProvider"
7+
8+
/**
9+
* Manages the lifecycle and access to core components used by ClineProvider:
10+
* - WorkspaceTracker: Tracks workspace file changes and open tabs
11+
* - McpHub: Manages MCP server connections and communication
12+
* - CustomModesManager: Handles custom mode configurations
13+
*/
14+
export class ComponentManager {
15+
private _workspaceTracker?: WorkspaceTracker
16+
private _mcpHub?: McpHub
17+
private _customModesManager: CustomModesManager
18+
private _isDisposed = false
19+
20+
constructor(
21+
private readonly context: vscode.ExtensionContext,
22+
private readonly provider: ClineProvider,
23+
) {
24+
// Initialize WorkspaceTracker
25+
this._workspaceTracker = new WorkspaceTracker(this.provider)
26+
27+
// Initialize CustomModesManager (always available)
28+
this._customModesManager = new CustomModesManager(this.context, async () => {
29+
await this.provider.postStateToWebview()
30+
})
31+
32+
// Initialize MCP Hub through the singleton manager
33+
this.initializeMcpHub()
34+
}
35+
36+
/**
37+
* Initializes the MCP Hub
38+
*/
39+
private initializeMcpHub(): void {
40+
McpServerManager.getInstance(this.context, this.provider)
41+
.then((hub) => {
42+
this._mcpHub = hub
43+
this._mcpHub.registerClient()
44+
})
45+
.catch((error) => {
46+
console.error(`Failed to initialize MCP Hub: ${error}`)
47+
})
48+
}
49+
50+
/**
51+
* Gets the WorkspaceTracker instance
52+
*/
53+
get workspaceTracker(): WorkspaceTracker | undefined {
54+
return this._workspaceTracker
55+
}
56+
57+
/**
58+
* Gets the McpHub instance
59+
*/
60+
get mcpHub(): McpHub | undefined {
61+
return this._mcpHub
62+
}
63+
64+
/**
65+
* Gets the CustomModesManager instance
66+
*/
67+
get customModesManager(): CustomModesManager {
68+
return this._customModesManager
69+
}
70+
71+
/**
72+
* Checks if the ComponentManager is disposed
73+
*/
74+
get isDisposed(): boolean {
75+
return this._isDisposed
76+
}
77+
78+
/**
79+
* Disposes all managed components and cleans up resources
80+
*/
81+
async dispose(): Promise<void> {
82+
if (this._isDisposed) {
83+
return
84+
}
85+
86+
this._isDisposed = true
87+
88+
// Dispose WorkspaceTracker
89+
if (this._workspaceTracker) {
90+
this._workspaceTracker.dispose()
91+
this._workspaceTracker = undefined
92+
}
93+
94+
// Dispose CustomModesManager
95+
this._customModesManager.dispose()
96+
97+
// Dispose MCP Hub
98+
if (this._mcpHub) {
99+
await this._mcpHub.unregisterClient()
100+
this._mcpHub = undefined
101+
}
102+
103+
// Unregister from McpServerManager
104+
McpServerManager.unregisterProvider(this.provider)
105+
}
106+
}

src/core/webview/__tests__/ClineProvider.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ describe("ClineProvider", () => {
321321
updateGlobalStateSpy = jest.spyOn(provider.contextProxy, "setValue")
322322

323323
// @ts-ignore - Accessing private property for testing.
324-
provider.customModesManager = mockCustomModesManager
324+
provider.componentManager._customModesManager = mockCustomModesManager
325325
})
326326

327327
test("constructor initializes correctly", () => {
@@ -1592,7 +1592,7 @@ describe("ClineProvider", () => {
15921592
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
15931593

15941594
// Mock CustomModesManager methods
1595-
;(provider as any).customModesManager = {
1595+
;(provider as any).componentManager._customModesManager = {
15961596
updateCustomMode: jest.fn().mockResolvedValue(undefined),
15971597
getCustomModes: jest.fn().mockResolvedValue([
15981598
{

0 commit comments

Comments
 (0)