diff options
Diffstat (limited to 'extension/src/terminal/terminalEmulator.ts')
-rw-r--r-- | extension/src/terminal/terminalEmulator.ts | 140 |
1 files changed, 122 insertions, 18 deletions
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(); |