PathInserter.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import * as vscode from "vscode"
  2. import * as path from "path"
  3. import * as fs from "fs"
  4. import { CommunicationBridge } from "../ui/CommunicationBridge"
  5. import { logger } from "../globals"
  6. /**
  7. * Path communication utility - mirrors PathInserter.kt
  8. * Handles sending paths to the webview with proper validation and error handling
  9. * Now uses CommunicationBridge for improved functionality
  10. */
  11. export class PathInserter {
  12. private static communicationBridge: CommunicationBridge | undefined
  13. private static bridges = new Set<CommunicationBridge>()
  14. static getCommunicationBridge(): CommunicationBridge | undefined {
  15. return this.communicationBridge
  16. }
  17. /**
  18. * Set the communication bridge for path operations
  19. * @param bridge The communication bridge to use for communication
  20. */
  21. static setCommunicationBridge(bridge: CommunicationBridge | undefined): void {
  22. if (bridge) {
  23. this.bridges.add(bridge)
  24. }
  25. this.communicationBridge = bridge
  26. if (bridge) {
  27. logger.appendLine("Communication bridge set for PathInserter")
  28. } else {
  29. logger.appendLine("Communication bridge cleared from PathInserter")
  30. }
  31. }
  32. /**
  33. * Remove a specific bridge and fall back to another registered bridge if it was active
  34. */
  35. static removeCommunicationBridge(bridge: CommunicationBridge): void {
  36. this.bridges.delete(bridge)
  37. if (this.communicationBridge === bridge) {
  38. const remaining = [...this.bridges]
  39. this.communicationBridge = remaining.length > 0 ? remaining[remaining.length - 1] : undefined
  40. logger.appendLine(
  41. this.communicationBridge
  42. ? "Communication bridge fell back to another instance"
  43. : "Communication bridge cleared from PathInserter",
  44. )
  45. }
  46. }
  47. /**
  48. * Set the webview panel for JavaScript execution (deprecated - use setCommunicationBridge)
  49. * @param panel The webview panel to use for communication
  50. * @deprecated Use setCommunicationBridge instead
  51. */
  52. static setWebviewPanel(panel: vscode.WebviewPanel | undefined): void {
  53. // For backward compatibility, but recommend using setCommunicationBridge
  54. logger.appendLine("setWebviewPanel is deprecated, use setCommunicationBridge instead")
  55. }
  56. /**
  57. * Insert file paths into the web UI
  58. * Mirrors the insertPaths functionality from PathInserter.kt
  59. * @param paths Array of file paths to insert
  60. */
  61. static insertPaths(paths: string[]): void {
  62. try {
  63. if (!this.communicationBridge) {
  64. logger.appendLine("No communication bridge available to insert paths")
  65. vscode.window.showWarningMessage("OpenCode: No active communication bridge to insert paths")
  66. return
  67. }
  68. if (!paths || paths.length === 0) {
  69. logger.appendLine("No paths provided to insert")
  70. return
  71. }
  72. // Use CommunicationBridge for improved path handling
  73. this.communicationBridge.insertPaths(paths)
  74. logger.appendLine(`Requested insertion of ${paths.length} paths via CommunicationBridge`)
  75. } catch (error) {
  76. logger.appendLine(`Unexpected error inserting paths: ${error}`)
  77. vscode.window.showErrorMessage(`OpenCode: Failed to insert paths - ${error}`)
  78. }
  79. }
  80. /**
  81. * Paste a directory path into the web UI input
  82. * Mirrors the pastePath functionality from PathInserter.kt
  83. * @param path Directory path to paste
  84. */
  85. static pastePath(path: string): void {
  86. try {
  87. if (!this.communicationBridge) {
  88. logger.appendLine("No communication bridge available to paste path")
  89. vscode.window.showWarningMessage("OpenCode: No active communication bridge to paste path")
  90. return
  91. }
  92. if (!path || path.trim().length === 0) {
  93. logger.appendLine("No path provided to paste")
  94. return
  95. }
  96. // Use CommunicationBridge for improved path handling
  97. this.communicationBridge.pastePath(path.trim())
  98. logger.appendLine(`Requested paste of path via CommunicationBridge: ${path}`)
  99. } catch (error) {
  100. logger.appendLine(`Unexpected error pasting path: ${error}`)
  101. vscode.window.showErrorMessage(`OpenCode: Failed to paste path - ${error}`)
  102. }
  103. }
  104. /**
  105. * Validate file paths before sending to web UI
  106. * @param paths Array of paths to validate
  107. * @returns Array of valid paths
  108. */
  109. private static validatePaths(paths: string[]): string[] {
  110. const validPaths: string[] = []
  111. for (const rawPath of paths) {
  112. try {
  113. const normalizedPath = this.normalizePath(rawPath)
  114. if (normalizedPath && this.isValidPath(normalizedPath)) {
  115. validPaths.push(normalizedPath)
  116. } else {
  117. logger.appendLine(`Skipping invalid path: ${rawPath}`)
  118. }
  119. } catch (error) {
  120. logger.appendLine(`Error validating path ${rawPath}: ${error}`)
  121. }
  122. }
  123. return validPaths
  124. }
  125. /**
  126. * Normalize a file path for consistent handling
  127. * @param rawPath Raw path string
  128. * @returns Normalized path or null if invalid
  129. */
  130. private static normalizePath(rawPath: string): string | null {
  131. try {
  132. if (!rawPath || rawPath.trim().length === 0) {
  133. return null
  134. }
  135. let normalizedPath = rawPath.trim()
  136. // Handle VSCode URI format
  137. if (normalizedPath.startsWith("file://")) {
  138. normalizedPath = vscode.Uri.parse(normalizedPath).fsPath
  139. }
  140. // Resolve relative paths against workspace
  141. if (!path.isAbsolute(normalizedPath)) {
  142. const workspaceFolder = vscode.workspace.workspaceFolders?.[0]
  143. if (workspaceFolder) {
  144. normalizedPath = path.resolve(workspaceFolder.uri.fsPath, normalizedPath)
  145. } else {
  146. // No workspace, can't resolve relative path
  147. return null
  148. }
  149. }
  150. // Normalize path separators
  151. normalizedPath = path.normalize(normalizedPath)
  152. return normalizedPath
  153. } catch (error) {
  154. logger.appendLine(`Error normalizing path ${rawPath}: ${error}`)
  155. return null
  156. }
  157. }
  158. /**
  159. * Check if a path is valid and accessible
  160. * @param normalizedPath Normalized path to check
  161. * @returns True if path is valid
  162. */
  163. private static isValidPath(normalizedPath: string): boolean {
  164. try {
  165. // Check if path exists and is accessible
  166. fs.accessSync(normalizedPath, fs.constants.R_OK)
  167. return true
  168. } catch (error) {
  169. // Path doesn't exist or isn't accessible
  170. // We still allow it since it might be a valid path that just doesn't exist yet
  171. // or the user might have different permissions
  172. logger.appendLine(`Path not accessible but allowing: ${normalizedPath} (${error})`)
  173. return true
  174. }
  175. }
  176. /**
  177. * Clear the communication bridge reference
  178. */
  179. static clearCommunicationBridge(): void {
  180. if (this.communicationBridge) {
  181. this.bridges.delete(this.communicationBridge)
  182. }
  183. const remaining = [...this.bridges]
  184. this.communicationBridge = remaining.length > 0 ? remaining[remaining.length - 1] : undefined
  185. logger.appendLine(
  186. this.communicationBridge
  187. ? "Communication bridge fell back to another instance"
  188. : "Communication bridge cleared from PathInserter",
  189. )
  190. }
  191. /**
  192. * Clear the webview panel reference (deprecated)
  193. * @deprecated Use clearCommunicationBridge instead
  194. */
  195. static clearWebviewPanel(): void {
  196. this.clearCommunicationBridge()
  197. }
  198. /**
  199. * Check if PathInserter is ready to send paths
  200. * @returns True if communication bridge is available
  201. */
  202. static isReady(): boolean {
  203. return this.communicationBridge !== undefined
  204. }
  205. }