registerCommands.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import * as vscode from "vscode"
  2. import delay from "delay"
  3. import { ClineProvider } from "../core/webview/ClineProvider"
  4. import { ContextProxy } from "../core/config/ContextProxy"
  5. import { telemetryService } from "../services/telemetry/TelemetryService"
  6. import { registerHumanRelayCallback, unregisterHumanRelayCallback, handleHumanRelayResponse } from "./humanRelay"
  7. import { handleNewTask } from "./handleTask"
  8. /**
  9. * Helper to get the visible ClineProvider instance or log if not found.
  10. */
  11. export function getVisibleProviderOrLog(outputChannel: vscode.OutputChannel): ClineProvider | undefined {
  12. const visibleProvider = ClineProvider.getVisibleInstance()
  13. if (!visibleProvider) {
  14. outputChannel.appendLine("Cannot find any visible Roo Code instances.")
  15. return undefined
  16. }
  17. return visibleProvider
  18. }
  19. // Store panel references in both modes
  20. let sidebarPanel: vscode.WebviewView | undefined = undefined
  21. let tabPanel: vscode.WebviewPanel | undefined = undefined
  22. /**
  23. * Get the currently active panel
  24. * @returns WebviewPanel或WebviewView
  25. */
  26. export function getPanel(): vscode.WebviewPanel | vscode.WebviewView | undefined {
  27. return tabPanel || sidebarPanel
  28. }
  29. /**
  30. * Set panel references
  31. */
  32. export function setPanel(
  33. newPanel: vscode.WebviewPanel | vscode.WebviewView | undefined,
  34. type: "sidebar" | "tab",
  35. ): void {
  36. if (type === "sidebar") {
  37. sidebarPanel = newPanel as vscode.WebviewView
  38. tabPanel = undefined
  39. } else {
  40. tabPanel = newPanel as vscode.WebviewPanel
  41. sidebarPanel = undefined
  42. }
  43. }
  44. export type RegisterCommandOptions = {
  45. context: vscode.ExtensionContext
  46. outputChannel: vscode.OutputChannel
  47. provider: ClineProvider
  48. }
  49. export const registerCommands = (options: RegisterCommandOptions) => {
  50. const { context } = options
  51. for (const [command, callback] of Object.entries(getCommandsMap(options))) {
  52. context.subscriptions.push(vscode.commands.registerCommand(command, callback))
  53. }
  54. }
  55. const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions) => {
  56. return {
  57. "roo-cline.activationCompleted": () => {},
  58. "roo-cline.plusButtonClicked": async () => {
  59. const visibleProvider = getVisibleProviderOrLog(outputChannel)
  60. if (!visibleProvider) {
  61. return
  62. }
  63. telemetryService.captureTitleButtonClicked("plus")
  64. await visibleProvider.removeClineFromStack()
  65. await visibleProvider.postStateToWebview()
  66. await visibleProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
  67. },
  68. "roo-cline.mcpButtonClicked": () => {
  69. const visibleProvider = getVisibleProviderOrLog(outputChannel)
  70. if (!visibleProvider) {
  71. return
  72. }
  73. telemetryService.captureTitleButtonClicked("mcp")
  74. visibleProvider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" })
  75. },
  76. "roo-cline.promptsButtonClicked": () => {
  77. const visibleProvider = getVisibleProviderOrLog(outputChannel)
  78. if (!visibleProvider) {
  79. return
  80. }
  81. telemetryService.captureTitleButtonClicked("prompts")
  82. visibleProvider.postMessageToWebview({ type: "action", action: "promptsButtonClicked" })
  83. },
  84. "roo-cline.popoutButtonClicked": () => {
  85. telemetryService.captureTitleButtonClicked("popout")
  86. return openClineInNewTab({ context, outputChannel })
  87. },
  88. "roo-cline.openInNewTab": () => openClineInNewTab({ context, outputChannel }),
  89. "roo-cline.settingsButtonClicked": () => {
  90. const visibleProvider = getVisibleProviderOrLog(outputChannel)
  91. if (!visibleProvider) {
  92. return
  93. }
  94. telemetryService.captureTitleButtonClicked("settings")
  95. visibleProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
  96. // Also explicitly post the visibility message to trigger scroll reliably
  97. visibleProvider.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
  98. },
  99. "roo-cline.historyButtonClicked": () => {
  100. const visibleProvider = getVisibleProviderOrLog(outputChannel)
  101. if (!visibleProvider) {
  102. return
  103. }
  104. telemetryService.captureTitleButtonClicked("history")
  105. visibleProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
  106. },
  107. "roo-cline.showHumanRelayDialog": (params: { requestId: string; promptText: string }) => {
  108. const panel = getPanel()
  109. if (panel) {
  110. panel?.webview.postMessage({
  111. type: "showHumanRelayDialog",
  112. requestId: params.requestId,
  113. promptText: params.promptText,
  114. })
  115. }
  116. },
  117. "roo-cline.registerHumanRelayCallback": registerHumanRelayCallback,
  118. "roo-cline.unregisterHumanRelayCallback": unregisterHumanRelayCallback,
  119. "roo-cline.handleHumanRelayResponse": handleHumanRelayResponse,
  120. "roo-cline.newTask": handleNewTask,
  121. "roo-cline.setCustomStoragePath": async () => {
  122. const { promptForCustomStoragePath } = await import("../shared/storagePathManager")
  123. await promptForCustomStoragePath()
  124. },
  125. "roo-cline.focusInput": async () => {
  126. try {
  127. const panel = getPanel()
  128. if (!panel) {
  129. await vscode.commands.executeCommand("workbench.view.extension.roo-cline-ActivityBar")
  130. } else if (panel === tabPanel) {
  131. panel.reveal(vscode.ViewColumn.Active, false)
  132. } else if (panel === sidebarPanel) {
  133. await vscode.commands.executeCommand(`${ClineProvider.sideBarId}.focus`)
  134. provider.postMessageToWebview({ type: "action", action: "focusInput" })
  135. }
  136. } catch (error) {
  137. outputChannel.appendLine(`Error focusing input: ${error}`)
  138. }
  139. },
  140. "roo.acceptInput": () => {
  141. const visibleProvider = getVisibleProviderOrLog(outputChannel)
  142. if (!visibleProvider) {
  143. return
  144. }
  145. visibleProvider.postMessageToWebview({ type: "acceptInput" })
  146. },
  147. }
  148. }
  149. export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {
  150. // (This example uses webviewProvider activation event which is necessary to
  151. // deserialize cached webview, but since we use retainContextWhenHidden, we
  152. // don't need to use that event).
  153. // https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
  154. const contextProxy = await ContextProxy.getInstance(context)
  155. const tabProvider = new ClineProvider(context, outputChannel, "editor", contextProxy)
  156. const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))
  157. // Check if there are any visible text editors, otherwise open a new group
  158. // to the right.
  159. const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0
  160. if (!hasVisibleEditors) {
  161. await vscode.commands.executeCommand("workbench.action.newGroupRight")
  162. }
  163. const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
  164. const newPanel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, {
  165. enableScripts: true,
  166. retainContextWhenHidden: true,
  167. localResourceRoots: [context.extensionUri],
  168. })
  169. // Save as tab type panel.
  170. setPanel(newPanel, "tab")
  171. // TODO: Use better svg icon with light and dark variants (see
  172. // https://stackoverflow.com/questions/58365687/vscode-extension-iconpath).
  173. newPanel.iconPath = {
  174. light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "panel_light.png"),
  175. dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "panel_dark.png"),
  176. }
  177. await tabProvider.resolveWebviewView(newPanel)
  178. // Add listener for visibility changes to notify webview
  179. newPanel.onDidChangeViewState(
  180. (e) => {
  181. const panel = e.webviewPanel
  182. if (panel.visible) {
  183. panel.webview.postMessage({ type: "action", action: "didBecomeVisible" }) // Use the same message type as in SettingsView.tsx
  184. }
  185. },
  186. null, // First null is for `thisArgs`
  187. context.subscriptions, // Register listener for disposal
  188. )
  189. // Handle panel closing events.
  190. newPanel.onDidDispose(
  191. () => {
  192. setPanel(undefined, "tab")
  193. },
  194. null,
  195. context.subscriptions, // Also register dispose listener
  196. )
  197. // Lock the editor group so clicking on files doesn't open them over the panel.
  198. await delay(100)
  199. await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
  200. return tabProvider
  201. }