From da3aa7a6580cf593cecfa2e179671e248e208244 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sat, 5 Oct 2024 23:16:25 -0400 Subject: [PATCH] Refactor ClineProvider --- src/core/ClaudeDev.ts | 6 +++--- src/core/webview/ClaudeDevProvider.ts | 18 +++++++++--------- src/exports/index.ts | 7 ++----- src/extension.ts | 12 ++++++------ src/integrations/workspace/WorkspaceTracker.ts | 6 +++--- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/core/ClaudeDev.ts b/src/core/ClaudeDev.ts index 8547a2c..b350560 100644 --- a/src/core/ClaudeDev.ts +++ b/src/core/ClaudeDev.ts @@ -41,7 +41,7 @@ import { parseAssistantMessage } from "./prompts/parse-assistant-message" import { formatResponse } from "./prompts/responses" import { addCustomInstructions, SYSTEM_PROMPT } from "./prompts/system" import { truncateHalfConversation } from "./sliding-window" -import { ClaudeDevProvider, GlobalFileNames } from "./webview/ClaudeDevProvider" +import { ClineProvider, GlobalFileNames } from "./webview/ClaudeDevProvider" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -66,7 +66,7 @@ export class ClaudeDev { private askResponseImages?: string[] private lastMessageTs?: number private consecutiveMistakeCount: number = 0 - private providerRef: WeakRef + private providerRef: WeakRef private abort: boolean = false didFinishAborting = false private diffViewProvider: DiffViewProvider @@ -82,7 +82,7 @@ export class ClaudeDev { private didCompleteReadingStream = false constructor( - provider: ClaudeDevProvider, + provider: ClineProvider, apiConfiguration: ApiConfiguration, customInstructions?: string, alwaysAllowReadOnly?: boolean, diff --git a/src/core/webview/ClaudeDevProvider.ts b/src/core/webview/ClaudeDevProvider.ts index b5d1489..e379b60 100644 --- a/src/core/webview/ClaudeDevProvider.ts +++ b/src/core/webview/ClaudeDevProvider.ts @@ -61,10 +61,10 @@ export const GlobalFileNames = { openRouterModels: "openrouter_models.json", } -export class ClaudeDevProvider implements vscode.WebviewViewProvider { +export class ClineProvider implements vscode.WebviewViewProvider { public static readonly sideBarId = "claude-dev.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension. public static readonly tabPanelId = "claude-dev.TabPanelProvider" - private static activeInstances: Set = new Set() + private static activeInstances: Set = new Set() private disposables: vscode.Disposable[] = [] private view?: vscode.WebviewView | vscode.WebviewPanel private claudeDev?: ClaudeDev @@ -72,8 +72,8 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider { private latestAnnouncementId = "sep-21-2024" // update to some unique identifier when we add a new announcement constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) { - this.outputChannel.appendLine("ClaudeDevProvider instantiated") - ClaudeDevProvider.activeInstances.add(this) + this.outputChannel.appendLine("ClineProvider instantiated") + ClineProvider.activeInstances.add(this) this.workspaceTracker = new WorkspaceTracker(this) this.revertKodu() } @@ -105,7 +105,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider { - https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts */ async dispose() { - this.outputChannel.appendLine("Disposing ClaudeDevProvider...") + this.outputChannel.appendLine("Disposing ClineProvider...") await this.clearTask() this.outputChannel.appendLine("Cleared task") if (this.view && "dispose" in this.view) { @@ -121,10 +121,10 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider { this.workspaceTracker?.dispose() this.workspaceTracker = undefined this.outputChannel.appendLine("Disposed all disposables") - ClaudeDevProvider.activeInstances.delete(this) + ClineProvider.activeInstances.delete(this) } - public static getVisibleInstance(): ClaudeDevProvider | undefined { + public static getVisibleInstance(): ClineProvider | undefined { return findLast(Array.from(this.activeInstances), (instance) => instance.view?.visible === true) } @@ -752,10 +752,10 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider { /* Now that we use retainContextWhenHidden, we don't have to store a cache of claude messages in the user's state, but we could to reduce memory footprint in long conversations. - - We have to be careful of what state is shared between ClaudeDevProvider instances since there could be multiple instances of the extension running at once. For example when we cached claude messages using the same key, two instances of the extension could end up using the same key and overwriting each other's messages. + - We have to be careful of what state is shared between ClineProvider instances since there could be multiple instances of the extension running at once. For example when we cached claude messages using the same key, two instances of the extension could end up using the same key and overwriting each other's messages. - Some state does need to be shared between the instances, i.e. the API key--however there doesn't seem to be a good way to notfy the other instances that the API key has changed. - We need to use a unique identifier for each ClaudeDevProvider instance's message cache since we could be running several instances of the extension outside of just the sidebar i.e. in editor panels. + We need to use a unique identifier for each ClineProvider instance's message cache since we could be running several instances of the extension outside of just the sidebar i.e. in editor panels. For now since we don't need to store task history, we'll just use an identifier unique to this provider instance (since there can be several provider instances open at once). However in the future when we implement task history, we'll need to use a unique identifier for each task. As well as manage a data structure that keeps track of task history with their associated identifiers and the task message itself, to present in a 'Task History' view. diff --git a/src/exports/index.ts b/src/exports/index.ts index 5ba0ea0..52859f3 100644 --- a/src/exports/index.ts +++ b/src/exports/index.ts @@ -1,11 +1,8 @@ import * as vscode from "vscode" -import { ClaudeDevProvider } from "../core/webview/ClaudeDevProvider" +import { ClineProvider } from "../core/webview/ClaudeDevProvider" import { ClaudeDevAPI } from "./claude-dev" -export function createClaudeDevAPI( - outputChannel: vscode.OutputChannel, - sidebarProvider: ClaudeDevProvider -): ClaudeDevAPI { +export function createClaudeDevAPI(outputChannel: vscode.OutputChannel, sidebarProvider: ClineProvider): ClaudeDevAPI { const api: ClaudeDevAPI = { setCustomInstructions: async (value: string) => { await sidebarProvider.updateCustomInstructions(value) diff --git a/src/extension.ts b/src/extension.ts index 6d7cca9..dd46dbb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,7 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below import * as vscode from "vscode" -import { ClaudeDevProvider } from "./core/webview/ClaudeDevProvider" +import { ClineProvider } from "./core/webview/ClaudeDevProvider" import delay from "delay" import { createClaudeDevAPI } from "./exports" import "./utils/path" // necessary to have access to String.prototype.toPosix @@ -39,10 +39,10 @@ export function activate(context: vscode.ExtensionContext) { // }) // context.subscriptions.push(disposable) - const sidebarProvider = new ClaudeDevProvider(context, outputChannel) + const sidebarProvider = new ClineProvider(context, outputChannel) context.subscriptions.push( - vscode.window.registerWebviewViewProvider(ClaudeDevProvider.sideBarId, sidebarProvider, { + vscode.window.registerWebviewViewProvider(ClineProvider.sideBarId, sidebarProvider, { webviewOptions: { retainContextWhenHidden: true }, }) ) @@ -60,7 +60,7 @@ export function activate(context: vscode.ExtensionContext) { outputChannel.appendLine("Opening Claude Dev in new tab") // (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event) // https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts - const tabProvider = new ClaudeDevProvider(context, outputChannel) + const tabProvider = new ClineProvider(context, outputChannel) //const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0)) @@ -71,7 +71,7 @@ export function activate(context: vscode.ExtensionContext) { } const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two - const panel = vscode.window.createWebviewPanel(ClaudeDevProvider.tabPanelId, "Claude Dev", targetCol, { + const panel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Claude Dev", targetCol, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [context.extensionUri], @@ -126,7 +126,7 @@ export function activate(context: vscode.ExtensionContext) { const handleUri = async (uri: vscode.Uri) => { const path = uri.path const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B")) - const visibleProvider = ClaudeDevProvider.getVisibleInstance() + const visibleProvider = ClineProvider.getVisibleInstance() if (!visibleProvider) { return } diff --git a/src/integrations/workspace/WorkspaceTracker.ts b/src/integrations/workspace/WorkspaceTracker.ts index fc60182..6999739 100644 --- a/src/integrations/workspace/WorkspaceTracker.ts +++ b/src/integrations/workspace/WorkspaceTracker.ts @@ -1,17 +1,17 @@ import * as vscode from "vscode" import * as path from "path" import { listFiles } from "../../services/glob/list-files" -import { ClaudeDevProvider } from "../../core/webview/ClaudeDevProvider" +import { ClineProvider } from "../../core/webview/ClaudeDevProvider" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) // Note: this is not a drop-in replacement for listFiles at the start of tasks, since that will be done for Desktops when there is no workspace selected class WorkspaceTracker { - private providerRef: WeakRef + private providerRef: WeakRef private disposables: vscode.Disposable[] = [] private filePaths: Set = new Set() - constructor(provider: ClaudeDevProvider) { + constructor(provider: ClineProvider) { this.providerRef = new WeakRef(provider) this.registerListeners() }