diff options
| author | Ty Dunn <ty@tydunn.com> | 2023-06-16 15:56:01 -0700 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-06-16 15:56:01 -0700 | 
| commit | d9e576d0f81a22a0c6e7f0659e67f3fa38a0d1aa (patch) | |
| tree | 7d798f83ae62f4d28dfb5c2256b01d52e9a5c2d3 /extension/src | |
| parent | c980e01d2f9328d5c37df14bea02f84a4890bc6a (diff) | |
| parent | 3aa4f014608c09b8da2f4ab95137a959487af245 (diff) | |
| download | sncontinue-d9e576d0f81a22a0c6e7f0659e67f3fa38a0d1aa.tar.gz sncontinue-d9e576d0f81a22a0c6e7f0659e67f3fa38a0d1aa.tar.bz2 sncontinue-d9e576d0f81a22a0c6e7f0659e67f3fa38a0d1aa.zip | |
Merge branch 'main' into too-large
Diffstat (limited to 'extension/src')
| -rw-r--r-- | extension/src/activation/activate.ts | 38 | ||||
| -rw-r--r-- | extension/src/continueIdeClient.ts | 12 | ||||
| -rw-r--r-- | extension/src/terminal/terminalEmulator.ts | 140 | ||||
| -rw-r--r-- | extension/src/util/lcs.ts | 30 | 
4 files changed, 199 insertions, 21 deletions
| diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 135a8ec7..32726c86 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -8,6 +8,7 @@ import * as path from "path";  import IdeProtocolClient from "../continueIdeClient";  import { getContinueServerUrl } from "../bridge";  import { setupDebugPanel, ContinueGUIWebviewViewProvider } from "../debugPanel"; +import { CapturedTerminal } from "../terminal/terminalEmulator";  export let extensionContext: vscode.ExtensionContext | undefined = undefined; @@ -47,5 +48,42 @@ export function activateExtension(      );    })(); +  // All opened terminals should be replaced by our own terminal +  vscode.window.onDidOpenTerminal((terminal) => { +    if (terminal.name === "Continue") { +      return; +    } +    const options = terminal.creationOptions; +    const capturedTerminal = new CapturedTerminal({ +      ...options, +      name: "Continue", +    }); +    terminal.dispose(); +    if (!ideProtocolClient.continueTerminal) { +      ideProtocolClient.continueTerminal = capturedTerminal; +    } +  }); + +  // If any terminals are open to start, replace them +  vscode.window.terminals.forEach((terminal) => { +    if (terminal.name === "Continue") { +      return; +    } +    const options = terminal.creationOptions; +    const capturedTerminal = new CapturedTerminal( +      { +        ...options, +        name: "Continue", +      }, +      (commandOutput: string) => { +        ideProtocolClient.sendCommandOutput(commandOutput); +      } +    ); +    terminal.dispose(); +    if (!ideProtocolClient.continueTerminal) { +      ideProtocolClient.continueTerminal = capturedTerminal; +    } +  }); +    extensionContext = context;  } diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index ef9a91c8..9a93a4ef 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -323,16 +323,22 @@ class IdeProtocolClient {      return rangeInFiles;    } -  private continueTerminal: CapturedTerminal | undefined; +  public continueTerminal: CapturedTerminal | undefined;    async runCommand(command: string) { -    if (!this.continueTerminal) { -      this.continueTerminal = new CapturedTerminal("Continue"); +    if (!this.continueTerminal || this.continueTerminal.isClosed()) { +      this.continueTerminal = new CapturedTerminal({ +        name: "Continue", +      });      }      this.continueTerminal.show();      return await this.continueTerminal.runCommand(command);    } + +  sendCommandOutput(output: string) { +    this.messenger?.send("commandOutput", { output }); +  }  }  export default IdeProtocolClient; diff --git a/extension/src/terminal/terminalEmulator.ts b/extension/src/terminal/terminalEmulator.ts index b3031baf..8e49737e 100644 --- a/extension/src/terminal/terminalEmulator.ts +++ b/extension/src/terminal/terminalEmulator.ts @@ -3,6 +3,7 @@  import * as vscode from "vscode";  import os = require("os");  import stripAnsi from "strip-ansi"; +import { longestCommonSubsequence } from "../util/lcs";  function loadNativeModule<T>(id: string): T | null {    try { @@ -62,21 +63,38 @@ export class CapturedTerminal {      this.terminal.show();    } +  isClosed(): boolean { +    return this.terminal.exitStatus !== undefined; +  } +    private commandQueue: [string, (output: string) => void][] = [];    private hasRunCommand: boolean = false; +  private dataEndsInPrompt(strippedData: string): boolean { +    const lines = strippedData.split("\n"); +    const lastLine = lines[lines.length - 1]; + +    return ( +      lines.length > 0 && +      (((lastLine.includes("bash-") || lastLine.includes(") $ ")) && +        lastLine.includes("$")) || +        (lastLine.includes("]> ") && lastLine.includes(") [")) || +        (lastLine.includes(" (") && lastLine.includes(")>")) || +        (typeof this.commandPromptString !== "undefined" && +          (lastLine.includes(this.commandPromptString) || +            this.commandPromptString.length - +              longestCommonSubsequence(lastLine, this.commandPromptString) +                .length < +              3))) +    ); +  } +    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(") $ ")) && -          lines[lines.length - 1].includes("$") -        ) { +        if (this.dataEndsInPrompt(strippedData)) {            resolve(this.dataBuffer);            this.dataBuffer = "";            this.onDataListeners = []; @@ -86,12 +104,6 @@ export class CapturedTerminal {    }    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]); @@ -99,8 +111,12 @@ export class CapturedTerminal {          while (this.commandQueue.length > 0) {            const [command, resolve] = this.commandQueue.shift()!; +          // Refresh the command prompt string every time in case it changes +          await this.refreshCommandPromptString(); +            this.terminal.sendText(command); -          resolve(await this.waitForCommandToFinish()); +          const output = await this.waitForCommandToFinish(); +          resolve(output);          }        });      } else { @@ -112,8 +128,48 @@ export class CapturedTerminal {    private readonly writeEmitter: vscode.EventEmitter<string>; -  constructor(terminalName: string) { -    this.shellCmd = "bash"; // getDefaultShell(); +  private splitByCommandsBuffer: string = ""; +  private readonly onCommandOutput: ((output: string) => void) | undefined; + +  splitByCommandsListener(data: string) { +    // Split the output by commands so it can be sent to Continue Server + +    const strippedData = stripAnsi(data); +    this.splitByCommandsBuffer += data; +    if (this.dataEndsInPrompt(strippedData)) { +      if (this.onCommandOutput) { +        this.onCommandOutput(stripAnsi(this.splitByCommandsBuffer)); +      } +      this.splitByCommandsBuffer = ""; +    } +  } + +  private runningClearToGetPrompt: boolean = false; +  private seenClear: boolean = false; +  private commandPromptString: string | undefined = undefined; +  private resolveMeWhenCommandPromptStringFound: +    | ((_: unknown) => void) +    | undefined = undefined; + +  private async refreshCommandPromptString(): Promise<string | undefined> { +    // Sends a message that will be received by the terminal to get the command prompt string, see the onData method below in constructor. +    this.runningClearToGetPrompt = true; +    this.terminal.sendText("echo"); +    const promise = new Promise((resolve, reject) => { +      this.resolveMeWhenCommandPromptStringFound = resolve; +    }); +    await promise; +    return this.commandPromptString; +  } + +  constructor( +    options: { name: string } & Partial<vscode.ExtensionTerminalOptions>, +    onCommandOutput?: (output: string) => void +  ) { +    this.onCommandOutput = onCommandOutput; + +    // this.shellCmd = "bash"; // getDefaultShell(); +    this.shellCmd = getDefaultShell();      const env = { ...(process.env as any) };      if (os.platform() !== "win32") { @@ -123,7 +179,7 @@ export class CapturedTerminal {      // 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 +      cols: 250, // No way to get the size of VS Code terminal, or listen to resize, so make it just bigger than most conceivable VS Code widths        rows: 26,        cwd: getRootDir(),        env, @@ -133,9 +189,57 @@ export class CapturedTerminal {      this.writeEmitter = new vscode.EventEmitter<string>();      this.ptyProcess.onData((data: any) => { +      if (this.runningClearToGetPrompt) { +        if ( +          stripAnsi(data) +            .split("\n") +            .flatMap((line) => line.split("\r")) +            .find((line) => line.trim() === "echo") !== undefined +        ) { +          this.seenClear = true; +          return; +        } else if (this.seenClear) { +          const strippedLines = stripAnsi(data) +            .split("\r") +            .filter( +              (line) => +                line.trim().length > 0 && +                line.trim() !== "%" && +                line.trim() !== "⏎" +            ); +          const lastLine = strippedLines[strippedLines.length - 1] || ""; +          const lines = lastLine +            .split("\n") +            .filter( +              (line) => +                line.trim().length > 0 && +                line.trim() !== "%" && +                line.trim() !== "⏎" +            ); +          const commandPromptString = (lines[lines.length - 1] || "").trim(); +          if ( +            commandPromptString.length > 0 && +            !commandPromptString.includes("echo") +          ) { +            this.runningClearToGetPrompt = false; +            this.seenClear = false; +            this.commandPromptString = commandPromptString; +            console.log( +              "Found command prompt string: " + this.commandPromptString +            ); +            if (this.resolveMeWhenCommandPromptStringFound) { +              this.resolveMeWhenCommandPromptStringFound(undefined); +            } +          } +          return; +        } +      } +        // Pass data through to terminal +      data = data.replace("⏎", "");        this.writeEmitter.fire(data); +      this.splitByCommandsListener(data);        for (let listener of this.onDataListeners) {          listener(data);        } @@ -154,7 +258,7 @@ export class CapturedTerminal {      // Create and clear the terminal      this.terminal = vscode.window.createTerminal({ -      name: terminalName, +      ...options,        pty: newPty,      });      this.terminal.show(); diff --git a/extension/src/util/lcs.ts b/extension/src/util/lcs.ts new file mode 100644 index 00000000..17ea63f9 --- /dev/null +++ b/extension/src/util/lcs.ts @@ -0,0 +1,30 @@ +export function longestCommonSubsequence(a: string, b: string) { +  const lengths: number[][] = []; +  for (let i = 0; i <= a.length; i++) { +    lengths[i] = []; +    for (let j = 0; j <= b.length; j++) { +      if (i === 0 || j === 0) { +        lengths[i][j] = 0; +      } else if (a[i - 1] === b[j - 1]) { +        lengths[i][j] = lengths[i - 1][j - 1] + 1; +      } else { +        lengths[i][j] = Math.max(lengths[i - 1][j], lengths[i][j - 1]); +      } +    } +  } +  let result = ""; +  let x = a.length; +  let y = b.length; +  while (x !== 0 && y !== 0) { +    if (lengths[x][y] === lengths[x - 1][y]) { +      x--; +    } else if (lengths[x][y] === lengths[x][y - 1]) { +      y--; +    } else { +      result = a[x - 1] + result; +      x--; +      y--; +    } +  } +  return result; +} | 
