Merge pull request #702 from RooVetGit/cte/activation-cleanup

This commit is contained in:
Chris Estreich
2025-02-01 08:39:39 -08:00
committed by GitHub
6 changed files with 246 additions and 212 deletions

32
src/activate/handleUri.ts Normal file
View File

@@ -0,0 +1,32 @@
import * as vscode from "vscode"
import { ClineProvider } from "../core/webview/ClineProvider"
export const handleUri = async (uri: vscode.Uri) => {
const path = uri.path
const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
const visibleProvider = ClineProvider.getVisibleInstance()
if (!visibleProvider) {
return
}
switch (path) {
case "/glama": {
const code = query.get("code")
if (code) {
await visibleProvider.handleGlamaCallback(code)
}
break
}
case "/openrouter": {
const code = query.get("code")
if (code) {
await visibleProvider.handleOpenRouterCallback(code)
}
break
}
default:
break
}
}

3
src/activate/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export { handleUri } from "./handleUri"
export { registerCommands } from "./registerCommands"
export { registerCodeActions } from "./registerCodeActions"

View File

@@ -0,0 +1,91 @@
import * as vscode from "vscode"
import { ACTION_NAMES, COMMAND_IDS } from "../core/CodeActionProvider"
import { EditorUtils } from "../core/EditorUtils"
import { ClineProvider } from "../core/webview/ClineProvider"
export const registerCodeActions = (context: vscode.ExtensionContext) => {
registerCodeActionPair(
context,
COMMAND_IDS.EXPLAIN,
"EXPLAIN",
"What would you like Roo to explain?",
"E.g. How does the error handling work?",
)
registerCodeActionPair(
context,
COMMAND_IDS.FIX,
"FIX",
"What would you like Roo to fix?",
"E.g. Maintain backward compatibility",
)
registerCodeActionPair(
context,
COMMAND_IDS.IMPROVE,
"IMPROVE",
"What would you like Roo to improve?",
"E.g. Focus on performance optimization",
)
registerCodeAction(context, COMMAND_IDS.ADD_TO_CONTEXT, "ADD_TO_CONTEXT")
}
const registerCodeAction = (
context: vscode.ExtensionContext,
command: string,
promptType: keyof typeof ACTION_NAMES,
inputPrompt?: string,
inputPlaceholder?: string,
) => {
let userInput: string | undefined
context.subscriptions.push(
vscode.commands.registerCommand(command, async (...args: any[]) => {
if (inputPrompt) {
userInput = await vscode.window.showInputBox({
prompt: inputPrompt,
placeHolder: inputPlaceholder,
})
}
// Handle both code action and direct command cases.
let filePath: string
let selectedText: string
let diagnostics: any[] | undefined
if (args.length > 1) {
// Called from code action.
;[filePath, selectedText, diagnostics] = args
} else {
// Called directly from command palette.
const context = EditorUtils.getEditorContext()
if (!context) return
;({ filePath, selectedText, diagnostics } = context)
}
const params = {
...{ filePath, selectedText },
...(diagnostics ? { diagnostics } : {}),
...(userInput ? { userInput } : {}),
}
await ClineProvider.handleCodeAction(command, promptType, params)
}),
)
}
const registerCodeActionPair = (
context: vscode.ExtensionContext,
baseCommand: string,
promptType: keyof typeof ACTION_NAMES,
inputPrompt?: string,
inputPlaceholder?: string,
) => {
// Register new task version.
registerCodeAction(context, baseCommand, promptType, inputPrompt, inputPlaceholder)
// Register current task version.
registerCodeAction(context, `${baseCommand}InCurrentTask`, promptType, inputPrompt, inputPlaceholder)
}

View File

