From 894cdbd52b30c291ab486efc0db0888f6342c9a6 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Fri, 15 Nov 2019 18:37:14 -0800 Subject: [PATCH] Added new VS Code setting "pyright.openFilesOnly", which is set to true. --- client/package.json | 6 ++ docs/settings.md | 2 + server/src/analyzer/program.ts | 14 +++-- server/src/analyzer/service.ts | 12 ++-- server/src/common/commandLineOptions.ts | 3 + server/src/common/configOptions.ts | 3 + server/src/pyright.ts | 1 + server/src/server.ts | 82 +++++++++++++------------ 8 files changed, 73 insertions(+), 50 deletions(-) diff --git a/client/package.json b/client/package.json index 0dce6440e..4220d4d65 100644 --- a/client/package.json +++ b/client/package.json @@ -87,6 +87,12 @@ "default": false, "description": "Disables type completion, definitions, and references.", "scope": "resource" + }, + "pyright.openFilesOnly": { + "type": "boolean", + "default": true, + "description": "Report errors for all files in the workspace or only currently-open files?", + "scope": "resource" } } }, diff --git a/docs/settings.md b/docs/settings.md index fb22e53fd..69a6b3f7a 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -4,6 +4,8 @@ The Pyright VS Code extension honors the following settings. **pyright.disableLanguageServices** [boolean]: Disables all language services except for “hover”. This includes type completion, signature completion, find definition, find references, and find symbols in file. This option is useful if you want to use pyright only as a type checker but want to run another Python language server for langue service features. +**pyright.openFilesOnly** [boolean]: Determines whether pyright analyzes (and reports errors for) all files in the workspace, as indicated by the config file. If this option is set to true, pyright analyzes only open files. + **python.analysis.typeshedPaths** [array of paths]: Paths to look for typeshed modules. Pyright currently honors only the first path in the array. **python.pythonPath** [path]: Path to Python interpreter. diff --git a/server/src/analyzer/program.ts b/server/src/analyzer/program.ts index 86123674f..ea5bd1a3c 100644 --- a/server/src/analyzer/program.ts +++ b/server/src/analyzer/program.ts @@ -35,8 +35,6 @@ import { createTypeEvaluator, TypeEvaluator } from './typeEvaluator'; import { TypeStubWriter } from './typeStubWriter'; const _maxImportDepth = 256; -const _maxAnalysisTimeForCompletions = 500; -const _analyzeOnlyOpenFiles = false; const _allowAllThirdPartyImports = true; export interface SourceFileInfo { @@ -146,7 +144,7 @@ export class Program { fileInfo.sourceFile.isBindingRequired() || fileInfo.sourceFile.isCheckingRequired()) { - if ((!_analyzeOnlyOpenFiles && fileInfo.isTracked) || fileInfo.isOpenByClient) { + if ((!this._configOptions.checkOnlyOpenFiles && fileInfo.isTracked) || fileInfo.isOpenByClient) { sourceFileCount++; } } @@ -155,6 +153,10 @@ export class Program { return sourceFileCount; } + isCheckingOnlyOpenFiles() { + return this._configOptions.checkOnlyOpenFiles; + } + addTrackedFiles(filePaths: string[]) { filePaths.forEach(filePath => { this.addTrackedFile(filePath); @@ -295,7 +297,7 @@ export class Program { } } - if (!_analyzeOnlyOpenFiles) { + if (!this._configOptions.checkOnlyOpenFiles) { // Do type analysis of remaining files. const allFiles = this._sourceFileList; @@ -700,7 +702,7 @@ export class Program { const fileDiagnostics: FileDiagnostics[] = this._removeUnneededFiles(); this._sourceFileList.forEach(sourceFileInfo => { - if ((!_analyzeOnlyOpenFiles && sourceFileInfo.isTracked) || sourceFileInfo.isOpenByClient) { + if ((!options.checkOnlyOpenFiles && sourceFileInfo.isTracked) || sourceFileInfo.isOpenByClient) { const diagnostics = sourceFileInfo.sourceFile.getDiagnostics( options, sourceFileInfo.diagnosticsVersion); if (diagnostics !== undefined) { @@ -954,7 +956,7 @@ export class Program { } else { // If we're showing the user errors only for open files, clear // out the errors for the now-closed file. - if (_analyzeOnlyOpenFiles && !fileInfo.isOpenByClient) { + if (this._configOptions.checkOnlyOpenFiles && !fileInfo.isOpenByClient) { fileDiagnostics.push({ filePath: fileInfo.sourceFile.getFilePath(), diagnostics: [] diff --git a/server/src/analyzer/service.ts b/server/src/analyzer/service.ts index c3c4bf0c1..00de6f886 100644 --- a/server/src/analyzer/service.ts +++ b/server/src/analyzer/service.ts @@ -37,6 +37,7 @@ export { MaxAnalysisTime } from './program'; export interface AnalysisResults { diagnostics: FileDiagnostics[]; filesInProgram: number; + checkingOnlyOpenFiles: boolean; filesRequiringAnalysis: number; fatalErrorOccurred: boolean; configParseErrorOccurred: boolean; @@ -325,9 +326,8 @@ export class AnalyzerService { } } - if (commandLineOptions.verboseOutput) { - configOptions.verboseOutput = true; - } + configOptions.verboseOutput = !!commandLineOptions.verboseOutput; + configOptions.checkOnlyOpenFiles = !!commandLineOptions.checkOnlyOpenFiles; // Do some sanity checks on the specified settings and report missing // or inconsistent information. @@ -809,7 +809,7 @@ export class AnalyzerService { // How long has it been since the user interacted with the service? // If the user is actively typing, back off to let him or her finish. const timeSinceLastUserInteractionInMs = Date.now() - this._lastUserInteractionTime; - const minBackoffTimeInMs = 3000; + const minBackoffTimeInMs = 1500; // We choose a small non-zero value here. If this value // is too small (like zero), the VS Code extension becomes @@ -866,6 +866,7 @@ export class AnalyzerService { diagnostics: this._program.getDiagnostics(this._configOptions), filesInProgram: this._program.getFileCount(), filesRequiringAnalysis: filesLeftToAnalyze, + checkingOnlyOpenFiles: this._program.isCheckingOnlyOpenFiles(), fatalErrorOccurred: false, configParseErrorOccurred: false, elapsedTime: duration.getDurationInSeconds() @@ -890,6 +891,7 @@ export class AnalyzerService { diagnostics: [], filesInProgram: 0, filesRequiringAnalysis: 0, + checkingOnlyOpenFiles: true, fatalErrorOccurred: true, configParseErrorOccurred: false, elapsedTime: 0 @@ -907,6 +909,7 @@ export class AnalyzerService { diagnostics: fileDiags, filesInProgram: this._program.getFileCount(), filesRequiringAnalysis: this._program.getFilesToAnalyzeCount(), + checkingOnlyOpenFiles: this._program.isCheckingOnlyOpenFiles(), fatalErrorOccurred: false, configParseErrorOccurred: false, elapsedTime: 0 @@ -921,6 +924,7 @@ export class AnalyzerService { diagnostics: [], filesInProgram: 0, filesRequiringAnalysis: 0, + checkingOnlyOpenFiles: true, fatalErrorOccurred: false, configParseErrorOccurred: true, elapsedTime: 0 diff --git a/server/src/common/commandLineOptions.ts b/server/src/common/commandLineOptions.ts index e2eab7741..759cf1afb 100644 --- a/server/src/common/commandLineOptions.ts +++ b/server/src/common/commandLineOptions.ts @@ -46,6 +46,9 @@ export class CommandLineOptions { // Emit verbose information to console? verboseOutput?: boolean; + // Indicates that only open files should be checked. + checkOnlyOpenFiles?: boolean; + // Indicates that the settings came from VS Code rather than // from the command-line. Useful for providing clearer error // messages. diff --git a/server/src/common/configOptions.ts b/server/src/common/configOptions.ts index 1a3ae5529..5ffe775ff 100644 --- a/server/src/common/configOptions.ts +++ b/server/src/common/configOptions.ts @@ -323,6 +323,9 @@ export class ConfigOptions { // Emit verbose information to console? verboseOutput: boolean; + // Perform type checking and report diagnostics only for open files? + checkOnlyOpenFiles: boolean; + //--------------------------------------------------------------- // Diagnostics Settings diff --git a/server/src/pyright.ts b/server/src/pyright.ts index 68de204a4..4c634f71d 100644 --- a/server/src/pyright.ts +++ b/server/src/pyright.ts @@ -109,6 +109,7 @@ function processArgs() { } options.verboseOutput = !!args.verbose; + options.checkOnlyOpenFiles = false; const watch = args.watch !== undefined; options.watch = watch; diff --git a/server/src/server.ts b/server/src/server.ts index 5b8a735dd..32b15a0f3 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -32,6 +32,7 @@ interface PythonSettings { interface PyrightSettings { disableLanguageServices?: boolean; + openFilesOnly?: boolean; } interface WorkspaceServiceInstance { @@ -92,7 +93,8 @@ function _createAnalyzerService(name: string): AnalyzerService { }); if (results.filesRequiringAnalysis > 0) { - if (!_isDisplayingProgress) { + // Display a progress spinner if we're checking the entire program. + if (!_isDisplayingProgress && !results.checkingOnlyOpenFiles) { _isDisplayingProgress = true; _connection.sendNotification('pyright/beginProgress'); } @@ -573,68 +575,72 @@ _connection.onDidCloseTextDocument(params => { service.setFileClosed(filePath); }); -function getConfiguration(workspace: WorkspaceServiceInstance, section: string) { - if (workspace.rootUri) { - return _connection.workspace.getConfiguration({ - scopeUri: workspace.rootUri || undefined, - section - }); - } else { - return _connection.workspace.getConfiguration(section); - } +function getConfiguration(workspace: WorkspaceServiceInstance, sections: string[]) { + const scopeUri = workspace.rootUri ? workspace.rootUri : undefined; + return _connection.workspace.getConfiguration( + sections.map(section => { + return { + scopeUri, + section + }; + }) + ); +} + +function fetchSettingsForWorkspace(workspace: WorkspaceServiceInstance, + callback: (pythonSettings: PythonSettings, pyrightSettings: PyrightSettings) => void) { + const pythonSettingsPromise = getConfiguration(workspace, ['python', 'pyright']); + pythonSettingsPromise.then((settings: [PythonSettings, PyrightSettings]) => { + callback(settings[0], settings[1]); + }, () => { + // An error occurred trying to read the settings + // for this workspace, so ignore. + }); } function updateSettingsForAllWorkspaces() { _workspaceMap.forEach(workspace => { - const pythonSettingsPromise = getConfiguration(workspace, 'python'); - pythonSettingsPromise.then((settings: PythonSettings) => { - updateOptionsAndRestartService(workspace, settings); - }, () => { - // An error occurred trying to read the settings - // for this workspace, so ignore. - }); + fetchSettingsForWorkspace(workspace, (pythonSettings, pyrightSettings) => { + updateOptionsAndRestartService(workspace, pythonSettings, pyrightSettings); - const pyrightSettingsPromise = getConfiguration(workspace, 'pyright'); - pyrightSettingsPromise.then((settings?: PyrightSettings) => { - workspace.disableLanguageServices = settings !== undefined && - !!settings.disableLanguageServices; - }, () => { - // An error occurred trying to read the settings - // for this workspace, so ignore. + workspace.disableLanguageServices = !!pyrightSettings.disableLanguageServices; }); }); } function updateOptionsAndRestartService(workspace: WorkspaceServiceInstance, - settings: PythonSettings, typeStubTargetImportName?: string) { + pythonSettings: PythonSettings, pyrightSettings?: PyrightSettings, + typeStubTargetImportName?: string) { const commandLineOptions = new CommandLineOptions(workspace.rootPath, true); commandLineOptions.watch = true; + commandLineOptions.checkOnlyOpenFiles = pyrightSettings ? + !!pyrightSettings.openFilesOnly : true; - if (settings.venvPath) { + if (pythonSettings.venvPath) { commandLineOptions.venvPath = combinePaths(workspace.rootPath || _rootPath, - normalizePath(_expandPathVariables(settings.venvPath))); + normalizePath(_expandPathVariables(pythonSettings.venvPath))); } - if (settings.pythonPath) { + if (pythonSettings.pythonPath) { // The Python VS Code extension treats the value "python" specially. This means // the local python interpreter should be used rather than interpreting the // setting value as a path to the interpreter. We'll simply ignore it in this case. - if (settings.pythonPath.trim() !== 'python') { + if (pythonSettings.pythonPath.trim() !== 'python') { commandLineOptions.pythonPath = combinePaths(workspace.rootPath || _rootPath, - normalizePath(_expandPathVariables(settings.pythonPath))); + normalizePath(_expandPathVariables(pythonSettings.pythonPath))); } } - if (settings.analysis && - settings.analysis.typeshedPaths && - settings.analysis.typeshedPaths.length > 0) { + if (pythonSettings.analysis && + pythonSettings.analysis.typeshedPaths && + pythonSettings.analysis.typeshedPaths.length > 0) { // Pyright supports only one typeshed path currently, whereas the // official VS Code Python extension supports multiple typeshed paths. // We'll use the first one specified and ignore the rest. commandLineOptions.typeshedPath = - _expandPathVariables(settings.analysis.typeshedPaths[0]); + _expandPathVariables(pythonSettings.analysis.typeshedPaths[0]); } if (typeStubTargetImportName) { @@ -712,12 +718,8 @@ _connection.onExecuteCommand((cmdParams: ExecuteCommandParams) => { disableLanguageServices: true }; - const pythonSettingsPromise = getConfiguration(workspace, 'python'); - pythonSettingsPromise.then((settings: PythonSettings) => { - updateOptionsAndRestartService(workspace, settings, importName); - }, () => { - // An error occurred trying to read the settings - // for this workspace, so ignore. + fetchSettingsForWorkspace(workspace, (pythonSettings, pyrightSettings) => { + updateOptionsAndRestartService(workspace, pythonSettings, pyrightSettings, importName); }); });