From 8b9d60a75e356a36aee636b40412881f67cff67f Mon Sep 17 00:00:00 2001 From: Vasilis Themelis Date: Mon, 3 Jun 2024 14:38:45 -0700 Subject: [PATCH] Use the python extension venv (#820) Summary: Fixes https://github.com/facebook/pyre-check/issues/6 Fixes https://github.com/facebook/pyre-check/issues/645 Fixes https://github.com/facebook/pyre-check/issues/183 If [the VsCode Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) is installed, use the Python interpreter that is selected in the extension to start the pyre language server. Also, subscribe to environment switches and restart the pyre language server with the new environments. If the extension is not present, we retain the original behaviour of the extension. This is very useful for people that may have multiple environments installed but is more useful for people that use the VsCode SSH extension and cannot benefit from [this workaround](https://github.com/facebook/pyre-check/issues/6#issuecomment-388575825). Pull Request resolved: https://github.com/facebook/pyre-check/pull/820 Test Plan: I ran the extension locally and it seems to be picking up the interpreter path. Reviewed By: stroxler, inseokhwang Differential Revision: D56205532 Pulled By: kinto0 fbshipit-source-id: dc0c00f5a41d37c5cb7d9f85a60ac0ba9230456a --- tools/ide_plugins/vscode/.gitignore | 3 + tools/ide_plugins/vscode/package.json | 9 ++- tools/ide_plugins/vscode/src/main.ts | 101 ++++++++++++++++++++----- tools/ide_plugins/vscode/tsconfig.json | 3 +- 4 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 tools/ide_plugins/vscode/.gitignore diff --git a/tools/ide_plugins/vscode/.gitignore b/tools/ide_plugins/vscode/.gitignore new file mode 100644 index 00000000000..cb89a529419 --- /dev/null +++ b/tools/ide_plugins/vscode/.gitignore @@ -0,0 +1,3 @@ +**/out +**/.vscode-test +*.vsix diff --git a/tools/ide_plugins/vscode/package.json b/tools/ide_plugins/vscode/package.json index a38bac708c4..209726ec5da 100755 --- a/tools/ide_plugins/vscode/package.json +++ b/tools/ide_plugins/vscode/package.json @@ -1,6 +1,6 @@ { "name": "pyre-vscode", - "version": "0.0.1", + "version": "0.0.2", "publisher": "fb-pyre-check", "engines": { "vscode": "^1.15.0" @@ -26,15 +26,18 @@ "lint": "tslint --force -p ." }, "dependencies": { - "vscode-languageclient": "^3.5.0" + "@vscode/python-extension": "^1.0.5", + "vscode-languageclient": "^3.5.0", + "which": "^4.0.0" }, "devDependencies": { "@types/mocha": "^2.2.44", "@types/node": "^8.0.53", + "@types/which": "^3.0.4", "cson": "^4.1.0", "tslint": "^5.8.0", "tslint-microsoft-contrib": "^5.0.1", - "typescript": "^2.6.1", + "typescript": "^3.7", "vscode": "^1.1.7" } } diff --git a/tools/ide_plugins/vscode/src/main.ts b/tools/ide_plugins/vscode/src/main.ts index d153d6c3845..db989a35176 100755 --- a/tools/ide_plugins/vscode/src/main.ts +++ b/tools/ide_plugins/vscode/src/main.ts @@ -11,34 +11,60 @@ import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, DidChangeConfigurationNotification } from 'vscode-languageclient'; +import { EnvironmentPath, PVSC_EXTENSION_ID, PythonExtension } from '@vscode/python-extension' +import { dirname, join } from 'path'; +import { existsSync, statSync } from 'fs'; +import which from 'which'; -let languageClient: LanguageClient; +type LanguageClientState = { + languageClient: LanguageClient, + configListener: Promise +} -namespace Configuration { +// Extension state +let state : LanguageClientState | undefined; +let envListener: vscode.Disposable | undefined; - let configurationListener: vscode.Disposable; +let outputChannel = vscode.window.createOutputChannel("pyre"); - export function initialize() { - configurationListener = vscode.workspace.onDidChangeConfiguration(() => { - languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: null }); - }); +export async function activate(_: vscode.ExtensionContext) { + + const pythonExtension = vscode.extensions.getExtension(PVSC_EXTENSION_ID); + + if (!pythonExtension) { + outputChannel.appendLine("Python extension not found. Will use the default console environment."); + state = createLanguageClient('pyre'); + outputChannel.appendLine("Done"); + return; } - export function dispose() { - if (configurationListener) { - configurationListener.dispose(); - } + const activatedEnvPath = pythonExtension.exports.environments.getActiveEnvironmentPath(); + const pyrePath = await findPyreCommand(activatedEnvPath); + + if (pyrePath) { + state = createLanguageClient(pyrePath); } + + envListener = pythonExtension.exports.environments.onDidChangeActiveEnvironmentPath(async (e) => { + state?.languageClient?.stop(); + state?.configListener.then((listener) => listener.dispose()); + state = undefined; + + const pyrePath = await findPyreCommand(e); + if (pyrePath) { + state = createLanguageClient(pyrePath); + } + }); } -export async function activate(_: vscode.ExtensionContext) { +function createLanguageClient(pyrePath: string) : LanguageClientState { - let serverOptions = { - command: "pyre", + const serverOptions = { + command: pyrePath, args: ["persistent"] }; - let clientOptions: LanguageClientOptions = { + const clientOptions: LanguageClientOptions = { documentSelector: [{scheme: 'file', language: 'python'}], synchronize: { // Notify the server about file changes to '.clientrc files contain in the workspace @@ -54,12 +80,51 @@ export async function activate(_: vscode.ExtensionContext) { ) languageClient.registerProposedFeatures(); - languageClient.onReady().then(() => { - Configuration.initialize(); + + const configListener = languageClient.onReady().then(() => { + return vscode.workspace.onDidChangeConfiguration(() => { + languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: null }); + }); }); + languageClient.start(); + + return {languageClient, configListener}; +} + +async function findPyreCommand(envPath: EnvironmentPath) : Promise { + + if (envPath.id === 'DEFAULT_PYTHON') { + outputChannel.appendLine(`Using the default python environment`); + return 'pyre'; + } + + const path = envPath.path; + const stat = statSync(path) + + const pyrePath = stat.isFile() + ? join(dirname(envPath.path), 'pyre') + : stat.isDirectory() + ? join(path, 'bin', 'pyre') + : undefined; + + if (pyrePath && existsSync(pyrePath) && statSync(pyrePath).isFile()) { + outputChannel.appendLine(`Using pyre path: ${pyrePath} from python environment: ${envPath.id} at ${envPath.path}`); + return pyrePath; + } + + const pyreFromPathEnvVariable = await which('pyre', { nothrow: true }); + if (pyreFromPathEnvVariable != null) { + outputChannel.appendLine(`Using pyre path: ${pyreFromPathEnvVariable} from PATH`); + return pyreFromPathEnvVariable; + } + + outputChannel.appendLine(`Could not find pyre path from python environment: ${envPath.id} at ${envPath.path}`); + return undefined; } export function deactivate() { - Configuration.dispose(); + state?.languageClient.stop(); + state?.configListener.then((listener) => listener.dispose()); + envListener?.dispose(); } diff --git a/tools/ide_plugins/vscode/tsconfig.json b/tools/ide_plugins/vscode/tsconfig.json index 74ea5311736..9e60fb2816b 100644 --- a/tools/ide_plugins/vscode/tsconfig.json +++ b/tools/ide_plugins/vscode/tsconfig.json @@ -12,7 +12,8 @@ "noImplicitAny": false, "noImplicitReturns": true, "noUnusedLocals": true, - "noUnusedParameters": true + "noUnusedParameters": true, + "esModuleInterop": true }, "exclude": [ "node_modules",