summaryrefslogtreecommitdiff
path: root/extension/src
diff options
context:
space:
mode:
authorTy Dunn <ty@tydunn.com>2023-06-16 15:56:01 -0700
committerGitHub <noreply@github.com>2023-06-16 15:56:01 -0700
commitd9e576d0f81a22a0c6e7f0659e67f3fa38a0d1aa (patch)
tree7d798f83ae62f4d28dfb5c2256b01d52e9a5c2d3 /extension/src
parentc980e01d2f9328d5c37df14bea02f84a4890bc6a (diff)
parent3aa4f014608c09b8da2f4ab95137a959487af245 (diff)
downloadsncontinue-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.ts38
-rw-r--r--extension/src/continueIdeClient.ts12
-rw-r--r--extension/src/terminal/terminalEmulator.ts140
-rw-r--r--extension/src/util/lcs.ts30
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;
+}