diff options
Diffstat (limited to 'extension/src/activation')
-rw-r--r-- | extension/src/activation/activate.ts | 71 | ||||
-rw-r--r-- | extension/src/activation/environmentSetup.ts | 226 | ||||
-rw-r--r-- | extension/src/activation/languageClient.ts | 115 |
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; +} |