summaryrefslogtreecommitdiff
path: root/extension/src/activation
diff options
context:
space:
mode:
Diffstat (limited to 'extension/src/activation')
-rw-r--r--extension/src/activation/activate.ts71
-rw-r--r--extension/src/activation/environmentSetup.ts226
-rw-r--r--extension/src/activation/languageClient.ts115
3 files changed, 412 insertions, 0 deletions
diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts
new file mode 100644
index 00000000..a0aa560b
--- /dev/null
+++ b/extension/src/activation/activate.ts
@@ -0,0 +1,71 @@
+import * as vscode from "vscode";
+import { registerAllCommands } from "../commands";
+import { registerAllCodeLensProviders } from "../lang-server/codeLens";
+import { sendTelemetryEvent, TelemetryEvent } from "../telemetry";
+import { getExtensionUri } from "../util/vscode";
+import * as path from "path";
+// import { openCapturedTerminal } from "../terminal/terminalEmulator";
+import IdeProtocolClient from "../continueIdeClient";
+import { getContinueServerUrl } from "../bridge";
+
+export let extensionContext: vscode.ExtensionContext | undefined = undefined;
+
+export let ideProtocolClient: IdeProtocolClient | undefined = undefined;
+
+export function activateExtension(
+ context: vscode.ExtensionContext,
+ showTutorial: boolean
+) {
+ sendTelemetryEvent(TelemetryEvent.ExtensionActivated);
+
+ registerAllCodeLensProviders(context);
+ registerAllCommands(context);
+
+ let serverUrl = getContinueServerUrl();
+
+ ideProtocolClient = new IdeProtocolClient(
+ serverUrl.replace("http", "ws") + "/ide/ws",
+ context
+ );
+
+ if (showTutorial && false) {
+ Promise.all([
+ vscode.workspace
+ .openTextDocument(
+ path.join(getExtensionUri().fsPath, "examples/python/sum.py")
+ )
+ .then((document) =>
+ vscode.window.showTextDocument(document, {
+ preview: false,
+ viewColumn: vscode.ViewColumn.One,
+ })
+ ),
+
+ vscode.workspace
+ .openTextDocument(
+ path.join(getExtensionUri().fsPath, "examples/python/main.py")
+ )
+ .then((document) =>
+ vscode.window
+ .showTextDocument(document, {
+ preview: false,
+ viewColumn: vscode.ViewColumn.One,
+ })
+ .then((editor) => {
+ editor.revealRange(
+ new vscode.Range(0, 0, 0, 0),
+ vscode.TextEditorRevealType.InCenter
+ );
+ })
+ ),
+ ]).then(() => {
+ ideProtocolClient?.openNotebook();
+ });
+ } else {
+ // ideProtocolClient?.openNotebook().then(() => {
+ // // openCapturedTerminal();
+ // });
+ }
+
+ extensionContext = context;
+}
diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts
new file mode 100644
index 00000000..4816b4b1
--- /dev/null
+++ b/extension/src/activation/environmentSetup.ts
@@ -0,0 +1,226 @@
+import { getExtensionUri } from "../util/vscode";
+const util = require("util");
+const exec = util.promisify(require("child_process").exec);
+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";
+
+async function runCommand(cmd: string): Promise<[string, string | undefined]> {
+ var stdout: any = "";
+ var stderr: any = "";
+ try {
+ var { stdout, stderr } = await exec(cmd);
+ } catch (e: any) {
+ stderr = e.stderr;
+ stdout = e.stdout;
+ }
+ if (stderr === "") {
+ stderr = undefined;
+ }
+ if (typeof stdout === "undefined") {
+ stdout = "";
+ }
+
+ return [stdout, stderr];
+}
+
+async function getPythonCmdAssumingInstalled() {
+ const [, stderr] = await runCommand("python3 --version");
+ if (stderr) {
+ return "python";
+ }
+ return "python3";
+}
+
+async function setupPythonEnv() {
+ console.log("Setting up python env for Continue extension...");
+ // First check that python3 is installed
+
+ var [stdout, stderr] = await runCommand("python3 --version");
+ let pythonCmd = "python3";
+ if (stderr) {
+ // If not, first see if python3 is aliased to python
+ var [stdout, stderr] = await runCommand("python --version");
+ if (
+ (typeof stderr === "undefined" || stderr === "") &&
+ stdout.split(" ")[1][0] === "3"
+ ) {
+ // Python3 is aliased to python
+ pythonCmd = "python";
+ } else {
+ // Python doesn't exist at all
+ console.log("Python3 not found, downloading...");
+ await downloadPython3();
+ }
+ }
+ let pipCmd = pythonCmd.endsWith("3") ? "pip3" : "pip";
+
+ let activateCmd = "source env/bin/activate";
+ let pipUpgradeCmd = `${pipCmd} install --upgrade pip`;
+ if (process.platform == "win32") {
+ activateCmd = ".\\env\\Scripts\\activate";
+ pipUpgradeCmd = `${pythonCmd} -m pip install --upgrade pip`;
+ }
+
+ let command = `cd ${path.join(
+ getExtensionUri().fsPath,
+ "scripts"
+ )} && ${pythonCmd} -m venv env && ${activateCmd} && ${pipUpgradeCmd} && ${pipCmd} install -r requirements.txt`;
+ var [stdout, stderr] = await runCommand(command);
+ if (stderr) {
+ throw new Error(stderr);
+ }
+ console.log(
+ "Successfully set up python env at ",
+ getExtensionUri().fsPath + "/scripts/env"
+ );
+
+ await startContinuePythonServer();
+}
+
+function readEnvFile(path: string) {
+ if (!fs.existsSync(path)) {
+ return {};
+ }
+ let envFile = fs.readFileSync(path, "utf8");
+
+ let env: { [key: string]: string } = {};
+ envFile.split("\n").forEach((line) => {
+ let [key, value] = line.split("=");
+ if (typeof key === "undefined" || typeof value === "undefined") {
+ return;
+ }
+ env[key] = value.replace(/"/g, "");
+ });
+ return env;
+}
+
+function writeEnvFile(path: string, key: string, value: string) {
+ if (!fs.existsSync(path)) {
+ fs.writeFileSync(path, `${key}="${value}"`);
+ return;
+ }
+
+ let env = readEnvFile(path);
+ env[key] = value;
+
+ let newEnvFile = "";
+ for (let key in env) {
+ newEnvFile += `${key}="${env[key]}"\n`;
+ }
+ fs.writeFileSync(path, newEnvFile);
+}
+
+export async function startContinuePythonServer() {
+ // Check vscode settings
+ let serverUrl = getContinueServerUrl();
+ if (serverUrl !== "http://localhost:8000") {
+ return;
+ }
+
+ let envFile = path.join(getExtensionUri().fsPath, "scripts", ".env");
+ let openai_api_key: string | undefined =
+ readEnvFile(envFile)["OPENAI_API_KEY"];
+ while (typeof openai_api_key === "undefined" || openai_api_key === "") {
+ openai_api_key = await vscode.window.showInputBox({
+ prompt: "Enter your OpenAI API key",
+ placeHolder: "Enter your OpenAI API key",
+ });
+ // Write to .env file
+ }
+ writeEnvFile(envFile, "OPENAI_API_KEY", openai_api_key);
+
+ console.log("Starting Continue python server...");
+
+ // Check if already running by calling /health
+ try {
+ let response = await fetch(serverUrl + "/health");
+ if (response.status === 200) {
+ console.log("Continue python server already running");
+ return;
+ }
+ } catch (e) {
+ console.log("Error checking for existing server", e);
+ }
+
+ let activateCmd = "source env/bin/activate";
+ let pythonCmd = "python3";
+ if (process.platform == "win32") {
+ activateCmd = ".\\env\\Scripts\\activate";
+ pythonCmd = "python";
+ }
+
+ let command = `cd ${path.join(
+ getExtensionUri().fsPath,
+ "scripts"
+ )} && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`;
+ try {
+ // exec(command);
+ let child = spawn(command, {
+ shell: true,
+ });
+ child.stdout.on("data", (data: any) => {
+ console.log(`stdout: ${data}`);
+ });
+ child.stderr.on("data", (data: any) => {
+ console.log(`stderr: ${data}`);
+ });
+ child.on("error", (error: any) => {
+ console.log(`error: ${error.message}`);
+ });
+ } catch (e) {
+ console.log("Failed to start Continue python server", e);
+ }
+ // Sleep for 3 seconds to give the server time to start
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ console.log("Successfully started Continue python server");
+}
+
+async function installNodeModules() {
+ console.log("Rebuilding node-pty for Continue extension...");
+ await rebuild({
+ buildPath: getExtensionUri().fsPath, // Folder containing node_modules
+ electronVersion: "19.1.8",
+ onlyModules: ["node-pty"],
+ });
+ console.log("Successfully rebuilt node-pty");
+}
+
+export function isPythonEnvSetup(): boolean {
+ let pathToEnvCfg = getExtensionUri().fsPath + "/scripts/env/pyvenv.cfg";
+ return fs.existsSync(path.join(pathToEnvCfg));
+}
+
+export async function setupExtensionEnvironment() {
+ console.log("Setting up environment for Continue extension...");
+ await Promise.all([setupPythonEnv()]);
+}
+
+export async function downloadPython3() {
+ // Download python3 and return the command to run it (python or python3)
+ let os = process.platform;
+ let command: string = "";
+ let pythonCmd = "python3";
+ if (os === "darwin") {
+ throw new Error("python3 not found");
+ } else if (os === "linux") {
+ command =
+ "sudo apt update && upgrade && sudo apt install python3 python3-pip";
+ } else if (os === "win32") {
+ command =
+ "wget -O python_installer.exe https://www.python.org/ftp/python/3.11.3/python-3.11.3-amd64.exe && python_installer.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0";
+ pythonCmd = "python";
+ }
+
+ var [stdout, stderr] = await runCommand(command);
+ if (stderr) {
+ throw new Error(stderr);
+ }
+ console.log("Successfully downloaded python3");
+
+ return pythonCmd;
+}
diff --git a/extension/src/activation/languageClient.ts b/extension/src/activation/languageClient.ts
new file mode 100644
index 00000000..5b0bd612
--- /dev/null
+++ b/extension/src/activation/languageClient.ts
@@ -0,0 +1,115 @@
+/**
+ * If we wanted to run or use another language server from our extension, this is how we would do it.
+ */
+
+import * as path from "path";
+import { workspace, ExtensionContext, extensions } from "vscode";
+
+import {
+ DefinitionParams,
+ LanguageClient,
+ LanguageClientOptions,
+ ServerOptions,
+ StateChangeEvent,
+ TransportKind,
+ State,
+} from "vscode-languageclient/node";
+import { getExtensionUri } from "../util/vscode";
+
+let client: LanguageClient;
+
+export async function startLanguageClient(context: ExtensionContext) {
+ let pythonLS = startPythonLanguageServer(context);
+ pythonLS.start();
+}
+
+export async function makeRequest(method: string, param: any): Promise<any> {
+ if (!client) {
+ return;
+ } else if (client.state === State.Starting) {
+ return new Promise((resolve, reject) => {
+ let stateListener = client.onDidChangeState((e: StateChangeEvent) => {
+ if (e.newState === State.Running) {
+ stateListener.dispose();
+ resolve(client.sendRequest(method, param));
+ } else if (e.newState === State.Stopped) {
+ stateListener.dispose();
+ reject(new Error("Language server stopped unexpectedly"));
+ }
+ });
+ });
+ } else {
+ return client.sendRequest(method, param);
+ }
+}
+
+export function deactivate(): Thenable<void> | undefined {
+ if (!client) {
+ return undefined;
+ }
+ return client.stop();
+}
+
+function startPythonLanguageServer(context: ExtensionContext): LanguageClient {
+ let extensionPath = getExtensionUri().fsPath;
+ const command = `cd ${path.join(
+ extensionPath,
+ "scripts"
+ )} && source env/bin/activate.fish && python -m pyls`;
+ const serverOptions: ServerOptions = {
+ command: command,
+ args: ["-vv"],
+ };
+ const clientOptions: LanguageClientOptions = {
+ documentSelector: ["python"],
+ synchronize: {
+ configurationSection: "pyls",
+ },
+ };
+ return new LanguageClient(command, serverOptions, clientOptions);
+}
+
+async function startPylance(context: ExtensionContext) {
+ let pylance = extensions.getExtension("ms-python.vscode-pylance");
+ await pylance?.activate();
+ if (!pylance) {
+ return;
+ }
+ let { path: lsPath } = await pylance.exports.languageServerFolder();
+
+ // The server is implemented in node
+ let serverModule = context.asAbsolutePath(lsPath);
+ // The debug options for the server
+ // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
+ let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] };
+
+ // If the extension is launched in debug mode then the debug server options are used
+ // Otherwise the run options are used
+ let serverOptions: ServerOptions = {
+ run: { module: serverModule, transport: TransportKind.ipc },
+ debug: {
+ module: serverModule,
+ transport: TransportKind.ipc,
+ options: debugOptions,
+ },
+ };
+
+ // Options to control the language client
+ let clientOptions: LanguageClientOptions = {
+ // Register the server for plain text documents
+ documentSelector: [{ scheme: "file", language: "python" }],
+ synchronize: {
+ // Notify the server about file changes to '.clientrc files contained in the workspace
+ fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
+ },
+ };
+
+ // Create the language client and start the client.
+ client = new LanguageClient(
+ "languageServerExample",
+ "Language Server Example",
+ serverOptions,
+ clientOptions
+ );
+ return client;
+}