summaryrefslogtreecommitdiff
path: root/extension/src/terminal
diff options
context:
space:
mode:
Diffstat (limited to 'extension/src/terminal')
-rw-r--r--extension/src/terminal/snoopers.ts133
-rw-r--r--extension/src/terminal/terminalEmulator.ts140
2 files changed, 273 insertions, 0 deletions
diff --git a/extension/src/terminal/snoopers.ts b/extension/src/terminal/snoopers.ts
new file mode 100644
index 00000000..a1f8993c
--- /dev/null
+++ b/extension/src/terminal/snoopers.ts
@@ -0,0 +1,133 @@
+export abstract class TerminalSnooper<T> {
+ abstract onData(data: string): void;
+ abstract onWrite(data: string): void;
+ callback: (data: T) => void;
+
+ constructor(callback: (data: T) => void) {
+ this.callback = callback;
+ }
+}
+
+function stripAnsi(data: string) {
+ const pattern = [
+ "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
+ "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))",
+ ].join("|");
+
+ let regex = new RegExp(pattern, "g");
+ return data.replace(regex, "");
+}
+
+export class CommandCaptureSnooper extends TerminalSnooper<string> {
+ stdinBuffer = "";
+ cursorPos = 0;
+ stdoutHasInterrupted = false;
+
+ static RETURN_KEY = "\r";
+ static DEL_KEY = "\x7F";
+ static UP_KEY = "\x1B[A";
+ static DOWN_KEY = "\x1B[B";
+ static RIGHT_KEY = "\x1B[C";
+ static LEFT_KEY = "\x1B[D";
+ static CONTROL_KEYS = new Set([
+ CommandCaptureSnooper.RETURN_KEY,
+ CommandCaptureSnooper.DEL_KEY,
+ CommandCaptureSnooper.UP_KEY,
+ CommandCaptureSnooper.DOWN_KEY,
+ CommandCaptureSnooper.RIGHT_KEY,
+ CommandCaptureSnooper.LEFT_KEY,
+ ]);
+
+ private _cursorLeft() {
+ this.cursorPos = Math.max(0, this.cursorPos - 1);
+ }
+ private _cursorRight() {
+ this.cursorPos = Math.min(this.stdinBuffer.length, this.cursorPos + 1);
+ }
+ // Known issue: This does not handle autocomplete.
+ // Would be preferable to find a way that didn't require this all, just parsing by command prompt
+ // but that has it's own challenges
+ private handleControlKey(data: string): void {
+ switch (data) {
+ case CommandCaptureSnooper.DEL_KEY:
+ this.stdinBuffer =
+ this.stdinBuffer.slice(0, this.cursorPos - 1) +
+ this.stdinBuffer.slice(this.cursorPos);
+ this._cursorLeft();
+ break;
+ case CommandCaptureSnooper.RETURN_KEY:
+ this.callback(this.stdinBuffer);
+ this.stdinBuffer = "";
+ break;
+ case CommandCaptureSnooper.UP_KEY:
+ case CommandCaptureSnooper.DOWN_KEY:
+ this.stdinBuffer = "";
+ break;
+ case CommandCaptureSnooper.RIGHT_KEY:
+ this._cursorRight();
+ break;
+ case CommandCaptureSnooper.LEFT_KEY:
+ this._cursorLeft();
+ break;
+ }
+ }
+
+ onWrite(data: string): void {
+ if (CommandCaptureSnooper.CONTROL_KEYS.has(data)) {
+ this.handleControlKey(data);
+ } else {
+ this.stdinBuffer =
+ this.stdinBuffer.substring(0, this.cursorPos) +
+ data +
+ this.stdinBuffer.substring(this.cursorPos);
+ this._cursorRight();
+ }
+ }
+
+ onData(data: string): void {}
+}
+
+export class PythonTracebackSnooper extends TerminalSnooper<string> {
+ static tracebackStart = "Traceback (most recent call last):";
+ tracebackBuffer = "";
+
+ static tracebackEnd = (buf: string): string | undefined => {
+ let lines = buf.split("\n");
+ for (let i = 0; i < lines.length; i++) {
+ if (
+ lines[i].startsWith(" File") &&
+ i + 2 < lines.length &&
+ lines[i + 2][0] != " "
+ ) {
+ return lines.slice(0, i + 3).join("\n");
+ }
+ }
+ return undefined;
+ };
+ override onWrite(data: string): void {}
+ override onData(data: string): void {
+ let strippedData = stripAnsi(data);
+ // Strip fully blank and squiggle lines
+ strippedData = strippedData
+ .split("\n")
+ .filter((line) => line.trim().length > 0 && line.trim() !== "~~^~~")
+ .join("\n");
+ // Snoop for traceback
+ let idx = strippedData.indexOf(PythonTracebackSnooper.tracebackStart);
+ if (idx >= 0) {
+ this.tracebackBuffer = strippedData.substr(idx);
+ } else if (this.tracebackBuffer.length > 0) {
+ this.tracebackBuffer += "\n" + strippedData;
+ }
+ // End of traceback, send to webview
+ if (this.tracebackBuffer.length > 0) {
+ let wholeTraceback = PythonTracebackSnooper.tracebackEnd(
+ this.tracebackBuffer
+ );
+ if (wholeTraceback) {
+ this.callback(wholeTraceback);
+ this.tracebackBuffer = "";
+ }
+ }
+ }
+}
diff --git a/extension/src/terminal/terminalEmulator.ts b/extension/src/terminal/terminalEmulator.ts
new file mode 100644
index 00000000..ba860b24
--- /dev/null
+++ b/extension/src/terminal/terminalEmulator.ts
@@ -0,0 +1,140 @@
+// /* 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.openDebugPanel", 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);
+// }