extension.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. // This method is called when your extension is deactivated
  2. export function deactivate() {}
  3. import * as vscode from "vscode"
  4. const TERMINAL_NAME = "opencode"
  5. export function activate(context: vscode.ExtensionContext) {
  6. let openNewTerminalDisposable = vscode.commands.registerCommand("opencode.openNewTerminal", async () => {
  7. await openTerminal()
  8. })
  9. let openTerminalDisposable = vscode.commands.registerCommand("opencode.openTerminal", async () => {
  10. // An opencode terminal already exists => focus it
  11. const existingTerminal = vscode.window.terminals.find((t) => t.name === TERMINAL_NAME)
  12. if (existingTerminal) {
  13. existingTerminal.show()
  14. return
  15. }
  16. await openTerminal()
  17. })
  18. let addFilepathDisposable = vscode.commands.registerCommand("opencode.addFilepathToTerminal", async () => {
  19. const fileRef = getActiveFile()
  20. if (!fileRef) {
  21. return
  22. }
  23. const terminal = vscode.window.activeTerminal
  24. if (!terminal) {
  25. return
  26. }
  27. if (terminal.name === TERMINAL_NAME) {
  28. // @ts-ignore
  29. const port = terminal.creationOptions.env?.["_EXTENSION_OPENCODE_PORT"]
  30. port ? await appendPrompt(parseInt(port), fileRef) : terminal.sendText(fileRef)
  31. terminal.show()
  32. }
  33. })
  34. context.subscriptions.push(openTerminalDisposable, addFilepathDisposable)
  35. async function openTerminal() {
  36. // Create a new terminal in split screen
  37. const port = Math.floor(Math.random() * (65535 - 16384 + 1)) + 16384
  38. const terminal = vscode.window.createTerminal({
  39. name: TERMINAL_NAME,
  40. iconPath: {
  41. light: vscode.Uri.file(context.asAbsolutePath("images/button-dark.svg")),
  42. dark: vscode.Uri.file(context.asAbsolutePath("images/button-light.svg")),
  43. },
  44. location: {
  45. viewColumn: vscode.ViewColumn.Beside,
  46. preserveFocus: false,
  47. },
  48. env: {
  49. _EXTENSION_OPENCODE_PORT: port.toString(),
  50. OPENCODE_CALLER: "vscode",
  51. },
  52. })
  53. terminal.show()
  54. terminal.sendText(`opencode --port ${port}`)
  55. const fileRef = getActiveFile()
  56. if (!fileRef) {
  57. return
  58. }
  59. // Wait for the terminal to be ready
  60. let tries = 10
  61. let connected = false
  62. do {
  63. await new Promise((resolve) => setTimeout(resolve, 200))
  64. try {
  65. await fetch(`http://localhost:${port}/app`)
  66. connected = true
  67. break
  68. } catch (e) {}
  69. tries--
  70. } while (tries > 0)
  71. // If connected, append the prompt to the terminal
  72. if (connected) {
  73. await appendPrompt(port, `In ${fileRef}`)
  74. terminal.show()
  75. }
  76. }
  77. async function appendPrompt(port: number, text: string) {
  78. await fetch(`http://localhost:${port}/tui/append-prompt`, {
  79. method: "POST",
  80. headers: {
  81. "Content-Type": "application/json",
  82. },
  83. body: JSON.stringify({ text }),
  84. })
  85. }
  86. function getActiveFile() {
  87. const activeEditor = vscode.window.activeTextEditor
  88. if (!activeEditor) {
  89. return
  90. }
  91. const document = activeEditor.document
  92. const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri)
  93. if (!workspaceFolder) {
  94. return
  95. }
  96. // Get the relative path from workspace root
  97. const relativePath = vscode.workspace.asRelativePath(document.uri)
  98. let filepathWithAt = `@${relativePath}`
  99. // Check if there's a selection and add line numbers
  100. const selection = activeEditor.selection
  101. if (!selection.isEmpty) {
  102. // Convert to 1-based line numbers
  103. const startLine = selection.start.line + 1
  104. const endLine = selection.end.line + 1
  105. if (startLine === endLine) {
  106. // Single line selection
  107. filepathWithAt += `#L${startLine}`
  108. } else {
  109. // Multi-line selection
  110. filepathWithAt += `#L${startLine}-${endLine}`
  111. }
  112. }
  113. return filepathWithAt
  114. }
  115. }