mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Add popout button to open instance in editor
This commit is contained in:
10
package.json
10
package.json
@@ -51,6 +51,11 @@
|
|||||||
"title": "New Task",
|
"title": "New Task",
|
||||||
"icon": "$(add)"
|
"icon": "$(add)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "claude-dev.popoutButtonTapped",
|
||||||
|
"title": "Open in Editor",
|
||||||
|
"icon": "$(link-external)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "claude-dev.settingsButtonTapped",
|
"command": "claude-dev.settingsButtonTapped",
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
@@ -64,6 +69,11 @@
|
|||||||
"group": "navigation",
|
"group": "navigation",
|
||||||
"when": "view == claude-dev.SidebarProvider"
|
"when": "view == claude-dev.SidebarProvider"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "claude-dev.popoutButtonTapped",
|
||||||
|
"group": "navigation",
|
||||||
|
"when": "view == claude-dev.SidebarProvider"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "claude-dev.settingsButtonTapped",
|
"command": "claude-dev.settingsButtonTapped",
|
||||||
"group": "navigation",
|
"group": "navigation",
|
||||||
|
|||||||
@@ -491,6 +491,7 @@ export class ClaudeDev {
|
|||||||
let resultToSend = result
|
let resultToSend = result
|
||||||
if (command) {
|
if (command) {
|
||||||
await this.say("completion_result", resultToSend)
|
await this.say("completion_result", resultToSend)
|
||||||
|
// TODO: currently we don't handle if this command fails, it could be useful to let claude know and retry
|
||||||
await this.executeCommand(command)
|
await this.executeCommand(command)
|
||||||
resultToSend = ""
|
resultToSend = ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
// })
|
// })
|
||||||
// context.subscriptions.push(disposable)
|
// context.subscriptions.push(disposable)
|
||||||
|
|
||||||
const provider = new SidebarProvider(context)
|
const sidebarProvider = new SidebarProvider(context)
|
||||||
|
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.window.registerWebviewViewProvider(SidebarProvider.viewType, provider, {
|
vscode.window.registerWebviewViewProvider(SidebarProvider.viewType, sidebarProvider, {
|
||||||
webviewOptions: { retainContextWhenHidden: true },
|
webviewOptions: { retainContextWhenHidden: true },
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -41,9 +41,32 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.commands.registerCommand("claude-dev.plusButtonTapped", async () => {
|
vscode.commands.registerCommand("claude-dev.plusButtonTapped", async () => {
|
||||||
//const message = "claude-dev.plusButtonTapped!"
|
//const message = "claude-dev.plusButtonTapped!"
|
||||||
//vscode.window.showInformationMessage(message)
|
//vscode.window.showInformationMessage(message)
|
||||||
await provider.clearTask()
|
await sidebarProvider.clearTask()
|
||||||
await provider.postStateToWebview()
|
await sidebarProvider.postStateToWebview()
|
||||||
await provider.postMessageToWebview({ type: "action", action: "plusButtonTapped" })
|
await sidebarProvider.postMessageToWebview({ type: "action", action: "plusButtonTapped" })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand("claude-dev.popoutButtonTapped", async () => {
|
||||||
|
// (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 editorProvider = new SidebarProvider(context)
|
||||||
|
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined
|
||||||
|
const panel = vscode.window.createWebviewPanel(
|
||||||
|
SidebarProvider.viewType,
|
||||||
|
"Claude Dev",
|
||||||
|
column || vscode.ViewColumn.One,
|
||||||
|
{
|
||||||
|
enableScripts: true,
|
||||||
|
retainContextWhenHidden: true,
|
||||||
|
localResourceRoots: [context.extensionUri],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// TODO: use better svg icon with light and dark variants (see https://stackoverflow.com/questions/58365687/vscode-extension-iconpath)
|
||||||
|
panel.iconPath = vscode.Uri.joinPath(context.extensionUri, "icon.png")
|
||||||
|
|
||||||
|
editorProvider.resolveWebviewView(panel)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,7 +74,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.commands.registerCommand("claude-dev.settingsButtonTapped", () => {
|
vscode.commands.registerCommand("claude-dev.settingsButtonTapped", () => {
|
||||||
//const message = "claude-dev.settingsButtonTapped!"
|
//const message = "claude-dev.settingsButtonTapped!"
|
||||||
//vscode.window.showInformationMessage(message)
|
//vscode.window.showInformationMessage(message)
|
||||||
provider.postMessageToWebview({ type: "action", action: "settingsButtonTapped" })
|
sidebarProvider.postMessageToWebview({ type: "action", action: "settingsButtonTapped" })
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as vscode from "vscode"
|
|||||||
import { ClaudeMessage, ExtensionMessage } from "../shared/ExtensionMessage"
|
import { ClaudeMessage, ExtensionMessage } from "../shared/ExtensionMessage"
|
||||||
import { WebviewMessage } from "../shared/WebviewMessage"
|
import { WebviewMessage } from "../shared/WebviewMessage"
|
||||||
import { ClaudeDev } from "../ClaudeDev"
|
import { ClaudeDev } from "../ClaudeDev"
|
||||||
|
import { WebviewPanel, WebviewView } from "vscode"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||||
@@ -18,15 +19,16 @@ type ExtensionWorkspaceStateKey = "claudeMessages"
|
|||||||
export class SidebarProvider implements vscode.WebviewViewProvider {
|
export class SidebarProvider implements vscode.WebviewViewProvider {
|
||||||
public static readonly viewType = "claude-dev.SidebarProvider"
|
public static readonly viewType = "claude-dev.SidebarProvider"
|
||||||
|
|
||||||
private view?: vscode.WebviewView
|
private view?: vscode.WebviewView | vscode.WebviewPanel
|
||||||
private claudeDev?: ClaudeDev
|
private claudeDev?: ClaudeDev
|
||||||
|
private claudeMessagesCache: ClaudeMessage[] = []
|
||||||
|
|
||||||
constructor(private readonly context: vscode.ExtensionContext) {}
|
constructor(private readonly context: vscode.ExtensionContext) {}
|
||||||
|
|
||||||
resolveWebviewView(
|
resolveWebviewView(
|
||||||
webviewView: vscode.WebviewView,
|
webviewView: vscode.WebviewView | vscode.WebviewPanel
|
||||||
context: vscode.WebviewViewResolveContext<unknown>,
|
//context: vscode.WebviewViewResolveContext<unknown>,
|
||||||
token: vscode.CancellationToken
|
//token: vscode.CancellationToken
|
||||||
): void | Thenable<void> {
|
): void | Thenable<void> {
|
||||||
this.view = webviewView
|
this.view = webviewView
|
||||||
|
|
||||||
@@ -46,10 +48,20 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
// Listen for when the panel becomes visible
|
// Listen for when the panel becomes visible
|
||||||
// https://github.com/microsoft/vscode-discussions/discussions/840
|
// https://github.com/microsoft/vscode-discussions/discussions/840
|
||||||
webviewView.onDidChangeVisibility((e: any) => {
|
if ("onDidChangeViewState" in webviewView) {
|
||||||
// we don't get any event back (so can't do e.visible), but this function does get called when the view changes visibility
|
// WebviewView and WebviewPanel have all the same properties except for this visibility listener
|
||||||
this.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
|
webviewView.onDidChangeViewState(() => {
|
||||||
})
|
if (this.view?.visible) {
|
||||||
|
this.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if ("onDidChangeVisibility" in webviewView) {
|
||||||
|
webviewView.onDidChangeVisibility(() => {
|
||||||
|
if (this.view?.visible) {
|
||||||
|
this.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for when color changes
|
// Listen for when color changes
|
||||||
vscode.workspace.onDidChangeConfiguration((e) => {
|
vscode.workspace.onDidChangeConfiguration((e) => {
|
||||||
@@ -61,6 +73,9 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
// if the extension is starting a new session, clear previous task state
|
// if the extension is starting a new session, clear previous task state
|
||||||
this.clearTask()
|
this.clearTask()
|
||||||
|
|
||||||
|
// Clear previous version's (0.0.6) claudeMessage cache. Now that we use retainContextWhenHidden, we don't need to cache them in user's state and can just store locally in this instance.
|
||||||
|
this.updateWorkspaceState("claudeMessages", undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryToInitClaudeDevWithTask(task: string) {
|
async tryToInitClaudeDevWithTask(task: string) {
|
||||||
@@ -243,13 +258,33 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
// client messages
|
// client messages
|
||||||
|
|
||||||
|
/*
|
||||||
|
Now that we use retainContextWhenHidden, we don't have to store a cache of claude messages in the user's workspace state. Instead, we can just use this provider instance to keep track of the messages. However in the future when we implement Task history
|
||||||
|
we will need to store the messages and conversation history in the workspace state.
|
||||||
|
|
||||||
|
- We have to be careful of what state is shared between SidebarProvider 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.
|
||||||
|
- For the interim we'll use a local variable to cache the claude messages that lives as long as the SidebarProvider (so a property of this class), but in the future we'll implement a more robust solution that uses workspace state so that the user can look at task history and pick up on old conversations.
|
||||||
|
|
||||||
|
In the future we'll cache these messages in the workspace state alongside the conversation history in order to reduce memory footprint in long conversations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// We need to use a unique identifier for each SidebarProvider instance's message cache since we could be running several instances of the extension outside of just the sidebar i.e. in editor panels.
|
||||||
|
// private startTsIdentifier = Date.now()
|
||||||
|
|
||||||
|
// getClaudeMessagesWorkspaceStateKey() {
|
||||||
|
// return `claudeMessages-${this.startTsIdentifier}`
|
||||||
|
// }
|
||||||
|
|
||||||
async getClaudeMessages(): Promise<ClaudeMessage[]> {
|
async getClaudeMessages(): Promise<ClaudeMessage[]> {
|
||||||
const messages = (await this.getWorkspaceState("claudeMessages")) as ClaudeMessage[]
|
// const messages = (await this.getWorkspaceState(this.getClaudeMessagesWorkspaceStateKey())) as ClaudeMessage[]
|
||||||
return messages || []
|
// return messages || []
|
||||||
|
return this.claudeMessagesCache
|
||||||
}
|
}
|
||||||
|
|
||||||
async setClaudeMessages(messages: ClaudeMessage[] | undefined) {
|
async setClaudeMessages(messages: ClaudeMessage[] | undefined) {
|
||||||
await this.updateWorkspaceState("claudeMessages", messages)
|
//await this.updateWorkspaceState(this.getClaudeMessagesWorkspaceStateKey(), messages)
|
||||||
|
this.claudeMessagesCache = messages || []
|
||||||
}
|
}
|
||||||
|
|
||||||
async addClaudeMessage(message: ClaudeMessage): Promise<ClaudeMessage[]> {
|
async addClaudeMessage(message: ClaudeMessage): Promise<ClaudeMessage[]> {
|
||||||
@@ -303,6 +338,12 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
return await this.context.workspaceState.get(key)
|
return await this.context.workspaceState.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async clearAllWorkspaceState() {
|
||||||
|
this.context.workspaceState.keys().forEach((key) => {
|
||||||
|
this.context.workspaceState.update(key, undefined)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// secrets
|
// secrets
|
||||||
|
|
||||||
private async storeSecret(key: ExtensionSecretKey, value: any) {
|
private async storeSecret(key: ExtensionSecretKey, value: any) {
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ const CodeBlock = ({ code, diff, language, path, syntaxHighlighterStyle }: CodeB
|
|||||||
borderRadius: "3px",
|
borderRadius: "3px",
|
||||||
backgroundColor: "var(--vscode-editor-background)",
|
backgroundColor: "var(--vscode-editor-background)",
|
||||||
overflow: "hidden", // This ensures the inner scrollable area doesn't overflow the rounded corners
|
overflow: "hidden", // This ensures the inner scrollable area doesn't overflow the rounded corners
|
||||||
|
border: "1px solid var(--vscode-sideBar-border)",
|
||||||
}}>
|
}}>
|
||||||
{path && (
|
{path && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPer
|
|||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
placeholder="Enter your Anthropic API Key"
|
placeholder="Enter your Anthropic API Key"
|
||||||
onInput={handleApiKeyChange}>
|
onInput={handleApiKeyChange}>
|
||||||
Anthropic API Key
|
<span style={{ fontWeight: "500" }}>Anthropic API Key</span>
|
||||||
</VSCodeTextField>
|
</VSCodeTextField>
|
||||||
{apiKeyErrorMessage && (
|
{apiKeyErrorMessage && (
|
||||||
<p
|
<p
|
||||||
@@ -127,7 +127,7 @@ const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPer
|
|||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
placeholder="20"
|
placeholder="20"
|
||||||
onInput={handleMaxRequestsChange}>
|
onInput={handleMaxRequestsChange}>
|
||||||
Maximum # Requests Per Task
|
<span style={{ fontWeight: "500" }}>Maximum # Requests Per Task</span>
|
||||||
</VSCodeTextField>
|
</VSCodeTextField>
|
||||||
{maxRequestsErrorMessage && (
|
{maxRequestsErrorMessage && (
|
||||||
<p
|
<p
|
||||||
|
|||||||
Reference in New Issue
Block a user