Skip to content

Commit 2ae821e

Browse files
committed
"refactor: implement dependency injection with tsyringe and restructure core components"
1 parent bbc38c6 commit 2ae821e

22 files changed

+1265
-678
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
"dist": true // set this to false to include "dist" folder in search results
1010
},
1111
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
12-
"typescript.tsc.autoDetect": "off"
12+
"typescript.tsc.autoDetect": "off",
13+
"python.languageServer": "None"
1314
}

src/activate/registerCommands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const registerCommands = (options: RegisterCommandOptions): void => {
6161
const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions) => {
6262
return {
6363
"roo-cline.plusButtonClicked": async () => {
64-
await provider.removeClineFromStack()
64+
await provider.clineStackManager.removeClineFromStack()
6565
await provider.postStateToWebview()
6666
await provider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
6767
},

src/container.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import "reflect-metadata"
2+
import { container } from "tsyringe"
3+
import { ExtensionContext } from "vscode"
4+
import { ContextHolder, IExtensionContext } from "./core/contextHolder"
5+
6+
// Register VS Code-specific dependencies
7+
export function configureContainer(context: ExtensionContext) {
8+
container.register(ExtensionContext, { useValue: context })
9+
container.registerSingleton(ContextHolder, ContextHolder)
10+
11+
return container
12+
}
13+
//todo: remove this so that we can build it properly after building the singletons

src/core/config/CustomModesManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { logger } from "../../utils/logging"
99
import { GlobalFileNames } from "../../shared/globalFileNames"
1010

1111
const ROOMODES_FILENAME = ".roomodes"
12-
12+
// TODO: Decouple this code
1313
export class CustomModesManager {
1414
private disposables: vscode.Disposable[] = []
1515
private isWriting = false

src/core/contextHolder.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import * as vscode from "vscode"
2+
/**
3+
* Interface for the vscode.ExtensionContext
4+
*/
5+
export interface IExtensionContext {
6+
/**
7+
* The absolute file path of the extension's directory.
8+
*/
9+
readonly extensionPath: string
10+
/**
11+
* The absolute file path of the extension's folder.
12+
*/
13+
readonly extensionUri: vscode.Uri
14+
/**
15+
* The storage path for the extension.
16+
*/
17+
readonly storagePath?: string | undefined
18+
/**
19+
* The global state of the extension.
20+
*/
21+
readonly globalState: vscode.Memento
22+
/**
23+
* The workspace state of the extension.
24+
*/
25+
readonly workspaceState: vscode.Memento
26+
/**
27+
* Logs a message at the 'log' level.
28+
* @param message The message to log.
29+
*/
30+
log(message: string): void
31+
/**
32+
* Logs an error message at the 'error' level.
33+
* @param message The message to log.
34+
*/
35+
error(message: string): void
36+
/**
37+
* Logs a warning message at the 'warn' level.
38+
* @param message The message to log.
39+
*/
40+
warn(message: string): void
41+
/**
42+
* Subscribe to a specific event.
43+
* @param event The event to subscribe to.
44+
* @param listener The callback function to be called when the event occurs.
45+
*/
46+
subscribe<T>(event: string, listener: (data: T) => void): vscode.Disposable
47+
}
48+
49+
export interface IContextHolder {
50+
getContext(): vscode.ExtensionContext
51+
}
52+
53+
/**
54+
* This class is a singleton that holds the vscode.ExtensionContext.
55+
* It can be obtained with ContextHolder.getInstance(context)
56+
*
57+
* Example of usage:
58+
* const contextHolder = ContextHolder.getInstance(context)
59+
* const extensionContext = contextHolder.getContext()
60+
*
61+
* OR
62+
* const contextHolder = ContextHolder.getInstanceWithoutArgs()
63+
* const extensionContext = contextHolder.getContext()
64+
*/
65+
export class ContextHolder implements IContextHolder {
66+
/**
67+
* A singleton class that holds the vscode.ExtensionContext.
68+
* It can be obtained with ContextHolder.getInstance(context)
69+
*
70+
* Example of usage:
71+
* const contextHolder = ContextHolder.getInstance(context)
72+
* const extensionContext = contextHolder.getContext()
73+
*/
74+
private static instance: ContextHolder | undefined
75+
private constructor(private readonly context: vscode.ExtensionContext) {}
76+
77+
/**
78+
* Gets the ContextHolder instance.
79+
* @param context The vscode.ExtensionContext.
80+
* @returns The ContextHolder instance.
81+
*/
82+
public static getInstance(context: vscode.ExtensionContext): ContextHolder {
83+
if (!ContextHolder.instance) {
84+
ContextHolder.instance = new ContextHolder(context)
85+
}
86+
return ContextHolder.instance
87+
}
88+
89+
/**
90+
* Gets the ContextHolder instance without passing the context.
91+
* @returns The ContextHolder instance.
92+
* @throws {Error} If the ContextHolder has not been initialized. Call getInstance() first.
93+
*/
94+
public static getInstanceWithoutArgs(): ContextHolder {
95+
if (!ContextHolder.instance) {
96+
throw new Error("ContextHolder has not been initialized. Call getInstance() first.")
97+
}
98+
return ContextHolder.instance
99+
}
100+
101+
/**
102+
* Gets the vscode.ExtensionContext.
103+
* @returns The vscode.ExtensionContext.
104+
*/
105+
public getContext(): vscode.ExtensionContext {
106+
return this.context
107+
}
108+
}

src/core/contextProxy.ts

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,63 @@ import {
1313
isPassThroughStateKey,
1414
} from "../shared/globalState"
1515
import { API_CONFIG_KEYS, ApiConfiguration } from "../shared/api"
16+
import { ContextHolder } from "./contextHolder"
17+
18+
/*
19+
* This class provides a proxy for the vscode.ExtensionContext.
20+
* It caches global state and secrets in memory for faster access.
21+
* It also provides a method to reset all state and secrets.
22+
*/
23+
24+
export interface ContextProxyInstance {
25+
get isInitialized(): boolean
26+
get extensionUri(): vscode.Uri
27+
get extensionPath(): string
28+
get globalStorageUri(): vscode.Uri
29+
get logUri(): vscode.Uri
30+
get extension(): vscode.Extension<any>
31+
get extensionMode(): vscode.ExtensionMode
32+
getGlobalState<T>(key: GlobalStateKey): T | undefined
33+
getGlobalState<T>(key: GlobalStateKey, defaultValue: T): T
34+
getGlobalState<T>(key: GlobalStateKey, defaultValue?: T): T | undefined
35+
updateGlobalState<T>(key: GlobalStateKey, value: T): any
36+
getSecret(key: SecretKey): string | undefined
37+
storeSecret(key: SecretKey, value?: string): Thenable<void>
38+
setValue(key: ConfigurationKey, value: any): Thenable<void>
39+
setValues(values: Partial<ConfigurationValues>): Thenable<void>
40+
setApiConfiguration(apiConfiguration: ApiConfiguration): Thenable<void>
41+
resetAllState(): Thenable<void>
42+
}
43+
44+
export class ContextProxy implements ContextProxyInstance {
45+
private static instance: ContextProxy
1646

17-
export class ContextProxy {
1847
private readonly originalContext: vscode.ExtensionContext
1948

2049
private stateCache: Map<GlobalStateKey, any>
2150
private secretCache: Map<SecretKey, string | undefined>
2251
private _isInitialized = false
2352

24-
constructor(context: vscode.ExtensionContext) {
25-
this.originalContext = context
53+
private constructor() {
54+
this.originalContext = ContextHolder.getInstanceWithoutArgs().getContext()
2655
this.stateCache = new Map()
2756
this.secretCache = new Map()
28-
this._isInitialized = false
57+
this.initialize() // Initialize immediately
2958
}
3059

31-
public get isInitialized() {
32-
return this._isInitialized
60+
public static getInstance(): ContextProxy {
61+
if (!ContextProxy.instance) {
62+
ContextProxy.instance = new ContextProxy()
63+
}
64+
return ContextProxy.instance
3365
}
3466

35-
public async initialize() {
67+
/**
68+
* Initialize the context proxy by loading global state and secrets.
69+
* This method is called automatically when the instance is created.
70+
*/
71+
private async initialize() {
72+
if (this._isInitialized) return
3673
for (const key of GLOBAL_STATE_KEYS) {
3774
try {
3875
this.stateCache.set(key, this.originalContext.globalState.get(key))
@@ -54,6 +91,10 @@ export class ContextProxy {
5491
this._isInitialized = true
5592
}
5693

94+
public get isInitialized() {
95+
return this._isInitialized
96+
}
97+
5798
get extensionUri() {
5899
return this.originalContext.extensionUri
59100
}
@@ -97,11 +138,23 @@ export class ContextProxy {
97138
return this.originalContext.globalState.update(key, value)
98139
}
99140

141+
/**
142+
* Retrieve a secret from the cache.
143+
* @param key The secret key to retrieve
144+
* @returns The cached secret value or undefined if not found
145+
*/
100146
getSecret(key: SecretKey) {
101147
return this.secretCache.get(key)
102148
}
103149

104-
storeSecret(key: SecretKey, value?: string) {
150+
/**
151+
* Store a secret in the cache and write it to the context.
152+
* If the value is undefined, the secret will be deleted from the context.
153+
* @param key The secret key to store
154+
* @param value The secret value to store
155+
* @returns A promise that resolves when the operation completes
156+
*/
157+
storeSecret(key: SecretKey, value?: string): Thenable<void> {
105158
// Update cache.
106159
this.secretCache.set(key, value)
107160

@@ -119,7 +172,7 @@ export class ContextProxy {
119172
* @param value The value to set
120173
* @returns A promise that resolves when the operation completes
121174
*/
122-
setValue(key: ConfigurationKey, value: any) {
175+
setValue(key: ConfigurationKey, value: any): Thenable<void> {
123176
if (isSecretKey(key)) {
124177
return this.storeSecret(key, value)
125178
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as vscode from "vscode"
2+
3+
export interface IOutputChannelManager {
4+
/**
5+
* Get the initialized output channel
6+
* @returns vscode.OutputChannel
7+
*/
8+
getOutputChannel(): vscode.OutputChannel
9+
10+
/**
11+
* Append a line to the output channel
12+
* @param message The message to append
13+
*/
14+
appendLine(message: string): void
15+
16+
/**
17+
* Show the output channel
18+
*/
19+
show(): void
20+
21+
/**
22+
* Clear the output channel
23+
*/
24+
clear(): void
25+
}

src/core/outputChannelManager.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as vscode from "vscode"
2+
import { IOutputChannelManager } from "./interfaces/IOutputChannelManager"
3+
4+
export class OutputChannelManager implements IOutputChannelManager {
5+
private static instance: OutputChannelManager
6+
private outputChannel: vscode.OutputChannel
7+
8+
private constructor() {
9+
this.outputChannel = vscode.window.createOutputChannel("Roo-Code")
10+
}
11+
12+
/**
13+
* Get the singleton instance of OutputChannelManager
14+
* @returns OutputChannelManager
15+
*/
16+
public static getInstance(): OutputChannelManager {
17+
if (!OutputChannelManager.instance) {
18+
OutputChannelManager.instance = new OutputChannelManager()
19+
}
20+
return OutputChannelManager.instance
21+
}
22+
23+
/**
24+
* Get the initialized output channel
25+
* @returns vscode.OutputChannel
26+
*/
27+
public getOutputChannel(): vscode.OutputChannel {
28+
return this.outputChannel
29+
}
30+
31+
/**
32+
* Append a line to the output channel
33+
* TODO: Refactor and use this rather than using outputchannel
34+
* @param message The message to append
35+
*/
36+
public appendLine(message: string): void {
37+
this.outputChannel.appendLine(message)
38+
}
39+
40+
/**
41+
* Show the output channel
42+
*/
43+
public show(): void {
44+
this.outputChannel.show()
45+
}
46+
47+
/**
48+
* Clear the output channel
49+
*/
50+
public clear(): void {
51+
this.outputChannel.clear()
52+
}
53+
}

0 commit comments

Comments
 (0)