@@ -0,0 +1,83 @@
import * as vscode from "vscode"
import delay from "delay"
import { ClineProvider } from "../core/webview/ClineProvider"
export type RegisterCommandOptions = {
context: vscode.ExtensionContext
outputChannel: vscode.OutputChannel
provider: ClineProvider
}
export const registerCommands = (options: RegisterCommandOptions) => {
const { context, outputChannel } = options
for (const [command, callback] of Object.entries(getCommandsMap(options))) {
context.subscriptions.push(vscode.commands.registerCommand(command, callback))
}
}
const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions) => {
return {
"roo-cline.plusButtonClicked": async () => {
await provider.clearTask()
await provider.postStateToWebview()
await provider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
},
"roo-cline.mcpButtonClicked": () => {
provider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" })
},
"roo-cline.promptsButtonClicked": () => {
provider.postMessageToWebview({ type: "action", action: "promptsButtonClicked" })
},
"roo-cline.popoutButtonClicked": () => openClineInNewTab({ context, outputChannel }),
"roo-cline.openInNewTab": () => openClineInNewTab({ context, outputChannel }),
"roo-cline.settingsButtonClicked": () => {
provider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
},
"roo-cline.historyButtonClicked": () => {
provider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
},
}
}
const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {
outputChannel.appendLine("Opening Roo Code 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 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))
// Check if there are any visible text editors, otherwise open a new group
// to the right.
const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0
if (!hasVisibleEditors) {
await vscode.commands.executeCommand("workbench.action.newGroupRight")
}
const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
const panel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, {
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 = {
light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
}
tabProvider.resolveWebviewView(panel)
// Lock the editor group so clicking on files doesn't open them over the panel
await delay(100)
await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
}

View File

@@ -9,7 +9,7 @@ export const ACTION_NAMES = {
ADD_TO_CONTEXT: "Roo Code: Add to Context", ADD_TO_CONTEXT: "Roo Code: Add to Context",
} as const } as const
const COMMAND_IDS = { export const COMMAND_IDS = {
EXPLAIN: "roo-cline.explainCode", EXPLAIN: "roo-cline.explainCode",
FIX: "roo-cline.fixCode", FIX: "roo-cline.fixCode",
IMPROVE: "roo-cline.improveCode", IMPROVE: "roo-cline.improveCode",

View File

@@ -1,37 +1,33 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import delay from "delay"
import * as vscode from "vscode" import * as vscode from "vscode"
import { ClineProvider } from "./core/webview/ClineProvider" import { ClineProvider } from "./core/webview/ClineProvider"
import { createClineAPI } from "./exports" import { createClineAPI } from "./exports"
import "./utils/path" // necessary to have access to String.prototype.toPosix import "./utils/path" // Necessary to have access to String.prototype.toPosix.
import { ACTION_NAMES, CodeActionProvider } from "./core/CodeActionProvider" import { CodeActionProvider } from "./core/CodeActionProvider"
import { EditorUtils } from "./core/EditorUtils"
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider" import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
import { handleUri, registerCommands, registerCodeActions } from "./activate"
/* /**
Built using https://github.com/microsoft/vscode-webview-ui-toolkit * Built using https://github.com/microsoft/vscode-webview-ui-toolkit
*
Inspired by * Inspired by:
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/default/weather-webview * - https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/default/weather-webview
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/frameworks/hello-world-react-cra * - https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/frameworks/hello-world-react-cra
*/
*/
let outputChannel: vscode.OutputChannel let outputChannel: vscode.OutputChannel
// This method is called when your extension is activated // This method is called when your extension is activated.
// Your extension is activated the very first time the command is executed // Your extension is activated the very first time the command is executed.
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
outputChannel = vscode.window.createOutputChannel("Roo-Code") outputChannel = vscode.window.createOutputChannel("Roo-Code")
context.subscriptions.push(outputChannel) context.subscriptions.push(outputChannel)
outputChannel.appendLine("Roo-Code extension activated") outputChannel.appendLine("Roo-Code extension activated")
// Get default commands from configuration // Get default commands from configuration.
const defaultCommands = vscode.workspace.getConfiguration("roo-cline").get<string[]>("allowedCommands") || [] const defaultCommands = vscode.workspace.getConfiguration("roo-cline").get<string[]>("allowedCommands") || []
// Initialize global state if not already set // Initialize global state if not already set.
if (!context.globalState.get("allowedCommands")) { if (!context.globalState.get("allowedCommands")) {
context.globalState.update("allowedCommands", defaultCommands) context.globalState.update("allowedCommands", defaultCommands)
} }
@@ -44,220 +40,49 @@ export function activate(context: vscode.ExtensionContext) {
}), }),
) )
context.subscriptions.push( registerCommands({ context, outputChannel, provider: sidebarProvider })
vscode.commands.registerCommand("roo-cline.plusButtonClicked", async () => {
outputChannel.appendLine("Plus button Clicked")
await sidebarProvider.clearTask()
await sidebarProvider.postStateToWebview()
await sidebarProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
}),
)
context.subscriptions.push( /**
vscode.commands.registerCommand("roo-cline.mcpButtonClicked", () => { * We use the text document content provider API to show the left side for diff
sidebarProvider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" }) * view by creating a virtual document for the original content. This makes it
}), * readonly so users know to edit the right side if they want to keep their changes.
) *
* This API allows you to create readonly documents in VSCode from arbitrary
context.subscriptions.push( * sources, and works by claiming an uri-scheme for which your provider then
vscode.commands.registerCommand("roo-cline.promptsButtonClicked", () => { * returns text contents. The scheme must be provided when registering a
sidebarProvider.postMessageToWebview({ type: "action", action: "promptsButtonClicked" }) * provider and cannot change afterwards.
}), *
) * Note how the provider doesn't create uris for virtual documents - its role
* is to provide contents given such an uri. In return, content providers are
const openClineInNewTab = async () => { * wired into the open document logic so that providers are always considered.
outputChannel.appendLine("Opening Roo Code 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://code.visualstudio.com/api/extension-guides/virtual-documents
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
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))
// Check if there are any visible text editors, otherwise open a new group to the right
const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0
if (!hasVisibleEditors) {
await vscode.commands.executeCommand("workbench.action.newGroupRight")
}
const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
const panel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, {
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 = {
light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
}
tabProvider.resolveWebviewView(panel)
// Lock the editor group so clicking on files doesn't open them over the panel
await delay(100)
await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
}
context.subscriptions.push(vscode.commands.registerCommand("roo-cline.popoutButtonClicked", openClineInNewTab))
context.subscriptions.push(vscode.commands.registerCommand("roo-cline.openInNewTab", openClineInNewTab))
context.subscriptions.push(
vscode.commands.registerCommand("roo-cline.settingsButtonClicked", () => {
//vscode.window.showInformationMessage(message)
sidebarProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
}),
)
context.subscriptions.push(
vscode.commands.registerCommand("roo-cline.historyButtonClicked", () => {
sidebarProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
}),
)
/*
We use the text document content provider API to show the left side for diff view by creating a virtual document for the original content. This makes it readonly so users know to edit the right side if they want to keep their changes.
- This API allows you to create readonly documents in VSCode from arbitrary sources, and works by claiming an uri-scheme for which your provider then returns text contents. The scheme must be provided when registering a provider and cannot change afterwards.
- Note how the provider doesn't create uris for virtual documents - its role is to provide contents given such an uri. In return, content providers are wired into the open document logic so that providers are always considered.
https://code.visualstudio.com/api/extension-guides/virtual-documents
*/ */
const diffContentProvider = new (class implements vscode.TextDocumentContentProvider { const diffContentProvider = new (class implements vscode.TextDocumentContentProvider {
provideTextDocumentContent(uri: vscode.Uri): string { provideTextDocumentContent(uri: vscode.Uri): string {
return Buffer.from(uri.query, "base64").toString("utf-8") return Buffer.from(uri.query, "base64").toString("utf-8")
} }
})() })()
context.subscriptions.push( context.subscriptions.push(
vscode.workspace.registerTextDocumentContentProvider(DIFF_VIEW_URI_SCHEME, diffContentProvider), vscode.workspace.registerTextDocumentContentProvider(DIFF_VIEW_URI_SCHEME, diffContentProvider),
) )
// URI Handler
const handleUri = async (uri: vscode.Uri) => {
const path = uri.path
const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
const visibleProvider = ClineProvider.getVisibleInstance()
if (!visibleProvider) {
return
}
switch (path) {
case "/glama": {
const code = query.get("code")
if (code) {
await visibleProvider.handleGlamaCallback(code)
}
break
}
case "/openrouter": {
const code = query.get("code")
if (code) {
await visibleProvider.handleOpenRouterCallback(code)
}
break
}
default:
break
}
}
context.subscriptions.push(vscode.window.registerUriHandler({ handleUri })) context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
// Register code actions provider // Register code actions provider.
context.subscriptions.push( context.subscriptions.push(
vscode.languages.registerCodeActionsProvider({ pattern: "**/*" }, new CodeActionProvider(), { vscode.languages.registerCodeActionsProvider({ pattern: "**/*" }, new CodeActionProvider(), {
providedCodeActionKinds: CodeActionProvider.providedCodeActionKinds, providedCodeActionKinds: CodeActionProvider.providedCodeActionKinds,
}), }),
) )
// Helper function to handle code actions registerCodeActions(context)
const registerCodeAction = (
context: vscode.ExtensionContext,
command: string,
promptType: keyof typeof ACTION_NAMES,
inputPrompt?: string,
inputPlaceholder?: string,
) => {
let userInput: string | undefined
context.subscriptions.push(
vscode.commands.registerCommand(command, async (...args: any[]) => {
if (inputPrompt) {
userInput = await vscode.window.showInputBox({
prompt: inputPrompt,
placeHolder: inputPlaceholder,
})
}
// Handle both code action and direct command cases
let filePath: string
let selectedText: string
let diagnostics: any[] | undefined
if (args.length > 1) {
// Called from code action
;[filePath, selectedText, diagnostics] = args
} else {
// Called directly from command palette
const context = EditorUtils.getEditorContext()
if (!context) return
;({ filePath, selectedText, diagnostics } = context)
}
const params = {
...{ filePath, selectedText },
...(diagnostics ? { diagnostics } : {}),
...(userInput ? { userInput } : {}),
}
await ClineProvider.handleCodeAction(command, promptType, params)
}),
)
}
// Helper function to register both versions of a code action
const registerCodeActionPair = (
context: vscode.ExtensionContext,
baseCommand: string,
promptType: keyof typeof ACTION_NAMES,
inputPrompt?: string,
inputPlaceholder?: string,
) => {
// Register new task version
registerCodeAction(context, baseCommand, promptType, inputPrompt, inputPlaceholder)
// Register current task version
registerCodeAction(context, `${baseCommand}InCurrentTask`, promptType, inputPrompt, inputPlaceholder)
}
// Register code action commands
registerCodeActionPair(
context,
"roo-cline.explainCode",
"EXPLAIN",
"What would you like Roo to explain?",
"E.g. How does the error handling work?",
)
registerCodeActionPair(
context,
"roo-cline.fixCode",
"FIX",
"What would you like Roo to fix?",
"E.g. Maintain backward compatibility",
)
registerCodeActionPair(
context,
"roo-cline.improveCode",
"IMPROVE",
"What would you like Roo to improve?",
"E.g. Focus on performance optimization",
)
registerCodeAction(context, "roo-cline.addToContext", "ADD_TO_CONTEXT")
return createClineAPI(outputChannel, sidebarProvider) return createClineAPI(outputChannel, sidebarProvider)
} }
// This method is called when your extension is deactivated // This method is called when your extension is deactivated.
export function deactivate() { export function deactivate() {
outputChannel.appendLine("Roo-Code extension deactivated") outputChannel.appendLine("Roo-Code extension deactivated")
} }