diff options
Diffstat (limited to 'extension/src')
-rw-r--r-- | extension/src/activation/environmentSetup.ts | 11 | ||||
-rw-r--r-- | extension/src/continueIdeClient.ts | 44 | ||||
-rw-r--r-- | extension/src/terminal/terminalEmulator.ts | 302 |
3 files changed, 210 insertions, 147 deletions
diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 21f867b1..bc071461 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -5,7 +5,6 @@ const { spawn } = require("child_process"); import * as path from "path"; import * as fs from "fs"; import rebuild from "@electron/rebuild"; -import * as vscode from "vscode"; import { getContinueServerUrl } from "../bridge"; import fetch from "node-fetch"; @@ -14,7 +13,9 @@ async function runCommand(cmd: string): Promise<[string, string | undefined]> { var stdout: any = ""; var stderr: any = ""; try { - var { stdout, stderr } = await exec(cmd); + var { stdout, stderr } = await exec(cmd, { + shell: process.platform === "win32" ? "powershell.exe" : undefined, + }); } catch (e: any) { stderr = e.stderr; stdout = e.stdout; @@ -70,7 +71,9 @@ function checkEnvExists() { ); return ( fs.existsSync(path.join(envBinPath, "activate")) && - fs.existsSync(path.join(envBinPath, "pip")) + fs.existsSync( + path.join(envBinPath, process.platform == "win32" ? "pip.exe" : "pip") + ) ); } @@ -88,7 +91,7 @@ async function setupPythonEnv() { const createEnvCommand = [ `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, `${pythonCmd} -m venv env`, - ].join(" ; "); + ].join("; "); // Repeat until it is successfully created (sometimes it fails to generate the bin, need to try again) while (true) { diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index a5a1c5dc..bbaf5f08 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -12,6 +12,7 @@ import { debugPanelWebview, setupDebugPanel } from "./debugPanel"; import { FileEditWithFullContents } from "../schema/FileEditWithFullContents"; import fs = require("fs"); import { WebsocketMessenger } from "./util/messenger"; +import { CapturedTerminal } from "./terminal/terminalEmulator"; class IdeProtocolClient { private messenger: WebsocketMessenger | null = null; @@ -96,6 +97,14 @@ class IdeProtocolClient { fileEdit, }); break; + case "highlightCode": + this.highlightCode(data.rangeInFile, data.color); + break; + case "runCommand": + this.messenger?.send("runCommand", { + output: await this.runCommand(data.command), + }); + break; case "saveFile": this.saveFile(data.filepath); break; @@ -117,6 +126,29 @@ class IdeProtocolClient { // ------------------------------------ // // On message handlers + async highlightCode(rangeInFile: RangeInFile, color: string) { + const range = new vscode.Range( + rangeInFile.range.start.line, + rangeInFile.range.start.character, + rangeInFile.range.end.line, + rangeInFile.range.end.character + ); + const editor = await openEditorAndRevealRange( + rangeInFile.filepath, + range, + vscode.ViewColumn.One + ); + if (editor) { + editor.setDecorations( + vscode.window.createTextEditorDecorationType({ + backgroundColor: color, + isWholeLine: true, + }), + [range] + ); + } + } + showSuggestion(edit: FileEdit) { // showSuggestion already exists showSuggestion( @@ -288,9 +320,15 @@ class IdeProtocolClient { return rangeInFiles; } - runCommand(command: string) { - vscode.window.terminals[0].sendText(command, true); - // But need to know when it's done executing... + private continueTerminal: CapturedTerminal | undefined; + + async runCommand(command: string) { + if (!this.continueTerminal) { + this.continueTerminal = new CapturedTerminal("Continue"); + } + + this.continueTerminal.show(); + return await this.continueTerminal.runCommand(command); } } diff --git a/extension/src/terminal/terminalEmulator.ts b/extension/src/terminal/terminalEmulator.ts index 6cf65970..67b47e2f 100644 --- a/extension/src/terminal/terminalEmulator.ts +++ b/extension/src/terminal/terminalEmulator.ts @@ -1,140 +1,162 @@ -// /* Terminal emulator - commented because node-pty is causing problems. */ - -// import * as vscode from "vscode"; -// import pty = require("node-pty"); -// import os = require("os"); -// import { extensionContext } from "../activation/activate"; -// import { debugPanelWebview } from "../debugPanel"; // Need to consider having multiple panels, where to store this state. -// import { -// CommandCaptureSnooper, -// PythonTracebackSnooper, -// TerminalSnooper, -// } from "./snoopers"; - -// export function tracebackToWebviewAction(traceback: string) { -// if (debugPanelWebview) { -// debugPanelWebview.postMessage({ -// type: "traceback", -// value: traceback, -// }); -// } else { -// vscode.commands -// .executeCommand("continue.openContinueGUI", extensionContext) -// .then(() => { -// // TODO: Waiting for the webview to load, but should add a hook to the onLoad message event. Same thing in autodebugTest command in commands.ts -// setTimeout(() => { -// debugPanelWebview?.postMessage({ -// type: "traceback", -// value: traceback, -// }); -// }, 500); -// }); -// } -// } - -// const DEFAULT_SNOOPERS = [ -// new PythonTracebackSnooper(tracebackToWebviewAction), -// new CommandCaptureSnooper((data: string) => { -// if (data.trim().startsWith("pytest ")) { -// let fileAndFunctionSpecifier = data.split(" ")[1]; -// vscode.commands.executeCommand( -// "continue.debugTest", -// fileAndFunctionSpecifier -// ); -// } -// }), -// ]; - -// // Whenever a user opens a terminal, replace it with ours -// vscode.window.onDidOpenTerminal((terminal) => { -// if (terminal.name != "Continue") { -// terminal.dispose(); -// openCapturedTerminal(); -// } -// }); - -// function getDefaultShell(): string { -// if (process.platform !== "win32") { -// return os.userInfo().shell; -// } -// switch (process.platform) { -// case "win32": -// return process.env.COMSPEC || "cmd.exe"; -// // case "darwin": -// // return process.env.SHELL || "/bin/zsh"; -// // default: -// // return process.env.SHELL || "/bin/sh"; -// } -// } - -// function getRootDir(): string | undefined { -// var isWindows = os.platform() === "win32"; -// let cwd = isWindows ? process.env.USERPROFILE : process.env.HOME; -// if ( -// vscode.workspace.workspaceFolders && -// vscode.workspace.workspaceFolders.length > 0 -// ) { -// cwd = vscode.workspace.workspaceFolders[0].uri.fsPath; -// } -// return cwd; -// } - -// export function openCapturedTerminal( -// snoopers: TerminalSnooper<string>[] = DEFAULT_SNOOPERS -// ) { -// // If there is another existing, non-Continue terminal, delete it -// let terminals = vscode.window.terminals; -// for (let i = 0; i < terminals.length; i++) { -// if (terminals[i].name != "Continue") { -// terminals[i].dispose(); -// } -// } - -// let env = { ...(process.env as any) }; -// if (os.platform() !== "win32") { -// env["PATH"] += ":" + ["/opt/homebrew/bin", "/opt/homebrew/sbin"].join(":"); -// } - -// var ptyProcess = pty.spawn(getDefaultShell(), [], { -// name: "xterm-256color", -// cols: 160, // TODO: Get size of vscode terminal, and change with resize -// rows: 26, -// cwd: getRootDir(), -// env, -// useConpty: true, -// }); - -// const writeEmitter = new vscode.EventEmitter<string>(); - -// ptyProcess.onData((data: any) => { -// // Let each of the snoopers see the new data -// for (let snooper of snoopers) { -// snooper.onData(data); -// } - -// // Pass data through to terminal -// writeEmitter.fire(data); -// }); -// process.on("exit", () => ptyProcess.kill()); - -// const newPty: vscode.Pseudoterminal = { -// onDidWrite: writeEmitter.event, -// open: () => {}, -// close: () => {}, -// handleInput: (data) => { -// for (let snooper of snoopers) { -// snooper.onWrite(data); -// } -// ptyProcess.write(data); -// }, -// }; -// const terminal = vscode.window.createTerminal({ -// name: "Continue", -// pty: newPty, -// }); -// terminal.show(); - -// setTimeout(() => { -// ptyProcess.write("clear\r"); -// }, 500); -// } +/* Terminal emulator - commented because node-pty is causing problems. */ + +import * as vscode from "vscode"; +import os = require("os"); +import stripAnsi from "strip-ansi"; + +function loadNativeModule<T>(id: string): T | null { + try { + return require(`${vscode.env.appRoot}/node_modules.asar/${id}`); + } catch (err) { + // ignore + } + + try { + return require(`${vscode.env.appRoot}/node_modules/${id}`); + } catch (err) { + // ignore + } + + return null; +} + +const pty = loadNativeModule<any>("node-pty"); + +function getDefaultShell(): string { + if (process.platform !== "win32") { + return os.userInfo().shell; + } + switch (process.platform) { + case "win32": + return process.env.COMSPEC || "cmd.exe"; + // case "darwin": + // return process.env.SHELL || "/bin/zsh"; + // default: + // return process.env.SHELL || "/bin/sh"; + } +} + +function getRootDir(): string | undefined { + const isWindows = os.platform() === "win32"; + let cwd = isWindows ? process.env.USERPROFILE : process.env.HOME; + if ( + vscode.workspace.workspaceFolders && + vscode.workspace.workspaceFolders.length > 0 + ) { + cwd = vscode.workspace.workspaceFolders[0].uri.fsPath; + } + return cwd; +} + +export class CapturedTerminal { + private readonly terminal: vscode.Terminal; + private readonly shellCmd: string; + private readonly ptyProcess: any; + + private shellPrompt: string | undefined = undefined; + private dataBuffer: string = ""; + + private onDataListeners: ((data: string) => void)[] = []; + + show() { + this.terminal.show(); + } + + private commandQueue: [string, (output: string) => void][] = []; + private hasRunCommand: boolean = false; + + private async waitForCommandToFinish() { + return new Promise<string>((resolve, reject) => { + this.onDataListeners.push((data: any) => { + const strippedData = stripAnsi(data); + this.dataBuffer += strippedData; + const lines = this.dataBuffer.split("\n"); + if ( + lines.length > 0 && + (lines[lines.length - 1].includes("bash-") || + lines[lines.length - 1].includes("(main)")) && + lines[lines.length - 1].includes("$") + ) { + resolve(this.dataBuffer); + this.dataBuffer = ""; + this.onDataListeners = []; + } + }); + }); + } + + async runCommand(command: string): Promise<string> { + if (!this.hasRunCommand) { + this.hasRunCommand = true; + // Let the first bash- prompt appear and let python env be opened + await this.waitForCommandToFinish(); + } + + if (this.commandQueue.length === 0) { + return new Promise(async (resolve, reject) => { + this.commandQueue.push([command, resolve]); + + while (this.commandQueue.length > 0) { + const [command, resolve] = this.commandQueue.shift()!; + + this.terminal.sendText(command); + resolve(await this.waitForCommandToFinish()); + } + }); + } else { + return new Promise((resolve, reject) => { + this.commandQueue.push([command, resolve]); + }); + } + } + + private readonly writeEmitter: vscode.EventEmitter<string>; + + constructor(terminalName: string) { + this.shellCmd = "bash"; // getDefaultShell(); + + const env = { ...(process.env as any) }; + if (os.platform() !== "win32") { + env.PATH += `:${["/opt/homebrew/bin", "/opt/homebrew/sbin"].join(":")}`; + } + + // Create the pseudo terminal + this.ptyProcess = pty.spawn(this.shellCmd, [], { + name: "xterm-256color", + cols: 160, // TODO: Get size of vscode terminal, and change with resize + rows: 26, + cwd: getRootDir(), + env, + useConpty: true, + }); + + this.writeEmitter = new vscode.EventEmitter<string>(); + + this.ptyProcess.onData((data: any) => { + // Pass data through to terminal + this.writeEmitter.fire(data); + + for (let listener of this.onDataListeners) { + listener(data); + } + }); + + process.on("exit", () => this.ptyProcess.kill()); + + const newPty: vscode.Pseudoterminal = { + onDidWrite: this.writeEmitter.event, + open: () => {}, + close: () => {}, + handleInput: (data) => { + this.ptyProcess.write(data); + }, + }; + + // Create and clear the terminal + this.terminal = vscode.window.createTerminal({ + name: terminalName, + pty: newPty, + }); + this.terminal.show(); + } +} |