diff --git a/client/src/extension.ts b/client/src/extension.ts index 415d9f785..79dfee510 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -11,13 +11,18 @@ import * as fs from 'fs'; import * as path from 'path'; -import { commands, ExtensionContext, Position, Range, TextEditor, TextEditorEdit } from 'vscode'; +import { commands, extensions, ExtensionContext, Position, Range, TextEditor, TextEditorEdit, Uri } from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TextEdit, TransportKind, + ConfigurationParams, + CancellationToken, + ConfigurationRequest, + HandlerResult, + ResponseError, } from 'vscode-languageclient/node'; import { Commands } from '../../server/src/commands/commands'; @@ -61,6 +66,60 @@ export function activate(context: ExtensionContext) { configurationSection: ['python', 'pyright'], }, connectionOptions: { cancellationStrategy: cancellationStrategy }, + middleware: { + // Use the middleware hook to override the configuration call. This allows + // us to inject the proper "python.pythonPath" setting from the Python extension's + // private settings store. + workspace: { + configuration: ( + params: ConfigurationParams, + token: CancellationToken, + next: ConfigurationRequest.HandlerSignature + ): HandlerResult => { + // Hand-collapse "Thenable | Thenable | Thenable" into just "Thenable" to make TS happy. + const result: any[] | ResponseError | Thenable> = next( + params, + token + ); + + // For backwards compatibility, set python.pythonPath to the configured + // value as though it were in the user's settings.json file. + const addPythonPath = ( + settings: any[] | ResponseError + ): Promise> => { + if (settings instanceof ResponseError) { + return Promise.resolve(settings); + } + + const pythonPathPromises: Promise[] = params.items.map((item) => { + if (item.section === 'python') { + const uri = item.scopeUri ? Uri.parse(item.scopeUri) : undefined; + return getPythonPathFromPythonExtension(uri); + } + return Promise.resolve(undefined); + }); + + return Promise.all(pythonPathPromises).then((pythonPaths) => { + pythonPaths.forEach((pythonPath, i) => { + // If there is a pythonPath returned by the Python extension, + // always prefer this over the pythonPath that uses the old + // mechanism. + if (pythonPath !== undefined) { + settings[i].pythonPath = pythonPath; + } + }); + return settings; + }); + }; + + if (isThenable(result)) { + return result.then(addPythonPath); + } + + return addPythonPath(result); + }, + }, + }, }; // Create the language client and start the client. @@ -133,3 +192,27 @@ export function deactivate() { // anything to do here. return undefined; } + +// The VS Code Python extension manages its own internal store of configuration settings. +// The setting that was traditionally named "python.pythonPath" has been moved to the +// Python extension's internal store for reasons of security and because it differs per +// project and by user. +async function getPythonPathFromPythonExtension(scopeUri?: Uri): Promise { + try { + const extension = extensions.getExtension('ms-python.python'); + if (extension && extension.packageJSON?.featureFlags?.usingNewInterpreterStorage) { + if (!extension.isActive) { + await extension.activate(); + } + return extension.exports.settings.getExecutionCommand(scopeUri).join(' '); + } + } catch (error) { + // Ignore exceptions. + } + + return undefined; +} + +function isThenable(v: any): v is Thenable { + return typeof v?.then === 'function'; +} diff --git a/docs/settings.md b/docs/settings.md index e670ab41d..71d00e9e8 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -26,7 +26,7 @@ The Pyright VS Code extension honors the following settings. **python.analysis.diagnosticSeverityOverrides** [map]: Allows a user to override the severity levels for individual diagnostic rules. "reportXXX" rules in the type check diagnostics settings in [configuration](https://github.com/microsoft/pyright/blob/master/docs/configuration.md#type-check-diagnostics-settings) are supported. Use the rule name as a key and one of "error," "warning," "information," "true," "false," or "none" as value. -**python.pythonPath** [path]: Path to Python interpreter. +**python.pythonPath** [path]: Path to Python interpreter. This setting is being deprecated by the VS Code Python extension in favor of a setting that is stored in the Python extension’s internal configuration store. Pyright supports both mechanisms but prefers the new one if both settings are present. **python.venvPath** [path]: Path to folder with subdirectories that contain virtual environments.