diff --git a/.vscodeignore b/.vscodeignore index f6ba7c39ff26..2bae3e5be984 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -46,5 +46,13 @@ scripts/** src/** test/** tmp/** +typeshed/tests/** +typeshed/.flake8 +typeshed/.git +typeshed/.gitignore +typeshed/.travis.yml +typeshed/CONTRIBUTING.md +typeshed/README.md +typeshed/*.txt typings/** vsc-extension-quickstart.md diff --git a/package.json b/package.json index ebf7f0cb38fc..4e36d3de2415 100644 --- a/package.json +++ b/package.json @@ -1275,6 +1275,57 @@ "description": "Path to directory containing the Jedi library (this path will contain the 'Jedi' sub directory).", "scope": "resource" }, + "python.analysis.openFilesOnly": { + "type": "boolean", + "default": false, + "description": "Only show errors and warnings for open files rather than for the entire workspace.", + "scope": "resource" + }, + "python.analysis.typeshedPaths": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "Paths to look for typeshed modules.", + "scope": "resource" + }, + "python.analysis.errors": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "List of diagnostics messages to be shown as errors.", + "scope": "resource" + }, + "python.analysis.warnings": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "List of diagnostics messages to be shown as warnings.", + "scope": "resource" + }, + "python.analysis.information": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "List of diagnostics messages to be shown as information.", + "scope": "resource" + }, + "python.analysis.disabled": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "List of suppressed diagnostic messages.", + "scope": "resource" + }, "python.linting.enabled": { "type": "boolean", "default": true, diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 01d2e911e902..3c50866ae56c 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -6,12 +6,12 @@ import * as path from 'path'; import { OutputChannel, Uri } from 'vscode'; import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; +import { PythonSettings } from '../common/configSettings'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; -import { IConfigurationService, IExtensionContext, IOutputChannel } from '../common/types'; -import { IInterpreterService } from '../interpreter/contracts'; +import { IConfigurationService, IExtensionContext, IOutputChannel, IPythonSettings } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { PYTHON_ANALYSIS_ENGINE_DOWNLOADED, @@ -38,7 +38,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private readonly fs: IFileSystem; private readonly sw = new StopWatch(); private readonly platformData: PlatformData; - private readonly interpreterService: IInterpreterService; private readonly startupCompleted: Deferred; private readonly disposables: Disposable[] = []; private readonly context: IExtensionContext; @@ -47,6 +46,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private languageClient: LanguageClient | undefined; private interpreterHash: string = ''; + private excludedFiles: string[] = []; + private typeshedPaths: string[] = []; private loadExtensionArgs: {} | undefined; constructor(@inject(IServiceContainer) private readonly services: IServiceContainer) { @@ -56,7 +57,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.fs = this.services.get(IFileSystem); this.platformData = new PlatformData(services.get(IPlatformService), this.fs); - this.interpreterService = this.services.get(IInterpreterService); this.workspace = this.services.get(IWorkspaceService); // Currently only a single root. Multi-root support is future. @@ -76,6 +76,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } } )); + + (this.configuration.getSettings() as PythonSettings).addListener('change', this.onSettingsChanged); } public async activate(): Promise { @@ -84,7 +86,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (!clientOptions) { return false; } - this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.restartLanguageServer())); return this.startLanguageServer(clientOptions); } @@ -96,19 +97,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { for (const d of this.disposables) { d.dispose(); } - } - - private async restartLanguageServer(): Promise { - if (!this.context) { - return; - } - const ids = new InterpreterDataService(this.context, this.services); - const idata = await ids.getInterpreterData(); - if (!idata || idata.hash !== this.interpreterHash) { - this.interpreterHash = idata ? idata.hash : ''; - await this.deactivate(); - await this.activate(); - } + (this.configuration.getSettings() as PythonSettings).removeListener('change', this.onSettingsChanged); } private async startLanguageServer(clientOptions: LanguageClientOptions): Promise { @@ -204,34 +193,27 @@ export class AnalysisExtensionActivator implements IExtensionActivator { properties['PrefixPath'] = interpreterData.prefix; } - let searchPathsString = interpreterData ? interpreterData.searchPaths : ''; - let typeshedPaths: string[] = []; + // tslint:disable-next-line:no-string-literal + properties['DatabasePath'] = path.join(this.context.extensionPath, analysisEngineFolder); + let searchPaths = interpreterData ? interpreterData.searchPaths.split(path.delimiter) : []; const settings = this.configuration.getSettings(); if (settings.autoComplete) { const extraPaths = settings.autoComplete.extraPaths; if (extraPaths && extraPaths.length > 0) { - searchPathsString = `${searchPathsString};${extraPaths.join(';')}`; + searchPaths.push(...extraPaths); } - typeshedPaths = settings.autoComplete.typeshedPaths; } - // tslint:disable-next-line:no-string-literal - properties['DatabasePath'] = path.join(this.context.extensionPath, analysisEngineFolder); - // Make sure paths do not contain multiple slashes so file URIs // in VS Code (Node.js) and in the language server (.NET) match. // Note: for the language server paths separator is always ; - const searchPaths = searchPathsString.split(path.delimiter).map(p => path.normalize(p)); - // tslint:disable-next-line:no-string-literal - properties['SearchPaths'] = `${searchPaths.join(';')};${pythonPath}`; - - if (!typeshedPaths || typeshedPaths.length === 0) { - typeshedPaths = [path.join(this.context.extensionPath, 'typeshed')]; - } + searchPaths.push(pythonPath); + searchPaths = searchPaths.map(p => path.normalize(p)); const selector = [{ language: PYTHON, scheme: 'file' }]; - const excludeFiles = this.getExcludedFiles(); + this.excludedFiles = this.getExcludedFiles(); + this.typeshedPaths = this.getTypeshedPaths(settings); // Options to control the language client return { @@ -253,9 +235,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { maxDocumentationTextLength: 0 }, searchPaths, - typeStubSearchPaths: typeshedPaths, - asyncStartup: true, - excludeFiles: excludeFiles, + typeStubSearchPaths: this.typeshedPaths, + excludeFiles: this.excludedFiles, testEnvironment: isTestExecution() } }; @@ -289,4 +270,49 @@ export class AnalysisExtensionActivator implements IExtensionActivator { .forEach(p => list.push(p)); } } + + private getTypeshedPaths(settings: IPythonSettings): string[] { + return settings.analysis.typeshedPaths && settings.analysis.typeshedPaths.length > 0 + ? settings.analysis.typeshedPaths + : [path.join(this.context.extensionPath, 'typeshed')]; + } + + private async onSettingsChanged(): Promise { + const ids = new InterpreterDataService(this.context, this.services); + const idata = await ids.getInterpreterData(); + if (!idata || idata.hash !== this.interpreterHash) { + this.interpreterHash = idata ? idata.hash : ''; + await this.restartLanguageServer(); + return; + } + + const excludedFiles = this.getExcludedFiles(); + await this.restartLanguageServerIfArrayChanged(this.excludedFiles, excludedFiles); + + const settings = this.configuration.getSettings(); + const typeshedPaths = this.getTypeshedPaths(settings); + await this.restartLanguageServerIfArrayChanged(this.typeshedPaths, typeshedPaths); + } + + private async restartLanguageServerIfArrayChanged(oldArray: string[], newArray: string[]): Promise { + if (newArray.length !== oldArray.length) { + await this.restartLanguageServer(); + return; + } + + for (let i = 0; i < oldArray.length; i += 1) { + if (oldArray[i] !== newArray[i]) { + await this.restartLanguageServer(); + return; + } + } + } + + private async restartLanguageServer(): Promise { + if (!this.context) { + return; + } + await this.deactivate(); + await this.activate(); + } } diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 4c55c4a2b3a3..fe3b4d570b7b 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -8,6 +8,7 @@ import { sendTelemetryEvent } from '../telemetry'; import { COMPLETION_ADD_BRACKETS, FORMAT_ON_TYPE } from '../telemetry/constants'; import { isTestExecution } from './constants'; import { + IAnalysisSettings, IAutoCompleteSettings, IFormattingSettings, ILintingSettings, @@ -44,6 +45,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { public workspaceSymbols!: IWorkspaceSymbolSettings; public disableInstallationChecks = false; public globalModuleInstallation = false; + public analysis!: IAnalysisSettings; private workspaceRoot: Uri; private disposables: Disposable[] = []; @@ -147,6 +149,14 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.linting = lintingSettings; } + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const analysisSettings = systemVariables.resolveAny(pythonSettings.get('analysis'))!; + if (this.analysis) { + Object.assign(this.analysis, analysisSettings); + } else { + this.analysis = analysisSettings; + } + this.disableInstallationChecks = pythonSettings.get('disableInstallationCheck') === true; this.globalModuleInstallation = pythonSettings.get('globalModuleInstallation') === true; diff --git a/src/client/common/types.ts b/src/client/common/types.ts index c671ae4e3bd5..8af84dd5d772 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -133,6 +133,7 @@ export interface IPythonSettings { readonly envFile: string; readonly disableInstallationChecks: boolean; readonly globalModuleInstallation: boolean; + readonly analysis: IAnalysisSettings; } export interface ISortImportSettings { readonly path: string; @@ -236,13 +237,16 @@ export interface ITerminalSettings { readonly launchArgs: string[]; readonly activateEnvironment: boolean; } -export interface IPythonAnalysisEngineSettings { - readonly showAdvancedMembers: boolean; +export interface IAnalysisSettings { + readonly openFilesOnly: boolean; readonly typeshedPaths: string[]; + readonly errors: string[]; + readonly warnings: string[]; + readonly information: string[]; + readonly disabled: string[]; } export const IConfigurationService = Symbol('IConfigurationService'); - export interface IConfigurationService { getSettings(resource?: Uri): IPythonSettings; isTestExecution(): boolean;