// This method is called when your extension is deactivated export function deactivate() {} import * as vscode from "vscode" const TERMINAL_NAME = "opencode" export function activate(context: vscode.ExtensionContext) { let openNewTerminalDisposable = vscode.commands.registerCommand("opencode.openNewTerminal", async () => { await openTerminal() }) let openTerminalDisposable = vscode.commands.registerCommand("opencode.openTerminal", async () => { // An opencode terminal already exists => focus it const existingTerminal = vscode.window.terminals.find((t) => t.name === TERMINAL_NAME) if (existingTerminal) { existingTerminal.show() return } await openTerminal() }) let addFilepathDisposable = vscode.commands.registerCommand("opencode.addFilepathToTerminal", async () => { const fileRef = getActiveFile() if (!fileRef) return const terminal = vscode.window.activeTerminal if (!terminal) return if (terminal.name === TERMINAL_NAME) { // @ts-ignore const port = terminal.creationOptions.env?.["_EXTENSION_OPENCODE_PORT"] port ? await appendPrompt(parseInt(port), fileRef) : terminal.sendText(fileRef) terminal.show() } }) context.subscriptions.push(openTerminalDisposable, addFilepathDisposable) async function openTerminal() { // Create a new terminal in split screen const port = Math.floor(Math.random() * (65535 - 16384 + 1)) + 16384 const terminal = vscode.window.createTerminal({ name: TERMINAL_NAME, iconPath: { light: vscode.Uri.file(context.asAbsolutePath("images/button-dark.svg")), dark: vscode.Uri.file(context.asAbsolutePath("images/button-light.svg")), }, location: { viewColumn: vscode.ViewColumn.Beside, preserveFocus: false, }, env: { _EXTENSION_OPENCODE_PORT: port.toString(), OPENCODE_CALLER: "vscode", }, }) terminal.show() terminal.sendText(`opencode --port ${port}`) const fileRef = getActiveFile() if (!fileRef) return // Wait for the terminal to be ready let tries = 10 let connected = false do { await new Promise((resolve) => setTimeout(resolve, 200)) try { await fetch(`http://localhost:${port}/app`) connected = true break } catch (e) {} tries-- } while (tries > 0) // If connected, append the prompt to the terminal if (connected) { await appendPrompt(port, `In ${fileRef}`) terminal.show() } } async function appendPrompt(port: number, text: string) { await fetch(`http://localhost:${port}/tui/append-prompt`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ text }), }) } function getActiveFile() { const activeEditor = vscode.window.activeTextEditor if (!activeEditor) return const document = activeEditor.document const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri) if (!workspaceFolder) return // Get the relative path from workspace root const relativePath = vscode.workspace.asRelativePath(document.uri) let filepathWithAt = `@${relativePath}` // Check if there's a selection and add line numbers const selection = activeEditor.selection if (!selection.isEmpty) { // Convert to 1-based line numbers const startLine = selection.start.line + 1 const endLine = selection.end.line + 1 if (startLine === endLine) { // Single line selection filepathWithAt += `#L${startLine}` } else { // Multi-line selection filepathWithAt += `#L${startLine}-${endLine}` } } return filepathWithAt } }