diff options
| author | Nate Sesti <sestinj@gmail.com> | 2023-06-07 16:31:44 -0400 | 
|---|---|---|
| committer | Nate Sesti <sestinj@gmail.com> | 2023-06-07 16:31:44 -0400 | 
| commit | 82247453a12c7e697351a4464013c690c848d552 (patch) | |
| tree | 0d3ed47124d46b4254b318a122ca6857ca7d2a29 /extension/src | |
| parent | 0be241fe44edcc35c79835ac59971d60869d1c34 (diff) | |
| parent | 69fda64ca0111c5be2f6967a7d82dd5b284dd995 (diff) | |
| download | sncontinue-82247453a12c7e697351a4464013c690c848d552.tar.gz sncontinue-82247453a12c7e697351a4464013c690c848d552.tar.bz2 sncontinue-82247453a12c7e697351a4464013c690c848d552.zip | |
Merge branch 'main' into dlt-transform
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(); +  } +} | 
