From a764bc9822e113a18c954286a62b26fde6514a64 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 13 Feb 2018 14:47:16 -0800 Subject: [PATCH 01/97] Undo changes --- pythonFiles/completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index e530be32b367..99f8582c614c 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -88,7 +88,7 @@ def _generate_signature(self, completion): return '' return '%s(%s)' % ( completion.name, - ', '.join(p.description[6:] for p in completion.params if p)) + ', '.join(self._get_param_name(p.description) for p in completion.params if p)) def _get_call_signatures(self, script): """Extract call signatures from jedi.api.Script object in failsafe way. From 9d1b2cc85c3563ccc9b7a242206ae6a5b6d644f6 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 13 Feb 2018 15:44:21 -0800 Subject: [PATCH 02/97] Test fixes --- pythonFiles/completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index 99f8582c614c..e530be32b367 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -88,7 +88,7 @@ def _generate_signature(self, completion): return '' return '%s(%s)' % ( completion.name, - ', '.join(self._get_param_name(p.description) for p in completion.params if p)) + ', '.join(p.description[6:] for p in completion.params if p)) def _get_call_signatures(self, script): """Extract call signatures from jedi.api.Script object in failsafe way. From a91291a072bc9730252c199969e7a40a698ca2d2 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 1 Mar 2018 22:03:47 -0800 Subject: [PATCH 03/97] Increase timeout --- src/test/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/index.ts b/src/test/index.ts index 135ca5d8b6c1..22ffd45a0675 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -21,7 +21,7 @@ const grep = IS_CI_SERVER && IS_CI_SERVER_TEST_DEBUGGER ? 'Debug' : undefined; const options: MochaSetupOptions & { retries: number } = { ui: 'tdd', useColors: true, - timeout: 25000, + timeout: 35000, retries: 3, grep }; From bf266af260b16e1021511a7274c3d6576182179f Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 6 Mar 2018 16:26:53 -0800 Subject: [PATCH 04/97] Remove double event listening --- src/client/providers/linterProvider.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/client/providers/linterProvider.ts b/src/client/providers/linterProvider.ts index fb66aab3971b..27aa85ffa61f 100644 --- a/src/client/providers/linterProvider.ts +++ b/src/client/providers/linterProvider.ts @@ -9,7 +9,6 @@ import { ConfigSettingMonitor } from '../common/configSettingMonitor'; import { isTestExecution } from '../common/constants'; import { IFileSystem } from '../common/platform/types'; import { IConfigurationService } from '../common/types'; -import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; import { ILinterManager, ILintingEngine } from '../linters/types'; @@ -17,7 +16,6 @@ export class LinterProvider implements vscode.Disposable { private context: vscode.ExtensionContext; private disposables: vscode.Disposable[]; private configMonitor: ConfigSettingMonitor; - private interpreterService: IInterpreterService; private documents: IDocumentManager; private configuration: IConfigurationService; private linterManager: ILinterManager; @@ -31,12 +29,9 @@ export class LinterProvider implements vscode.Disposable { this.fs = serviceContainer.get(IFileSystem); this.engine = serviceContainer.get(ILintingEngine); this.linterManager = serviceContainer.get(ILinterManager); - this.interpreterService = serviceContainer.get(IInterpreterService); this.documents = serviceContainer.get(IDocumentManager); this.configuration = serviceContainer.get(IConfigurationService); - this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.engine.lintOpenPythonFiles())); - this.documents.onDidOpenTextDocument(e => this.onDocumentOpened(e), this.context.subscriptions); this.documents.onDidCloseTextDocument(e => this.onDocumentClosed(e), this.context.subscriptions); this.documents.onDidSaveTextDocument((e) => this.onDocumentSaved(e), this.context.subscriptions); From 7bc6bd643e5ec53eb71a259ad2a5a43a643f7e33 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 6 Mar 2018 16:35:39 -0800 Subject: [PATCH 05/97] Remove test --- src/test/linters/lint.provider.test.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/test/linters/lint.provider.test.ts b/src/test/linters/lint.provider.test.ts index 023ee86223be..51e49d3d35b9 100644 --- a/src/test/linters/lint.provider.test.ts +++ b/src/test/linters/lint.provider.test.ts @@ -113,16 +113,6 @@ suite('Linting - Provider', () => { engine.verify(x => x.lintDocument(document.object, 'save'), TypeMoq.Times.never()); }); - test('Lint on change interpreters', () => { - const e = new vscode.EventEmitter(); - interpreterService.setup(x => x.onDidChangeInterpreter).returns(() => e.event); - - // tslint:disable-next-line:no-unused-variable - const provider = new LinterProvider(context.object, serviceContainer); - e.fire(); - engine.verify(x => x.lintOpenPythonFiles(), TypeMoq.Times.once()); - }); - test('Lint on save pylintrc', async () => { docManager.setup(x => x.onDidSaveTextDocument).returns(() => emitter.event); document.setup(x => x.uri).returns(() => vscode.Uri.file('.pylintrc')); From 8ce8b48db3aedb785026b2fe22071b40d2f6c048 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 6 Mar 2018 17:02:12 -0800 Subject: [PATCH 06/97] Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. --- src/test/linters/lint.provider.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/linters/lint.provider.test.ts b/src/test/linters/lint.provider.test.ts index 51e49d3d35b9..023ee86223be 100644 --- a/src/test/linters/lint.provider.test.ts +++ b/src/test/linters/lint.provider.test.ts @@ -113,6 +113,16 @@ suite('Linting - Provider', () => { engine.verify(x => x.lintDocument(document.object, 'save'), TypeMoq.Times.never()); }); + test('Lint on change interpreters', () => { + const e = new vscode.EventEmitter(); + interpreterService.setup(x => x.onDidChangeInterpreter).returns(() => e.event); + + // tslint:disable-next-line:no-unused-variable + const provider = new LinterProvider(context.object, serviceContainer); + e.fire(); + engine.verify(x => x.lintOpenPythonFiles(), TypeMoq.Times.once()); + }); + test('Lint on save pylintrc', async () => { docManager.setup(x => x.onDidSaveTextDocument).returns(() => emitter.event); document.setup(x => x.uri).returns(() => vscode.Uri.file('.pylintrc')); From e3a549e58e0f888d79364e353cbcdf00d86b3416 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 6 Mar 2018 17:02:47 -0800 Subject: [PATCH 07/97] Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. --- src/client/providers/linterProvider.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/providers/linterProvider.ts b/src/client/providers/linterProvider.ts index 27aa85ffa61f..fb66aab3971b 100644 --- a/src/client/providers/linterProvider.ts +++ b/src/client/providers/linterProvider.ts @@ -9,6 +9,7 @@ import { ConfigSettingMonitor } from '../common/configSettingMonitor'; import { isTestExecution } from '../common/constants'; import { IFileSystem } from '../common/platform/types'; import { IConfigurationService } from '../common/types'; +import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; import { ILinterManager, ILintingEngine } from '../linters/types'; @@ -16,6 +17,7 @@ export class LinterProvider implements vscode.Disposable { private context: vscode.ExtensionContext; private disposables: vscode.Disposable[]; private configMonitor: ConfigSettingMonitor; + private interpreterService: IInterpreterService; private documents: IDocumentManager; private configuration: IConfigurationService; private linterManager: ILinterManager; @@ -29,9 +31,12 @@ export class LinterProvider implements vscode.Disposable { this.fs = serviceContainer.get(IFileSystem); this.engine = serviceContainer.get(ILintingEngine); this.linterManager = serviceContainer.get(ILinterManager); + this.interpreterService = serviceContainer.get(IInterpreterService); this.documents = serviceContainer.get(IDocumentManager); this.configuration = serviceContainer.get(IConfigurationService); + this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.engine.lintOpenPythonFiles())); + this.documents.onDidOpenTextDocument(e => this.onDocumentOpened(e), this.context.subscriptions); this.documents.onDidCloseTextDocument(e => this.onDocumentClosed(e), this.context.subscriptions); this.documents.onDidSaveTextDocument((e) => this.onDocumentSaved(e), this.context.subscriptions); From c879aa604bb70311acb52e40aab317b22c78680e Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 13 Feb 2018 14:47:16 -0800 Subject: [PATCH 08/97] Undo changes --- pythonFiles/completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index e530be32b367..99f8582c614c 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -88,7 +88,7 @@ def _generate_signature(self, completion): return '' return '%s(%s)' % ( completion.name, - ', '.join(p.description[6:] for p in completion.params if p)) + ', '.join(self._get_param_name(p.description) for p in completion.params if p)) def _get_call_signatures(self, script): """Extract call signatures from jedi.api.Script object in failsafe way. From 716968f8977dafd3bcd7966f1d3f24bbd0c2dc36 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 13 Feb 2018 15:44:21 -0800 Subject: [PATCH 09/97] Test fixes --- pythonFiles/completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index 99f8582c614c..e530be32b367 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -88,7 +88,7 @@ def _generate_signature(self, completion): return '' return '%s(%s)' % ( completion.name, - ', '.join(self._get_param_name(p.description) for p in completion.params if p)) + ', '.join(p.description[6:] for p in completion.params if p)) def _get_call_signatures(self, script): """Extract call signatures from jedi.api.Script object in failsafe way. From 63a138508b7c4c6addad9c7ae58aca8dfa24a0f2 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Mon, 5 Mar 2018 15:52:36 -0800 Subject: [PATCH 10/97] .NET Core check --- src/client/common/configuration/service.ts | 47 +++++++++++++++++++++- src/client/common/types.ts | 1 + 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/client/common/configuration/service.ts b/src/client/common/configuration/service.ts index effbba72efed..6ead14a0e7cf 100644 --- a/src/client/common/configuration/service.ts +++ b/src/client/common/configuration/service.ts @@ -1,16 +1,24 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; +import * as path from 'path'; import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from 'vscode'; +import { IServiceContainer } from '../../ioc/types'; +import { IApplicationShell } from '../application/types'; import { PythonSettings } from '../configSettings'; +import { IFileSystem, IPlatformService } from '../platform/types'; import { IConfigurationService, IPythonSettings } from '../types'; @injectable() export class ConfigurationService implements IConfigurationService { + constructor(@inject(IServiceContainer) private services: IServiceContainer) { + } + public getSettings(resource?: Uri): IPythonSettings { return PythonSettings.getInstance(resource); } + public async updateSettingAsync(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise { const settingsInfo = PythonSettings.getSettingsUriAndTarget(resource); @@ -32,6 +40,10 @@ export class ConfigurationService implements IConfigurationService { return process.env.VSC_PYTHON_CI_TEST === '1'; } + public async checkDependencies(): Promise { + return this.checkDotNet(); + } + private async verifySetting(pythonConfig: WorkspaceConfiguration, target: ConfigurationTarget, settingName: string, value?: {}): Promise { if (this.isTestExecution()) { let retries = 0; @@ -55,4 +67,37 @@ export class ConfigurationService implements IConfigurationService { } while (retries < 20); } } + + private async checkDotNet(): Promise { + if (!await this.isDotNetInstalled()) { + const appShell = this.services.get(IApplicationShell); + if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { + appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); + appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); + } + return false; + } + return true; + } + + private async isDotNetInstalled(): Promise { + const platform = this.services.get(IPlatformService); + const fs = this.services.get(IFileSystem); + const versions = ['2.0.0']; + let prefix: string; + + if (platform.isWindows) { + const drive = process.env.SystemDrive; + prefix = `${drive}\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\`; + } else { + prefix = '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/'; + } + + for (const ver of versions) { + if (await fs.directoryExistsAsync(path.join(prefix, ver))) { + return true; + } + } + return false; + } } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 4083b0965fd9..9c245e2ee69b 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -218,6 +218,7 @@ export interface IConfigurationService { getSettings(resource?: Uri): IPythonSettings; isTestExecution(): boolean; updateSettingAsync(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise; + checkDependencies(): Promise; } export const ISocketServer = Symbol('ISocketServer'); From 7ea7717da83d47c4c59fb589fc9524129109ffa1 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 6 Mar 2018 22:52:43 -0800 Subject: [PATCH 11/97] Better find dotnet --- src/client/common/configuration/service.ts | 24 ++++------------------ 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/client/common/configuration/service.ts b/src/client/common/configuration/service.ts index 6ead14a0e7cf..e5d862930ca3 100644 --- a/src/client/common/configuration/service.ts +++ b/src/client/common/configuration/service.ts @@ -2,12 +2,11 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import * as path from 'path'; import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; import { IApplicationShell } from '../application/types'; import { PythonSettings } from '../configSettings'; -import { IFileSystem, IPlatformService } from '../platform/types'; +import { IProcessService } from '../process/types'; import { IConfigurationService, IPythonSettings } from '../types'; @injectable() @@ -81,23 +80,8 @@ export class ConfigurationService implements IConfigurationService { } private async isDotNetInstalled(): Promise { - const platform = this.services.get(IPlatformService); - const fs = this.services.get(IFileSystem); - const versions = ['2.0.0']; - let prefix: string; - - if (platform.isWindows) { - const drive = process.env.SystemDrive; - prefix = `${drive}\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\`; - } else { - prefix = '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/'; - } - - for (const ver of versions) { - if (await fs.directoryExistsAsync(path.join(prefix, ver))) { - return true; - } - } - return false; + const ps = this.services.get(IProcessService); + const result = await ps.exec('dotnet', ['--version']); + return result.stdout.trim().startsWith('2.'); } } From 75be0da9a411569956e9989f5fc30302f78f6fc2 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 13 Mar 2018 11:43:28 -0700 Subject: [PATCH 12/97] Fix pip test --- src/test/common/moduleInstaller.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index b1b4d5c52fcb..efa57c0c3b00 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -19,7 +19,7 @@ import { CurrentProcess } from '../../client/common/process/currentProcess'; import { IProcessService, IPythonExecutionFactory } from '../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../client/common/terminal/types'; import { IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, IPythonSettings, IsWindows } from '../../client/common/types'; -import { ICondaService, IInterpreterLocatorService, IInterpreterService, INTERPRETER_LOCATOR_SERVICE, InterpreterType, PIPENV_SERVICE } from '../../client/interpreter/contracts'; +import { ICondaService, IInterpreterLocatorService, IInterpreterService, INTERPRETER_LOCATOR_SERVICE, InterpreterType, PIPENV_SERVICE, PythonInterpreter } from '../../client/interpreter/contracts'; import { IServiceContainer } from '../../client/ioc/types'; import { rootWorkspaceUri } from '../common'; import { MockModuleInstaller } from '../mocks/moduleInstaller'; @@ -71,8 +71,10 @@ suite('Module Installer', () => { ioc.serviceManager.addSingleton(IModuleInstaller, PipEnvInstaller); condaService = TypeMoq.Mock.ofType(); ioc.serviceManager.addSingletonInstance(ICondaService, condaService.object); + interpreterService = TypeMoq.Mock.ofType(); ioc.serviceManager.addSingletonInstance(IInterpreterService, interpreterService.object); + ioc.serviceManager.addSingleton(IPathUtils, PathUtils); ioc.serviceManager.addSingleton(ICurrentProcess, CurrentProcess); ioc.serviceManager.addSingleton(IFileSystem, FileSystem); @@ -191,6 +193,12 @@ suite('Module Installer', () => { ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); + const interpreter: PythonInterpreter = { + type: InterpreterType.Unknown, + path: 'python' + }; + interpreterService.setup(x => x.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(interpreter)); + const moduleName = 'xyz'; const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); From 2701768f466df33e682504097d24a053f7c609e6 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 13 Mar 2018 12:28:27 -0700 Subject: [PATCH 13/97] Linting tests --- src/test/linters/lint.commands.test.ts | 3 ++- src/test/linters/lint.manager.test.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/linters/lint.commands.test.ts b/src/test/linters/lint.commands.test.ts index d843c7128f4b..5cac8d6f997f 100644 --- a/src/test/linters/lint.commands.test.ts +++ b/src/test/linters/lint.commands.test.ts @@ -36,6 +36,7 @@ suite('Linting - Linter Selector', () => { const cont = new Container(); const serviceManager = new ServiceManager(cont); serviceContainer = new ServiceContainer(cont); + serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); appShell = TypeMoq.Mock.ofType(); serviceManager.addSingleton(IConfigurationService, ConfigurationService); @@ -74,7 +75,7 @@ suite('Linting - Linter Selector', () => { }); test('Run linter command', async () => { - commands.runLinting(); + await commands.runLinting(); engine.verify(p => p.lintOpenPythonFiles(), TypeMoq.Times.once()); }); diff --git a/src/test/linters/lint.manager.test.ts b/src/test/linters/lint.manager.test.ts index 68f1e20e39c3..06bbd289d4ba 100644 --- a/src/test/linters/lint.manager.test.ts +++ b/src/test/linters/lint.manager.test.ts @@ -8,6 +8,7 @@ import { EnumEx } from '../../client/common/enumUtils'; import { IConfigurationService, ILintingSettings, IPythonSettings, Product } from '../../client/common/types'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; +import { IServiceContainer } from '../../client/ioc/types'; import { LinterManager } from '../../client/linters/linterManager'; import { ILinterManager, LinterId } from '../../client/linters/types'; import { initialize } from '../initialize'; @@ -23,6 +24,7 @@ suite('Linting - Manager', () => { const cont = new Container(); const serviceManager = new ServiceManager(cont); const serviceContainer = new ServiceContainer(cont); + serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); serviceManager.addSingleton(IConfigurationService, ConfigurationService); configService = serviceManager.get(IConfigurationService); From a22e705c83665187fee675f2cfd1a9ccd75179d2 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 15 Mar 2018 16:24:35 -0700 Subject: [PATCH 14/97] Undo accidental changes --- src/test/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/index.ts b/src/test/index.ts index 22ffd45a0675..135ca5d8b6c1 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -21,7 +21,7 @@ const grep = IS_CI_SERVER && IS_CI_SERVER_TEST_DEBUGGER ? 'Debug' : undefined; const options: MochaSetupOptions & { retries: number } = { ui: 'tdd', useColors: true, - timeout: 35000, + timeout: 25000, retries: 3, grep }; From 5441644c0ef9780419336ba0ed4638dd1a611d8c Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 16 Mar 2018 13:50:32 -0700 Subject: [PATCH 15/97] Add clone and build PTVS --- appveyor.yml | 12 ++++++++++++ src/test/.vscode/settings.json | 10 ++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 774b4d93ca18..ac4963577275 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,6 @@ +image: Visual Studio 2017 +shallow_clone: true + environment: matrix: - PYTHON: "C:\\Python36" @@ -20,6 +23,15 @@ install: - python -m easy_install -U setuptools - "%PYTHON%/Scripts/pip.exe install --upgrade -r requirements.txt" +build_script: + - ps: git clone https://github.com/Microsoft/PTVS.git c:/Projects/PTVS + - ps: c:\Projects\PTVS + - ps: git checkout origin/master + - ps: cd c:\Projects\PTVS\Python\Product\VSCode\AnalysisVsc + - ps: dotnet --info + - ps: dotnet build + - ps: xcopy /S /D C:\Projects\PTVS\BuildOutput\VsCode\raw c:\Projects\vscode-python\analysis + test_script: - yarn run clean - yarn run vscode:prepublish diff --git a/src/test/.vscode/settings.json b/src/test/.vscode/settings.json index cc64e708bea1..ba9c239e801d 100644 --- a/src/test/.vscode/settings.json +++ b/src/test/.vscode/settings.json @@ -5,11 +5,8 @@ "python.unitTest.nosetestArgs": [], "python.unitTest.pyTestArgs": [], "python.unitTest.unittestArgs": [ - "-v", - "-s", - ".", - "-p", - "*test*.py" + "-s=./tests", + "-p=test_*.py" ], "python.sortImports.args": [], "python.linting.lintOnSave": false, @@ -20,5 +17,6 @@ "python.linting.pylamaEnabled": false, "python.linting.mypyEnabled": false, "python.formatting.provider": "yapf", - "python.linting.pylintUseMinimalCheckers": false + "python.linting.pylintUseMinimalCheckers": false, + "python.pythonPath": "python" } From 97175e1a434abb26539f54e1b0d11235a5c6687d Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 16 Mar 2018 16:10:59 -0700 Subject: [PATCH 16/97] Appveyor PTVS build --- appveyor.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ac4963577275..cb1d7fbf2673 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,13 +24,14 @@ install: - "%PYTHON%/Scripts/pip.exe install --upgrade -r requirements.txt" build_script: - - ps: git clone https://github.com/Microsoft/PTVS.git c:/Projects/PTVS - - ps: c:\Projects\PTVS - - ps: git checkout origin/master - - ps: cd c:\Projects\PTVS\Python\Product\VSCode\AnalysisVsc - - ps: dotnet --info - - ps: dotnet build - - ps: xcopy /S /D C:\Projects\PTVS\BuildOutput\VsCode\raw c:\Projects\vscode-python\analysis + - git clone https://github.com/MikhailArkhipov/PTVS.git ./PTVS + - "cd PTVS" + - git checkout origin/vsc + - "cd Python\Product\VSCode\AnalysisVsc" + - "dotnet --info" + - "dotnet build" + - "cd ..\..\..\..\.." + - "xcopy /S /D PTVS\BuildOutput\VsCode\raw analysis" test_script: - yarn run clean From 1e97e6ac410efdccd296895956dd70f08a0df6ed Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 16 Mar 2018 16:39:58 -0700 Subject: [PATCH 17/97] Fix slashes --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index cb1d7fbf2673..22417a007790 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -27,11 +27,11 @@ build_script: - git clone https://github.com/MikhailArkhipov/PTVS.git ./PTVS - "cd PTVS" - git checkout origin/vsc - - "cd Python\Product\VSCode\AnalysisVsc" + - "cd Python\\Product\\VSCode\\AnalysisVsc" - "dotnet --info" - "dotnet build" - - "cd ..\..\..\..\.." - - "xcopy /S /D PTVS\BuildOutput\VsCode\raw analysis" + - "cd ..\\..\\..\\..\\.." + - "xcopy /S /D PTVS\\BuildOutput\\VsCode\\raw analysis" test_script: - yarn run clean From 86ac26949930ec4d4908e62c2e6b5a9334109a82 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 16 Mar 2018 16:48:21 -0700 Subject: [PATCH 18/97] Enable build --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 22417a007790..09297a762f53 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -40,4 +40,4 @@ test_script: - yarn run testSingleWorkspace --silent - yarn run testMultiWorkspace --silent -build: off + From 7b4e73cbac9eb013934ebe79c2691f2a2975ac4a Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Sat, 17 Mar 2018 21:16:39 -0700 Subject: [PATCH 19/97] Try absolute path --- appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 09297a762f53..6b5a6435375a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,14 +24,14 @@ install: - "%PYTHON%/Scripts/pip.exe install --upgrade -r requirements.txt" build_script: - - git clone https://github.com/MikhailArkhipov/PTVS.git ./PTVS - - "cd PTVS" + - git clone https://github.com/MikhailArkhipov/PTVS.git c:/projects/PTVS + - "cd c:\\projects\\PTVS" - git checkout origin/vsc - "cd Python\\Product\\VSCode\\AnalysisVsc" - "dotnet --info" - "dotnet build" - - "cd ..\\..\\..\\..\\.." - - "xcopy /S /D PTVS\\BuildOutput\\VsCode\\raw analysis" + - "cd c:\\projects\\vscode-python" + - "xcopy /S /D c:\\projects\\PTVS\\BuildOutput\\VsCode\\raw analysis" test_script: - yarn run clean From bd1fd77a4a599a0769d72bf400ad6bf208d9b053 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Sat, 17 Mar 2018 22:56:08 -0700 Subject: [PATCH 20/97] Fix xcopy switch --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 6b5a6435375a..c657cd684393 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,7 +31,7 @@ build_script: - "dotnet --info" - "dotnet build" - "cd c:\\projects\\vscode-python" - - "xcopy /S /D c:\\projects\\PTVS\\BuildOutput\\VsCode\\raw analysis" + - "xcopy /S /I c:\\projects\\PTVS\\BuildOutput\\VsCode\\raw analysis" test_script: - yarn run clean From 72b91f13efacbfa62a5ba413c35b56dfad40cd44 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Sun, 18 Mar 2018 09:03:59 -0700 Subject: [PATCH 21/97] Activate Analysis Engine test on Appveyor --- appveyor.yml | 1 + package.json | 3 ++- src/test/analysisEngineTest.ts | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/test/analysisEngineTest.ts diff --git a/appveyor.yml b/appveyor.yml index c657cd684393..7460123aaeb6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -39,5 +39,6 @@ test_script: - yarn run testDebugger --silent - yarn run testSingleWorkspace --silent - yarn run testMultiWorkspace --silent + - yarn run testAnalysisEngine --silent diff --git a/package.json b/package.json index 86cc43cbf4b0..965bb38ee087 100644 --- a/package.json +++ b/package.json @@ -1723,10 +1723,11 @@ "vscode:prepublish": "tsc -p ./", "compile": "tsc -watch -p ./", "postinstall": "node ./node_modules/vscode/bin/install", - "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js", + "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js && node ./out/test/analysisEngineTest.js", "testDebugger": "node ./out/test/debuggerTest.js", "testSingleWorkspace": "node ./out/test/standardTest.js", "testMultiWorkspace": "node ./out/test/multiRootTest.js", + "testAnalysisEngine": "node ./out/test/analysisEngineTest.js", "precommit": "node gulpfile.js", "lint-staged": "node gulpfile.js", "lint": "tslint src/**/*.ts -t verbose", diff --git a/src/test/analysisEngineTest.ts b/src/test/analysisEngineTest.ts new file mode 100644 index 000000000000..509cba41160c --- /dev/null +++ b/src/test/analysisEngineTest.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// tslint:disable:no-console no-require-imports no-var-requires +import * as path from 'path'; + +process.env.CODE_TESTS_WORKSPACE = path.join(__dirname, '..', '..', 'src', 'test'); +process.env.IS_CI_SERVER_TEST_DEBUGGER = ''; +process.env.VSC_PYTHON_ANALYSIS = '1'; + +function start() { + console.log('*'.repeat(100)); + console.log('Start Analysis Engine tests'); + require('../../node_modules/vscode/bin/test'); +} +start(); From e32320b20550fcdcde334bf7fd940fcc996f25fc Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 20 Mar 2018 14:00:15 -0700 Subject: [PATCH 22/97] Temporary only run new tests --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 7460123aaeb6..94c3d904fbe2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,9 +36,9 @@ build_script: test_script: - yarn run clean - yarn run vscode:prepublish - - yarn run testDebugger --silent - - yarn run testSingleWorkspace --silent - - yarn run testMultiWorkspace --silent + # - yarn run testDebugger --silent + # - yarn run testSingleWorkspace --silent + # - yarn run testMultiWorkspace --silent - yarn run testAnalysisEngine --silent From 55bb349dcb295e30f3ecbab538094674fac827e0 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 20 Mar 2018 15:07:18 -0700 Subject: [PATCH 23/97] Disable PEP hint tests --- src/test/autocomplete/pep484.test.ts | 21 +++++++----- src/test/autocomplete/pep526.test.ts | 49 ++++++++++------------------ 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/test/autocomplete/pep484.test.ts b/src/test/autocomplete/pep484.test.ts index 1eb13a792f1c..c21471ead780 100644 --- a/src/test/autocomplete/pep484.test.ts +++ b/src/test/autocomplete/pep484.test.ts @@ -2,6 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -12,9 +13,19 @@ suite('Autocomplete PEP 484', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; suiteSetup(async () => { + // https://github.com/Microsoft/PTVS/issues/3917 + if (IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } await initialize(); initializeDI(); isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + return; + } }); setup(initializeTest); suiteTeardown(closeActiveWindows); @@ -29,12 +40,7 @@ suite('Autocomplete PEP 484', () => { ioc.registerProcessTypes(); } - test('argument', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('argument', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep484); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -46,9 +52,6 @@ suite('Autocomplete PEP 484', () => { }); test('return value', async () => { - if (isPython2) { - return; - } const textDocument = await vscode.workspace.openTextDocument(filePep484); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); diff --git a/src/test/autocomplete/pep526.test.ts b/src/test/autocomplete/pep526.test.ts index 099df5af10ab..24aadb2eb04d 100644 --- a/src/test/autocomplete/pep526.test.ts +++ b/src/test/autocomplete/pep526.test.ts @@ -2,19 +2,30 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IS_ANALYSIS_ENGINE_TEST } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); const filePep526 = path.join(autoCompPath, 'pep526.py'); +// tslint:disable-next-line:max-func-body-length suite('Autocomplete PEP 526', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; - suiteSetup(async () => { + suiteSetup(async function () { + // https://github.com/Microsoft/PTVS/issues/3917 + if (IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } await initialize(); initializeDI(); isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + return; + } }); setup(initializeTest); suiteTeardown(closeActiveWindows); @@ -29,12 +40,7 @@ suite('Autocomplete PEP 526', () => { ioc.registerProcessTypes(); } - test('variable (abc:str)', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('variable (abc:str)', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -45,11 +51,7 @@ suite('Autocomplete PEP 526', () => { assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); - test('variable (abc: str = "")', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - } + test('variable (abc: str = "")', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -60,12 +62,7 @@ suite('Autocomplete PEP 526', () => { assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); - test('variable (abc = UNKNOWN # type: str)', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('variable (abc = UNKNOWN # type: str)', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -76,12 +73,7 @@ suite('Autocomplete PEP 526', () => { assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); - test('class methods', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('class methods', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -94,12 +86,7 @@ suite('Autocomplete PEP 526', () => { assert.notEqual(list!.items.filter(item => item.label === 'b').length, 0, 'method b not found'); }); - test('class method types', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('class method types', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); From 3826ffa8954557038cec2df93f1ef8d25750831f Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 20 Mar 2018 15:53:37 -0700 Subject: [PATCH 24/97] Test fix --- src/test/autocomplete/pep484.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/autocomplete/pep484.test.ts b/src/test/autocomplete/pep484.test.ts index c21471ead780..bac83c7afefa 100644 --- a/src/test/autocomplete/pep484.test.ts +++ b/src/test/autocomplete/pep484.test.ts @@ -12,7 +12,7 @@ const filePep484 = path.join(autoCompPath, 'pep484.py'); suite('Autocomplete PEP 484', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; - suiteSetup(async () => { + suiteSetup(async function () { // https://github.com/Microsoft/PTVS/issues/3917 if (IS_ANALYSIS_ENGINE_TEST) { // tslint:disable-next-line:no-invalid-this From b62ba2550b62088ef36c96d04a3d6b45a138075b Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 21 Mar 2018 10:56:25 -0700 Subject: [PATCH 25/97] Disable appveyor build and tests for PTVS for now --- appveyor.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 94c3d904fbe2..0ecd5fd62148 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,22 +23,22 @@ install: - python -m easy_install -U setuptools - "%PYTHON%/Scripts/pip.exe install --upgrade -r requirements.txt" -build_script: - - git clone https://github.com/MikhailArkhipov/PTVS.git c:/projects/PTVS - - "cd c:\\projects\\PTVS" - - git checkout origin/vsc - - "cd Python\\Product\\VSCode\\AnalysisVsc" - - "dotnet --info" - - "dotnet build" - - "cd c:\\projects\\vscode-python" - - "xcopy /S /I c:\\projects\\PTVS\\BuildOutput\\VsCode\\raw analysis" +# build_script: +# - git clone https://github.com/MikhailArkhipov/PTVS.git c:/projects/PTVS +# - "cd c:\\projects\\PTVS" +# - git checkout origin/vsc +# - "cd Python\\Product\\VSCode\\AnalysisVsc" +# - "dotnet --info" +# - "dotnet build" +# - "cd c:\\projects\\vscode-python" +# - "xcopy /S /I c:\\projects\\PTVS\\BuildOutput\\VsCode\\raw analysis" test_script: - yarn run clean - yarn run vscode:prepublish - # - yarn run testDebugger --silent - # - yarn run testSingleWorkspace --silent - # - yarn run testMultiWorkspace --silent - - yarn run testAnalysisEngine --silent + - yarn run testDebugger --silent + - yarn run testSingleWorkspace --silent + - yarn run testMultiWorkspace --silent + # - yarn run testAnalysisEngine --silent From a13770b896164aa37f3b0a667799bcafc90346fb Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 21 Mar 2018 11:56:11 -0700 Subject: [PATCH 26/97] Remove analysis engine test from the set --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 965bb38ee087..d90a68c877e4 100644 --- a/package.json +++ b/package.json @@ -1723,7 +1723,7 @@ "vscode:prepublish": "tsc -p ./", "compile": "tsc -watch -p ./", "postinstall": "node ./node_modules/vscode/bin/install", - "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js && node ./out/test/analysisEngineTest.js", + "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js", "testDebugger": "node ./out/test/debuggerTest.js", "testSingleWorkspace": "node ./out/test/standardTest.js", "testMultiWorkspace": "node ./out/test/multiRootTest.js", @@ -1830,4 +1830,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} +} \ No newline at end of file From ac646c81bebb97fcc8cd84e528027bdb3ebbdc06 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 21 Mar 2018 16:12:45 -0700 Subject: [PATCH 27/97] Remove VS image for now --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 0ecd5fd62148..6200779b90fc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ -image: Visual Studio 2017 -shallow_clone: true +#image: Visual Studio 2017 +#shallow_clone: true environment: matrix: From f75481245d06bf15e198b8d34554ef503266f0c6 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Mon, 26 Mar 2018 14:19:38 -0700 Subject: [PATCH 28/97] Build/sign VSXI project --- vscode-python.proj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 vscode-python.proj diff --git a/vscode-python.proj b/vscode-python.proj new file mode 100644 index 000000000000..4f80f69b3b17 --- /dev/null +++ b/vscode-python.proj @@ -0,0 +1,16 @@ + + + + + + $(OutputPath)\python-$(Branch).vsix + + + + + + + VsixSHA2 + + + \ No newline at end of file From 826dcee762e9b75547623c5affa3e0277cb3ff11 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:10:10 -0700 Subject: [PATCH 29/97] Run vsce from cmd --- build/run-vcse.cmd | 1 + build/vscode-python.proj | 17 +++++++++++++++++ vscode-python.proj | 16 ---------------- 3 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 build/run-vcse.cmd create mode 100644 build/vscode-python.proj delete mode 100644 vscode-python.proj diff --git a/build/run-vcse.cmd b/build/run-vcse.cmd new file mode 100644 index 000000000000..52a263e98310 --- /dev/null +++ b/build/run-vcse.cmd @@ -0,0 +1 @@ +vsce package --out %1 diff --git a/build/vscode-python.proj b/build/vscode-python.proj new file mode 100644 index 000000000000..98929e5a160f --- /dev/null +++ b/build/vscode-python.proj @@ -0,0 +1,17 @@ + + + + + + $(UserProfile)\.nuget\packages\ + $(OutputPath)\python-$(Branch).vsix + + + + + + + VsixSHA2 + + + diff --git a/vscode-python.proj b/vscode-python.proj deleted file mode 100644 index 4f80f69b3b17..000000000000 --- a/vscode-python.proj +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - $(OutputPath)\python-$(Branch).vsix - - - - - - - VsixSHA2 - - - \ No newline at end of file From 8cb0c447dd2ecc34d130bf2cd28a05291c733cd1 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:26:27 -0700 Subject: [PATCH 30/97] Rename --- build/run-vcse.cmd | 1 - build/run-vsce.cmd | 2 ++ build/vscode-python.proj | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 build/run-vcse.cmd create mode 100644 build/run-vsce.cmd diff --git a/build/run-vcse.cmd b/build/run-vcse.cmd deleted file mode 100644 index 52a263e98310..000000000000 --- a/build/run-vcse.cmd +++ /dev/null @@ -1 +0,0 @@ -vsce package --out %1 diff --git a/build/run-vsce.cmd b/build/run-vsce.cmd new file mode 100644 index 000000000000..e3af675aeb56 --- /dev/null +++ b/build/run-vsce.cmd @@ -0,0 +1,2 @@ +cd %1 +vsce package --out %2 diff --git a/build/vscode-python.proj b/build/vscode-python.proj index 98929e5a160f..a65c499a34bb 100644 --- a/build/vscode-python.proj +++ b/build/vscode-python.proj @@ -3,11 +3,10 @@ - $(UserProfile)\.nuget\packages\ $(OutputPath)\python-$(Branch).vsix - + From 05d4d239116eaf91c0103916de000a7b95a03547 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:41:25 -0700 Subject: [PATCH 31/97] Abs path vsce --- build/vscode-python.proj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/vscode-python.proj b/build/vscode-python.proj index a65c499a34bb..cd9aa5cde7a9 100644 --- a/build/vscode-python.proj +++ b/build/vscode-python.proj @@ -4,9 +4,10 @@ $(OutputPath)\python-$(Branch).vsix + $(UserProfile)\Roaming\npm\vsce - + From 7fd6a8b27004b4d12b58a2255518bcc6bda3b5de Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:43:52 -0700 Subject: [PATCH 32/97] Path --- build/vscode-python.proj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/vscode-python.proj b/build/vscode-python.proj index cd9aa5cde7a9..7ac33da15bb3 100644 --- a/build/vscode-python.proj +++ b/build/vscode-python.proj @@ -4,7 +4,7 @@ $(OutputPath)\python-$(Branch).vsix - $(UserProfile)\Roaming\npm\vsce + $(UserProfile)\AppData\Roaming\npm\vsce From bbcc65d20de62b24dd3dbe99cfa6e1d05483e6a3 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:48:19 -0700 Subject: [PATCH 33/97] Move project --- build/run-vsce.cmd | 2 -- build/vscode-python.proj => vscode-python.proj | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 build/run-vsce.cmd rename build/vscode-python.proj => vscode-python.proj (87%) diff --git a/build/run-vsce.cmd b/build/run-vsce.cmd deleted file mode 100644 index e3af675aeb56..000000000000 --- a/build/run-vsce.cmd +++ /dev/null @@ -1,2 +0,0 @@ -cd %1 -vsce package --out %2 diff --git a/build/vscode-python.proj b/vscode-python.proj similarity index 87% rename from build/vscode-python.proj rename to vscode-python.proj index 7ac33da15bb3..f049f40cf794 100644 --- a/build/vscode-python.proj +++ b/vscode-python.proj @@ -7,7 +7,7 @@ $(UserProfile)\AppData\Roaming\npm\vsce - + From 8d170c06ccce6d87a2457888af30347e0a43578f Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 09:39:40 -0700 Subject: [PATCH 34/97] Ignore publishing project --- .vscodeignore | 3 +++ vscode-python.proj | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.vscodeignore b/.vscodeignore index ae242dcf887d..fe52f16487a2 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -37,3 +37,6 @@ CODING_STANDARDS.md CONTRIBUTING.md news/** debug_coverage*/** +analysis/publish*.* +vscode-python.proj + diff --git a/vscode-python.proj b/vscode-python.proj index f049f40cf794..06ea7e34d077 100644 --- a/vscode-python.proj +++ b/vscode-python.proj @@ -6,8 +6,8 @@ $(OutputPath)\python-$(Branch).vsix $(UserProfile)\AppData\Roaming\npm\vsce - - + + From 869b38c6e1cdda6c9bec8c3212ab41fd06bc4f76 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 09:52:05 -0700 Subject: [PATCH 35/97] Try csproj --- .vscodeignore | 2 ++ vscode-python.proj => vscode-python.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) rename vscode-python.proj => vscode-python.csproj (93%) diff --git a/.vscodeignore b/.vscodeignore index fe52f16487a2..c018ddfb234c 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -39,4 +39,6 @@ news/** debug_coverage*/** analysis/publish*.* vscode-python.proj +bin/** +obj/** diff --git a/vscode-python.proj b/vscode-python.csproj similarity index 93% rename from vscode-python.proj rename to vscode-python.csproj index 06ea7e34d077..53880440ac3c 100644 --- a/vscode-python.proj +++ b/vscode-python.csproj @@ -1,4 +1,4 @@ - + From d8c80f69bb48f68dca031ae03e567d385cc8c5c7 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 10:03:32 -0700 Subject: [PATCH 36/97] Add framework --- vscode-python.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vscode-python.csproj b/vscode-python.csproj index 53880440ac3c..3cce8e5d5257 100644 --- a/vscode-python.csproj +++ b/vscode-python.csproj @@ -1,4 +1,7 @@ + + netcoreapp2.0 + From 56e6b56f6a96a25f5ce143ffcba85cb69435ac62 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 10:31:15 -0700 Subject: [PATCH 37/97] Ignore build output folder --- .vscodeignore | 1 + vscode-python.csproj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscodeignore b/.vscodeignore index c018ddfb234c..63ccc3f6f744 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -41,4 +41,5 @@ analysis/publish*.* vscode-python.proj bin/** obj/** +BuildOutput/** diff --git a/vscode-python.csproj b/vscode-python.csproj index 3cce8e5d5257..cddcdb107e6c 100644 --- a/vscode-python.csproj +++ b/vscode-python.csproj @@ -9,7 +9,7 @@ $(OutputPath)\python-$(Branch).vsix $(UserProfile)\AppData\Roaming\npm\vsce - + From ce6b360d936627b91531a9c7370f87ae61aef841 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 10:34:45 -0700 Subject: [PATCH 38/97] Package before build --- vscode-python.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vscode-python.csproj b/vscode-python.csproj index cddcdb107e6c..7fb333c4b277 100644 --- a/vscode-python.csproj +++ b/vscode-python.csproj @@ -9,7 +9,7 @@ $(OutputPath)\python-$(Branch).vsix $(UserProfile)\AppData\Roaming\npm\vsce - + From a7847d81be8a65488685be211be06c0b45268612 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 11:38:16 -0700 Subject: [PATCH 39/97] Try batch instead of PS --- .vscodeignore | 3 ++- packageExtension.cmd | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 packageExtension.cmd diff --git a/.vscodeignore b/.vscodeignore index 63ccc3f6f744..b87d3320567b 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -38,7 +38,8 @@ CONTRIBUTING.md news/** debug_coverage*/** analysis/publish*.* -vscode-python.proj +vscode-python-signing.* +packageExtension.cmd bin/** obj/** BuildOutput/** diff --git a/packageExtension.cmd b/packageExtension.cmd new file mode 100644 index 000000000000..37f0ecca9228 --- /dev/null +++ b/packageExtension.cmd @@ -0,0 +1 @@ +%1\vsce" package --out %2 \ No newline at end of file From 05c0bf01f0bffc2391c4790c5f9addf622633cd9 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 11:41:08 -0700 Subject: [PATCH 40/97] Fix path quotes --- packageExtension.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packageExtension.cmd b/packageExtension.cmd index 37f0ecca9228..dc3026c20f3e 100644 --- a/packageExtension.cmd +++ b/packageExtension.cmd @@ -1 +1 @@ -%1\vsce" package --out %2 \ No newline at end of file +%1\vsce package --out %2 \ No newline at end of file From 92e8c1ee2e264784b76a250d1f39b157ba198bbe Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 15:32:47 -0700 Subject: [PATCH 41/97] #1096 The if statement is automatically formatted incorrectly --- src/client/formatters/lineFormatter.ts | 10 ++++++---- src/test/format/extension.onEnterFormat.test.ts | 7 ++++++- src/test/pythonFiles/formatting/fileToFormatOnEnter.py | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 4b3bff70aa8d..835e7e82b92a 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -5,14 +5,15 @@ import Char from 'typescript-char'; import { BraceCounter } from '../language/braceCounter'; import { TextBuilder } from '../language/textBuilder'; +import { TextRangeCollection } from '../language/textRangeCollection'; import { Tokenizer } from '../language/tokenizer'; import { ITextRangeCollection, IToken, TokenType } from '../language/types'; export class LineFormatter { - private builder: TextBuilder; - private tokens: ITextRangeCollection; - private braceCounter: BraceCounter; - private text: string; + private builder = new TextBuilder(); + private tokens: ITextRangeCollection = new TextRangeCollection([]); + private braceCounter = new BraceCounter(); + private text = ''; // tslint:disable-next-line:cyclomatic-complexity public formatLine(text: string): string { @@ -123,6 +124,7 @@ export class LineFormatter { if (this.isBraceType(t.type)) { this.braceCounter.countBrace(t); } + this.builder.softAppendSpace(); this.builder.append(this.text.substring(t.start, t.end)); } diff --git a/src/test/format/extension.onEnterFormat.test.ts b/src/test/format/extension.onEnterFormat.test.ts index 74597ce19be7..23e5cbecb8fa 100644 --- a/src/test/format/extension.onEnterFormat.test.ts +++ b/src/test/format/extension.onEnterFormat.test.ts @@ -59,8 +59,13 @@ suite('Formatting - OnEnter provider', () => { assert.equal(text, 'x.y', 'Line ending with period was reformatted'); }); - test('Formatting line ending in string', async () => { + test('Formatting line with unknown neighboring tokens', async () => { const text = await formatAtPosition(9, 0); + assert.equal(text, 'if x <= 1:', 'Line with unknown neighboring tokens was not formatted'); + }); + + test('Formatting line ending in string', async () => { + const text = await formatAtPosition(10, 0); assert.equal(text, 'x + """', 'Line ending in multiline string was not formatted'); }); diff --git a/src/test/pythonFiles/formatting/fileToFormatOnEnter.py b/src/test/pythonFiles/formatting/fileToFormatOnEnter.py index bbd025363098..67d533125ab2 100644 --- a/src/test/pythonFiles/formatting/fileToFormatOnEnter.py +++ b/src/test/pythonFiles/formatting/fileToFormatOnEnter.py @@ -6,4 +6,5 @@ x+1 # @x x.y +if x<=1: x+""" From b540a1dc43b376e05ddb29bbf6a16dd1fb51843e Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 15:35:31 -0700 Subject: [PATCH 42/97] Merge fix --- src/test/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/index.ts b/src/test/index.ts index 22ffd45a0675..135ca5d8b6c1 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -21,7 +21,7 @@ const grep = IS_CI_SERVER && IS_CI_SERVER_TEST_DEBUGGER ? 'Debug' : undefined; const options: MochaSetupOptions & { retries: number } = { ui: 'tdd', useColors: true, - timeout: 35000, + timeout: 25000, retries: 3, grep }; From 7b0573ed946ec6ed34d077b0f824f2a7aaaba613 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 16:08:23 -0700 Subject: [PATCH 43/97] Add more tests --- src/client/formatters/lineFormatter.ts | 41 ++++++++++++++++--- .../format/extension.onEnterFormat.test.ts | 12 +++++- .../formatting/fileToFormatOnEnter.py | 2 + 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 835e7e82b92a..7684c9337755 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -73,7 +73,7 @@ export class LineFormatter { break; default: - this.handleOther(t); + this.handleOther(t, i); break; } } @@ -109,10 +109,7 @@ export class LineFormatter { if (this.braceCounter.isOpened(TokenType.OpenBrace)) { // Check if this is = in function arguments. If so, do not // add spaces around it. - const prev = this.tokens.getItemAt(index - 1); - const prevPrev = this.tokens.getItemAt(index - 2); - if (prev.type === TokenType.Identifier && - (prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace)) { + if (this.isEqualsInsideArguments(index)) { this.builder.append('='); return true; } @@ -120,14 +117,46 @@ export class LineFormatter { return false; } - private handleOther(t: IToken): void { + private handleOther(t: IToken, index: number): void { if (this.isBraceType(t.type)) { this.braceCounter.countBrace(t); + this.builder.append(this.text.substring(t.start, t.end)); + return; + } + + if (this.isEqualsInsideArguments(index - 1)) { + // Don't add space around = inside function arguments + this.builder.append(this.text.substring(t.start, t.end)); + return; + } + + if (index > 0) { + const prev = this.tokens.getItemAt(index - 1); + if (this.isOpenBraceType(prev.type)) { + // Don't insert space after (, [ or { + this.builder.append(this.text.substring(t.start, t.end)); + return; + } } + + // In general, keep tokes separated this.builder.softAppendSpace(); this.builder.append(this.text.substring(t.start, t.end)); } + private isEqualsInsideArguments(index: number): boolean { + if (index < 2) { + return false; + } + const prev = this.tokens.getItemAt(index - 1); + const prevPrev = this.tokens.getItemAt(index - 2); + if (prev.type === TokenType.Identifier && + (prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace)) { + return true; + } + return false; + } + private isOpenBraceType(type: TokenType): boolean { return type === TokenType.OpenBrace || type === TokenType.OpenBracket || type === TokenType.OpenCurly; } diff --git a/src/test/format/extension.onEnterFormat.test.ts b/src/test/format/extension.onEnterFormat.test.ts index 23e5cbecb8fa..e34aeb0a0ed3 100644 --- a/src/test/format/extension.onEnterFormat.test.ts +++ b/src/test/format/extension.onEnterFormat.test.ts @@ -64,8 +64,18 @@ suite('Formatting - OnEnter provider', () => { assert.equal(text, 'if x <= 1:', 'Line with unknown neighboring tokens was not formatted'); }); - test('Formatting line ending in string', async () => { + test('Formatting method definition with arguments', async () => { const text = await formatAtPosition(10, 0); + assert.equal(text, 'def __init__(self, age=23)', 'Method definition with arguments was not formatted'); + }); + + test('Formatting space after open brace', async () => { + const text = await formatAtPosition(11, 0); + assert.equal(text, 'while(1)', 'Method definition with arguments was not formatted'); + }); + + test('Formatting line ending in string', async () => { + const text = await formatAtPosition(12, 0); assert.equal(text, 'x + """', 'Line ending in multiline string was not formatted'); }); diff --git a/src/test/pythonFiles/formatting/fileToFormatOnEnter.py b/src/test/pythonFiles/formatting/fileToFormatOnEnter.py index 67d533125ab2..779167118ffc 100644 --- a/src/test/pythonFiles/formatting/fileToFormatOnEnter.py +++ b/src/test/pythonFiles/formatting/fileToFormatOnEnter.py @@ -7,4 +7,6 @@ @x x.y if x<=1: +def __init__(self, age = 23) +while(1) x+""" From facb10613596b28f82e672266f130e57b4311847 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 16:30:21 -0700 Subject: [PATCH 44/97] More tests --- src/test/format/extension.onEnterFormat.test.ts | 11 ++++++++--- .../pythonFiles/formatting/fileToFormatOnEnter.py | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test/format/extension.onEnterFormat.test.ts b/src/test/format/extension.onEnterFormat.test.ts index e34aeb0a0ed3..689129da78c8 100644 --- a/src/test/format/extension.onEnterFormat.test.ts +++ b/src/test/format/extension.onEnterFormat.test.ts @@ -64,18 +64,23 @@ suite('Formatting - OnEnter provider', () => { assert.equal(text, 'if x <= 1:', 'Line with unknown neighboring tokens was not formatted'); }); - test('Formatting method definition with arguments', async () => { + test('Formatting line with unknown neighboring tokens', async () => { const text = await formatAtPosition(10, 0); + assert.equal(text, 'if 1 <= x:', 'Line with unknown neighboring tokens was not formatted'); + }); + + test('Formatting method definition with arguments', async () => { + const text = await formatAtPosition(11, 0); assert.equal(text, 'def __init__(self, age=23)', 'Method definition with arguments was not formatted'); }); test('Formatting space after open brace', async () => { - const text = await formatAtPosition(11, 0); + const text = await formatAtPosition(12, 0); assert.equal(text, 'while(1)', 'Method definition with arguments was not formatted'); }); test('Formatting line ending in string', async () => { - const text = await formatAtPosition(12, 0); + const text = await formatAtPosition(13, 0); assert.equal(text, 'x + """', 'Line ending in multiline string was not formatted'); }); diff --git a/src/test/pythonFiles/formatting/fileToFormatOnEnter.py b/src/test/pythonFiles/formatting/fileToFormatOnEnter.py index 779167118ffc..8adfd1fa1233 100644 --- a/src/test/pythonFiles/formatting/fileToFormatOnEnter.py +++ b/src/test/pythonFiles/formatting/fileToFormatOnEnter.py @@ -7,6 +7,7 @@ @x x.y if x<=1: +if 1<=x: def __init__(self, age = 23) while(1) x+""" From f113881370f26a52f159862bb822f0119225013c Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 16:32:06 -0700 Subject: [PATCH 45/97] Typo --- src/client/formatters/lineFormatter.ts | 2 +- src/test/format/extension.onEnterFormat.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 7684c9337755..56e9b6cf7237 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -139,7 +139,7 @@ export class LineFormatter { } } - // In general, keep tokes separated + // In general, keep tokens separated this.builder.softAppendSpace(); this.builder.append(this.text.substring(t.start, t.end)); } diff --git a/src/test/format/extension.onEnterFormat.test.ts b/src/test/format/extension.onEnterFormat.test.ts index 689129da78c8..8f594d5e2559 100644 --- a/src/test/format/extension.onEnterFormat.test.ts +++ b/src/test/format/extension.onEnterFormat.test.ts @@ -76,7 +76,7 @@ suite('Formatting - OnEnter provider', () => { test('Formatting space after open brace', async () => { const text = await formatAtPosition(12, 0); - assert.equal(text, 'while(1)', 'Method definition with arguments was not formatted'); + assert.equal(text, 'while(1)', 'Space after open brace was not formatted'); }); test('Formatting line ending in string', async () => { From 3e76718c097e23b52a9ffbe5de27f18f512f3f5f Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 27 Mar 2018 18:15:11 -0700 Subject: [PATCH 46/97] Test --- src/client/formatters/lineFormatter.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 56e9b6cf7237..4c30d8c17baa 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -72,6 +72,10 @@ export class LineFormatter { this.builder.append(this.text.substring(t.start, t.end)); break; + case TokenType.Semicolon: + this.builder.append(';'); + break; + default: this.handleOther(t, i); break; @@ -132,7 +136,7 @@ export class LineFormatter { if (index > 0) { const prev = this.tokens.getItemAt(index - 1); - if (this.isOpenBraceType(prev.type)) { + if (this.isOpenBraceType(prev.type) || prev.type === TokenType.Colon) { // Don't insert space after (, [ or { this.builder.append(this.text.substring(t.start, t.end)); return; From 6e85dc68a3516175546fc6c65cddede03a58bcea Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 27 Mar 2018 21:47:51 -0700 Subject: [PATCH 47/97] Also better handle multiline arguments --- src/client/formatters/lineFormatter.ts | 47 ++++++++++++++----- .../format/extension.lineFormatter.test.ts | 16 +++++++ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 4c30d8c17baa..694af69ea5ff 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -90,7 +90,7 @@ export class LineFormatter { const opCode = this.text.charCodeAt(t.start); switch (opCode) { case Char.Equal: - if (index >= 2 && this.handleEqual(t, index)) { + if (this.handleEqual(t, index)) { return; } break; @@ -110,13 +110,13 @@ export class LineFormatter { } private handleEqual(t: IToken, index: number): boolean { - if (this.braceCounter.isOpened(TokenType.OpenBrace)) { - // Check if this is = in function arguments. If so, do not - // add spaces around it. - if (this.isEqualsInsideArguments(index)) { - this.builder.append('='); - return true; - } + if (this.isMultipleStatements(index) && !this.braceCounter.isOpened(TokenType.OpenBrace)) { + return false; // x = 1; x, y = y, x + } + // Check if this is = in function arguments. If so, do not add spaces around it. + if (this.isEqualsInsideArguments(index)) { + this.builder.append('='); + return true; } return false; } @@ -149,14 +149,23 @@ export class LineFormatter { } private isEqualsInsideArguments(index: number): boolean { - if (index < 2) { + if (index < 1) { return false; } const prev = this.tokens.getItemAt(index - 1); - const prevPrev = this.tokens.getItemAt(index - 2); - if (prev.type === TokenType.Identifier && - (prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace)) { - return true; + if (prev.type === TokenType.Identifier) { + if (index >= 2) { + // (x=1 or ,x=1 + const prevPrev = this.tokens.getItemAt(index - 2); + return prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace; + } else if (index < this.tokens.count - 2) { + const next = this.tokens.getItemAt(index + 1); + const nextNext = this.tokens.getItemAt(index + 2); + // x=1, or x=1) + if (this.isValueType(next.type)) { + return nextNext.type === TokenType.Comma || nextNext.type === TokenType.CloseBrace; + } + } } return false; } @@ -170,4 +179,16 @@ export class LineFormatter { private isBraceType(type: TokenType): boolean { return this.isOpenBraceType(type) || this.isCloseBraceType(type); } + private isValueType(type: TokenType): boolean { + return type === TokenType.Identifier || type === TokenType.Unknown || + type === TokenType.Number || type === TokenType.String; + } + private isMultipleStatements(index: number): boolean { + for (let i = index; i >= 0; i -= 1) { + if (this.tokens.getItemAt(i).type === TokenType.Semicolon) { + return true; + } + } + return false; + } } diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index 842cb02d735d..79de72c5774a 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -65,4 +65,20 @@ suite('Formatting - line formatter', () => { const actual = formatter.formatLine(' # comment'); assert.equal(actual, ' # comment'); }); + test('Equals in first argument', () => { + const actual = formatter.formatLine('foo(x =0)'); + assert.equal(actual, 'foo(x=0)'); + }); + test('Equals in second argument', () => { + const actual = formatter.formatLine('foo(x,y= \"a\",'); + assert.equal(actual, 'foo(x, y=\"a\",'); + }); + test('Equals in multiline arguments', () => { + const actual = formatter.formatLine('x = 1,y =-2)'); + assert.equal(actual, 'x=1, y=-2)'); + }); + test('Equals in multiline arguments starting comma', () => { + const actual = formatter.formatLine(',x = 1,y =m)'); + assert.equal(actual, ', x=1, y=m)'); + }); }); From 3e071e0499da69e68d48753796d405ec399d8bf3 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 27 Mar 2018 22:08:06 -0700 Subject: [PATCH 48/97] Changes lost on squash --- src/client/activation/analysis.ts | 233 ++++++++ src/client/activation/analysisEngineHashes.ts | 9 + src/client/activation/classic.ts | 62 ++ src/client/activation/downloader.ts | 128 ++++ src/client/activation/hashVerifier.ts | 28 + src/client/activation/platformData.ts | 39 ++ src/client/activation/types.ts | 9 + src/client/extension.ts | 396 ++++++------- src/test/autocomplete/base.test.ts | 470 ++++++++------- .../{hover.test.ts => hover.jedi.test.ts} | 559 +++++++++--------- src/test/definitions/hover.ptvs.test.ts | 193 ++++++ ...parallel.test.ts => parallel.jedi.test.ts} | 94 +-- src/test/definitions/parallel.ptvs.test.ts | 57 ++ src/test/signature/signature.jedi.test.ts | Bin 0 -> 6055 bytes src/test/signature/signature.ptvs.test.ts | Bin 0 -> 6149 bytes src/test/signature/signature.test.ts | 140 ----- 16 files changed, 1530 insertions(+), 887 deletions(-) create mode 100644 src/client/activation/analysis.ts create mode 100644 src/client/activation/analysisEngineHashes.ts create mode 100644 src/client/activation/classic.ts create mode 100644 src/client/activation/downloader.ts create mode 100644 src/client/activation/hashVerifier.ts create mode 100644 src/client/activation/platformData.ts create mode 100644 src/client/activation/types.ts rename src/test/definitions/{hover.test.ts => hover.jedi.test.ts} (96%) create mode 100644 src/test/definitions/hover.ptvs.test.ts rename src/test/definitions/{parallel.test.ts => parallel.jedi.test.ts} (87%) create mode 100644 src/test/definitions/parallel.ptvs.test.ts create mode 100644 src/test/signature/signature.jedi.test.ts create mode 100644 src/test/signature/signature.ptvs.test.ts delete mode 100644 src/test/signature/signature.test.ts diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts new file mode 100644 index 000000000000..6db64af0f8d6 --- /dev/null +++ b/src/client/activation/analysis.ts @@ -0,0 +1,233 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { ExtensionContext, OutputChannel } from 'vscode'; +import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; +import { IApplicationShell } from '../common/application/types'; +import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import '../common/extensions'; +import { IProcessService, IPythonExecutionFactory } from '../common/process/types'; +import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; +import { IInterpreterService } from '../interpreter/contracts'; +import { IServiceContainer } from '../ioc/types'; +import { StopWatch } from '../telemetry/stopWatch'; +import { AnalysisEngineDownloader } from './downloader'; +import { IExtensionActivator } from './types'; + +const PYTHON = 'python'; +const analysisEngineBinaryName = 'Microsoft.PythonTools.VsCode.dll'; +const dotNetCommand = 'dotnet'; +const languageClientName = 'Python Tools'; +const analysisEngineFolder = 'analysis'; + +class InterpreterData { + constructor(public readonly version: string, public readonly prefix: string) { } +} + +export class AnalysisExtensionActivator implements IExtensionActivator { + private readonly executionFactory: IPythonExecutionFactory; + private readonly configuration: IConfigurationService; + private readonly appShell: IApplicationShell; + private readonly output: OutputChannel; + private languageClient: LanguageClient | undefined; + + constructor(private readonly services: IServiceContainer, pythonSettings: IPythonSettings) { + this.executionFactory = this.services.get(IPythonExecutionFactory); + this.configuration = this.services.get(IConfigurationService); + this.appShell = this.services.get(IApplicationShell); + this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + } + + public async activate(context: ExtensionContext): Promise { + const sw = new StopWatch(); + + const clientOptions = await this.getAnalysisOptions(context); + if (!clientOptions) { + return false; + } + this.output.appendLine(`Options determined: ${sw.elapsedTime} ms`); + + //if (!await this.tryStartLanguageServer(context, clientOptions, true)) { + const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); + await downloader.downloadAnalysisEngine(context); + if (!await this.tryStartLanguageServer(context, clientOptions, false)) { + return false; + } + //} + + // tslint:disable-next-line:no-console + this.output.appendLine(`Language server started: ${sw.elapsedTime} ms`); + await this.languageClient!.onReady(); + this.output.appendLine(`Language server ready: ${sw.elapsedTime} ms`); + return true; + } + + public async deactivate(): Promise { + if (this.languageClient) { + await this.languageClient.stop(); + } + } + + private async tryStartLanguageServer(context: ExtensionContext, clientOptions: LanguageClientOptions, checkRuntime: boolean): Promise { + const commandOptions = { stdio: 'pipe' }; + const serverModule = path.join(context.extensionPath, analysisEngineFolder, analysisEngineBinaryName); + const serverOptions: ServerOptions = { + run: { command: dotNetCommand, args: [serverModule], options: commandOptions }, + debug: { command: dotNetCommand, args: [serverModule, '--debug'], options: commandOptions } + }; + + try { + // Create the language client and start the client. + this.languageClient = new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); + context.subscriptions.push(this.languageClient.start()); + return true; + } catch (ex) { + if (checkRuntime && !await this.checkRuntime()) { + this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${ex}`); + } else { + this.appShell.showErrorMessage(`Language server failed to start. Error ${ex}`); + } + } + return false; + } + + private async getAnalysisOptions(context: ExtensionContext): Promise { + // tslint:disable-next-line:no-any + const properties = new Map(); + + // Microsoft Python code analysis engine needs full path to the interpreter + const interpreterService = this.services.get(IInterpreterService); + const interpreter = await interpreterService.getActiveInterpreter(); + + if (interpreter) { + // tslint:disable-next-line:no-string-literal + properties['InterpreterPath'] = interpreter.path; + if (interpreter.displayName) { + // tslint:disable-next-line:no-string-literal + properties['Description'] = interpreter.displayName; + } + const interpreterData = await this.getInterpreterData(); + + // tslint:disable-next-line:no-string-literal + properties['Version'] = interpreterData.version; + // tslint:disable-next-line:no-string-literal + properties['PrefixPath'] = interpreterData.prefix; + // tslint:disable-next-line:no-string-literal + properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); + + let searchPaths = await this.getSearchPaths(); + const settings = this.configuration.getSettings(); + if (settings.autoComplete) { + const extraPaths = settings.autoComplete.extraPaths; + if (extraPaths && extraPaths.length > 0) { + searchPaths = `${searchPaths};${extraPaths.join(';')}`; + } + } + // tslint:disable-next-line:no-string-literal + properties['SearchPaths'] = searchPaths; + + if (isTestExecution()) { + // tslint:disable-next-line:no-string-literal + properties['TestEnvironment'] = true; + } + } else { + const appShell = this.services.get(IApplicationShell); + const pythonPath = this.configuration.getSettings().pythonPath; + appShell.showErrorMessage(`Interpreter ${pythonPath} does not exist.`); + return; + } + + const selector: string[] = [PYTHON]; + // Options to control the language client + return { + // Register the server for Python documents + documentSelector: selector, + synchronize: { + configurationSection: PYTHON + }, + outputChannel: this.output, + initializationOptions: { + interpreter: { + properties + } + } + }; + } + + private async getInterpreterData(): Promise { + // Not appropriate for multiroot workspaces. + // See https://github.com/Microsoft/vscode-python/issues/1149 + const execService = await this.executionFactory.create(); + const result = await execService.exec(['-c', 'import sys; print(sys.version_info); print(sys.prefix)'], {}); + // 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:19:30) <> + // [MSC v.1500 32 bit (Intel)] + // C:\Python27 + if (!result.stdout) { + throw Error('Unable to determine Python interpreter version and system prefix.'); + } + const output = result.stdout.splitLines({ removeEmptyEntries: true, trim: true }); + if (!output || output.length < 2) { + throw Error('Unable to parse version and and system prefix from the Python interpreter output.'); + } + const majorMatches = output[0].match(/major=(\d*?),/); + const minorMatches = output[0].match(/minor=(\d*?),/); + if (!majorMatches || majorMatches.length < 2 || !minorMatches || minorMatches.length < 2) { + throw Error('Unable to parse interpreter version.'); + } + const prefix = output[output.length - 1]; + return new InterpreterData(`${majorMatches[1]}.${minorMatches[1]}`, prefix); + } + + private async getSearchPaths(): Promise { + // Not appropriate for multiroot workspaces. + // See https://github.com/Microsoft/vscode-python/issues/1149 + const execService = await this.executionFactory.create(); + const result = await execService.exec(['-c', 'import sys; print(sys.path);'], {}); + if (!result.stdout) { + throw Error('Unable to determine Python interpreter search paths.'); + } + // tslint:disable-next-line:no-unnecessary-local-variable + const paths = result.stdout.split(',') + .filter(p => this.isValidPath(p)) + .map(p => this.pathCleanup(p)); + return paths.join(';'); + } + + private pathCleanup(s: string): string { + s = s.trim(); + if (s[0] === '\'') { + s = s.substr(1); + } + if (s[s.length - 1] === ']') { + s = s.substr(0, s.length - 1); + } + if (s[s.length - 1] === '\'') { + s = s.substr(0, s.length - 1); + } + return s; + } + + private isValidPath(s: string): boolean { + return s.length > 0 && s[0] !== '['; + } + + private async checkRuntime(): Promise { + if (!await this.isDotNetInstalled()) { + const appShell = this.services.get(IApplicationShell); + if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { + appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); + appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); + } + return false; + } + return true; + } + + private async isDotNetInstalled(): Promise { + const ps = this.services.get(IProcessService); + const result = await ps.exec('dotnet', ['--version']).catch(() => { return { stdout: '' }; }); + return result.stdout.trim().startsWith('2.'); + } + +} diff --git a/src/client/activation/analysisEngineHashes.ts b/src/client/activation/analysisEngineHashes.ts new file mode 100644 index 000000000000..52761329113e --- /dev/null +++ b/src/client/activation/analysisEngineHashes.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// This file will be replaced by a generated one during the release build +// with actual hashes of the uploaded packages. +export const analysis_engine_win_x86_sha512 = ''; +export const analysis_engine_win_x64_sha512 = ''; +export const analysis_engine_osx_x64_sha512 = ''; +export const analysis_engine_linux_x64_sha512 = ''; diff --git a/src/client/activation/classic.ts b/src/client/activation/classic.ts new file mode 100644 index 000000000000..61b82fae79f9 --- /dev/null +++ b/src/client/activation/classic.ts @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { DocumentFilter, ExtensionContext, languages, OutputChannel } from 'vscode'; +import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { IOutputChannel, IPythonSettings } from '../common/types'; +import { IShebangCodeLensProvider } from '../interpreter/contracts'; +import { IServiceManager } from '../ioc/types'; +import { JediFactory } from '../languageServices/jediProxyFactory'; +import { PythonCompletionItemProvider } from '../providers/completionProvider'; +import { PythonDefinitionProvider } from '../providers/definitionProvider'; +import { PythonHoverProvider } from '../providers/hoverProvider'; +import { activateGoToObjectDefinitionProvider } from '../providers/objectDefinitionProvider'; +import { PythonReferenceProvider } from '../providers/referenceProvider'; +import { PythonRenameProvider } from '../providers/renameProvider'; +import { PythonSignatureProvider } from '../providers/signatureProvider'; +import { activateSimplePythonRefactorProvider } from '../providers/simpleRefactorProvider'; +import { PythonSymbolProvider } from '../providers/symbolProvider'; +import { TEST_OUTPUT_CHANNEL } from '../unittests/common/constants'; +import * as tests from '../unittests/main'; +import { IExtensionActivator } from './types'; + +const PYTHON: DocumentFilter = { language: 'python' }; + +export class ClassicExtensionActivator implements IExtensionActivator { + constructor(private serviceManager: IServiceManager, private pythonSettings: IPythonSettings) { + } + + public async activate(context: ExtensionContext): Promise { + const standardOutputChannel = this.serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + activateSimplePythonRefactorProvider(context, standardOutputChannel, this.serviceManager); + + const jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager); + context.subscriptions.push(jediFactory); + context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); + + context.subscriptions.push(jediFactory); + context.subscriptions.push(languages.registerRenameProvider(PYTHON, new PythonRenameProvider(this.serviceManager))); + const definitionProvider = new PythonDefinitionProvider(jediFactory); + + context.subscriptions.push(languages.registerDefinitionProvider(PYTHON, definitionProvider)); + context.subscriptions.push(languages.registerHoverProvider(PYTHON, new PythonHoverProvider(jediFactory))); + context.subscriptions.push(languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(jediFactory))); + context.subscriptions.push(languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(jediFactory, this.serviceManager), '.')); + context.subscriptions.push(languages.registerCodeLensProvider(PYTHON, this.serviceManager.get(IShebangCodeLensProvider))); + + const symbolProvider = new PythonSymbolProvider(jediFactory); + context.subscriptions.push(languages.registerDocumentSymbolProvider(PYTHON, symbolProvider)); + + if (this.pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { + context.subscriptions.push(languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(jediFactory), '(', ',')); + } + + const unitTestOutChannel = this.serviceManager.get(IOutputChannel, TEST_OUTPUT_CHANNEL); + tests.activate(context, unitTestOutChannel, symbolProvider, this.serviceManager); + + return true; + } + + // tslint:disable-next-line:no-empty + public async deactivate(): Promise { } +} diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts new file mode 100644 index 000000000000..11cc140d2863 --- /dev/null +++ b/src/client/activation/downloader.ts @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as fs from 'fs'; +import { IncomingMessage } from 'http'; +import * as https from 'https'; +import * as path from 'path'; +import * as unzip from 'unzip'; +import { ExtensionContext, OutputChannel } from 'vscode'; +import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { noop } from '../common/core.utils'; +import { createDeferred, createTemporaryFile } from '../common/helpers'; +import { IPlatformService } from '../common/platform/types'; +import { IOutputChannel } from '../common/types'; +import { IServiceContainer } from '../ioc/types'; +import { HashVerifier } from './hashVerifier'; +import { PlatformData } from './platformData'; + +const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-analysis'; +const downloadBaseFileName = 'python-analysis-vscode'; +const downloadVersion = '0.1.0'; +const downloadFileExtension = '.nupkg'; + +export class AnalysisEngineDownloader { + private readonly output: OutputChannel; + private readonly platformData: PlatformData; + + constructor(private readonly services: IServiceContainer, private engineFolder: string) { + this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + const platform = this.services.get(IPlatformService); + this.platformData = new PlatformData(platform); + } + + public async downloadAnalysisEngine(context: ExtensionContext): Promise { + const localTempFilePath = await this.downloadFile(); + try { + await this.verifyDownload(localTempFilePath); + await this.unpackArchive(context.extensionPath, localTempFilePath); + } finally { + fs.unlink(localTempFilePath, noop); + } + } + + private async downloadFile(): Promise { + const platformString = this.platformData.getPlatformDesignator(); + const remoteFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; + const uri = `${downloadUriPrefix}/${remoteFileName}`; + this.output.append(`Downloading ${uri}... `); + const tempFile = await createTemporaryFile(downloadFileExtension); + + const deferred = createDeferred(); + const fileStream = fs.createWriteStream(tempFile.filePath); + fileStream.on('finish', () => { + fileStream.close(); + deferred.resolve(); + }).on('error', (err) => { + tempFile.cleanupCallback(); + this.handleError(`Unable to download Python Analysis Engine. Error ${err}`); + }); + + let firstResponse = true; + https.get(uri, (response) => { + this.checkHttpResponse(response); + if (firstResponse) { + this.reportDownloadSize(response); + firstResponse = false; + } + response.pipe(fileStream); + }); + + await deferred.promise; + this.output.append('complete.'); + return tempFile.filePath; + } + + private async verifyDownload(filePath: string): Promise { + this.output.appendLine(''); + this.output.append('Verifying download... '); + const verifier = new HashVerifier(); + if (!await verifier.verifyHash(filePath, this.platformData.getExpectedHash())) { + this.handleError('Hash of the downloaded file does not match.'); + } + this.output.append('valid.'); + } + + private async unpackArchive(extensionPath: string, tempFilePath: string): Promise { + this.output.appendLine(''); + this.output.append('Unpacking archive... '); + + const installFolder = path.join(extensionPath, this.engineFolder); + const deferred = createDeferred(); + + fs.createReadStream(tempFilePath) + .pipe(unzip.Extract({ path: installFolder })) + .on('finish', () => { + deferred.resolve(); + }) + .on('error', (err) => { + this.handleError(`Unable to unpack downloaded file. Error ${err}.`); + }); + await deferred.promise; + this.output.append('done.'); + } + + private handleError(message: string) { + this.output.appendLine('failed.'); + this.output.appendLine(message); + throw new Error(message); + } + + private checkHttpResponse(response: IncomingMessage): boolean { + if (response.statusCode && response.statusCode !== 0 && response.statusCode !== 200) { + this.handleError(`HTTPS request failed: ${response.statusCode} : ${(response.statusMessage ? response.statusMessage : '')}`); + return false; + } + return true; + } + + private reportDownloadSize(response: IncomingMessage): number { + if (response.rawHeaders.length >= 2 && response.rawHeaders[0] === 'Content-Length') { + const size = parseInt(response.rawHeaders[1], 10); + if (size > 0) { + this.output.append(` ${Math.round(size / 1024)} KB...`); + } + } + return 0; + } +} diff --git a/src/client/activation/hashVerifier.ts b/src/client/activation/hashVerifier.ts new file mode 100644 index 000000000000..8f1b157be58d --- /dev/null +++ b/src/client/activation/hashVerifier.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { createHash } from 'crypto'; +import * as fs from 'fs'; +import { createDeferred } from '../common/helpers'; + +export class HashVerifier { + public async verifyHash(filePath: string, expectedDigest: string): Promise { + const readStream = fs.createReadStream(filePath); + const deferred = createDeferred(); + const hash = createHash('sha512'); + hash.setEncoding('hex'); + readStream + .on('end', () => { + hash.end(); + deferred.resolve(); + }) + .on('error', (err) => { + throw new Error(`Unable to calculate file hash. Error ${err}`); + }); + + readStream.pipe(hash); + await deferred.promise; + const actual = hash.read(); + return expectedDigest === '' ? true : actual === expectedDigest; + } +} diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts new file mode 100644 index 000000000000..133c7f17144f --- /dev/null +++ b/src/client/activation/platformData.ts @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IPlatformService } from '../common/platform/types'; +import { + analysis_engine_linux_x64_sha512, + analysis_engine_osx_x64_sha512, + analysis_engine_win_x64_sha512, + analysis_engine_win_x86_sha512 +} from './analysisEngineHashes'; + +export class PlatformData { + constructor(private platform: IPlatformService) { } + public getPlatformDesignator(): string { + if (this.platform.isWindows) { + return this.platform.is64bit ? 'win-x64' : 'win-x86'; + } + if (this.platform.isMac) { + return 'osx-x64'; + } + if (this.platform.isLinux && this.platform.is64bit) { + return 'linux-x64'; + } + throw new Error('Python Analysis Engine does not support 32-bit Linux.'); + } + + public getExpectedHash(): string { + if (this.platform.isWindows) { + return this.platform.is64bit ? analysis_engine_win_x64_sha512 : analysis_engine_win_x86_sha512; + } + if (this.platform.isMac) { + return analysis_engine_osx_x64_sha512; + } + if (this.platform.isLinux && this.platform.is64bit) { + return analysis_engine_linux_x64_sha512; + } + throw new Error('Unknown platform.'); + } +} diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts new file mode 100644 index 000000000000..f8366a6ce5dd --- /dev/null +++ b/src/client/activation/types.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as vscode from 'vscode'; + +export interface IExtensionActivator { + activate(context: vscode.ExtensionContext): Promise; + deactivate(): Promise; +} diff --git a/src/client/extension.ts b/src/client/extension.ts index e680bfb21553..e86959d62255 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -1,205 +1,191 @@ -'use strict'; -// This line should always be right on top. -// tslint:disable-next-line:no-any -if ((Reflect as any).metadata === undefined) { - // tslint:disable-next-line:no-require-imports no-var-requires - require('reflect-metadata'); -} -import { Container } from 'inversify'; -import { - debug, Disposable, DocumentFilter, ExtensionContext, - extensions, IndentAction, languages, Memento, - OutputChannel, window -} from 'vscode'; -import { PythonSettings } from './common/configSettings'; -import { STANDARD_OUTPUT_CHANNEL } from './common/constants'; -import { FeatureDeprecationManager } from './common/featureDeprecationManager'; -import { createDeferred } from './common/helpers'; -import { PythonInstaller } from './common/installer/pythonInstallation'; -import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; -import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; -import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; -import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; -import { StopWatch } from './common/stopWatch'; -import { GLOBAL_MEMENTO, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; -import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; -import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; -import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; -import { IDebugConfigurationProvider } from './debugger/types'; -import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; -import { IInterpreterSelector } from './interpreter/configuration/types'; -import { ICondaService, IInterpreterService, IShebangCodeLensProvider } from './interpreter/contracts'; -import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; -import { ServiceContainer } from './ioc/container'; -import { ServiceManager } from './ioc/serviceManager'; -import { IServiceContainer } from './ioc/types'; -import { JediFactory } from './languageServices/jediProxyFactory'; -import { LinterCommands } from './linters/linterCommands'; -import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; -import { ILintingEngine } from './linters/types'; -import { PythonCompletionItemProvider } from './providers/completionProvider'; -import { PythonDefinitionProvider } from './providers/definitionProvider'; -import { PythonFormattingEditProvider } from './providers/formatProvider'; -import { PythonHoverProvider } from './providers/hoverProvider'; -import { LinterProvider } from './providers/linterProvider'; -import { activateGoToObjectDefinitionProvider } from './providers/objectDefinitionProvider'; -import { PythonReferenceProvider } from './providers/referenceProvider'; -import { PythonRenameProvider } from './providers/renameProvider'; -import { ReplProvider } from './providers/replProvider'; -import { PythonSignatureProvider } from './providers/signatureProvider'; -import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider'; -import { PythonSymbolProvider } from './providers/symbolProvider'; -import { TerminalProvider } from './providers/terminalProvider'; -import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider'; -import * as sortImports from './sortImports'; -import { sendTelemetryEvent } from './telemetry'; -import { EDITOR_LOAD } from './telemetry/constants'; -import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; -import { ICodeExecutionManager } from './terminals/types'; -import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; -import { OnEnterFormatter } from './typeFormatters/onEnterFormatter'; -import { TEST_OUTPUT_CHANNEL } from './unittests/common/constants'; -import * as tests from './unittests/main'; -import { registerTypes as unitTestsRegisterTypes } from './unittests/serviceRegistry'; -import { WorkspaceSymbols } from './workspaceSymbols/main'; - -const PYTHON: DocumentFilter = { language: 'python' }; -const activationDeferred = createDeferred(); -export const activated = activationDeferred.promise; - -// tslint:disable-next-line:max-func-body-length -export async function activate(context: ExtensionContext) { - const cont = new Container(); - const serviceManager = new ServiceManager(cont); - const serviceContainer = new ServiceContainer(cont); - serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); - serviceManager.addSingletonInstance(IDisposableRegistry, context.subscriptions); - serviceManager.addSingletonInstance(IMemento, context.globalState, GLOBAL_MEMENTO); - serviceManager.addSingletonInstance(IMemento, context.workspaceState, WORKSPACE_MEMENTO); - - const standardOutputChannel = window.createOutputChannel('Python'); - const unitTestOutChannel = window.createOutputChannel('Python Test Log'); - serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); - serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); - - commonRegisterTypes(serviceManager); - processRegisterTypes(serviceManager); - variableRegisterTypes(serviceManager); - unitTestsRegisterTypes(serviceManager); - lintersRegisterTypes(serviceManager); - interpretersRegisterTypes(serviceManager); - formattersRegisterTypes(serviceManager); - platformRegisterTypes(serviceManager); - installerRegisterTypes(serviceManager); - commonRegisterTerminalTypes(serviceManager); - debugConfigurationRegisterTypes(serviceManager); - - serviceManager.get(ICodeExecutionManager).registerCommands(); - - const persistentStateFactory = serviceManager.get(IPersistentStateFactory); - const pythonSettings = PythonSettings.getInstance(); - // tslint:disable-next-line:no-floating-promises - sendStartupTelemetry(activated, serviceContainer); - - sortImports.activate(context, standardOutputChannel, serviceContainer); - const interpreterManager = serviceContainer.get(IInterpreterService); - - // This must be completed before we can continue. - interpreterManager.initialize(); - await interpreterManager.autoSetInterpreter(); - - const pythonInstaller = new PythonInstaller(serviceContainer); - pythonInstaller.checkPythonInstallation(PythonSettings.getInstance()) - .catch(ex => console.error('Python Extension: pythonInstaller.checkPythonInstallation', ex)); - - interpreterManager.refresh() - .catch(ex => console.error('Python Extension: interpreterManager.refresh', ex)); - - context.subscriptions.push(serviceContainer.get(IInterpreterSelector)); - context.subscriptions.push(activateUpdateSparkLibraryProvider()); - activateSimplePythonRefactorProvider(context, standardOutputChannel, serviceContainer); - const jediFactory = new JediFactory(context.asAbsolutePath('.'), serviceContainer); - context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); - - context.subscriptions.push(new ReplProvider(serviceContainer)); - context.subscriptions.push(new TerminalProvider(serviceContainer)); - context.subscriptions.push(new LinterCommands(serviceContainer)); - - // Enable indentAction - // tslint:disable-next-line:no-non-null-assertion - languages.setLanguageConfiguration(PYTHON.language!, { - onEnterRules: [ - { - beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*/, - action: { indentAction: IndentAction.Indent } - }, - { - beforeText: /^\s*#.*/, - afterText: /.+$/, - action: { indentAction: IndentAction.None, appendText: '# ' } - }, - { - beforeText: /^\s+(continue|break|return)\b.*/, - afterText: /\s+$/, - action: { indentAction: IndentAction.Outdent } - } - ] - }); - - context.subscriptions.push(jediFactory); - context.subscriptions.push(languages.registerRenameProvider(PYTHON, new PythonRenameProvider(serviceContainer))); - const definitionProvider = new PythonDefinitionProvider(jediFactory); - context.subscriptions.push(languages.registerDefinitionProvider(PYTHON, definitionProvider)); - context.subscriptions.push(languages.registerHoverProvider(PYTHON, new PythonHoverProvider(jediFactory))); - context.subscriptions.push(languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(jediFactory))); - context.subscriptions.push(languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(jediFactory, serviceContainer), '.')); - context.subscriptions.push(languages.registerCodeLensProvider(PYTHON, serviceContainer.get(IShebangCodeLensProvider))); - - const symbolProvider = new PythonSymbolProvider(jediFactory); - context.subscriptions.push(languages.registerDocumentSymbolProvider(PYTHON, symbolProvider)); - if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { - context.subscriptions.push(languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(jediFactory), '(', ',')); - } - if (pythonSettings.formatting.provider !== 'none') { - const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); - context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); - context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); - } - - const linterProvider = new LinterProvider(context, serviceContainer); - context.subscriptions.push(linterProvider); - - const jupyterExtension = extensions.getExtension('donjayamanne.jupyter'); - const lintingEngine = serviceContainer.get(ILintingEngine); - lintingEngine.linkJupiterExtension(jupyterExtension).ignoreErrors(); - - tests.activate(context, unitTestOutChannel, symbolProvider, serviceContainer); - - context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); - context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); - context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); - - serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { - context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); - }); - activationDeferred.resolve(); - - const deprecationMgr = new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension); - deprecationMgr.initialize(); - context.subscriptions.push(new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension)); -} - -async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { - const stopWatch = new StopWatch(); - const logger = serviceContainer.get(ILogger); - try { - await activatedPromise; - const duration = stopWatch.elapsedTime; - const condaLocator = serviceContainer.get(ICondaService); - const condaVersion = await condaLocator.getCondaVersion().catch(() => undefined); - const props = condaVersion ? { condaVersion } : undefined; - sendTelemetryEvent(EDITOR_LOAD, duration, props); - } catch (ex) { - logger.logError('sendStartupTelemetry failed.', ex); - } -} +'use strict'; +// This line should always be right on top. +// tslint:disable-next-line:no-any +if ((Reflect as any).metadata === undefined) { + // tslint:disable-next-line:no-require-imports no-var-requires + require('reflect-metadata'); +} +import { Container } from 'inversify'; +import { + debug, Disposable, DocumentFilter, ExtensionContext, + extensions, IndentAction, languages, Memento, + OutputChannel, window +} from 'vscode'; +import { IS_ANALYSIS_ENGINE_TEST } from '../test/constants'; +import { AnalysisExtensionActivator } from './activation/analysis'; +import { ClassicExtensionActivator } from './activation/classic'; +import { IExtensionActivator } from './activation/types'; +import { PythonSettings } from './common/configSettings'; +import { STANDARD_OUTPUT_CHANNEL } from './common/constants'; +import { FeatureDeprecationManager } from './common/featureDeprecationManager'; +import { createDeferred } from './common/helpers'; +import { PythonInstaller } from './common/installer/pythonInstallation'; +import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; +import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; +import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; +import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; +import { StopWatch } from './common/stopWatch'; +import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; +import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; +import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; +import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; +import { IDebugConfigurationProvider } from './debugger/types'; +import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; +import { IInterpreterSelector } from './interpreter/configuration/types'; +import { ICondaService, IInterpreterService } from './interpreter/contracts'; +import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; +import { ServiceContainer } from './ioc/container'; +import { ServiceManager } from './ioc/serviceManager'; +import { IServiceContainer } from './ioc/types'; +import { LinterCommands } from './linters/linterCommands'; +import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; +import { ILintingEngine } from './linters/types'; +import { PythonFormattingEditProvider } from './providers/formatProvider'; +import { LinterProvider } from './providers/linterProvider'; +import { ReplProvider } from './providers/replProvider'; +import { TerminalProvider } from './providers/terminalProvider'; +import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider'; +import * as sortImports from './sortImports'; +import { sendTelemetryEvent } from './telemetry'; +import { EDITOR_LOAD } from './telemetry/constants'; +import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; +import { ICodeExecutionManager } from './terminals/types'; +import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; +import { OnEnterFormatter } from './typeFormatters/onEnterFormatter'; +import { TEST_OUTPUT_CHANNEL } from './unittests/common/constants'; +import { registerTypes as unitTestsRegisterTypes } from './unittests/serviceRegistry'; +import { WorkspaceSymbols } from './workspaceSymbols/main'; + +const activationDeferred = createDeferred(); +export const activated = activationDeferred.promise; +const PYTHON: DocumentFilter = { language: 'python' }; + +// tslint:disable-next-line:max-func-body-length +export async function activate(context: ExtensionContext) { + const cont = new Container(); + const serviceManager = new ServiceManager(cont); + const serviceContainer = new ServiceContainer(cont); + registerServices(context, serviceManager, serviceContainer); + + const interpreterManager = serviceContainer.get(IInterpreterService); + // This must be completed before we can continue as language server needs the interpreter path. + interpreterManager.initialize(); + await interpreterManager.autoSetInterpreter(); + + const configuration = serviceManager.get(IConfigurationService); + const pythonSettings = configuration.getSettings(); + + const activator: IExtensionActivator = IS_ANALYSIS_ENGINE_TEST || !pythonSettings.jediEnabled + ? new AnalysisExtensionActivator(serviceManager, pythonSettings) + : new ClassicExtensionActivator(serviceManager, pythonSettings); + + await activator.activate(context); + + const standardOutputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + sortImports.activate(context, standardOutputChannel, serviceManager); + + serviceManager.get(ICodeExecutionManager).registerCommands(); + // tslint:disable-next-line:no-floating-promises + sendStartupTelemetry(activated, serviceContainer); + + const pythonInstaller = new PythonInstaller(serviceContainer); + pythonInstaller.checkPythonInstallation(PythonSettings.getInstance()) + .catch(ex => console.error('Python Extension: pythonInstaller.checkPythonInstallation', ex)); + + interpreterManager.refresh() + .catch(ex => console.error('Python Extension: interpreterManager.refresh', ex)); + + const jupyterExtension = extensions.getExtension('donjayamanne.jupyter'); + const lintingEngine = serviceManager.get(ILintingEngine); + lintingEngine.linkJupiterExtension(jupyterExtension).ignoreErrors(); + + context.subscriptions.push(new LinterCommands(serviceManager)); + const linterProvider = new LinterProvider(context, serviceManager); + context.subscriptions.push(linterProvider); + + // Enable indentAction + // tslint:disable-next-line:no-non-null-assertion + languages.setLanguageConfiguration(PYTHON.language!, { + onEnterRules: [ + { + beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*/, + action: { indentAction: IndentAction.Indent } + }, + { + beforeText: /^\s*#.*/, + afterText: /.+$/, + action: { indentAction: IndentAction.None, appendText: '# ' } + }, + { + beforeText: /^\s+(continue|break|return)\b.*/, + afterText: /\s+$/, + action: { indentAction: IndentAction.Outdent } + } + ] + }); + + if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'none') { + const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); + context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); + context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); + } + + context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); + context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); + + const persistentStateFactory = serviceManager.get(IPersistentStateFactory); + const deprecationMgr = new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension); + deprecationMgr.initialize(); + context.subscriptions.push(new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension)); + + context.subscriptions.push(serviceContainer.get(IInterpreterSelector)); + context.subscriptions.push(activateUpdateSparkLibraryProvider()); + + context.subscriptions.push(new ReplProvider(serviceContainer)); + context.subscriptions.push(new TerminalProvider(serviceContainer)); + context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); + + serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { + context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); + }); + activationDeferred.resolve(); +} + +function registerServices(context: ExtensionContext, serviceManager: ServiceManager, serviceContainer: ServiceContainer) { + serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); + serviceManager.addSingletonInstance(IDisposableRegistry, context.subscriptions); + serviceManager.addSingletonInstance(IMemento, context.globalState, GLOBAL_MEMENTO); + serviceManager.addSingletonInstance(IMemento, context.workspaceState, WORKSPACE_MEMENTO); + + const standardOutputChannel = window.createOutputChannel('Python'); + const unitTestOutChannel = window.createOutputChannel('Python Test Log'); + serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); + serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); + + commonRegisterTypes(serviceManager); + processRegisterTypes(serviceManager); + variableRegisterTypes(serviceManager); + unitTestsRegisterTypes(serviceManager); + lintersRegisterTypes(serviceManager); + interpretersRegisterTypes(serviceManager); + formattersRegisterTypes(serviceManager); + platformRegisterTypes(serviceManager); + installerRegisterTypes(serviceManager); + commonRegisterTerminalTypes(serviceManager); + debugConfigurationRegisterTypes(serviceManager); +} + +async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { + const stopWatch = new StopWatch(); + const logger = serviceContainer.get(ILogger); + try { + await activatedPromise; + const duration = stopWatch.elapsedTime; + const condaLocator = serviceContainer.get(ICondaService); + const condaVersion = await condaLocator.getCondaVersion().catch(() => undefined); + const props = condaVersion ? { condaVersion } : undefined; + sendTelemetryEvent(EDITOR_LOAD, duration, props); + } catch (ex) { + logger.logError('sendStartupTelemetry failed.', ex); + } +} diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index 4c4b8fd65992..5219e4ababd9 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -1,222 +1,248 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable:no-unused-variable -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { rootWorkspaceUri } from '../common'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; -import { UnitTestIocContainer } from '../unittests/serviceRegistry'; - -const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); -const fileOne = path.join(autoCompPath, 'one.py'); -const fileImport = path.join(autoCompPath, 'imp.py'); -const fileDoc = path.join(autoCompPath, 'doc.py'); -const fileLambda = path.join(autoCompPath, 'lamb.py'); -const fileDecorator = path.join(autoCompPath, 'deco.py'); -const fileEncoding = path.join(autoCompPath, 'four.py'); -const fileEncodingUsed = path.join(autoCompPath, 'five.py'); -const fileSuppress = path.join(autoCompPath, 'suppress.py'); - -// tslint:disable-next-line:max-func-body-length -suite('Autocomplete', () => { - let isPython2: boolean; - let ioc: UnitTestIocContainer; - suiteSetup(async () => { - await initialize(); - initializeDI(); - isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(async () => { - await closeActiveWindows(); - ioc.dispose(); - }); - function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - } - - test('For "sys."', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileOne).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(3, 10); - return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - }).then(list => { - assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); - }).then(done, done); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/975 - test('For "import *"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileImport); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(1, 4); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.equal(list!.items.filter(item => item.label === 'fstat').length, 1, 'fstat not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/898 - test('For "f.readlines()"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileDoc); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(5, 27); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - // These are not known to work, jedi issue - // assert.equal(list.items.filter(item => item.label === 'capitalize').length, 1, 'capitalize not found (known not to work, Jedi issue)'); - // assert.notEqual(list.items.filter(item => item.label === 'upper').length, 1, 'upper not found'); - // assert.notEqual(list.items.filter(item => item.label === 'lower').length, 1, 'lower not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/265 - test('For "lambda"', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } - const textDocument = await vscode.workspace.openTextDocument(fileLambda); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(1, 19); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list!.items.filter(item => item.label === 'append').length, 0, 'append not found'); - assert.notEqual(list!.items.filter(item => item.label === 'clear').length, 0, 'clear not found'); - assert.notEqual(list!.items.filter(item => item.label === 'count').length, 0, 'cound not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/630 - test('For "abc.decorators"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileDecorator); - await vscode.window.showTextDocument(textDocument); - let position = new vscode.Position(3, 9); - let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); - - position = new vscode.Position(4, 9); - list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); - - position = new vscode.Position(2, 30); - list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/727 - // https://github.com/DonJayamanne/pythonVSCode/issues/746 - // https://github.com/davidhalter/jedi/issues/859 - test('For "time.slee"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileDoc); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(10, 9); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - - const items = list!.items.filter(item => item.label === 'sleep'); - assert.notEqual(items.length, 0, 'sleep not found'); - - checkDocumentation(items[0], 'Delay execution for a given number of seconds. The argument may be'); - }); - - test('For custom class', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileOne).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(30, 4); - return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - }).then(list => { - assert.notEqual(list!.items.filter(item => item.label === 'method1').length, 0, 'method1 not found'); - assert.notEqual(list!.items.filter(item => item.label === 'method2').length, 0, 'method2 not found'); - }).then(done, done); - }); - - test('With Unicode Characters', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileEncoding).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(25, 4); - return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - }).then(list => { - const items = list!.items.filter(item => item.label === 'bar'); - assert.equal(items.length, 1, 'bar not found'); - - const expected = `说明 - keep this line, it works${EOL}delete following line, it works${EOL}如果存在需要等待审批或正在执行的任务,将不刷新页面`; - checkDocumentation(items[0], expected); - }).then(done, done); - }); - - test('Across files With Unicode Characters', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(1, 5); - return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - }).then(list => { - let items = list!.items.filter(item => item.label === 'Foo'); - assert.equal(items.length, 1, 'Foo not found'); - checkDocumentation(items[0], '说明'); - - items = list!.items.filter(item => item.label === 'showMessage'); - assert.equal(items.length, 1, 'showMessage not found'); - - const expected = `Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ${EOL}Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.`; - checkDocumentation(items[0], expected); - }).then(done, done); - }); - - // https://github.com/Microsoft/vscode-python/issues/110 - test('Suppress in strings/comments', async () => { - const positions = [ - new vscode.Position(0, 1), // false - new vscode.Position(0, 9), // true - new vscode.Position(0, 12), // false - new vscode.Position(1, 1), // false - new vscode.Position(1, 3), // false - new vscode.Position(2, 7), // false - new vscode.Position(3, 0), // false - new vscode.Position(4, 2), // false - new vscode.Position(4, 8), // false - new vscode.Position(5, 4), // false - new vscode.Position(5, 10) // false - ]; - const expected = [ - false, true, false, false, false, false, false, false, false, false, false - ]; - const textDocument = await vscode.workspace.openTextDocument(fileSuppress); - await vscode.window.showTextDocument(textDocument); - for (let i = 0; i < positions.length; i += 1) { - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, positions[i]); - const result = list!.items.filter(item => item.label === 'abs').length; - assert.equal(result > 0, expected[i], - `Expected ${expected[i]} at position ${positions[i].line}:${positions[i].character} but got ${result}`); - } - }); -}); - -// tslint:disable-next-line:no-any -function checkDocumentation(item: vscode.CompletionItem, expectedContains: string): void { - const documentation = item.documentation as vscode.MarkdownString; - assert.notEqual(documentation, null, 'Documentation is not MarkdownString'); - - const inDoc = documentation.value.indexOf(expectedContains) >= 0; - assert.equal(inDoc, true, 'Documentation incorrect'); -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// tslint:disable:no-unused-variable +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IConfigurationService } from '../../client/common/types'; +import { rootWorkspaceUri } from '../common'; +import { closeActiveWindows, initialize, initializeTest, IS_ANALYSIS_ENGINE_TEST } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const fileOne = path.join(autoCompPath, 'one.py'); +const fileImport = path.join(autoCompPath, 'imp.py'); +const fileDoc = path.join(autoCompPath, 'doc.py'); +const fileLambda = path.join(autoCompPath, 'lamb.py'); +const fileDecorator = path.join(autoCompPath, 'deco.py'); +const fileEncoding = path.join(autoCompPath, 'four.py'); +const fileEncodingUsed = path.join(autoCompPath, 'five.py'); +const fileSuppress = path.join(autoCompPath, 'suppress.py'); + +// tslint:disable-next-line:max-func-body-length +suite('Autocomplete', () => { + let isPython2: boolean; + let ioc: UnitTestIocContainer; + + suiteSetup(async () => { + await initialize(); + initializeDI(); + isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(async () => { + await closeActiveWindows(); + ioc.dispose(); + }); + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerVariableTypes(); + ioc.registerProcessTypes(); + } + + test('For "sys."', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileOne).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(3, 10); + return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + }).then(list => { + assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); + }).then(done, done); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/975 + test('For "import *"', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileImport); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(1, 4); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.equal(list!.items.filter(item => item.label === 'fstat').length, 1, 'fstat not found'); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/898 + test('For "f.readlines()"', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileDoc); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(5, 27); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + // These are not known to work, jedi issue + // assert.equal(list.items.filter(item => item.label === 'capitalize').length, 1, 'capitalize not found (known not to work, Jedi issue)'); + // assert.notEqual(list.items.filter(item => item.label === 'upper').length, 1, 'upper not found'); + // assert.notEqual(list.items.filter(item => item.label === 'lower').length, 1, 'lower not found'); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/265 + test('For "lambda"', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + return; + } + const textDocument = await vscode.workspace.openTextDocument(fileLambda); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(1, 19); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'append').length, 0, 'append not found'); + assert.notEqual(list!.items.filter(item => item.label === 'clear').length, 0, 'clear not found'); + assert.notEqual(list!.items.filter(item => item.label === 'count').length, 0, 'cound not found'); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/630 + test('For "abc.decorators"', async () => { + // Disabled for MS Python Code Analysis, see https://github.com/Microsoft/PTVS/issues/3857 + if (IS_ANALYSIS_ENGINE_TEST) { + return; + } + const textDocument = await vscode.workspace.openTextDocument(fileDecorator); + await vscode.window.showTextDocument(textDocument); + let position = new vscode.Position(3, 9); + let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); + assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); + + position = new vscode.Position(4, 9); + list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); + assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); + + position = new vscode.Position(2, 30); + list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); + assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/727 + // https://github.com/DonJayamanne/pythonVSCode/issues/746 + // https://github.com/davidhalter/jedi/issues/859 + test('For "time.slee"', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileDoc); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(10, 9); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + + const items = list!.items.filter(item => item.label === 'sleep'); + assert.notEqual(items.length, 0, 'sleep not found'); + + checkDocumentation(items[0], 'Delay execution for a given number of seconds. The argument may be'); + }); + + test('For custom class', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileOne).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(30, 4); + return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + }).then(list => { + assert.notEqual(list!.items.filter(item => item.label === 'method1').length, 0, 'method1 not found'); + assert.notEqual(list!.items.filter(item => item.label === 'method2').length, 0, 'method2 not found'); + }).then(done, done); + }); + + test('With Unicode Characters', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileEncoding).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(25, 4); + return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + }).then(list => { + const items = list!.items.filter(item => item.label === 'bar'); + assert.equal(items.length, 1, 'bar not found'); + + const expected1 = '说明 - keep this line, it works'; + checkDocumentation(items[0], expected1); + + const expected2 = '如果存在需要等待审批或正在执行的任务,将不刷新页面'; + checkDocumentation(items[0], expected2); + }).then(done, done); + }); + + test('Across files With Unicode Characters', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(1, 5); + return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + }).then(list => { + let items = list!.items.filter(item => item.label === 'Foo'); + assert.equal(items.length, 1, 'Foo not found'); + checkDocumentation(items[0], '说明'); + + items = list!.items.filter(item => item.label === 'showMessage'); + assert.equal(items.length, 1, 'showMessage not found'); + + const expected1 = 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.'; + checkDocumentation(items[0], expected1); + + const expected2 = 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.'; + checkDocumentation(items[0], expected2); + }).then(done, done); + }); + + // https://github.com/Microsoft/vscode-python/issues/110 + test('Suppress in strings/comments', async () => { + // Excluded from MS Python Code Analysis b/c skipping of strings and comments + // is not yet there. See https://github.com/Microsoft/PTVS/issues/3798 + if (IS_ANALYSIS_ENGINE_TEST) { + return; + } + const positions = [ + new vscode.Position(0, 1), // false + new vscode.Position(0, 9), // true + new vscode.Position(0, 12), // false + new vscode.Position(1, 1), // false + new vscode.Position(1, 3), // false + new vscode.Position(2, 7), // false + new vscode.Position(3, 0), // false + new vscode.Position(4, 2), // false + new vscode.Position(4, 8), // false + new vscode.Position(5, 4), // false + new vscode.Position(5, 10) // false + ]; + const expected = [ + false, true, false, false, false, false, false, false, false, false, false + ]; + const textDocument = await vscode.workspace.openTextDocument(fileSuppress); + await vscode.window.showTextDocument(textDocument); + for (let i = 0; i < positions.length; i += 1) { + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, positions[i]); + const result = list!.items.filter(item => item.label === 'abs').length; + assert.equal(result > 0, expected[i], + `Expected ${expected[i]} at position ${positions[i].line}:${positions[i].character} but got ${result}`); + } + }); +}); + +// tslint:disable-next-line:no-any +function checkDocumentation(item: vscode.CompletionItem, expectedContains: string): void { + let isValidType = false; + let documentation: string; + + if (typeof item.documentation === 'string') { + isValidType = true; + documentation = item.documentation; + } else { + documentation = (item.documentation as vscode.MarkdownString).value; + isValidType = documentation !== undefined && documentation !== null; + } + assert.equal(isValidType, true, 'Documentation is neither string nor vscode.MarkdownString'); + + const inDoc = documentation.indexOf(expectedContains) >= 0; + assert.equal(inDoc, true, 'Documentation incorrect'); +} diff --git a/src/test/definitions/hover.test.ts b/src/test/definitions/hover.jedi.test.ts similarity index 96% rename from src/test/definitions/hover.test.ts rename to src/test/definitions/hover.jedi.test.ts index ba194a902446..5d1ea8b386cd 100644 --- a/src/test/definitions/hover.test.ts +++ b/src/test/definitions/hover.jedi.test.ts @@ -1,276 +1,283 @@ -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; -import { normalizeMarkedString } from '../textUtils'; - -const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); -const hoverPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'hover'); -const fileOne = path.join(autoCompPath, 'one.py'); -const fileThree = path.join(autoCompPath, 'three.py'); -const fileEncoding = path.join(autoCompPath, 'four.py'); -const fileEncodingUsed = path.join(autoCompPath, 'five.py'); -const fileHover = path.join(autoCompPath, 'hoverTest.py'); -const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); - -// tslint:disable-next-line:max-func-body-length -suite('Hover Definition', () => { - suiteSetup(initialize); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); - - test('Method', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileOne).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(30, 5); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '30,4', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); - assert.equal(def[0].contents.length, 1, 'Invalid content items'); - // tslint:disable-next-line:prefer-template - const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; - assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'function signature incorrect'); - }).then(done, done); - }); - - test('Across files', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileThree).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(1, 12); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,9', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + 'def fun()' + EOL + '```' + EOL + 'This is fun', 'Invalid conents'); - }).then(done, done); - }); - - test('With Unicode Characters', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileEncoding).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(25, 6); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,4', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + 'def bar()' + EOL + '```' + EOL + - '说明 - keep this line, it works' + EOL + 'delete following line, it works' + - EOL + '如果存在需要等待审批或正在执行的任务,将不刷新页面', 'Invalid conents'); - }).then(done, done); - }); - - test('Across files with Unicode Characters', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(1, 11); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,5', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + - 'def showMessage()' + EOL + - '```' + EOL + - 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ' + EOL + - 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', 'Invalid conents'); - }).then(done, done); - }); - - test('Nothing for keywords (class)', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileOne).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(5, 1); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(def => { - assert.equal(def!.length, 0, 'Definition length is incorrect'); - }).then(done, done); - }); - - test('Nothing for keywords (for)', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(3, 1); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(def => { - assert.equal(def!.length, 0, 'Definition length is incorrect'); - }).then(done, done); - }); - - test('Highlighting Class', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(11, 15); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,12', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - const documentation = '```python' + EOL + - 'class Random(x=None)' + EOL + - '```' + EOL + - 'Random number generator base class used by bound module functions.' + EOL + - '' + EOL + - 'Used to instantiate instances of Random to get generators that don\'t' + EOL + - 'share state.' + EOL + - '' + EOL + - 'Class Random can also be subclassed if you want to use a different basic' + EOL + - 'generator of your own devising: in that case, override the following' + EOL + - 'methods: random(), seed(), getstate(), and setstate().' + EOL + - 'Optionally, implement a getrandbits() method so that randrange()' + EOL + - 'can cover arbitrarily large ranges.'; - - assert.equal(normalizeMarkedString(def[0].contents[0]), documentation, 'Invalid conents'); - }).then(done, done); - }); - - test('Highlight Method', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(12, 10); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,5', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + - 'def randint(a, b)' + EOL + - '```' + EOL + - 'Return random integer in range [a, b], including both end points.', 'Invalid conents'); - }).then(done, done); - }); - - test('Highlight Function', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(8, 14); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,11', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + - 'def acos(x)' + EOL + - '```' + EOL + - 'Return the arc cosine (measured in radians) of x.', 'Invalid conents'); - }).then(done, done); - }); - - test('Highlight Multiline Method Signature', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(14, 14); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,9', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + - 'class Thread(group=None, target=None, name=None, args=(), kwargs=None, verbose=None)' + EOL + - '```' + EOL + - 'A class that represents a thread of control.' + EOL + - '' + EOL + - 'This class can be safely subclassed in a limited fashion.', 'Invalid content items'); - }).then(done, done); - }); - - test('Variable', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(6, 2); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(def[0].contents.length, 1, 'Only expected one result'); - const contents = normalizeMarkedString(def[0].contents[0]); - if (contents.indexOf('```python') === -1) { - assert.fail(contents, '', 'First line is incorrect', 'compare'); - } - if (contents.indexOf('rnd: Random') === -1) { - assert.fail(contents, '', 'Variable name or type are missing', 'compare'); - } - }).then(done, done); - }); - - test('format().capitalize()', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileStringFormat); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(5, 41); - const def = (await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position))!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(def[0].contents.length, 1, 'Only expected one result'); - const contents = normalizeMarkedString(def[0].contents[0]); - if (contents.indexOf('def capitalize') === -1) { - assert.fail(contents, '', '\'def capitalize\' is missing', 'compare'); - } - if (contents.indexOf('Return a capitalized version of S') === -1 && - contents.indexOf('Return a copy of the string S with only its first character') === -1) { - assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); - } - }); -}); +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const hoverPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'hover'); +const fileOne = path.join(autoCompPath, 'one.py'); +const fileThree = path.join(autoCompPath, 'three.py'); +const fileEncoding = path.join(autoCompPath, 'four.py'); +const fileEncodingUsed = path.join(autoCompPath, 'five.py'); +const fileHover = path.join(autoCompPath, 'hoverTest.py'); +const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); + +// tslint:disable-next-line:max-func-body-length +suite('Hover Definition (Jedi)', () => { + suiteSetup(async function () { + if (IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + test('Method', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileOne).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(30, 5); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '30,4', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(def[0].contents.length, 1, 'Invalid content items'); + // tslint:disable-next-line:prefer-template + const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; + assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'function signature incorrect'); + }).then(done, done); + }); + + test('Across files', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileThree).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(1, 12); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,9', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + 'def fun()' + EOL + '```' + EOL + 'This is fun', 'Invalid conents'); + }).then(done, done); + }); + + test('With Unicode Characters', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileEncoding).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(25, 6); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,4', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + 'def bar()' + EOL + '```' + EOL + + '说明 - keep this line, it works' + EOL + 'delete following line, it works' + + EOL + '如果存在需要等待审批或正在执行的任务,将不刷新页面', 'Invalid conents'); + }).then(done, done); + }); + + test('Across files with Unicode Characters', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(1, 11); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,5', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + + 'def showMessage()' + EOL + + '```' + EOL + + 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ' + EOL + + 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', 'Invalid conents'); + }).then(done, done); + }); + + test('Nothing for keywords (class)', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileOne).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(5, 1); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(def => { + assert.equal(def!.length, 0, 'Definition length is incorrect'); + }).then(done, done); + }); + + test('Nothing for keywords (for)', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(3, 1); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(def => { + assert.equal(def!.length, 0, 'Definition length is incorrect'); + }).then(done, done); + }); + + test('Highlighting Class', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(11, 15); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,12', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + const documentation = '```python' + EOL + + 'class Random(x=None)' + EOL + + '```' + EOL + + 'Random number generator base class used by bound module functions.' + EOL + + '' + EOL + + 'Used to instantiate instances of Random to get generators that don\'t' + EOL + + 'share state.' + EOL + + '' + EOL + + 'Class Random can also be subclassed if you want to use a different basic' + EOL + + 'generator of your own devising: in that case, override the following' + EOL + + 'methods: random(), seed(), getstate(), and setstate().' + EOL + + 'Optionally, implement a getrandbits() method so that randrange()' + EOL + + 'can cover arbitrarily large ranges.'; + + assert.equal(normalizeMarkedString(def[0].contents[0]), documentation, 'Invalid conents'); + }).then(done, done); + }); + + test('Highlight Method', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(12, 10); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,5', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + + 'def randint(a, b)' + EOL + + '```' + EOL + + 'Return random integer in range [a, b], including both end points.', 'Invalid conents'); + }).then(done, done); + }); + + test('Highlight Function', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(8, 14); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,11', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + + 'def acos(x)' + EOL + + '```' + EOL + + 'Return the arc cosine (measured in radians) of x.', 'Invalid conents'); + }).then(done, done); + }); + + test('Highlight Multiline Method Signature', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(14, 14); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,9', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + + 'class Thread(group=None, target=None, name=None, args=(), kwargs=None, verbose=None)' + EOL + + '```' + EOL + + 'A class that represents a thread of control.' + EOL + + '' + EOL + + 'This class can be safely subclassed in a limited fashion.', 'Invalid content items'); + }).then(done, done); + }); + + test('Variable', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(6, 2); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('```python') === -1) { + assert.fail(contents, '', 'First line is incorrect', 'compare'); + } + if (contents.indexOf('rnd: Random') === -1) { + assert.fail(contents, '', 'Variable name or type are missing', 'compare'); + } + }).then(done, done); + }); + + test('format().capitalize()', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileStringFormat); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(5, 41); + const def = (await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position))!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('def capitalize') === -1) { + assert.fail(contents, '', '\'def capitalize\' is missing', 'compare'); + } + if (contents.indexOf('Return a capitalized version of S') === -1 && + contents.indexOf('Return a copy of the string S with only its first character') === -1) { + assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); + } + }); +}); diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ptvs.test.ts new file mode 100644 index 000000000000..21da0f9a2342 --- /dev/null +++ b/src/test/definitions/hover.ptvs.test.ts @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const hoverPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'hover'); +const fileOne = path.join(autoCompPath, 'one.py'); +const fileThree = path.join(autoCompPath, 'three.py'); +const fileEncoding = path.join(autoCompPath, 'four.py'); +const fileEncodingUsed = path.join(autoCompPath, 'five.py'); +const fileHover = path.join(autoCompPath, 'hoverTest.py'); +const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); + +let textDocument: vscode.TextDocument; + +// tslint:disable-next-line:max-func-body-length +suite('Hover Definition (MS Python Code Analysis)', () => { + suiteSetup(async function () { + if (!IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + async function openAndHover(file: string, line: number, character: number): Promise { + textDocument = await vscode.workspace.openTextDocument(file); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(line, character); + const result = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + return result ? result : []; + } + + test('Method', async () => { + const def = await openAndHover(fileOne, 30, 5); + assert.equal(def.length, 1, 'Definition length is incorrect'); + + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '30,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(def[0].contents.length, 1, 'Invalid content items'); + // tslint:disable-next-line:prefer-template + const expectedContent = 'method method1 of one.Class1 objects ' + EOL + EOL + '```html ' + EOL + ' This is method1 ' + EOL + '```'; + assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'function signature incorrect'); + }); + + test('Across files', async () => { + const def = await openAndHover(fileThree, 1, 12); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + const expectedContent = 'method fun of two.ct objects ' + EOL + EOL + '```html ' + EOL + ' This is fun ' + EOL + '```'; + assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'Invalid conents'); + }); + + test('With Unicode Characters', async () => { + const def = await openAndHover(fileEncoding, 25, 6); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + const expectedContent = 'def four.Foo.bar()' + EOL + EOL + + '```html ' + EOL + + ' 说明 - keep this line, it works ' + EOL + + ' delete following line, it works ' + EOL + + ' 如果存在需要等待审批或正在执行的任务,将不刷新页面 ' + EOL + + '```'; + assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'Invalid contents'); + }); + + test('Across files with Unicode Characters', async () => { + const def = await openAndHover(fileEncodingUsed, 1, 11); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + const expectedContent = 'def four.showMessage()' + EOL + EOL + + '```html ' + EOL + + ' Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ' + EOL + + ' Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку. ' + EOL + + '```'; + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'Invalid contents'); + }); + + test('Nothing for keywords (class)', async () => { + const def = await openAndHover(fileOne, 5, 1); + assert.equal(def.length, 0, 'Definition length is incorrect'); + }); + + test('Nothing for keywords (for)', async () => { + const def = await openAndHover(fileHover, 3, 1); + assert.equal(def!.length, 0, 'Definition length is incorrect'); + }); + + test('Highlighting Class', async () => { + const def = await openAndHover(fileHover, 11, 15); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,7', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + const documentation = 'Random' + EOL + EOL + + 'Random number generator base class used by bound module functions. ' + EOL + + '```html ' + EOL + + ' Used to instantiate instances of Random to get generators that don\'t ' + EOL + + ' share state. ' + EOL + + ' ' + EOL + + ' Class Random can also be subclassed if you want to use a different basic ' + EOL + + ' generator of your own devising: in that case, override the following ' + EOL + + ' methods: random(), seed(), getstate(), and setstate(). ' + EOL + + ' Optionally, implement a getrandbits() method so that randrange() ' + EOL + + ' can cover arbitrarily large ranges. ' + EOL + + '```'; + assert.equal(normalizeMarkedString(def[0].contents[0]), documentation, 'Invalid conents'); + }); + + test('Highlight Method', async () => { + const def = await openAndHover(fileHover, 12, 10); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); + assert.equal(normalizeMarkedString(def[0].contents[0]), + // tslint:disable-next-line:prefer-template + 'method randint of misc.Random objects -> int' + EOL + EOL + + 'Return random integer in range [a, b], including both end points.', 'Invalid conents'); + }); + + test('Highlight Function', async () => { + const def = await openAndHover(fileHover, 8, 14); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,6', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), + // tslint:disable-next-line:prefer-template + 'built-in function acos(x)' + EOL + EOL + + 'acos(x) ' + EOL + + ' ' + EOL + + 'Return the arc cosine (measured in radians) of x.', 'Invalid conents'); + }); + + test('Highlight Multiline Method Signature', async () => { + const def = await openAndHover(fileHover, 14, 14); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,4', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), + // tslint:disable-next-line:prefer-template + 'Thread' + EOL + EOL + + 'A class that represents a thread of control. ' + EOL + + '```html ' + EOL + + ' This class can be safely subclassed in a limited fashion. ' + EOL + + '```', 'Invalid content items'); + }); + + test('Variable', async () => { + const def = await openAndHover(fileHover, 6, 2); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('Random') === -1) { + assert.fail(contents, '', 'Variable type is missing', 'compare'); + } + }); + + test('format().capitalize()', async function () { + // https://github.com/Microsoft/PTVS/issues/3868 + // tslint:disable-next-line:no-invalid-this + this.skip(); + const def = await openAndHover(fileStringFormat, 5, 41); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('capitalize') === -1) { + assert.fail(contents, '', '\'capitalize\' is missing', 'compare'); + } + if (contents.indexOf('Return a capitalized version of S') === -1 && + contents.indexOf('Return a copy of the string S with only its first character') === -1) { + assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); + } + }); +}); diff --git a/src/test/definitions/parallel.test.ts b/src/test/definitions/parallel.jedi.test.ts similarity index 87% rename from src/test/definitions/parallel.test.ts rename to src/test/definitions/parallel.jedi.test.ts index 535587ba5e03..627352c60947 100644 --- a/src/test/definitions/parallel.test.ts +++ b/src/test/definitions/parallel.jedi.test.ts @@ -1,44 +1,50 @@ -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { Hover } from 'vscode'; -import { IS_WINDOWS } from '../../client/common/platform/constants'; -import { closeActiveWindows, initialize } from '../initialize'; -import { normalizeMarkedString } from '../textUtils'; - -const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); -const fileOne = path.join(autoCompPath, 'one.py'); - -suite('Code, Hover Definition and Intellisense', () => { - suiteSetup(initialize); - suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); - - test('All three together', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileOne); - - let position = new vscode.Position(30, 5); - const hoverDef = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - const codeDef = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); - position = new vscode.Position(3, 10); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - - assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); - - assert.equal(codeDef!.length, 1, 'Definition length is incorrect'); - const expectedPath = IS_WINDOWS ? fileOne.toUpperCase() : fileOne; - const actualPath = IS_WINDOWS ? codeDef![0].uri.fsPath.toUpperCase() : codeDef![0].uri.fsPath; - assert.equal(actualPath, expectedPath, 'Incorrect file'); - assert.equal(`${codeDef![0].range!.start.line},${codeDef![0].range!.start.character}`, '17,4', 'Start position is incorrect'); - assert.equal(`${codeDef![0].range!.end.line},${codeDef![0].range!.end.character}`, '21,11', 'End position is incorrect'); - - assert.equal(hoverDef!.length, 1, 'Definition length is incorrect'); - assert.equal(`${hoverDef![0].range!.start.line},${hoverDef![0].range!.start.character}`, '30,4', 'Start position is incorrect'); - assert.equal(`${hoverDef![0].range!.end.line},${hoverDef![0].range!.end.character}`, '30,11', 'End position is incorrect'); - assert.equal(hoverDef![0].contents.length, 1, 'Invalid content items'); - // tslint:disable-next-line:prefer-template - const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; - assert.equal(normalizeMarkedString(hoverDef![0].contents[0]), expectedContent, 'function signature incorrect'); - }); -}); +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IS_WINDOWS } from '../../client/common/platform/constants'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { closeActiveWindows, initialize } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const fileOne = path.join(autoCompPath, 'one.py'); + +suite('Code, Hover Definition and Intellisense (Jedi)', () => { + suiteSetup(async function () { + if (IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + test('All three together', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileOne); + + let position = new vscode.Position(30, 5); + const hoverDef = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + const codeDef = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); + position = new vscode.Position(3, 10); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + + assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); + + assert.equal(codeDef!.length, 1, 'Definition length is incorrect'); + const expectedPath = IS_WINDOWS ? fileOne.toUpperCase() : fileOne; + const actualPath = IS_WINDOWS ? codeDef![0].uri.fsPath.toUpperCase() : codeDef![0].uri.fsPath; + assert.equal(actualPath, expectedPath, 'Incorrect file'); + assert.equal(`${codeDef![0].range!.start.line},${codeDef![0].range!.start.character}`, '17,4', 'Start position is incorrect'); + assert.equal(`${codeDef![0].range!.end.line},${codeDef![0].range!.end.character}`, '21,11', 'End position is incorrect'); + + assert.equal(hoverDef!.length, 1, 'Definition length is incorrect'); + assert.equal(`${hoverDef![0].range!.start.line},${hoverDef![0].range!.start.character}`, '30,4', 'Start position is incorrect'); + assert.equal(`${hoverDef![0].range!.end.line},${hoverDef![0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(hoverDef![0].contents.length, 1, 'Invalid content items'); + // tslint:disable-next-line:prefer-template + const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; + assert.equal(normalizeMarkedString(hoverDef![0].contents[0]), expectedContent, 'function signature incorrect'); + }); +}); diff --git a/src/test/definitions/parallel.ptvs.test.ts b/src/test/definitions/parallel.ptvs.test.ts new file mode 100644 index 000000000000..6740350d374d --- /dev/null +++ b/src/test/definitions/parallel.ptvs.test.ts @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IS_WINDOWS } from '../../client/common/platform/constants'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { closeActiveWindows, initialize } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const fileOne = path.join(autoCompPath, 'one.py'); + +suite('Code, Hover Definition and Intellisense (MS Python Code Analysis)', () => { + suiteSetup(async function () { + // https://github.com/Microsoft/vscode-python/issues/1061 + // tslint:disable-next-line:no-invalid-this + this.skip(); + + if (!IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + test('All three together', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileOne); + + let position = new vscode.Position(30, 5); + const hoverDef = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + const codeDef = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); + position = new vscode.Position(3, 10); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + + assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); + + assert.equal(codeDef!.length, 1, 'Definition length is incorrect'); + const expectedPath = IS_WINDOWS ? fileOne.toUpperCase() : fileOne; + const actualPath = IS_WINDOWS ? codeDef![0].uri.fsPath.toUpperCase() : codeDef![0].uri.fsPath; + assert.equal(actualPath, expectedPath, 'Incorrect file'); + assert.equal(`${codeDef![0].range!.start.line},${codeDef![0].range!.start.character}`, '17,4', 'Start position is incorrect'); + assert.equal(`${codeDef![0].range!.end.line},${codeDef![0].range!.end.character}`, '21,11', 'End position is incorrect'); + + assert.equal(hoverDef!.length, 1, 'Definition length is incorrect'); + assert.equal(`${hoverDef![0].range!.start.line},${hoverDef![0].range!.start.character}`, '30,4', 'Start position is incorrect'); + assert.equal(`${hoverDef![0].range!.end.line},${hoverDef![0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(hoverDef![0].contents.length, 1, 'Invalid content items'); + // tslint:disable-next-line:prefer-template + const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; + assert.equal(normalizeMarkedString(hoverDef![0].contents[0]), expectedContent, 'function signature incorrect'); + }); +}); diff --git a/src/test/signature/signature.jedi.test.ts b/src/test/signature/signature.jedi.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9f4535e612518ebd2d79fbccf3a556811e386174 GIT binary patch literal 6055 xcmeIuF#!Mo0K%a4Pi+hzh(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pG71_l?W00961 literal 0 HcmV?d00001 diff --git a/src/test/signature/signature.ptvs.test.ts b/src/test/signature/signature.ptvs.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..5eeb71ae5f1391fe953303913759a118e070c60d GIT binary patch literal 6149 xcmeIu0Sy2E0K%a6Pi+qe5hx58Fkrxd0RsjM7%*VKfB^#r3>YwAz<`0vfdLo=00961 literal 0 HcmV?d00001 diff --git a/src/test/signature/signature.test.ts b/src/test/signature/signature.test.ts deleted file mode 100644 index a4d002e4e750..000000000000 --- a/src/test/signature/signature.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as assert from 'assert'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { rootWorkspaceUri } from '../common'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; -import { UnitTestIocContainer } from '../unittests/serviceRegistry'; - -const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'signature'); - -class SignatureHelpResult { - constructor( - public line: number, - public index: number, - public signaturesCount: number, - public activeParameter: number, - public parameterName: string | null) { } -} - -// tslint:disable-next-line:max-func-body-length -suite('Signatures', () => { - let isPython2: boolean; - let ioc: UnitTestIocContainer; - suiteSetup(async () => { - await initialize(); - initializeDI(); - isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(async () => { - await closeActiveWindows(); - ioc.dispose(); - }); - function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - } - - test('For ctor', async () => { - const expected = [ - new SignatureHelpResult(5, 11, 0, 0, null), - new SignatureHelpResult(5, 12, 1, 0, 'name'), - new SignatureHelpResult(5, 13, 1, 0, 'name'), - new SignatureHelpResult(5, 14, 1, 0, 'name'), - new SignatureHelpResult(5, 15, 1, 0, 'name'), - new SignatureHelpResult(5, 16, 1, 0, 'name'), - new SignatureHelpResult(5, 17, 1, 0, 'name'), - new SignatureHelpResult(5, 18, 1, 1, 'age'), - new SignatureHelpResult(5, 19, 1, 1, 'age'), - new SignatureHelpResult(5, 20, 0, 0, null) - ]; - - const document = await openDocument(path.join(autoCompPath, 'classCtor.py')); - for (let i = 0; i < expected.length; i += 1) { - await checkSignature(expected[i], document!.uri, i); - } - }); - - test('For intrinsic', async () => { - const expected = [ - new SignatureHelpResult(0, 0, 0, 0, null), - new SignatureHelpResult(0, 1, 0, 0, null), - new SignatureHelpResult(0, 2, 0, 0, null), - new SignatureHelpResult(0, 3, 0, 0, null), - new SignatureHelpResult(0, 4, 0, 0, null), - new SignatureHelpResult(0, 5, 0, 0, null), - new SignatureHelpResult(0, 6, 1, 0, 'start'), - new SignatureHelpResult(0, 7, 1, 0, 'start'), - new SignatureHelpResult(0, 8, 1, 1, 'stop'), - new SignatureHelpResult(0, 9, 1, 1, 'stop'), - new SignatureHelpResult(0, 10, 1, 1, 'stop'), - new SignatureHelpResult(0, 11, 1, 2, 'step'), - new SignatureHelpResult(1, 0, 1, 2, 'step') - ]; - - const document = await openDocument(path.join(autoCompPath, 'basicSig.py')); - for (let i = 0; i < expected.length; i += 1) { - await checkSignature(expected[i], document!.uri, i); - } - }); - - test('For ellipsis', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } - const expected = [ - new SignatureHelpResult(0, 5, 0, 0, null), - new SignatureHelpResult(0, 6, 1, 0, 'value'), - new SignatureHelpResult(0, 7, 1, 0, 'value'), - new SignatureHelpResult(0, 8, 1, 1, '...'), - new SignatureHelpResult(0, 9, 1, 1, '...'), - new SignatureHelpResult(0, 10, 1, 1, '...'), - new SignatureHelpResult(0, 11, 1, 2, 'sep'), - new SignatureHelpResult(0, 12, 1, 2, 'sep') - ]; - - const document = await openDocument(path.join(autoCompPath, 'ellipsis.py')); - for (let i = 0; i < expected.length; i += 1) { - await checkSignature(expected[i], document!.uri, i); - } - }); - - test('For pow', async () => { - let expected: SignatureHelpResult; - if (isPython2) { - expected = new SignatureHelpResult(0, 4, 1, 0, 'x'); - } else { - expected = new SignatureHelpResult(0, 4, 1, 0, null); - } - - const document = await openDocument(path.join(autoCompPath, 'noSigPy3.py')); - await checkSignature(expected, document!.uri, 0); - }); -}); - -async function openDocument(documentPath: string): Promise { - const document = await vscode.workspace.openTextDocument(documentPath); - await vscode.window.showTextDocument(document!); - return document; -} - -async function checkSignature(expected: SignatureHelpResult, uri: vscode.Uri, caseIndex: number) { - const position = new vscode.Position(expected.line, expected.index); - const actual = await vscode.commands.executeCommand('vscode.executeSignatureHelpProvider', uri, position); - assert.equal(actual!.signatures.length, expected.signaturesCount, `Signature count does not match, case ${caseIndex}`); - if (expected.signaturesCount > 0) { - assert.equal(actual!.activeParameter, expected.activeParameter, `Parameter index does not match, case ${caseIndex}`); - if (expected.parameterName) { - const parameter = actual!.signatures[0].parameters[expected.activeParameter]; - assert.equal(parameter.label, expected.parameterName, `Parameter name is incorrect, case ${caseIndex}`); - } - } -} From 818a46fc1bcb171a17940681807dedc6f8ccc9da Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 27 Mar 2018 22:21:24 -0700 Subject: [PATCH 49/97] More lost changes --- .gitignore | 2 + .vscodeignore | 1 + src/client/common/configSettings.ts | 761 +++++++++--------- src/client/common/types.ts | 14 +- src/test/constants.ts | 39 +- ...hon.csproj => vscode-python-signing.csproj | 0 6 files changed, 417 insertions(+), 400 deletions(-) rename vscode-python.csproj => vscode-python-signing.csproj (100%) diff --git a/.gitignore b/.gitignore index cc941e968ccb..47c9018661ee 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ coverage/ pythonFiles/experimental/ptvsd/** debug_coverage*/** analysis/** +bin/** +obj/** diff --git a/.vscodeignore b/.vscodeignore index b87d3320567b..b762bf75dfc7 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -44,3 +44,4 @@ bin/** obj/** BuildOutput/** + diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index d4a16e776b0c..b88b82ce65bf 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -1,376 +1,385 @@ -'use strict'; - -import * as child_process from 'child_process'; -import { EventEmitter } from 'events'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { ConfigurationTarget, Uri } from 'vscode'; -import { isTestExecution } from './constants'; -import { - IAutoCompeteSettings, - IFormattingSettings, - ILintingSettings, - IPythonSettings, - ISortImportSettings, - ITerminalSettings, - IUnitTestSettings, - IWorkspaceSymbolSettings -} from './types'; -import { SystemVariables } from './variables/systemVariables'; - -// tslint:disable-next-line:no-require-imports no-var-requires -const untildify = require('untildify'); - -export const IS_WINDOWS = /^win/.test(process.platform); - -// tslint:disable-next-line:completed-docs -export class PythonSettings extends EventEmitter implements IPythonSettings { - private static pythonSettings: Map = new Map(); - - public jediPath: string; - public jediMemoryLimit: number; - public envFile: string; - public disablePromptForFeatures: string[]; - public venvPath: string; - public venvFolders: string[]; - public devOptions: string[]; - public linting: ILintingSettings; - public formatting: IFormattingSettings; - public autoComplete: IAutoCompeteSettings; - public unitTest: IUnitTestSettings; - public terminal: ITerminalSettings; - public sortImports: ISortImportSettings; - public workspaceSymbols: IWorkspaceSymbolSettings; - public disableInstallationChecks: boolean; - public globalModuleInstallation: boolean; - - private workspaceRoot: vscode.Uri; - private disposables: vscode.Disposable[] = []; - // tslint:disable-next-line:variable-name - private _pythonPath: string; - constructor(workspaceFolder?: Uri) { - super(); - this.workspaceRoot = workspaceFolder ? workspaceFolder : vscode.Uri.file(__dirname); - this.disposables.push(vscode.workspace.onDidChangeConfiguration(() => { - this.initializeSettings(); - - // If workspace config changes, then we could have a cascading effect of on change events. - // Let's defer the change notification. - setTimeout(() => this.emit('change'), 1); - })); - - this.initializeSettings(); - } - // tslint:disable-next-line:function-name - public static getInstance(resource?: Uri): PythonSettings { - const workspaceFolderUri = PythonSettings.getSettingsUriAndTarget(resource).uri; - const workspaceFolderKey = workspaceFolderUri ? workspaceFolderUri.fsPath : ''; - - if (!PythonSettings.pythonSettings.has(workspaceFolderKey)) { - const settings = new PythonSettings(workspaceFolderUri); - PythonSettings.pythonSettings.set(workspaceFolderKey, settings); - } - // tslint:disable-next-line:no-non-null-assertion - return PythonSettings.pythonSettings.get(workspaceFolderKey)!; - } - - public static getSettingsUriAndTarget(resource?: Uri): { uri: Uri | undefined, target: ConfigurationTarget } { - const workspaceFolder = resource ? vscode.workspace.getWorkspaceFolder(resource) : undefined; - let workspaceFolderUri: Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined; - - if (!workspaceFolderUri && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) { - workspaceFolderUri = vscode.workspace.workspaceFolders[0].uri; - } - - const target = workspaceFolderUri ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Global; - return { uri: workspaceFolderUri, target }; - } - - // tslint:disable-next-line:function-name - public static dispose() { - if (!isTestExecution()) { - throw new Error('Dispose can only be called from unit tests'); - } - // tslint:disable-next-line:no-void-expression - PythonSettings.pythonSettings.forEach(item => item.dispose()); - PythonSettings.pythonSettings.clear(); - } - public dispose() { - // tslint:disable-next-line:no-unsafe-any - this.disposables.forEach(disposable => disposable.dispose()); - this.disposables = []; - } - - // tslint:disable-next-line:cyclomatic-complexity max-func-body-length - private initializeSettings() { - const workspaceRoot = this.workspaceRoot.fsPath; - const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined); - const pythonSettings = vscode.workspace.getConfiguration('python', this.workspaceRoot); - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.pythonPath = systemVariables.resolveAny(pythonSettings.get('pythonPath'))!; - this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot); - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; - this.venvFolders = systemVariables.resolveAny(pythonSettings.get('venvFolders'))!; - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.jediPath = systemVariables.resolveAny(pythonSettings.get('jediPath'))!; - if (typeof this.jediPath === 'string' && this.jediPath.length > 0) { - this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), workspaceRoot); - } else { - this.jediPath = ''; - } - this.jediMemoryLimit = pythonSettings.get('jediMemoryLimit')!; - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.envFile = systemVariables.resolveAny(pythonSettings.get('envFile'))!; - // tslint:disable-next-line:no-any - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion no-any - this.devOptions = systemVariables.resolveAny(pythonSettings.get('devOptions'))!; - this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : []; - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.disablePromptForFeatures = pythonSettings.get('disablePromptForFeatures')!; - this.disablePromptForFeatures = Array.isArray(this.disablePromptForFeatures) ? this.disablePromptForFeatures : []; - if (this.linting) { - Object.assign(this.linting, lintingSettings); - } else { - this.linting = lintingSettings; - } - - this.disableInstallationChecks = pythonSettings.get('disableInstallationCheck') === true; - this.globalModuleInstallation = pythonSettings.get('globalModuleInstallation') === true; - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; - if (this.sortImports) { - Object.assign(this.sortImports, sortImportSettings); - } else { - this.sortImports = sortImportSettings; - } - // Support for travis. - this.sortImports = this.sortImports ? this.sortImports : { path: '', args: [] }; - // Support for travis. - this.linting = this.linting ? this.linting : { - enabled: false, - ignorePatterns: [], - flake8Args: [], flake8Enabled: false, flake8Path: 'flake', - lintOnSave: false, maxNumberOfProblems: 100, - mypyArgs: [], mypyEnabled: false, mypyPath: 'mypy', - pep8Args: [], pep8Enabled: false, pep8Path: 'pep8', - pylamaArgs: [], pylamaEnabled: false, pylamaPath: 'pylama', - prospectorArgs: [], prospectorEnabled: false, prospectorPath: 'prospector', - pydocstyleArgs: [], pydocstyleEnabled: false, pydocstylePath: 'pydocstyle', - pylintArgs: [], pylintEnabled: false, pylintPath: 'pylint', - pylintCategorySeverity: { - convention: vscode.DiagnosticSeverity.Hint, - error: vscode.DiagnosticSeverity.Error, - fatal: vscode.DiagnosticSeverity.Error, - refactor: vscode.DiagnosticSeverity.Hint, - warning: vscode.DiagnosticSeverity.Warning - }, - pep8CategorySeverity: { - E: vscode.DiagnosticSeverity.Error, - W: vscode.DiagnosticSeverity.Warning - }, - flake8CategorySeverity: { - E: vscode.DiagnosticSeverity.Error, - W: vscode.DiagnosticSeverity.Warning, - // Per http://flake8.pycqa.org/en/latest/glossary.html#term-error-code - // 'F' does not mean 'fatal as in PyLint but rather 'pyflakes' such as - // unused imports, variables, etc. - F: vscode.DiagnosticSeverity.Warning - }, - mypyCategorySeverity: { - error: vscode.DiagnosticSeverity.Error, - note: vscode.DiagnosticSeverity.Hint - }, - pylintUseMinimalCheckers: false - }; - this.linting.pylintPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylintPath), workspaceRoot); - this.linting.flake8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.flake8Path), workspaceRoot); - this.linting.pep8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.pep8Path), workspaceRoot); - this.linting.pylamaPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylamaPath), workspaceRoot); - this.linting.prospectorPath = getAbsolutePath(systemVariables.resolveAny(this.linting.prospectorPath), workspaceRoot); - this.linting.pydocstylePath = getAbsolutePath(systemVariables.resolveAny(this.linting.pydocstylePath), workspaceRoot); - this.linting.mypyPath = getAbsolutePath(systemVariables.resolveAny(this.linting.mypyPath), workspaceRoot); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; - if (this.formatting) { - Object.assign(this.formatting, formattingSettings); - } else { - this.formatting = formattingSettings; - } - // Support for travis. - this.formatting = this.formatting ? this.formatting : { - autopep8Args: [], autopep8Path: 'autopep8', - provider: 'autopep8', - yapfArgs: [], yapfPath: 'yapf' - }; - this.formatting.autopep8Path = getAbsolutePath(systemVariables.resolveAny(this.formatting.autopep8Path), workspaceRoot); - this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; - if (this.autoComplete) { - Object.assign(this.autoComplete, autoCompleteSettings); - } else { - this.autoComplete = autoCompleteSettings; - } - // Support for travis. - this.autoComplete = this.autoComplete ? this.autoComplete : { - extraPaths: [], - addBrackets: false, - preloadModules: [] - }; - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get('workspaceSymbols'))!; - if (this.workspaceSymbols) { - Object.assign(this.workspaceSymbols, workspaceSymbolsSettings); - } else { - this.workspaceSymbols = workspaceSymbolsSettings; - } - // Support for travis. - this.workspaceSymbols = this.workspaceSymbols ? this.workspaceSymbols : { - ctagsPath: 'ctags', - enabled: true, - exclusionPatterns: [], - rebuildOnFileSave: true, - rebuildOnStart: true, - tagFilePath: path.join(workspaceRoot, 'tags') - }; - this.workspaceSymbols.tagFilePath = getAbsolutePath(systemVariables.resolveAny(this.workspaceSymbols.tagFilePath), workspaceRoot); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const unitTestSettings = systemVariables.resolveAny(pythonSettings.get('unitTest'))!; - if (this.unitTest) { - Object.assign(this.unitTest, unitTestSettings); - } else { - this.unitTest = unitTestSettings; - if (isTestExecution() && !this.unitTest) { - // tslint:disable-next-line:prefer-type-cast - this.unitTest = { - nosetestArgs: [], pyTestArgs: [], unittestArgs: [], - promptToConfigure: true, debugPort: 3000, - nosetestsEnabled: false, pyTestEnabled: false, unittestEnabled: false, - nosetestPath: 'nosetests', pyTestPath: 'pytest' - } as IUnitTestSettings; - } - } - - // Support for travis. - this.unitTest = this.unitTest ? this.unitTest : { - promptToConfigure: true, - debugPort: 3000, - nosetestArgs: [], nosetestPath: 'nosetest', nosetestsEnabled: false, - pyTestArgs: [], pyTestEnabled: false, pyTestPath: 'pytest', - unittestArgs: [], unittestEnabled: false - }; - this.unitTest.pyTestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.pyTestPath), workspaceRoot); - this.unitTest.nosetestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.nosetestPath), workspaceRoot); - if (this.unitTest.cwd) { - this.unitTest.cwd = getAbsolutePath(systemVariables.resolveAny(this.unitTest.cwd), workspaceRoot); - } - - // Resolve any variables found in the test arguments. - this.unitTest.nosetestArgs = this.unitTest.nosetestArgs.map(arg => systemVariables.resolveAny(arg)); - this.unitTest.pyTestArgs = this.unitTest.pyTestArgs.map(arg => systemVariables.resolveAny(arg)); - this.unitTest.unittestArgs = this.unitTest.unittestArgs.map(arg => systemVariables.resolveAny(arg)); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; - if (this.terminal) { - Object.assign(this.terminal, terminalSettings); - } else { - this.terminal = terminalSettings; - if (isTestExecution() && !this.terminal) { - // tslint:disable-next-line:prefer-type-cast - this.terminal = {} as ITerminalSettings; - } - } - // Support for travis. - this.terminal = this.terminal ? this.terminal : { - executeInFileDir: true, - launchArgs: [], - activateEnvironment: true - }; - } - - public get pythonPath(): string { - return this._pythonPath; - } - public set pythonPath(value: string) { - if (this._pythonPath === value) { - return; - } - // Add support for specifying just the directory where the python executable will be located. - // E.g. virtual directory name. - try { - this._pythonPath = getPythonExecutable(value); - } catch (ex) { - this._pythonPath = value; - } - } -} - -function getAbsolutePath(pathToCheck: string, rootDir: string): string { - // tslint:disable-next-line:prefer-type-cast no-unsafe-any - pathToCheck = untildify(pathToCheck) as string; - if (isTestExecution() && !pathToCheck) { return rootDir; } - if (pathToCheck.indexOf(path.sep) === -1) { - return pathToCheck; - } - return path.isAbsolute(pathToCheck) ? pathToCheck : path.resolve(rootDir, pathToCheck); -} - -function getPythonExecutable(pythonPath: string): string { - // tslint:disable-next-line:prefer-type-cast no-unsafe-any - pythonPath = untildify(pythonPath) as string; - - // If only 'python'. - if (pythonPath === 'python' || - pythonPath.indexOf(path.sep) === -1 || - path.basename(pythonPath) === path.dirname(pythonPath)) { - return pythonPath; - } - - if (isValidPythonPath(pythonPath)) { - return pythonPath; - } - // Keep python right on top, for backwards compatibility. - // tslint:disable-next-line:variable-name - const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2']; - - for (let executableName of KnownPythonExecutables) { - // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'. - if (IS_WINDOWS) { - executableName = `${executableName}.exe`; - if (isValidPythonPath(path.join(pythonPath, executableName))) { - return path.join(pythonPath, executableName); - } - if (isValidPythonPath(path.join(pythonPath, 'scripts', executableName))) { - return path.join(pythonPath, 'scripts', executableName); - } - } else { - if (isValidPythonPath(path.join(pythonPath, executableName))) { - return path.join(pythonPath, executableName); - } - if (isValidPythonPath(path.join(pythonPath, 'bin', executableName))) { - return path.join(pythonPath, 'bin', executableName); - } - } - } - - return pythonPath; -} - -function isValidPythonPath(pythonPath: string): boolean { - try { - const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); - return output.startsWith('1234'); - } catch (ex) { - return false; - } -} +'use strict'; + +import * as child_process from 'child_process'; +import { EventEmitter } from 'events'; +import * as path from 'path'; +import { ConfigurationTarget, DiagnosticSeverity, Disposable, Uri, workspace } from 'vscode'; +import { isTestExecution } from './constants'; +import { + IAutoCompeteSettings, + IFormattingSettings, + ILintingSettings, + IPythonSettings, + ISortImportSettings, + ITerminalSettings, + IUnitTestSettings, + IWorkspaceSymbolSettings +} from './types'; +import { SystemVariables } from './variables/systemVariables'; + +// tslint:disable-next-line:no-require-imports no-var-requires +const untildify = require('untildify'); + +export const IS_WINDOWS = /^win/.test(process.platform); + +// tslint:disable-next-line:completed-docs +export class PythonSettings extends EventEmitter implements IPythonSettings { + private static pythonSettings: Map = new Map(); + public jediEnabled = true; + public jediPath = ''; + public jediMemoryLimit = 1024; + public envFile = ''; + public disablePromptForFeatures: string[] = []; + public venvPath = ''; + public venvFolders: string[] = []; + public devOptions: string[] = []; + public linting?: ILintingSettings; + public formatting?: IFormattingSettings; + public autoComplete?: IAutoCompeteSettings; + public unitTest?: IUnitTestSettings; + public terminal?: ITerminalSettings; + public sortImports?: ISortImportSettings; + public workspaceSymbols?: IWorkspaceSymbolSettings; + public disableInstallationChecks = false; + public globalModuleInstallation = false; + + private workspaceRoot: Uri; + private disposables: Disposable[] = []; + // tslint:disable-next-line:variable-name + private _pythonPath = ''; + + constructor(workspaceFolder?: Uri) { + super(); + this.workspaceRoot = workspaceFolder ? workspaceFolder : Uri.file(__dirname); + this.disposables.push(workspace.onDidChangeConfiguration(() => { + this.initializeSettings(); + + // If workspace config changes, then we could have a cascading effect of on change events. + // Let's defer the change notification. + setTimeout(() => this.emit('change'), 1); + })); + + this.initializeSettings(); + } + // tslint:disable-next-line:function-name + public static getInstance(resource?: Uri): PythonSettings { + const workspaceFolderUri = PythonSettings.getSettingsUriAndTarget(resource).uri; + const workspaceFolderKey = workspaceFolderUri ? workspaceFolderUri.fsPath : ''; + + if (!PythonSettings.pythonSettings.has(workspaceFolderKey)) { + const settings = new PythonSettings(workspaceFolderUri); + PythonSettings.pythonSettings.set(workspaceFolderKey, settings); + } + // tslint:disable-next-line:no-non-null-assertion + return PythonSettings.pythonSettings.get(workspaceFolderKey)!; + } + + // tslint:disable-next-line:type-literal-delimiter + public static getSettingsUriAndTarget(resource?: Uri): { uri: Uri | undefined, target: ConfigurationTarget } { + const workspaceFolder = resource ? workspace.getWorkspaceFolder(resource) : undefined; + let workspaceFolderUri: Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined; + + if (!workspaceFolderUri && Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0) { + workspaceFolderUri = workspace.workspaceFolders[0].uri; + } + + const target = workspaceFolderUri ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Global; + return { uri: workspaceFolderUri, target }; + } + + // tslint:disable-next-line:function-name + public static dispose() { + if (!isTestExecution()) { + throw new Error('Dispose can only be called from unit tests'); + } + // tslint:disable-next-line:no-void-expression + PythonSettings.pythonSettings.forEach(item => item.dispose()); + PythonSettings.pythonSettings.clear(); + } + public dispose() { + // tslint:disable-next-line:no-unsafe-any + this.disposables.forEach(disposable => disposable.dispose()); + this.disposables = []; + } + + // tslint:disable-next-line:cyclomatic-complexity max-func-body-length + private initializeSettings() { + const workspaceRoot = this.workspaceRoot.fsPath; + const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined); + const pythonSettings = workspace.getConfiguration('python', this.workspaceRoot); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.pythonPath = systemVariables.resolveAny(pythonSettings.get('pythonPath'))!; + this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot); + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; + this.venvFolders = systemVariables.resolveAny(pythonSettings.get('venvFolders'))!; + + this.jediEnabled = systemVariables.resolveAny(pythonSettings.get('jediEnabled'))!; + if (this.jediEnabled) { + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.jediPath = systemVariables.resolveAny(pythonSettings.get('jediPath'))!; + if (typeof this.jediPath === 'string' && this.jediPath.length > 0) { + this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), workspaceRoot); + } else { + this.jediPath = ''; + } + this.jediMemoryLimit = pythonSettings.get('jediMemoryLimit')!; + } + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.envFile = systemVariables.resolveAny(pythonSettings.get('envFile'))!; + // tslint:disable-next-line:no-any + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion no-any + this.devOptions = systemVariables.resolveAny(pythonSettings.get('devOptions'))!; + this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : []; + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.disablePromptForFeatures = pythonSettings.get('disablePromptForFeatures')!; + this.disablePromptForFeatures = Array.isArray(this.disablePromptForFeatures) ? this.disablePromptForFeatures : []; + if (this.linting) { + Object.assign(this.linting, lintingSettings); + } else { + this.linting = lintingSettings; + } + + this.disableInstallationChecks = pythonSettings.get('disableInstallationCheck') === true; + this.globalModuleInstallation = pythonSettings.get('globalModuleInstallation') === true; + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; + if (this.sortImports) { + Object.assign(this.sortImports, sortImportSettings); + } else { + this.sortImports = sortImportSettings; + } + // Support for travis. + this.sortImports = this.sortImports ? this.sortImports : { path: '', args: [] }; + // Support for travis. + this.linting = this.linting ? this.linting : { + enabled: false, + ignorePatterns: [], + flake8Args: [], flake8Enabled: false, flake8Path: 'flake', + lintOnSave: false, maxNumberOfProblems: 100, + mypyArgs: [], mypyEnabled: false, mypyPath: 'mypy', + pep8Args: [], pep8Enabled: false, pep8Path: 'pep8', + pylamaArgs: [], pylamaEnabled: false, pylamaPath: 'pylama', + prospectorArgs: [], prospectorEnabled: false, prospectorPath: 'prospector', + pydocstyleArgs: [], pydocstyleEnabled: false, pydocstylePath: 'pydocstyle', + pylintArgs: [], pylintEnabled: false, pylintPath: 'pylint', + pylintCategorySeverity: { + convention: DiagnosticSeverity.Hint, + error: DiagnosticSeverity.Error, + fatal: DiagnosticSeverity.Error, + refactor: DiagnosticSeverity.Hint, + warning: DiagnosticSeverity.Warning + }, + pep8CategorySeverity: { + E: DiagnosticSeverity.Error, + W: DiagnosticSeverity.Warning + }, + flake8CategorySeverity: { + E: DiagnosticSeverity.Error, + W: DiagnosticSeverity.Warning, + // Per http://flake8.pycqa.org/en/latest/glossary.html#term-error-code + // 'F' does not mean 'fatal as in PyLint but rather 'pyflakes' such as + // unused imports, variables, etc. + F: DiagnosticSeverity.Warning + }, + mypyCategorySeverity: { + error: DiagnosticSeverity.Error, + note: DiagnosticSeverity.Hint + }, + pylintUseMinimalCheckers: false + }; + this.linting.pylintPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylintPath), workspaceRoot); + this.linting.flake8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.flake8Path), workspaceRoot); + this.linting.pep8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.pep8Path), workspaceRoot); + this.linting.pylamaPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylamaPath), workspaceRoot); + this.linting.prospectorPath = getAbsolutePath(systemVariables.resolveAny(this.linting.prospectorPath), workspaceRoot); + this.linting.pydocstylePath = getAbsolutePath(systemVariables.resolveAny(this.linting.pydocstylePath), workspaceRoot); + this.linting.mypyPath = getAbsolutePath(systemVariables.resolveAny(this.linting.mypyPath), workspaceRoot); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; + if (this.formatting) { + Object.assign(this.formatting, formattingSettings); + } else { + this.formatting = formattingSettings; + } + // Support for travis. + this.formatting = this.formatting ? this.formatting : { + autopep8Args: [], autopep8Path: 'autopep8', + provider: 'autopep8', + yapfArgs: [], yapfPath: 'yapf' + }; + this.formatting.autopep8Path = getAbsolutePath(systemVariables.resolveAny(this.formatting.autopep8Path), workspaceRoot); + this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; + if (this.autoComplete) { + Object.assign(this.autoComplete, autoCompleteSettings); + } else { + this.autoComplete = autoCompleteSettings; + } + // Support for travis. + this.autoComplete = this.autoComplete ? this.autoComplete : { + extraPaths: [], + addBrackets: false, + preloadModules: [] + }; + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get('workspaceSymbols'))!; + if (this.workspaceSymbols) { + Object.assign(this.workspaceSymbols, workspaceSymbolsSettings); + } else { + this.workspaceSymbols = workspaceSymbolsSettings; + } + // Support for travis. + this.workspaceSymbols = this.workspaceSymbols ? this.workspaceSymbols : { + ctagsPath: 'ctags', + enabled: true, + exclusionPatterns: [], + rebuildOnFileSave: true, + rebuildOnStart: true, + tagFilePath: path.join(workspaceRoot, 'tags') + }; + this.workspaceSymbols.tagFilePath = getAbsolutePath(systemVariables.resolveAny(this.workspaceSymbols.tagFilePath), workspaceRoot); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const unitTestSettings = systemVariables.resolveAny(pythonSettings.get('unitTest'))!; + if (this.unitTest) { + Object.assign(this.unitTest, unitTestSettings); + } else { + this.unitTest = unitTestSettings; + if (isTestExecution() && !this.unitTest) { + // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:no-object-literal-type-assertion + this.unitTest = { + nosetestArgs: [], pyTestArgs: [], unittestArgs: [], + promptToConfigure: true, debugPort: 3000, + nosetestsEnabled: false, pyTestEnabled: false, unittestEnabled: false, + nosetestPath: 'nosetests', pyTestPath: 'pytest' + } as IUnitTestSettings; + } + } + + // Support for travis. + this.unitTest = this.unitTest ? this.unitTest : { + promptToConfigure: true, + debugPort: 3000, + nosetestArgs: [], nosetestPath: 'nosetest', nosetestsEnabled: false, + pyTestArgs: [], pyTestEnabled: false, pyTestPath: 'pytest', + unittestArgs: [], unittestEnabled: false + }; + this.unitTest.pyTestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.pyTestPath), workspaceRoot); + this.unitTest.nosetestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.nosetestPath), workspaceRoot); + if (this.unitTest.cwd) { + this.unitTest.cwd = getAbsolutePath(systemVariables.resolveAny(this.unitTest.cwd), workspaceRoot); + } + + // Resolve any variables found in the test arguments. + this.unitTest.nosetestArgs = this.unitTest.nosetestArgs.map(arg => systemVariables.resolveAny(arg)); + this.unitTest.pyTestArgs = this.unitTest.pyTestArgs.map(arg => systemVariables.resolveAny(arg)); + this.unitTest.unittestArgs = this.unitTest.unittestArgs.map(arg => systemVariables.resolveAny(arg)); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; + if (this.terminal) { + Object.assign(this.terminal, terminalSettings); + } else { + this.terminal = terminalSettings; + if (isTestExecution() && !this.terminal) { + // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:no-object-literal-type-assertion + this.terminal = {} as ITerminalSettings; + } + } + // Support for travis. + this.terminal = this.terminal ? this.terminal : { + executeInFileDir: true, + launchArgs: [], + activateEnvironment: true + }; + } + + public get pythonPath(): string { + return this._pythonPath; + } + public set pythonPath(value: string) { + if (this._pythonPath === value) { + return; + } + // Add support for specifying just the directory where the python executable will be located. + // E.g. virtual directory name. + try { + this._pythonPath = getPythonExecutable(value); + } catch (ex) { + this._pythonPath = value; + } + } +} + +function getAbsolutePath(pathToCheck: string, rootDir: string): string { + // tslint:disable-next-line:prefer-type-cast no-unsafe-any + pathToCheck = untildify(pathToCheck) as string; + if (isTestExecution() && !pathToCheck) { return rootDir; } + if (pathToCheck.indexOf(path.sep) === -1) { + return pathToCheck; + } + return path.isAbsolute(pathToCheck) ? pathToCheck : path.resolve(rootDir, pathToCheck); +} + +function getPythonExecutable(pythonPath: string): string { + // tslint:disable-next-line:prefer-type-cast no-unsafe-any + pythonPath = untildify(pythonPath) as string; + + // If only 'python'. + if (pythonPath === 'python' || + pythonPath.indexOf(path.sep) === -1 || + path.basename(pythonPath) === path.dirname(pythonPath)) { + return pythonPath; + } + + if (isValidPythonPath(pythonPath)) { + return pythonPath; + } + // Keep python right on top, for backwards compatibility. + // tslint:disable-next-line:variable-name + const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2']; + + for (let executableName of KnownPythonExecutables) { + // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'. + if (IS_WINDOWS) { + executableName = `${executableName}.exe`; + if (isValidPythonPath(path.join(pythonPath, executableName))) { + return path.join(pythonPath, executableName); + } + if (isValidPythonPath(path.join(pythonPath, 'scripts', executableName))) { + return path.join(pythonPath, 'scripts', executableName); + } + } else { + if (isValidPythonPath(path.join(pythonPath, executableName))) { + return path.join(pythonPath, executableName); + } + if (isValidPythonPath(path.join(pythonPath, 'bin', executableName))) { + return path.join(pythonPath, 'bin', executableName); + } + } + } + + return pythonPath; +} + +function isValidPythonPath(pythonPath: string): boolean { + try { + const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); + return output.startsWith('1234'); + } catch (ex) { + return false; + } +} diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 9c245e2ee69b..a58cb8cdbfe1 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -101,13 +101,13 @@ export interface IPythonSettings { readonly jediPath: string; readonly jediMemoryLimit: number; readonly devOptions: string[]; - readonly linting: ILintingSettings; - readonly formatting: IFormattingSettings; - readonly unitTest: IUnitTestSettings; - readonly autoComplete: IAutoCompeteSettings; - readonly terminal: ITerminalSettings; - readonly sortImports: ISortImportSettings; - readonly workspaceSymbols: IWorkspaceSymbolSettings; + readonly linting?: ILintingSettings; + readonly formatting?: IFormattingSettings; + readonly unitTest?: IUnitTestSettings; + readonly autoComplete?: IAutoCompeteSettings; + readonly terminal?: ITerminalSettings; + readonly sortImports?: ISortImportSettings; + readonly workspaceSymbols?: IWorkspaceSymbolSettings; readonly envFile: string; readonly disablePromptForFeatures: string[]; readonly disableInstallationChecks: boolean; diff --git a/src/test/constants.ts b/src/test/constants.ts index 1b468a7d9d57..72f6dcf1b696 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -1,17 +1,22 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable:no-string-literal -import { workspace } from 'vscode'; - -export const IS_APPVEYOR = process.env['APPVEYOR'] === 'true'; -export const IS_CI_SERVER = process.env['TRAVIS'] === 'true' || IS_APPVEYOR; -export const TEST_TIMEOUT = 25000; -export const IS_MULTI_ROOT_TEST = isMultitrootTest(); -export const IS_CI_SERVER_TEST_DEBUGGER = process.env['IS_CI_SERVER_TEST_DEBUGGER'] === '1'; -// If running on CI server, then run debugger tests ONLY if the corresponding flag is enabled. -export const TEST_DEBUGGER = IS_CI_SERVER ? IS_CI_SERVER_TEST_DEBUGGER : true; - -function isMultitrootTest() { - return Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 1; -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// tslint:disable:no-string-literal +import { workspace } from 'vscode'; +import { PythonSettings } from '../client/common/configSettings'; + +export const IS_APPVEYOR = process.env['APPVEYOR'] === 'true'; +export const IS_TRAVIS = process.env['TRAVIS'] === 'true'; +export const IS_CI_SERVER = IS_TRAVIS || IS_APPVEYOR; +export const TEST_TIMEOUT = 25000; +export const IS_MULTI_ROOT_TEST = isMultitrootTest(); +export const IS_CI_SERVER_TEST_DEBUGGER = process.env['IS_CI_SERVER_TEST_DEBUGGER'] === '1'; +// If running on CI server, then run debugger tests ONLY if the corresponding flag is enabled. +export const TEST_DEBUGGER = IS_CI_SERVER ? IS_CI_SERVER_TEST_DEBUGGER : true; + +function isMultitrootTest() { + return Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 1; +} + +export const IS_ANALYSIS_ENGINE_TEST = + !IS_TRAVIS && (process.env['VSC_PYTHON_ANALYSIS'] === '1' || !PythonSettings.getInstance().jediEnabled); diff --git a/vscode-python.csproj b/vscode-python-signing.csproj similarity index 100% rename from vscode-python.csproj rename to vscode-python-signing.csproj From ac975323a7b738d21994cc0256ca5f2b43cc389b Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 28 Mar 2018 08:09:08 -0700 Subject: [PATCH 50/97] Restore Jedi/PTVS setting --- package.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d90a68c877e4..3a84c1ae5879 100644 --- a/package.json +++ b/package.json @@ -1052,6 +1052,12 @@ "default": "${workspaceFolder}/.env", "scope": "resource" }, + "python.jediEnabled": { + "type": "string", + "default": "true", + "description": "Enables Jedi as IntelliSense engine instead of Microsoft Python Analysis Engine.", + "scope": "resource" + }, "python.jediPath": { "type": "string", "default": "", @@ -1830,4 +1836,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} \ No newline at end of file +} From 54a090b3249660913c92c6a2b25ce6523db3f040 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 28 Mar 2018 14:30:44 -0700 Subject: [PATCH 51/97] Update tests to new PTVS --- src/client/activation/analysis.ts | 6 +- src/client/common/types.ts | 1 + .../pythonProc.simple.multiroot.test.ts | 5 +- src/test/definitions/hover.ptvs.test.ts | 105 ++++++++---------- src/test/pythonFiles/autocomp/four.py | 2 +- src/test/pythonFiles/definition/four.py | 2 +- 6 files changed, 56 insertions(+), 65 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 6db64af0f8d6..412b27f00d8d 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -8,10 +8,10 @@ import { IApplicationShell } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import '../common/extensions'; import { IProcessService, IPythonExecutionFactory } from '../common/process/types'; +import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; -import { StopWatch } from '../telemetry/stopWatch'; import { AnalysisEngineDownloader } from './downloader'; import { IExtensionActivator } from './types'; @@ -48,13 +48,13 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } this.output.appendLine(`Options determined: ${sw.elapsedTime} ms`); - //if (!await this.tryStartLanguageServer(context, clientOptions, true)) { + if (!await this.tryStartLanguageServer(context, clientOptions, true)) { const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); await downloader.downloadAnalysisEngine(context); if (!await this.tryStartLanguageServer(context, clientOptions, false)) { return false; } - //} + } // tslint:disable-next-line:no-console this.output.appendLine(`Language server started: ${sw.elapsedTime} ms`); diff --git a/src/client/common/types.ts b/src/client/common/types.ts index a58cb8cdbfe1..266d1a812cb0 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -98,6 +98,7 @@ export interface IPythonSettings { readonly pythonPath: string; readonly venvPath: string; readonly venvFolders: string[]; + readonly jediEnabled: boolean; readonly jediPath: string; readonly jediMemoryLimit: number; readonly devOptions: string[]; diff --git a/src/test/common/process/pythonProc.simple.multiroot.test.ts b/src/test/common/process/pythonProc.simple.multiroot.test.ts index 2b9a3b513ad7..d8ff7c09cbb4 100644 --- a/src/test/common/process/pythonProc.simple.multiroot.test.ts +++ b/src/test/common/process/pythonProc.simple.multiroot.test.ts @@ -45,11 +45,9 @@ suite('PythonExecutableService', () => { this.skip(); } await clearPythonPathInWorkspaceFolder(workspace4Path); - - await (new ConfigurationService()).updateSettingAsync('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); await initialize(); }); - setup(() => { + setup(async () => { cont = new Container(); serviceContainer = new ServiceContainer(cont); const serviceManager = new ServiceManager(cont); @@ -69,6 +67,7 @@ suite('PythonExecutableService', () => { configService = serviceManager.get(IConfigurationService); pythonExecFactory = serviceContainer.get(IPythonExecutionFactory); + await configService.updateSettingAsync('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); return initializeTest(); }); suiteTeardown(closeActiveWindows); diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ptvs.test.ts index 21da0f9a2342..8c3b981ca4bb 100644 --- a/src/test/definitions/hover.ptvs.test.ts +++ b/src/test/definitions/hover.ptvs.test.ts @@ -2,9 +2,9 @@ // Licensed under the MIT License. import * as assert from 'assert'; -import { EOL } from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; +import '../../client/common/extensions'; import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { normalizeMarkedString } from '../textUtils'; @@ -21,7 +21,7 @@ const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); let textDocument: vscode.TextDocument; // tslint:disable-next-line:max-func-body-length -suite('Hover Definition (MS Python Code Analysis)', () => { +suite('Hover Definition (Analysis Engine)', () => { suiteSetup(async function () { if (!IS_ANALYSIS_ENGINE_TEST) { // tslint:disable-next-line:no-invalid-this @@ -48,9 +48,11 @@ suite('Hover Definition (MS Python Code Analysis)', () => { assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '30,0', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); assert.equal(def[0].contents.length, 1, 'Invalid content items'); - // tslint:disable-next-line:prefer-template - const expectedContent = 'method method1 of one.Class1 objects ' + EOL + EOL + '```html ' + EOL + ' This is method1 ' + EOL + '```'; - assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'function signature incorrect'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 2, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'obj.method1: method method1 of one.Class1 objects', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'This is method1', 'function signature line #2 is incorrect'); }); test('Across files', async () => { @@ -58,9 +60,11 @@ suite('Hover Definition (MS Python Code Analysis)', () => { assert.equal(def.length, 1, 'Definition length is incorrect'); assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - const expectedContent = 'method fun of two.ct objects ' + EOL + EOL + '```html ' + EOL + ' This is fun ' + EOL + '```'; - assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'Invalid conents'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 2, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'two.ct().fun: method fun of two.ct objects', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'This is fun', 'function signature line #2 is incorrect'); }); test('With Unicode Characters', async () => { @@ -68,14 +72,14 @@ suite('Hover Definition (MS Python Code Analysis)', () => { assert.equal(def.length, 1, 'Definition length is incorrect'); assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,0', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - const expectedContent = 'def four.Foo.bar()' + EOL + EOL + - '```html ' + EOL + - ' 说明 - keep this line, it works ' + EOL + - ' delete following line, it works ' + EOL + - ' 如果存在需要等待审批或正在执行的任务,将不刷新页面 ' + EOL + - '```'; - assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'Invalid contents'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 5, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'Foo.bar: def four.Foo.bar()', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), '说明 - keep this line, it works', 'function signature line #2 is incorrect'); + assert.equal(lines[2].trim(), 'delete following line, it works', 'function signature line #3 is incorrect'); + assert.equal(lines[3].trim(), '如果存在需要等待审批或正在执行的任务,将不刷新页面', 'function signature line #4 is incorrect'); + assert.equal(lines[4].trim(), 'declared in Foo', 'function signature line #5 is incorrect'); }); test('Across files with Unicode Characters', async () => { @@ -83,14 +87,12 @@ suite('Hover Definition (MS Python Code Analysis)', () => { assert.equal(def.length, 1, 'Definition length is incorrect'); assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - const expectedContent = 'def four.showMessage()' + EOL + EOL + - '```html ' + EOL + - ' Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ' + EOL + - ' Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку. ' + EOL + - '```'; - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'Invalid contents'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 3, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'four.showMessage: def four.showMessage()', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', 'function signature line #2 is incorrect'); + assert.equal(lines[2].trim(), 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', 'function signature line #3 is incorrect'); }); test('Nothing for keywords (class)', async () => { @@ -108,20 +110,11 @@ suite('Hover Definition (MS Python Code Analysis)', () => { assert.equal(def.length, 1, 'Definition length is incorrect'); assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,7', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - const documentation = 'Random' + EOL + EOL + - 'Random number generator base class used by bound module functions. ' + EOL + - '```html ' + EOL + - ' Used to instantiate instances of Random to get generators that don\'t ' + EOL + - ' share state. ' + EOL + - ' ' + EOL + - ' Class Random can also be subclassed if you want to use a different basic ' + EOL + - ' generator of your own devising: in that case, override the following ' + EOL + - ' methods: random(), seed(), getstate(), and setstate(). ' + EOL + - ' Optionally, implement a getrandbits() method so that randrange() ' + EOL + - ' can cover arbitrarily large ranges. ' + EOL + - '```'; - assert.equal(normalizeMarkedString(def[0].contents[0]), documentation, 'Invalid conents'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 9, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'misc.Random: class misc.Random(_random.Random)', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'Random number generator base class used by bound module functions.', 'function signature line #2 is incorrect'); }); test('Highlight Method', async () => { @@ -129,10 +122,11 @@ suite('Hover Definition (MS Python Code Analysis)', () => { assert.equal(def.length, 1, 'Definition length is incorrect'); assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,0', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); - assert.equal(normalizeMarkedString(def[0].contents[0]), - // tslint:disable-next-line:prefer-template - 'method randint of misc.Random objects -> int' + EOL + EOL + - 'Return random integer in range [a, b], including both end points.', 'Invalid conents'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 2, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'rnd2.randint: method randint of misc.Random objects -> int', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'Return random integer in range [a, b], including both end points.', 'function signature line #2 is incorrect'); }); test('Highlight Function', async () => { @@ -140,13 +134,12 @@ suite('Hover Definition (MS Python Code Analysis)', () => { assert.equal(def.length, 1, 'Definition length is incorrect'); assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,6', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), - // tslint:disable-next-line:prefer-template - 'built-in function acos(x)' + EOL + EOL + - 'acos(x) ' + EOL + - ' ' + EOL + - 'Return the arc cosine (measured in radians) of x.', 'Invalid conents'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 3, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'math.acos: built-in function acos(x)', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'acos(x)', 'function signature line #2 is incorrect'); + assert.equal(lines[2].trim(), 'Return the arc cosine (measured in radians) of x.', 'function signature line #3 is incorrect'); }); test('Highlight Multiline Method Signature', async () => { @@ -154,14 +147,12 @@ suite('Hover Definition (MS Python Code Analysis)', () => { assert.equal(def.length, 1, 'Definition length is incorrect'); assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,4', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), - // tslint:disable-next-line:prefer-template - 'Thread' + EOL + EOL + - 'A class that represents a thread of control. ' + EOL + - '```html ' + EOL + - ' This class can be safely subclassed in a limited fashion. ' + EOL + - '```', 'Invalid content items'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 3, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'misc.Thread: class misc.Thread(_Verbose)', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'A class that represents a thread of control.', 'function signature line #2 is incorrect'); + }); test('Variable', async () => { diff --git a/src/test/pythonFiles/autocomp/four.py b/src/test/pythonFiles/autocomp/four.py index f67f78af0856..470338f71157 100644 --- a/src/test/pythonFiles/autocomp/four.py +++ b/src/test/pythonFiles/autocomp/four.py @@ -1,4 +1,4 @@ -# -*- coding:utf-8 -*- +# -*- coding: utf-8 -*- # pylint: disable=E0401, W0512 import os diff --git a/src/test/pythonFiles/definition/four.py b/src/test/pythonFiles/definition/four.py index f67f78af0856..470338f71157 100644 --- a/src/test/pythonFiles/definition/four.py +++ b/src/test/pythonFiles/definition/four.py @@ -1,4 +1,4 @@ -# -*- coding:utf-8 -*- +# -*- coding: utf-8 -*- # pylint: disable=E0401, W0512 import os From 3f7468647da96b3212541b6289a9b1253f32fd4f Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 28 Mar 2018 14:34:49 -0700 Subject: [PATCH 52/97] Signature tests --- src/test/signature/signature.jedi.test.ts | Bin 6055 -> 5910 bytes src/test/signature/signature.ptvs.test.ts | Bin 6149 -> 6003 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/test/signature/signature.jedi.test.ts b/src/test/signature/signature.jedi.test.ts index 9f4535e612518ebd2d79fbccf3a556811e386174..1ab80cce964d0fc157baf4286d6442398b1a98c9 100644 GIT binary patch literal 5910 zcmd5=ZEqVl4F2w4!2$z(-ki%z_v$tUf;8PSw1LsMEry^-<+Bp2Jxe!9m&D8R-$&|B zI@z&P+slUO;*F^4zD13#R$T4I8WSoj{8!GDOfaG})%+SQNtvl)qk z*>OEjU*~48Or}P>p09p161Sk0vLBTGVhWb=cUr(*Et;m=nVL+LjHQoH&ks%xUjKE9 zUye?GKR!7+9~_+yWII+9=2Kf>S^C2hWSQ z+?2V6+ZN!*DWc2FDxJ6i8Z63TE;7i`q#np}GUU2DKZ!2nH%n)O66R2q(k_)_?mYTT zqpgYd=QO+$0(|2y(>%mZ|oHO~%dBn}B ztOtw2p%QVsoN`+h2{Tif0qH^Pq)n<*Ag7B9NS4HQEJmUa87kG5kXIPfqb)lYCL9~# zXdpFTM3G=<&;Whz!Z&u!1X>z65lc0rFOO%&yX3K#rzZ<*MKvPX{M!Hn{~s+E3>mTu8T-wr z0-oohFoH_-uT_De)|^jLk6QOp*3(#Q>AI4o-%TyJVG)2a_PVwuxh$jGVRpHQlaFErMW~nQ~mjJi+Use!QbftK5r(%@aI(*DIMSg-xrM74xnb% z1$He){^`D?&aBbbzUh?G*6~_NynU^9Wvqv~7E)@)>UtsWNe!wpY$zVl@K$Ti>}+w- zxiAj9fW>DIY9+8&)`g52ejGPr{H;;hj&E4(abdHedXm=2emxpW+?2W`-4Jf6Ywy%H zR10CNret|2)$4_*KgZu;GUG TXG&}ALOgIZv}yhPLTdd7d8qUW literal 6055 xcmeIuF#!Mo0K%a4Pi+hzh(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pG71_l?W00961 diff --git a/src/test/signature/signature.ptvs.test.ts b/src/test/signature/signature.ptvs.test.ts index 5eeb71ae5f1391fe953303913759a118e070c60d..68720e33cde16f339f7de175424135abbea7ddac 100644 GIT binary patch literal 6003 zcmd5=QE%He5PtWsxB>$#Z?>GITi3cxfgnw{0BvA2Zi^u(Qdwqfwlb-aR1z=Cf8U)b zQnX@cv5|)wX&RIFk$1=Yj>qfw;ZWua#iug^QQCp`JXKQ53xne-mntz_iWm;E3`!vl zifN`U*(7fD`|ysZOlUTNLQI$fGh^`nc;trTR*TPZ5d%L%qVWfj1}>DGL)(67zg&{Y z6Emyk>CeXOrB3C9RrB^sEpY=%O7mH&?>bLX_EB-TtwiIvpUU}MictFa^!(uD;N4%R z_{Y)7AIB$0=cA+3(K1MAlL(_jT$)ME4pPG}*=H^$@=AAs3vPIl@qbwO_lRi|E>}y1 z>wQElNgm7ePzsZ9fqo1$FL2)AyzZ0R(cK@|lxw3FxJWCNs39bUk%w}g56OG`K%R@g zNiL%E^9ffXnWI|mIBqw7Yn9RvEl&UE3p11AEzg*yubNLqVhY9D9oi_v^MX@1_9x5o z4@?)CftwcKr&L50sgWuw2WT)a#u-l`Lz4y|iustSZgUb{$gbDUdL{IsEQDDrmz3u* zOcdH`m|82BJBbq%900nJiz)nz%4JyxZs4|c+oG^G8kIJK3D?OuV?DvHO|LX#o?Q1X z3X%54aihNsLFd6ejdNP$!$qS7YzK*<060-`5t;yjci+53zZvd6_G z#`mOWW?cItBpeOI`a91f3=iH2$)uV5Ym&ToAFqSL4Oyemt)?e?F zZ&H0?N~66|lk@1~{e9T8s@zs8>C#j6c0@NfMMpPci9#Za=q|S_G23W)C;)^z)}zt4JORPg17n48g8e28COXRN=UdDl-IAm=iGRjTSl6>qI^! zic^T+D>PIxWm+%EAWvvJN*+=BtyDnN$0%RzAe*Pyb zbzyfG|8=2<|4Ne93FQBPdnIrOQyFEzcIV;9r&}X`-5N=zc3~TBpKXo&eQV_N5{U}8 zlj%mTeX%8Q4?Poxfe)WAcsWrd(8osEM5e`@3FEVe%$ayo4Mz3l7~Yy_NO{8@Vi3pq zqTLCS*#!o7R3>Fyapxs)cm*z!V|xS8;4k~I8&Z}Os`A2&rQdycM-F?&zjVPV`y?(D z$KoN_*CtWQ5e=r{&4kyM=5%{MD&5e>4L3iGGZqvc&x3K6q#Lpu_p=n;9A^o-InL5^ zb6ibMZDjt?B!mXm+_XR+c zF#-JWnT2H;&#`LtiN#kY%hd?hmA-my=DbHOBq`VxR&T1Rjp=(`Ty9xa;DIBZ@i+c# zP*b3E&1S5Iiw9qPh^*FM9Z`C8%4YdQkYnB&fSFeCkT}ni>E^Jo?-M z%QOpKCN#L=iY4cQB_FAP6B11sE}k|I`Q9SzP6f788B29nS$j1e*h|HvtKO*b;<^c) z0pNRS&NX{wTU7A~y9ciR#9jk+$}X@cFmcU*HDzmszH%L{n6?hrio)$HwXLxZTK1=6 zJ(E`}aZf6c&9J@}MZ;UIhS}-r8g^wIb^&YI0aQw0Bd!Z+qS2bG)wH+VD*g(Vf zg?-~z$YC|=E9RJ5FkK&RaoIbnY_JPqAE-z&U-?%HQS&bm6CJZ_mKKK5yBNQ_^I2lR z>!@9!D&eJBJlRW*jZP$KS+6_Ao%hQIXUzUaB_gZKlUTc^nXX<7fd%zk+288ISHB&; zX0YRhhr%=nX<(k1bY?vXKiznT-hQq2CeAw|278w4!fV)Z9w)-u-fKh8Mh`8}^3Kdx z-)Y0v4)*UJt|jfhr0G&eF4U1EtLSh|J380$fJ3LA?R<&-BKpj`prJGtbF`xSEP24g p<187oEU?_qP`?mQXXsU-o&vV;(Ed~^g>8*{j)pd^UUNvT{{SFX4PgKP literal 6149 xcmeIu0Sy2E0K%a6Pi+qe5hx58Fkrxd0RsjM7%*VKfB^#r3>YwAz<`0vfdLo=00961 From 355edfb3d21e5d563dcad193129e15011907f640 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 28 Mar 2018 14:42:30 -0700 Subject: [PATCH 53/97] Add PTVS tests task --- .vscode/launch.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 4daf91a9249b..35f13ae91111 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -87,6 +87,26 @@ ], "preLaunchTask": "Compile" }, + { + "name": "Launch Analysis Engine Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "${workspaceFolder}/src/test", + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test" + ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "Compile", + "env": { + "VSC_PYTHON_ANALYSIS": "1" + } + }, { "name": "Launch Tests (with code coverage)", "type": "extensionHost", @@ -114,4 +134,4 @@ ] } ] -} +} \ No newline at end of file From ccc101771e432d9fc68ed9aaf6c0b2ee15d3c4b2 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 28 Mar 2018 15:15:58 -0700 Subject: [PATCH 54/97] Analysis Engine contribution --- CONTRIBUTING - PYTHON_ANALYSIS.md | 58 +++++++++++++++++++++++++++++++ CONTRIBUTING.md | 3 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING - PYTHON_ANALYSIS.md diff --git a/CONTRIBUTING - PYTHON_ANALYSIS.md b/CONTRIBUTING - PYTHON_ANALYSIS.md new file mode 100644 index 000000000000..a037915a0466 --- /dev/null +++ b/CONTRIBUTING - PYTHON_ANALYSIS.md @@ -0,0 +1,58 @@ +# Contributing to Microsoft Python Analysis Engine +[![Contributing to Python Tools for Visual Studio](https://github.com/Microsoft/PTVS/blob/master/CONTRIBUTING.md)] + +[![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![Build status (AppVeyor)](https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true)](https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) + + +## Contributing a pull request + +### Prerequisites + +1. [.NET Core 2.0+ SDK](https://www.microsoft.com/net/learn/get-started/windows) +2. C# Extension to VS Code (all platforms) +3. Python 2.7 +4. Python 3.6 + +*Alternative:* Visual Studio 2017 (Windows only) with .NET Core and C# Workloads + +### Setup + +```shell +git clone https://github.com/microsoft/ptvs +cd Python/Product/VsCode/AnalysisVsc +dotnet build +``` + +Visual Studio 2017: +Open solution in Python/Product/VsCode + +Binaries arrive in *Python/BuildOutput/VsCode/raw* +Delete contents of the *analysis* folder in the Python Extension folder +Copy *.dll, *.pdb, *.json fron *Python/BuildOutput/VsCode/raw* to *analysis* + +### Debugging code in Python Extension to VS Code +Folow regular TypeScript debugging steps + +### Debugging C# code in Python Analysis Engine +1. Launch another instance of VS Code +2. Open Python/Product/VsCode/AnalysisVsc folder +3. Python Analysis Engine code is in *Python/Product/VsCode/Analysis* +4. Run extension from VS Code +5. In the instance with C# code select Dotnet Attach launch task. +6. Attach to *dotnet* process running *Microsoft.PythonTools.VsCode.dll* + +On Windows you can also attach from Visual Studio 2017. + +### Validate your changes + +1. Build C# code +2. Copy binaries to *analysis* folder +3. Use the `Launch Extension` launch option. + +### Unit Tests +1. Run the Unit Tests via the `Launch Analysis Engine Tests`. +2. On Windows you can also open complete PTVS solution in Visual Studio and run its tests (or at least the Analysis section). + + +### Coding Standards +See [![Contributing to Python Tools for Visual Studio](https://github.com/Microsoft/PTVS/blob/master/CONTRIBUTING.md)] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef238b941add..918d1bb8a714 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,8 @@ [![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![Build status (AppVeyor)](https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true)](https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) - +# Contributing to Microsoft Python Analysis Engine +[![Contributing to Python Analysis Engine](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING - PYTHON_ANALYSIS.md)] ## Contributing a pull request From 5c87964af35ce66240fd05098ef1489422e4abc9 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 28 Mar 2018 15:21:39 -0700 Subject: [PATCH 55/97] Add Mac/Linux info --- CONTRIBUTING - PYTHON_ANALYSIS.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING - PYTHON_ANALYSIS.md b/CONTRIBUTING - PYTHON_ANALYSIS.md index a037915a0466..d9d336646e6e 100644 --- a/CONTRIBUTING - PYTHON_ANALYSIS.md +++ b/CONTRIBUTING - PYTHON_ANALYSIS.md @@ -8,7 +8,10 @@ ### Prerequisites -1. [.NET Core 2.0+ SDK](https://www.microsoft.com/net/learn/get-started/windows) +1. .NET Core 2.0+ SDK + - [Windows](https://www.microsoft.com/net/learn/get-started/windows) + - [Mac OS](https://www.microsoft.com/net/learn/get-started/macos) + - [Linux](https://www.microsoft.com/net/learn/get-started/linux/rhel) 2. C# Extension to VS Code (all platforms) 3. Python 2.7 4. Python 3.6 From 68e3384867d6aa329d8b3141df9f7044f3a6ebfc Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 28 Mar 2018 15:36:15 -0700 Subject: [PATCH 56/97] Disable csproj build --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 6200779b90fc..3b80a7267a18 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,6 +23,7 @@ install: - python -m easy_install -U setuptools - "%PYTHON%/Scripts/pip.exe install --upgrade -r requirements.txt" +build: off # build_script: # - git clone https://github.com/MikhailArkhipov/PTVS.git c:/projects/PTVS # - "cd c:\\projects\\PTVS" From abb3daabc123b6e4ee7b862dd6de02194c92a083 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 28 Mar 2018 16:09:47 -0700 Subject: [PATCH 57/97] Add unzip to dependencies --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fdbb9cd7ebdb..8dd72f45ab6c 100644 --- a/package.json +++ b/package.json @@ -1766,6 +1766,7 @@ "uint64be": "^1.0.1", "unicode": "^10.0.0", "untildify": "^3.0.2", + "unzip": "^0.1.11", "vscode-debugadapter": "^1.0.1", "vscode-debugprotocol": "^1.0.1", "vscode-extension-telemetry": "^0.0.14", @@ -1836,4 +1837,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} +} \ No newline at end of file From 78fd4953aec0b859fee5355f603b5dbe79d69aa9 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Thu, 29 Mar 2018 12:32:45 -0700 Subject: [PATCH 58/97] Minor fixes to doc --- CONTRIBUTING - PYTHON_ANALYSIS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING - PYTHON_ANALYSIS.md b/CONTRIBUTING - PYTHON_ANALYSIS.md index d9d336646e6e..03563dac7efd 100644 --- a/CONTRIBUTING - PYTHON_ANALYSIS.md +++ b/CONTRIBUTING - PYTHON_ANALYSIS.md @@ -16,7 +16,7 @@ 3. Python 2.7 4. Python 3.6 -*Alternative:* Visual Studio 2017 (Windows only) with .NET Core and C# Workloads +*Alternative:* [Visual Studio 2017](https://www.visualstudio.com/downloads/) (Windows only) with .NET Core and C# Workloads. Community Edition is free and is fully functional. ### Setup @@ -27,11 +27,11 @@ dotnet build ``` Visual Studio 2017: -Open solution in Python/Product/VsCode - -Binaries arrive in *Python/BuildOutput/VsCode/raw* -Delete contents of the *analysis* folder in the Python Extension folder -Copy *.dll, *.pdb, *.json fron *Python/BuildOutput/VsCode/raw* to *analysis* +1. Open solution in Python/Product/VsCode +2. Build AnalysisVsc project +3. Binaries arrive in *Python/BuildOutput/VsCode/raw* +4. Delete contents of the *analysis* folder in the Python Extension folder +5. Copy *.dll, *.pdb, *.json fron *Python/BuildOutput/VsCode/raw* to *analysis* ### Debugging code in Python Extension to VS Code Folow regular TypeScript debugging steps From 84c8b0fcdc8b1e6ad659d41a4a8129d21aaabd2c Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 29 Mar 2018 13:03:47 -0700 Subject: [PATCH 59/97] Change setting type to bool --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ebd6237332fc..482e60ecbf7f 100644 --- a/package.json +++ b/package.json @@ -1053,8 +1053,8 @@ "scope": "resource" }, "python.jediEnabled": { - "type": "string", - "default": "true", + "type": "boolean", + "default": true, "description": "Enables Jedi as IntelliSense engine instead of Microsoft Python Analysis Engine.", "scope": "resource" }, @@ -1837,4 +1837,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} \ No newline at end of file +} From 7761a1598de8af601b4d8352cb3d6d692409de5b Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 30 Mar 2018 11:15:11 -0700 Subject: [PATCH 60/97] Report progress on status bar --- package.json | 1 + src/client/activation/downloader.ts | 62 ++++++++++++++--------------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index ebd6237332fc..aeedf6e3856b 100644 --- a/package.json +++ b/package.json @@ -1757,6 +1757,7 @@ "opn": "^5.1.0", "pidusage": "^1.2.0", "reflect-metadata": "^0.1.12", + "request-progress": "^3.0.0", "rxjs": "^5.5.2", "semver": "^5.4.1", "sudo-prompt": "^8.0.0", diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 11cc140d2863..2e27316f1121 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -2,11 +2,11 @@ // Licensed under the MIT License. import * as fs from 'fs'; -import { IncomingMessage } from 'http'; -import * as https from 'https'; import * as path from 'path'; +import * as request from 'request'; +import * as requestProgress from 'request-progress'; import * as unzip from 'unzip'; -import { ExtensionContext, OutputChannel } from 'vscode'; +import { ExtensionContext, OutputChannel, ProgressLocation, window } from 'vscode'; import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { noop } from '../common/core.utils'; import { createDeferred, createTemporaryFile } from '../common/helpers'; @@ -52,24 +52,38 @@ export class AnalysisEngineDownloader { const fileStream = fs.createWriteStream(tempFile.filePath); fileStream.on('finish', () => { fileStream.close(); - deferred.resolve(); }).on('error', (err) => { tempFile.cleanupCallback(); this.handleError(`Unable to download Python Analysis Engine. Error ${err}`); }); - let firstResponse = true; - https.get(uri, (response) => { - this.checkHttpResponse(response); - if (firstResponse) { - this.reportDownloadSize(response); - firstResponse = false; - } - response.pipe(fileStream); + await window.withProgress({ + location: ProgressLocation.Window, + title: 'Downloading Python Analysis Engine... ' + }, (progress) => { + + requestProgress(request(uri)) + .on('progress', (state) => { + // https://www.npmjs.com/package/request-progress + const received = Math.round(state.size.transferred / 1024); + const total = Math.round(state.size.total / 1024); + const percentage = Math.round(100 * state.percent); + progress.report({ + message: `${received} of ${total} KB (${percentage}%)` + }); + }) + .on('error', (err) => { + this.handleError(err); + deferred.reject(err); + }) + .on('end', () => { + this.output.append('complete.'); + deferred.resolve(); + }) + .pipe(fileStream); + return deferred.promise; }); - await deferred.promise; - this.output.append('complete.'); return tempFile.filePath; } @@ -99,7 +113,7 @@ export class AnalysisEngineDownloader { this.handleError(`Unable to unpack downloaded file. Error ${err}.`); }); await deferred.promise; - this.output.append('done.'); + this.output.appendLine('done.'); } private handleError(message: string) { @@ -107,22 +121,4 @@ export class AnalysisEngineDownloader { this.output.appendLine(message); throw new Error(message); } - - private checkHttpResponse(response: IncomingMessage): boolean { - if (response.statusCode && response.statusCode !== 0 && response.statusCode !== 200) { - this.handleError(`HTTPS request failed: ${response.statusCode} : ${(response.statusMessage ? response.statusMessage : '')}`); - return false; - } - return true; - } - - private reportDownloadSize(response: IncomingMessage): number { - if (response.rawHeaders.length >= 2 && response.rawHeaders[0] === 'Content-Length') { - const size = parseInt(response.rawHeaders[1], 10); - if (size > 0) { - this.output.append(` ${Math.round(size / 1024)} KB...`); - } - } - return 0; - } } From 240d26f2b15414f5bb309a9c1471efff672e6e6d Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 30 Mar 2018 11:22:52 -0700 Subject: [PATCH 61/97] Simplify --- src/client/activation/downloader.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 2e27316f1121..ed090351d957 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -36,6 +36,10 @@ export class AnalysisEngineDownloader { try { await this.verifyDownload(localTempFilePath); await this.unpackArchive(context.extensionPath, localTempFilePath); + } catch (err) { + this.output.appendLine('failed.'); + this.output.appendLine(err); + throw new Error(err); } finally { fs.unlink(localTempFilePath, noop); } @@ -54,7 +58,7 @@ export class AnalysisEngineDownloader { fileStream.close(); }).on('error', (err) => { tempFile.cleanupCallback(); - this.handleError(`Unable to download Python Analysis Engine. Error ${err}`); + deferred.reject(err); }); await window.withProgress({ @@ -73,7 +77,6 @@ export class AnalysisEngineDownloader { }); }) .on('error', (err) => { - this.handleError(err); deferred.reject(err); }) .on('end', () => { @@ -92,7 +95,7 @@ export class AnalysisEngineDownloader { this.output.append('Verifying download... '); const verifier = new HashVerifier(); if (!await verifier.verifyHash(filePath, this.platformData.getExpectedHash())) { - this.handleError('Hash of the downloaded file does not match.'); + throw new Error('Hash of the downloaded file does not match.'); } this.output.append('valid.'); } @@ -110,15 +113,9 @@ export class AnalysisEngineDownloader { deferred.resolve(); }) .on('error', (err) => { - this.handleError(`Unable to unpack downloaded file. Error ${err}.`); + deferred.reject(err); }); await deferred.promise; this.output.appendLine('done.'); } - - private handleError(message: string) { - this.output.appendLine('failed.'); - this.output.appendLine(message); - throw new Error(message); - } } From 736846aefe5700d4b32e066ff936f48c83a7d733 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 30 Mar 2018 12:12:09 -0700 Subject: [PATCH 62/97] CR feedback --- src/client/activation/hashVerifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/activation/hashVerifier.ts b/src/client/activation/hashVerifier.ts index 8f1b157be58d..950f02d869f9 100644 --- a/src/client/activation/hashVerifier.ts +++ b/src/client/activation/hashVerifier.ts @@ -17,7 +17,7 @@ export class HashVerifier { deferred.resolve(); }) .on('error', (err) => { - throw new Error(`Unable to calculate file hash. Error ${err}`); + deferred.reject(`Unable to calculate file hash. Error ${err}`); }); readStream.pipe(hash); From f03c18aac90c6564ee1b2b1e75b4e9bd7a0dba6d Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 30 Mar 2018 12:22:22 -0700 Subject: [PATCH 63/97] Fix launching fx-independent code on Mac/Linux --- src/client/activation/analysis.ts | 125 +++++++++++++++++--------- src/client/activation/downloader.ts | 11 ++- src/client/activation/platformData.ts | 10 +++ 3 files changed, 100 insertions(+), 46 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 412b27f00d8d..3590d52b951c 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -3,20 +3,21 @@ import * as path from 'path'; import { ExtensionContext, OutputChannel } from 'vscode'; -import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; +import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import '../common/extensions'; +import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IProcessService, IPythonExecutionFactory } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; import { AnalysisEngineDownloader } from './downloader'; +import { PlatformData } from './platformData'; import { IExtensionActivator } from './types'; const PYTHON = 'python'; -const analysisEngineBinaryName = 'Microsoft.PythonTools.VsCode.dll'; const dotNetCommand = 'dotnet'; const languageClientName = 'Python Tools'; const analysisEngineFolder = 'analysis'; @@ -30,6 +31,9 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private readonly configuration: IConfigurationService; private readonly appShell: IApplicationShell; private readonly output: OutputChannel; + private readonly fs: IFileSystem; + private readonly sw = new StopWatch(); + private readonly platformData: PlatformData; private languageClient: LanguageClient | undefined; constructor(private readonly services: IServiceContainer, pythonSettings: IPythonSettings) { @@ -37,59 +41,93 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.configuration = this.services.get(IConfigurationService); this.appShell = this.services.get(IApplicationShell); this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + this.fs = this.services.get(IFileSystem); + this.platformData = new PlatformData(services.get(IPlatformService)); } public async activate(context: ExtensionContext): Promise { - const sw = new StopWatch(); - const clientOptions = await this.getAnalysisOptions(context); if (!clientOptions) { return false; } - this.output.appendLine(`Options determined: ${sw.elapsedTime} ms`); + this.output.appendLine(`Options determined: ${this.sw.elapsedTime} ms`); + return this.startLanguageServer(context, clientOptions); + } - if (!await this.tryStartLanguageServer(context, clientOptions, true)) { - const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); - await downloader.downloadAnalysisEngine(context); - if (!await this.tryStartLanguageServer(context, clientOptions, false)) { + public async deactivate(): Promise { + if (this.languageClient) { + await this.languageClient.stop(); + } + } + + private async startLanguageServer(context: ExtensionContext, clientOptions: LanguageClientOptions): Promise { + // Determine if we are running MSIL/Universal via dotnet or self-contained app. + const mscorlib = path.join(context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); + let downloadPackage = false; + + if (!await this.fs.fileExistsAsync(mscorlib)) { + // Depends on .NET Runtime or SDK + this.languageClient = this.createSimpleLanguageClient(context, clientOptions); + const e = await this.tryStartLanguageClient(context, this.languageClient); + if (!e) { + return true; + } + if (await this.isDotNetInstalled()) { + this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${e}`); return false; } + // No .NET Runtime, no mscorlib - need to download self-contained package. + downloadPackage = true; + } + + if (downloadPackage) { + const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); + await downloader.downloadAnalysisEngine(context); } - // tslint:disable-next-line:no-console - this.output.appendLine(`Language server started: ${sw.elapsedTime} ms`); - await this.languageClient!.onReady(); - this.output.appendLine(`Language server ready: ${sw.elapsedTime} ms`); - return true; + const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineExecutableName()); + // Now try to start self-contained app + this.languageClient = this.createSelfContainedLanguageClient(context, serverModule, clientOptions); + const error = await this.tryStartLanguageClient(context, this.languageClient); + if (!error) { + return true; + } + this.appShell.showErrorMessage(`Language server failed to start. Error ${error}`); + return false; } - public async deactivate(): Promise { - if (this.languageClient) { - await this.languageClient.stop(); + private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { + let disposable: Disposable | undefined; + try { + disposable = lc.start(); + await lc.onReady(); + this.output.appendLine(`Language server ready: ${this.sw.elapsedTime} ms`); + context.subscriptions.push(disposable); + } catch (ex) { + if (disposable) { + disposable.dispose(); + return ex; + } } } - private async tryStartLanguageServer(context: ExtensionContext, clientOptions: LanguageClientOptions, checkRuntime: boolean): Promise { + private createSimpleLanguageClient(context: ExtensionContext, clientOptions: LanguageClientOptions): LanguageClient { const commandOptions = { stdio: 'pipe' }; - const serverModule = path.join(context.extensionPath, analysisEngineFolder, analysisEngineBinaryName); + const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineDllName()); const serverOptions: ServerOptions = { run: { command: dotNetCommand, args: [serverModule], options: commandOptions }, debug: { command: dotNetCommand, args: [serverModule, '--debug'], options: commandOptions } }; + return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); + } - try { - // Create the language client and start the client. - this.languageClient = new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); - context.subscriptions.push(this.languageClient.start()); - return true; - } catch (ex) { - if (checkRuntime && !await this.checkRuntime()) { - this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${ex}`); - } else { - this.appShell.showErrorMessage(`Language server failed to start. Error ${ex}`); - } - } - return false; + private createSelfContainedLanguageClient(context: ExtensionContext, serverModule: string, clientOptions: LanguageClientOptions): LanguageClient { + const options = { stdio: 'pipe' }; + const serverOptions: ServerOptions = { + run: { command: serverModule, rgs: [], options: options }, + debug: { command: serverModule, args: ['--debug'], options } + }; + return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); } private async getAnalysisOptions(context: ExtensionContext): Promise { @@ -212,22 +250,21 @@ export class AnalysisExtensionActivator implements IExtensionActivator { return s.length > 0 && s[0] !== '['; } - private async checkRuntime(): Promise { - if (!await this.isDotNetInstalled()) { - const appShell = this.services.get(IApplicationShell); - if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { - appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); - appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); - } - return false; - } - return true; - } + // private async checkNetCoreRuntime(): Promise { + // if (!await this.isDotNetInstalled()) { + // const appShell = this.services.get(IApplicationShell); + // if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { + // appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); + // appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); + // } + // return false; + // } + // return true; + // } private async isDotNetInstalled(): Promise { const ps = this.services.get(IProcessService); const result = await ps.exec('dotnet', ['--version']).catch(() => { return { stdout: '' }; }); return result.stdout.trim().startsWith('2.'); } - } diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 11cc140d2863..e57d76869bc9 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -23,12 +23,13 @@ const downloadFileExtension = '.nupkg'; export class AnalysisEngineDownloader { private readonly output: OutputChannel; + private readonly platform: IPlatformService; private readonly platformData: PlatformData; constructor(private readonly services: IServiceContainer, private engineFolder: string) { this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - const platform = this.services.get(IPlatformService); - this.platformData = new PlatformData(platform); + this.platform = this.services.get(IPlatformService); + this.platformData = new PlatformData(this.platform); } public async downloadAnalysisEngine(context: ExtensionContext): Promise { @@ -100,6 +101,12 @@ export class AnalysisEngineDownloader { }); await deferred.promise; this.output.append('done.'); + + // Set file to executable + if (!this.platform.isWindows) { + const executablePath = path.join(installFolder, this.platformData.getEngineExecutableName()); + fs.chmodSync(executablePath, '0764'); // -rwxrw-r-- + } } private handleError(message: string) { diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts index 133c7f17144f..541e5a602bef 100644 --- a/src/client/activation/platformData.ts +++ b/src/client/activation/platformData.ts @@ -24,6 +24,16 @@ export class PlatformData { throw new Error('Python Analysis Engine does not support 32-bit Linux.'); } + public getEngineDllName(): string { + return 'Microsoft.PythonTools.VsCode.dll'; + } + + public getEngineExecutableName(): string { + return this.platform.isWindows + ? 'Microsoft.PythonTools.VsCode.exe' + : 'Microsoft.PythonTools.VsCode'; + } + public getExpectedHash(): string { if (this.platform.isWindows) { return this.platform.is64bit ? analysis_engine_win_x64_sha512 : analysis_engine_win_x86_sha512; From 70ce69b124a002854b140c131bae862a31b68120 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 30 Mar 2018 13:05:06 -0700 Subject: [PATCH 64/97] Add title --- src/client/activation/downloader.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index ed090351d957..4efd6725080a 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -61,9 +61,10 @@ export class AnalysisEngineDownloader { deferred.reject(err); }); + const title = 'Downloading Python Analysis Engine... '; await window.withProgress({ location: ProgressLocation.Window, - title: 'Downloading Python Analysis Engine... ' + title }, (progress) => { requestProgress(request(uri)) @@ -73,7 +74,7 @@ export class AnalysisEngineDownloader { const total = Math.round(state.size.total / 1024); const percentage = Math.round(100 * state.percent); progress.report({ - message: `${received} of ${total} KB (${percentage}%)` + message: `${title}${received} of ${total} KB (${percentage}%)` }); }) .on('error', (err) => { From 3ee75f423dc3db4746931482afba4afd2954748c Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 3 Apr 2018 13:26:57 -0700 Subject: [PATCH 65/97] PTVS startup time --- src/client/activation/analysis.ts | 163 ++++++------------ .../activation/interpreterDataService.ts | 96 +++++++++++ 2 files changed, 148 insertions(+), 111 deletions(-) create mode 100644 src/client/activation/interpreterDataService.ts diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 3590d52b951c..116d986fb49f 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -2,18 +2,19 @@ // Licensed under the MIT License. import * as path from 'path'; -import { ExtensionContext, OutputChannel } from 'vscode'; +import { ExtensionContext, OutputChannel, Uri } from 'vscode'; import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import '../common/extensions'; import { IFileSystem, IPlatformService } from '../common/platform/types'; -import { IProcessService, IPythonExecutionFactory } from '../common/process/types'; +import { IProcessService } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; -import { IInterpreterService } from '../interpreter/contracts'; +import { IEnvironmentVariablesProvider } from '../common/variables/types'; import { IServiceContainer } from '../ioc/types'; import { AnalysisEngineDownloader } from './downloader'; +import { InterpreterDataService } from './interpreterDataService'; import { PlatformData } from './platformData'; import { IExtensionActivator } from './types'; @@ -22,12 +23,7 @@ const dotNetCommand = 'dotnet'; const languageClientName = 'Python Tools'; const analysisEngineFolder = 'analysis'; -class InterpreterData { - constructor(public readonly version: string, public readonly prefix: string) { } -} - export class AnalysisExtensionActivator implements IExtensionActivator { - private readonly executionFactory: IPythonExecutionFactory; private readonly configuration: IConfigurationService; private readonly appShell: IApplicationShell; private readonly output: OutputChannel; @@ -37,7 +33,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private languageClient: LanguageClient | undefined; constructor(private readonly services: IServiceContainer, pythonSettings: IPythonSettings) { - this.executionFactory = this.services.get(IPythonExecutionFactory); this.configuration = this.services.get(IConfigurationService); this.appShell = this.services.get(IApplicationShell); this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); @@ -50,7 +45,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (!clientOptions) { return false; } - this.output.appendLine(`Options determined: ${this.sw.elapsedTime} ms`); return this.startLanguageServer(context, clientOptions); } @@ -96,7 +90,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { return false; } - private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { + private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { let disposable: Disposable | undefined; try { disposable = lc.start(); @@ -135,45 +129,37 @@ export class AnalysisExtensionActivator implements IExtensionActivator { const properties = new Map(); // Microsoft Python code analysis engine needs full path to the interpreter - const interpreterService = this.services.get(IInterpreterService); - const interpreter = await interpreterService.getActiveInterpreter(); + const interpreterDataService = new InterpreterDataService(context, this.services); + const interpreterData = await interpreterDataService.getInterpreterData(); + if (!interpreterData) { + const appShell = this.services.get(IApplicationShell); + appShell.showErrorMessage('Unable to determine path to Python interpreter.'); + return; + } - if (interpreter) { - // tslint:disable-next-line:no-string-literal - properties['InterpreterPath'] = interpreter.path; - if (interpreter.displayName) { - // tslint:disable-next-line:no-string-literal - properties['Description'] = interpreter.displayName; + // tslint:disable-next-line:no-string-literal + properties['InterpreterPath'] = interpreterData.path; + // tslint:disable-next-line:no-string-literal + properties['Version'] = interpreterData.version; + // tslint:disable-next-line:no-string-literal + properties['PrefixPath'] = interpreterData.prefix; + // tslint:disable-next-line:no-string-literal + properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); + + let searchPaths = await this.getSearchPaths(); + const settings = this.configuration.getSettings(); + if (settings.autoComplete) { + const extraPaths = settings.autoComplete.extraPaths; + if (extraPaths && extraPaths.length > 0) { + searchPaths = `${searchPaths};${extraPaths.join(';')}`; } - const interpreterData = await this.getInterpreterData(); - - // tslint:disable-next-line:no-string-literal - properties['Version'] = interpreterData.version; - // tslint:disable-next-line:no-string-literal - properties['PrefixPath'] = interpreterData.prefix; - // tslint:disable-next-line:no-string-literal - properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); + } + // tslint:disable-next-line:no-string-literal + properties['SearchPaths'] = searchPaths; - let searchPaths = await this.getSearchPaths(); - const settings = this.configuration.getSettings(); - if (settings.autoComplete) { - const extraPaths = settings.autoComplete.extraPaths; - if (extraPaths && extraPaths.length > 0) { - searchPaths = `${searchPaths};${extraPaths.join(';')}`; - } - } + if (isTestExecution()) { // tslint:disable-next-line:no-string-literal - properties['SearchPaths'] = searchPaths; - - if (isTestExecution()) { - // tslint:disable-next-line:no-string-literal - properties['TestEnvironment'] = true; - } - } else { - const appShell = this.services.get(IApplicationShell); - const pythonPath = this.configuration.getSettings().pythonPath; - appShell.showErrorMessage(`Interpreter ${pythonPath} does not exist.`); - return; + properties['TestEnvironment'] = true; } const selector: string[] = [PYTHON]; @@ -188,80 +174,35 @@ export class AnalysisExtensionActivator implements IExtensionActivator { initializationOptions: { interpreter: { properties + }, + displayOptions: { + trimDocumentationLines: false, + maxDocumentationLineLength: 0, + trimDocumentationText: false, + maxDocumentationTextLength: 0 } } }; } - private async getInterpreterData(): Promise { - // Not appropriate for multiroot workspaces. - // See https://github.com/Microsoft/vscode-python/issues/1149 - const execService = await this.executionFactory.create(); - const result = await execService.exec(['-c', 'import sys; print(sys.version_info); print(sys.prefix)'], {}); - // 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:19:30) <> - // [MSC v.1500 32 bit (Intel)] - // C:\Python27 - if (!result.stdout) { - throw Error('Unable to determine Python interpreter version and system prefix.'); - } - const output = result.stdout.splitLines({ removeEmptyEntries: true, trim: true }); - if (!output || output.length < 2) { - throw Error('Unable to parse version and and system prefix from the Python interpreter output.'); - } - const majorMatches = output[0].match(/major=(\d*?),/); - const minorMatches = output[0].match(/minor=(\d*?),/); - if (!majorMatches || majorMatches.length < 2 || !minorMatches || minorMatches.length < 2) { - throw Error('Unable to parse interpreter version.'); - } - const prefix = output[output.length - 1]; - return new InterpreterData(`${majorMatches[1]}.${minorMatches[1]}`, prefix); - } - - private async getSearchPaths(): Promise { - // Not appropriate for multiroot workspaces. - // See https://github.com/Microsoft/vscode-python/issues/1149 - const execService = await this.executionFactory.create(); - const result = await execService.exec(['-c', 'import sys; print(sys.path);'], {}); - if (!result.stdout) { - throw Error('Unable to determine Python interpreter search paths.'); + private async getSearchPaths(resource?: Uri): Promise { + const envProvider = this.services.get(IEnvironmentVariablesProvider); + const platform = this.services.get(IPlatformService); + const vars = await envProvider.getEnvironmentVariables(resource); + if (!vars) { + return ''; } - // tslint:disable-next-line:no-unnecessary-local-variable - const paths = result.stdout.split(',') - .filter(p => this.isValidPath(p)) - .map(p => this.pathCleanup(p)); - return paths.join(';'); - } - - private pathCleanup(s: string): string { - s = s.trim(); - if (s[0] === '\'') { - s = s.substr(1); - } - if (s[s.length - 1] === ']') { - s = s.substr(0, s.length - 1); - } - if (s[s.length - 1] === '\'') { - s = s.substr(0, s.length - 1); + // tslint:disable-next-line:no-string-literal + const pythonPath = vars['PYTHONPATH']; + // tslint:disable-next-line:no-string-literal + let systemPath = vars[platform.isWindows ? 'PATH' : 'path']; + if (pythonPath && pythonPath.length > 0) { + // PTVS engine always uses ; + systemPath = `${systemPath};${pythonPath}`; } - return s; + return systemPath; } - private isValidPath(s: string): boolean { - return s.length > 0 && s[0] !== '['; - } - - // private async checkNetCoreRuntime(): Promise { - // if (!await this.isDotNetInstalled()) { - // const appShell = this.services.get(IApplicationShell); - // if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { - // appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); - // appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); - // } - // return false; - // } - // return true; - // } - private async isDotNetInstalled(): Promise { const ps = this.services.get(IProcessService); const result = await ps.exec('dotnet', ['--version']).catch(() => { return { stdout: '' }; }); diff --git a/src/client/activation/interpreterDataService.ts b/src/client/activation/interpreterDataService.ts new file mode 100644 index 000000000000..5062ac5213a7 --- /dev/null +++ b/src/client/activation/interpreterDataService.ts @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { createHash } from 'crypto'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ExtensionContext, Uri } from 'vscode'; +import { createDeferred } from '../common/helpers'; +import { IPlatformService } from '../common/platform/types'; +import { IPythonExecutionFactory, IPythonExecutionService } from '../common/process/types'; +import { IServiceContainer } from '../ioc/types'; + +const DataVersion = 1; + +export class InterpreterData { + constructor( + public readonly dataVersion: number, + // tslint:disable-next-line:no-shadowed-variable + public readonly path: string, + public readonly version: string, + public readonly prefix: string, + public readonly hash: string + ) { } +} + +export class InterpreterDataService { + constructor( + private readonly context: ExtensionContext, + private readonly serviceContainer: IServiceContainer) { } + + public async getInterpreterData(resource?: Uri): Promise { + const executionFactory = this.serviceContainer.get(IPythonExecutionFactory); + const execService = await executionFactory.create(resource); + + const interpreterPath = await execService.getExecutablePath(); + if (interpreterPath.length === 0) { + return; + } + + let interpreterData = this.context.globalState.get(interpreterPath) as InterpreterData; + let interpreterChanged = false; + if (interpreterData) { + // Check if interpreter executable changed + if (interpreterData.dataVersion !== DataVersion) { + interpreterChanged = true; + } else { + const currentHash = await this.getInterpreterHash(interpreterPath); + interpreterChanged = currentHash !== interpreterData.hash; + } + } + + if (interpreterChanged || !interpreterData) { + interpreterData = await this.getInterpreterDataFromPython(execService, interpreterPath); + this.context.globalState.update(interpreterPath, interpreterData); + } + return interpreterData; + } + + private async getInterpreterDataFromPython(execService: IPythonExecutionService, interpreterPath: string): Promise { + const result = await execService.exec(['-c', 'import sys; print(sys.version_info); print(sys.prefix)'], {}); + // 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:19:30) <> + // [MSC v.1500 32 bit (Intel)] + // C:\Python27 + if (!result.stdout) { + throw Error('Unable to determine Python interpreter version and system prefix.'); + } + const output = result.stdout.splitLines({ removeEmptyEntries: true, trim: true }); + if (!output || output.length < 2) { + throw Error('Unable to parse version and and system prefix from the Python interpreter output.'); + } + const majorMatches = output[0].match(/major=(\d*?),/); + const minorMatches = output[0].match(/minor=(\d*?),/); + if (!majorMatches || majorMatches.length < 2 || !minorMatches || minorMatches.length < 2) { + throw Error('Unable to parse interpreter version.'); + } + const prefix = output[output.length - 1]; + const hash = await this.getInterpreterHash(interpreterPath); + return new InterpreterData(DataVersion, interpreterPath, `${majorMatches[1]}.${minorMatches[1]}`, prefix, hash); + } + + private getInterpreterHash(interpreterPath: string): Promise { + const platform = this.serviceContainer.get(IPlatformService); + const pythonExecutable = path.join(path.dirname(interpreterPath), platform.isWindows ? 'python.exe' : 'python'); + // Hash mod time and creation time + const deferred = createDeferred(); + fs.lstat(pythonExecutable, (err, stats) => { + if (err) { + deferred.resolve(''); + } else { + const actual = createHash('sha512').update(`${stats.ctimeMs}-${stats.mtimeMs}`).digest('hex'); + deferred.resolve(actual); + } + }); + return deferred.promise; + } +} From d0ba56a7053992d27fed03f4d8c3f74adeda4b72 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 4 Apr 2018 12:21:54 -0700 Subject: [PATCH 66/97] PTVS startup time --- src/client/activation/analysis.ts | 24 +-------- .../activation/interpreterDataService.ts | 51 ++++++++++++++++++- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 116d986fb49f..ac281ba7a2db 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -2,16 +2,14 @@ // Licensed under the MIT License. import * as path from 'path'; -import { ExtensionContext, OutputChannel, Uri } from 'vscode'; +import { ExtensionContext, OutputChannel } from 'vscode'; import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import '../common/extensions'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IProcessService } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; -import { IEnvironmentVariablesProvider } from '../common/variables/types'; import { IServiceContainer } from '../ioc/types'; import { AnalysisEngineDownloader } from './downloader'; import { InterpreterDataService } from './interpreterDataService'; @@ -146,7 +144,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { // tslint:disable-next-line:no-string-literal properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); - let searchPaths = await this.getSearchPaths(); + let searchPaths = interpreterData.searchPaths; const settings = this.configuration.getSettings(); if (settings.autoComplete) { const extraPaths = settings.autoComplete.extraPaths; @@ -185,24 +183,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { }; } - private async getSearchPaths(resource?: Uri): Promise { - const envProvider = this.services.get(IEnvironmentVariablesProvider); - const platform = this.services.get(IPlatformService); - const vars = await envProvider.getEnvironmentVariables(resource); - if (!vars) { - return ''; - } - // tslint:disable-next-line:no-string-literal - const pythonPath = vars['PYTHONPATH']; - // tslint:disable-next-line:no-string-literal - let systemPath = vars[platform.isWindows ? 'PATH' : 'path']; - if (pythonPath && pythonPath.length > 0) { - // PTVS engine always uses ; - systemPath = `${systemPath};${pythonPath}`; - } - return systemPath; - } - private async isDotNetInstalled(): Promise { const ps = this.services.get(IProcessService); const result = await ps.exec('dotnet', ['--version']).catch(() => { return { stdout: '' }; }); diff --git a/src/client/activation/interpreterDataService.ts b/src/client/activation/interpreterDataService.ts index 5062ac5213a7..48e773b2e19a 100644 --- a/src/client/activation/interpreterDataService.ts +++ b/src/client/activation/interpreterDataService.ts @@ -5,6 +5,8 @@ import { createHash } from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; import { ExtensionContext, Uri } from 'vscode'; +import { IApplicationShell } from '../common/application/types'; +import '../common/extensions'; import { createDeferred } from '../common/helpers'; import { IPlatformService } from '../common/platform/types'; import { IPythonExecutionFactory, IPythonExecutionService } from '../common/process/types'; @@ -19,6 +21,7 @@ export class InterpreterData { public readonly path: string, public readonly version: string, public readonly prefix: string, + public readonly searchPaths: string, public readonly hash: string ) { } } @@ -53,6 +56,10 @@ export class InterpreterDataService { interpreterData = await this.getInterpreterDataFromPython(execService, interpreterPath); this.context.globalState.update(interpreterPath, interpreterData); } + + // Make sure we verify that search paths did not change. This must be done + // completely async so we don't delay Python language server startup. + this.verifySearchPathsAsync(interpreterData.searchPaths, interpreterPath, execService); return interpreterData; } @@ -75,7 +82,8 @@ export class InterpreterDataService { } const prefix = output[output.length - 1]; const hash = await this.getInterpreterHash(interpreterPath); - return new InterpreterData(DataVersion, interpreterPath, `${majorMatches[1]}.${minorMatches[1]}`, prefix, hash); + const searchPaths = await this.getSearchPaths(execService); + return new InterpreterData(DataVersion, interpreterPath, `${majorMatches[1]}.${minorMatches[1]}`, prefix, searchPaths, hash); } private getInterpreterHash(interpreterPath: string): Promise { @@ -93,4 +101,45 @@ export class InterpreterDataService { }); return deferred.promise; } + + private async getSearchPaths(execService: IPythonExecutionService): Promise { + const result = await execService.exec(['-c', 'import sys; print(sys.path);'], {}); + if (!result.stdout) { + throw Error('Unable to determine Python interpreter search paths.'); + } + // tslint:disable-next-line:no-unnecessary-local-variable + const paths = result.stdout.split(',') + .filter(p => this.isValidPath(p)) + .map(p => this.pathCleanup(p)); + return paths.join(';'); // PTVS uses ; on all platforms + } + + private pathCleanup(s: string): string { + s = s.trim(); + if (s[0] === '\'') { + s = s.substr(1); + } + if (s[s.length - 1] === ']') { + s = s.substr(0, s.length - 1); + } + if (s[s.length - 1] === '\'') { + s = s.substr(0, s.length - 1); + } + return s; + } + + private isValidPath(s: string): boolean { + return s.length > 0 && s[0] !== '['; + } + + private verifySearchPathsAsync(currentPaths: string, interpreterPath: string, execService: IPythonExecutionService): void { + this.getSearchPaths(execService) + .then(async paths => { + if (paths !== currentPaths) { + this.context.globalState.update(interpreterPath, undefined); + const appShell = this.serviceContainer.get(IApplicationShell); + await appShell.showWarningMessage('Search paths have changed for this Python interpreter. Please reload the extension to ensure that the IntelliSense works correctly.'); + } + }).ignoreErrors(); + } } From 7748dded1582342d59f45a3ee6ab2cb152d0a5f6 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 6 Mar 2018 16:35:39 -0800 Subject: [PATCH 67/97] Remove test --- src/test/linters/lint.provider.test.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/test/linters/lint.provider.test.ts b/src/test/linters/lint.provider.test.ts index 023ee86223be..51e49d3d35b9 100644 --- a/src/test/linters/lint.provider.test.ts +++ b/src/test/linters/lint.provider.test.ts @@ -113,16 +113,6 @@ suite('Linting - Provider', () => { engine.verify(x => x.lintDocument(document.object, 'save'), TypeMoq.Times.never()); }); - test('Lint on change interpreters', () => { - const e = new vscode.EventEmitter(); - interpreterService.setup(x => x.onDidChangeInterpreter).returns(() => e.event); - - // tslint:disable-next-line:no-unused-variable - const provider = new LinterProvider(context.object, serviceContainer); - e.fire(); - engine.verify(x => x.lintOpenPythonFiles(), TypeMoq.Times.once()); - }); - test('Lint on save pylintrc', async () => { docManager.setup(x => x.onDidSaveTextDocument).returns(() => emitter.event); document.setup(x => x.uri).returns(() => vscode.Uri.file('.pylintrc')); From 993db9564cc7bdebc99e940eb72f2e1b2e154902 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 6 Mar 2018 17:02:12 -0800 Subject: [PATCH 68/97] Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. --- src/test/linters/lint.provider.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/linters/lint.provider.test.ts b/src/test/linters/lint.provider.test.ts index 51e49d3d35b9..023ee86223be 100644 --- a/src/test/linters/lint.provider.test.ts +++ b/src/test/linters/lint.provider.test.ts @@ -113,6 +113,16 @@ suite('Linting - Provider', () => { engine.verify(x => x.lintDocument(document.object, 'save'), TypeMoq.Times.never()); }); + test('Lint on change interpreters', () => { + const e = new vscode.EventEmitter(); + interpreterService.setup(x => x.onDidChangeInterpreter).returns(() => e.event); + + // tslint:disable-next-line:no-unused-variable + const provider = new LinterProvider(context.object, serviceContainer); + e.fire(); + engine.verify(x => x.lintOpenPythonFiles(), TypeMoq.Times.once()); + }); + test('Lint on save pylintrc', async () => { docManager.setup(x => x.onDidSaveTextDocument).returns(() => emitter.event); document.setup(x => x.uri).returns(() => vscode.Uri.file('.pylintrc')); From 5130ee5da5cef86d51ce00c9a6861078730fae00 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 13 Feb 2018 14:47:16 -0800 Subject: [PATCH 69/97] Undo changes --- pythonFiles/completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index e530be32b367..99f8582c614c 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -88,7 +88,7 @@ def _generate_signature(self, completion): return '' return '%s(%s)' % ( completion.name, - ', '.join(p.description[6:] for p in completion.params if p)) + ', '.join(self._get_param_name(p.description) for p in completion.params if p)) def _get_call_signatures(self, script): """Extract call signatures from jedi.api.Script object in failsafe way. From d32b9d4515fd03d43753f6d99665094cc9bda436 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 13 Feb 2018 15:44:21 -0800 Subject: [PATCH 70/97] Test fixes --- pythonFiles/completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index 99f8582c614c..e530be32b367 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -88,7 +88,7 @@ def _generate_signature(self, completion): return '' return '%s(%s)' % ( completion.name, - ', '.join(self._get_param_name(p.description) for p in completion.params if p)) + ', '.join(p.description[6:] for p in completion.params if p)) def _get_call_signatures(self, script): """Extract call signatures from jedi.api.Script object in failsafe way. From 54d364e909a2d03f6b8742d42e340547c3b1b77c Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 4 Apr 2018 12:38:23 -0700 Subject: [PATCH 71/97] Merge master --- src/client/common/configuration/service.ts | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/client/common/configuration/service.ts b/src/client/common/configuration/service.ts index e5d862930ca3..6ead14a0e7cf 100644 --- a/src/client/common/configuration/service.ts +++ b/src/client/common/configuration/service.ts @@ -2,11 +2,12 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; +import * as path from 'path'; import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; import { IApplicationShell } from '../application/types'; import { PythonSettings } from '../configSettings'; -import { IProcessService } from '../process/types'; +import { IFileSystem, IPlatformService } from '../platform/types'; import { IConfigurationService, IPythonSettings } from '../types'; @injectable() @@ -80,8 +81,23 @@ export class ConfigurationService implements IConfigurationService { } private async isDotNetInstalled(): Promise { - const ps = this.services.get(IProcessService); - const result = await ps.exec('dotnet', ['--version']); - return result.stdout.trim().startsWith('2.'); + const platform = this.services.get(IPlatformService); + const fs = this.services.get(IFileSystem); + const versions = ['2.0.0']; + let prefix: string; + + if (platform.isWindows) { + const drive = process.env.SystemDrive; + prefix = `${drive}\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\`; + } else { + prefix = '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/'; + } + + for (const ver of versions) { + if (await fs.directoryExistsAsync(path.join(prefix, ver))) { + return true; + } + } + return false; } } From 5ff98363420bcf35dad5f635358a8bd6169f487c Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 4 Apr 2018 12:41:37 -0700 Subject: [PATCH 72/97] Remove unused code --- src/client/common/configuration/service.ts | 46 ---------------------- src/client/common/types.ts | 1 - 2 files changed, 47 deletions(-) diff --git a/src/client/common/configuration/service.ts b/src/client/common/configuration/service.ts index 6ead14a0e7cf..76e0b6227d32 100644 --- a/src/client/common/configuration/service.ts +++ b/src/client/common/configuration/service.ts @@ -1,20 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable } from 'inversify'; -import * as path from 'path'; import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from 'vscode'; -import { IServiceContainer } from '../../ioc/types'; -import { IApplicationShell } from '../application/types'; import { PythonSettings } from '../configSettings'; -import { IFileSystem, IPlatformService } from '../platform/types'; import { IConfigurationService, IPythonSettings } from '../types'; -@injectable() export class ConfigurationService implements IConfigurationService { - constructor(@inject(IServiceContainer) private services: IServiceContainer) { - } - public getSettings(resource?: Uri): IPythonSettings { return PythonSettings.getInstance(resource); } @@ -40,10 +31,6 @@ export class ConfigurationService implements IConfigurationService { return process.env.VSC_PYTHON_CI_TEST === '1'; } - public async checkDependencies(): Promise { - return this.checkDotNet(); - } - private async verifySetting(pythonConfig: WorkspaceConfiguration, target: ConfigurationTarget, settingName: string, value?: {}): Promise { if (this.isTestExecution()) { let retries = 0; @@ -67,37 +54,4 @@ export class ConfigurationService implements IConfigurationService { } while (retries < 20); } } - - private async checkDotNet(): Promise { - if (!await this.isDotNetInstalled()) { - const appShell = this.services.get(IApplicationShell); - if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { - appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); - appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); - } - return false; - } - return true; - } - - private async isDotNetInstalled(): Promise { - const platform = this.services.get(IPlatformService); - const fs = this.services.get(IFileSystem); - const versions = ['2.0.0']; - let prefix: string; - - if (platform.isWindows) { - const drive = process.env.SystemDrive; - prefix = `${drive}\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\`; - } else { - prefix = '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/'; - } - - for (const ver of versions) { - if (await fs.directoryExistsAsync(path.join(prefix, ver))) { - return true; - } - } - return false; - } } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 266d1a812cb0..f64617178288 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -219,7 +219,6 @@ export interface IConfigurationService { getSettings(resource?: Uri): IPythonSettings; isTestExecution(): boolean; updateSettingAsync(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise; - checkDependencies(): Promise; } export const ISocketServer = Symbol('ISocketServer'); From 8c955a945f1284102d1d93a9261c6016ec975f82 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 16 Mar 2018 13:50:32 -0700 Subject: [PATCH 73/97] Add clone and build PTVS --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 3b80a7267a18..e148dcdce2c2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,9 +23,10 @@ install: - python -m easy_install -U setuptools - "%PYTHON%/Scripts/pip.exe install --upgrade -r requirements.txt" + build: off # build_script: -# - git clone https://github.com/MikhailArkhipov/PTVS.git c:/projects/PTVS +# - git clone https://github.com/MicrosoftArkhipov/PTVS.git c:/projects/PTVS # - "cd c:\\projects\\PTVS" # - git checkout origin/vsc # - "cd Python\\Product\\VSCode\\AnalysisVsc" From 5047d9683c09579821f2d4294be27a7432edab71 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 16 Mar 2018 16:39:58 -0700 Subject: [PATCH 74/97] Fix slashes --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e148dcdce2c2..2323990f5be3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ install: build: off # build_script: -# - git clone https://github.com/MicrosoftArkhipov/PTVS.git c:/projects/PTVS +# - git clone https://github.com/MikhailArkhipov/PTVS.git c:/projects/PTVS # - "cd c:\\projects\\PTVS" # - git checkout origin/vsc # - "cd Python\\Product\\VSCode\\AnalysisVsc" From b8c9edc4368ebd056d734b606cafeb08b163505e Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 20 Mar 2018 15:07:18 -0700 Subject: [PATCH 75/97] Disable PEP hint tests --- src/test/autocomplete/pep484.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/autocomplete/pep484.test.ts b/src/test/autocomplete/pep484.test.ts index bac83c7afefa..c21471ead780 100644 --- a/src/test/autocomplete/pep484.test.ts +++ b/src/test/autocomplete/pep484.test.ts @@ -12,7 +12,7 @@ const filePep484 = path.join(autoCompPath, 'pep484.py'); suite('Autocomplete PEP 484', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; - suiteSetup(async function () { + suiteSetup(async () => { // https://github.com/Microsoft/PTVS/issues/3917 if (IS_ANALYSIS_ENGINE_TEST) { // tslint:disable-next-line:no-invalid-this From 965753cc8313b58e6d5ed581146e0e78d31149b0 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 20 Mar 2018 15:53:37 -0700 Subject: [PATCH 76/97] Test fix --- src/test/autocomplete/pep484.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/autocomplete/pep484.test.ts b/src/test/autocomplete/pep484.test.ts index c21471ead780..bac83c7afefa 100644 --- a/src/test/autocomplete/pep484.test.ts +++ b/src/test/autocomplete/pep484.test.ts @@ -12,7 +12,7 @@ const filePep484 = path.join(autoCompPath, 'pep484.py'); suite('Autocomplete PEP 484', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; - suiteSetup(async () => { + suiteSetup(async function () { // https://github.com/Microsoft/PTVS/issues/3917 if (IS_ANALYSIS_ENGINE_TEST) { // tslint:disable-next-line:no-invalid-this From 9345b9fc7fd30e9abd5b1a820ca2094a288e585c Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 21 Mar 2018 11:56:11 -0700 Subject: [PATCH 77/97] Remove analysis engine test from the set --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 81128aa16bb6..50088b24c5ba 100644 --- a/package.json +++ b/package.json @@ -1838,4 +1838,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} +} \ No newline at end of file From 82f965746117a6b749a888bb828f6d09a1e6cdb4 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Mon, 26 Mar 2018 14:19:38 -0700 Subject: [PATCH 78/97] Build/sign VSXI project --- vscode-python.proj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 vscode-python.proj diff --git a/vscode-python.proj b/vscode-python.proj new file mode 100644 index 000000000000..4f80f69b3b17 --- /dev/null +++ b/vscode-python.proj @@ -0,0 +1,16 @@ + + + + + + $(OutputPath)\python-$(Branch).vsix + + + + + + + VsixSHA2 + + + \ No newline at end of file From 8f16e9a2d2424adca105c27ff3a97515bf6b331a Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:10:10 -0700 Subject: [PATCH 79/97] Run vsce from cmd --- build/run-vcse.cmd | 1 + build/vscode-python.proj | 17 +++++++++++++++++ vscode-python.proj | 16 ---------------- 3 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 build/run-vcse.cmd create mode 100644 build/vscode-python.proj delete mode 100644 vscode-python.proj diff --git a/build/run-vcse.cmd b/build/run-vcse.cmd new file mode 100644 index 000000000000..52a263e98310 --- /dev/null +++ b/build/run-vcse.cmd @@ -0,0 +1 @@ +vsce package --out %1 diff --git a/build/vscode-python.proj b/build/vscode-python.proj new file mode 100644 index 000000000000..98929e5a160f --- /dev/null +++ b/build/vscode-python.proj @@ -0,0 +1,17 @@ + + + + + + $(UserProfile)\.nuget\packages\ + $(OutputPath)\python-$(Branch).vsix + + + + + + + VsixSHA2 + + + diff --git a/vscode-python.proj b/vscode-python.proj deleted file mode 100644 index 4f80f69b3b17..000000000000 --- a/vscode-python.proj +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - $(OutputPath)\python-$(Branch).vsix - - - - - - - VsixSHA2 - - - \ No newline at end of file From a15cedf4e1a5d8a6667905c2ffbb9da5014a0710 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:26:27 -0700 Subject: [PATCH 80/97] Rename --- build/run-vcse.cmd | 1 - build/run-vsce.cmd | 2 ++ build/vscode-python.proj | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 build/run-vcse.cmd create mode 100644 build/run-vsce.cmd diff --git a/build/run-vcse.cmd b/build/run-vcse.cmd deleted file mode 100644 index 52a263e98310..000000000000 --- a/build/run-vcse.cmd +++ /dev/null @@ -1 +0,0 @@ -vsce package --out %1 diff --git a/build/run-vsce.cmd b/build/run-vsce.cmd new file mode 100644 index 000000000000..e3af675aeb56 --- /dev/null +++ b/build/run-vsce.cmd @@ -0,0 +1,2 @@ +cd %1 +vsce package --out %2 diff --git a/build/vscode-python.proj b/build/vscode-python.proj index 98929e5a160f..a65c499a34bb 100644 --- a/build/vscode-python.proj +++ b/build/vscode-python.proj @@ -3,11 +3,10 @@ - $(UserProfile)\.nuget\packages\ $(OutputPath)\python-$(Branch).vsix - + From 3245d5694a97733069cb865ab9ad850d7f1e5c75 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:41:25 -0700 Subject: [PATCH 81/97] Abs path vsce --- build/vscode-python.proj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/vscode-python.proj b/build/vscode-python.proj index a65c499a34bb..cd9aa5cde7a9 100644 --- a/build/vscode-python.proj +++ b/build/vscode-python.proj @@ -4,9 +4,10 @@ $(OutputPath)\python-$(Branch).vsix + $(UserProfile)\Roaming\npm\vsce - + From 35bf343b3b3d1ded99da55eabfd04d3d98631615 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:43:52 -0700 Subject: [PATCH 82/97] Path --- build/vscode-python.proj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/vscode-python.proj b/build/vscode-python.proj index cd9aa5cde7a9..7ac33da15bb3 100644 --- a/build/vscode-python.proj +++ b/build/vscode-python.proj @@ -4,7 +4,7 @@ $(OutputPath)\python-$(Branch).vsix - $(UserProfile)\Roaming\npm\vsce + $(UserProfile)\AppData\Roaming\npm\vsce From 2ba533eed99d9079ff742af233997c0016394767 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 26 Mar 2018 21:48:19 -0700 Subject: [PATCH 83/97] Move project --- build/run-vsce.cmd | 2 -- build/vscode-python.proj => vscode-python.proj | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 build/run-vsce.cmd rename build/vscode-python.proj => vscode-python.proj (87%) diff --git a/build/run-vsce.cmd b/build/run-vsce.cmd deleted file mode 100644 index e3af675aeb56..000000000000 --- a/build/run-vsce.cmd +++ /dev/null @@ -1,2 +0,0 @@ -cd %1 -vsce package --out %2 diff --git a/build/vscode-python.proj b/vscode-python.proj similarity index 87% rename from build/vscode-python.proj rename to vscode-python.proj index 7ac33da15bb3..f049f40cf794 100644 --- a/build/vscode-python.proj +++ b/vscode-python.proj @@ -7,7 +7,7 @@ $(UserProfile)\AppData\Roaming\npm\vsce - + From 3529ec2b618f4af98b31a5d621926b8b0d12655c Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 09:39:40 -0700 Subject: [PATCH 84/97] Ignore publishing project --- vscode-python.proj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vscode-python.proj b/vscode-python.proj index f049f40cf794..06ea7e34d077 100644 --- a/vscode-python.proj +++ b/vscode-python.proj @@ -6,8 +6,8 @@ $(OutputPath)\python-$(Branch).vsix $(UserProfile)\AppData\Roaming\npm\vsce - - + + From 0e57ab76d90bf433e4554324b238a7db457debfd Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 09:52:05 -0700 Subject: [PATCH 85/97] Try csproj --- .vscodeignore | 1 - vscode-python.proj => vscode-python.csproj | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) rename vscode-python.proj => vscode-python.csproj (93%) diff --git a/.vscodeignore b/.vscodeignore index b762bf75dfc7..b87d3320567b 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -44,4 +44,3 @@ bin/** obj/** BuildOutput/** - diff --git a/vscode-python.proj b/vscode-python.csproj similarity index 93% rename from vscode-python.proj rename to vscode-python.csproj index 06ea7e34d077..53880440ac3c 100644 --- a/vscode-python.proj +++ b/vscode-python.csproj @@ -1,4 +1,4 @@ - + From 1e0df435b4ff71e329cac5fe02dbbb7eec56b70b Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 10:03:32 -0700 Subject: [PATCH 86/97] Add framework --- vscode-python.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vscode-python.csproj b/vscode-python.csproj index 53880440ac3c..3cce8e5d5257 100644 --- a/vscode-python.csproj +++ b/vscode-python.csproj @@ -1,4 +1,7 @@ + + netcoreapp2.0 + From d7a5d7894749a755e54e0b405df1e42f6d3ba997 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 10:31:15 -0700 Subject: [PATCH 87/97] Ignore build output folder --- vscode-python.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vscode-python.csproj b/vscode-python.csproj index 3cce8e5d5257..cddcdb107e6c 100644 --- a/vscode-python.csproj +++ b/vscode-python.csproj @@ -9,7 +9,7 @@ $(OutputPath)\python-$(Branch).vsix $(UserProfile)\AppData\Roaming\npm\vsce - + From 5bd5a716a33fa2c85501f20767531843eb96caa4 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 27 Mar 2018 10:34:45 -0700 Subject: [PATCH 88/97] Package before build --- vscode-python.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vscode-python.csproj b/vscode-python.csproj index cddcdb107e6c..7fb333c4b277 100644 --- a/vscode-python.csproj +++ b/vscode-python.csproj @@ -9,7 +9,7 @@ $(OutputPath)\python-$(Branch).vsix $(UserProfile)\AppData\Roaming\npm\vsce - + From 0414216f8cc8f278e2cf85e5ce03fce01ea954fc Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 27 Mar 2018 22:21:24 -0700 Subject: [PATCH 89/97] More lost changes --- .vscodeignore | 1 + vscode-python.csproj | 20 -------------------- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 vscode-python.csproj diff --git a/.vscodeignore b/.vscodeignore index b87d3320567b..b762bf75dfc7 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -44,3 +44,4 @@ bin/** obj/** BuildOutput/** + diff --git a/vscode-python.csproj b/vscode-python.csproj deleted file mode 100644 index 7fb333c4b277..000000000000 --- a/vscode-python.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - netcoreapp2.0 - - - - - - $(OutputPath)\python-$(Branch).vsix - $(UserProfile)\AppData\Roaming\npm\vsce - - - - - - - VsixSHA2 - - - From 462a19dacfb22d1b3068b29023ad3f75362b4cd9 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 29 Mar 2018 13:03:47 -0700 Subject: [PATCH 90/97] Change setting type to bool --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 50088b24c5ba..81128aa16bb6 100644 --- a/package.json +++ b/package.json @@ -1838,4 +1838,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} \ No newline at end of file +} From 3666dee9beefa29a0f9f1a38df6f89d053f5c148 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 4 Apr 2018 13:31:48 -0700 Subject: [PATCH 91/97] Merge issues --- appveyor.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e40b8f838336..bdf7d4ea2c51 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,9 +56,11 @@ build: off test_script: - yarn run clean - yarn run vscode:prepublish - - yarn run testDebugger --silent - - yarn run testSingleWorkspace --silent - - yarn run testMultiWorkspace --silent - # - yarn run testAnalysisEngine --silent - - + - if [%DEBUGGER_TEST%]==[true] ( + - yarn run testDebugger --silent + yarn run testDebugger --silent) + - yarn run testSingleWorkspace --silent + - if [%SINGLE_WORKSPACE_TEST%]==[true] ( + - yarn run testMultiWorkspace --silent + yarn run testSingleWorkspace --silent) + # - yarn run testAnalysisEngine --silent + - if [%MULTIROOT_WORKSPACE_TEST%]==[true] ( + + yarn run testMultiWorkspace --silent) + + # - if [%ANALYSIS_TEST%]==[true] ( + # yarn run testAnalysisEngine --silent) \ No newline at end of file From 2e2d4e6b23464fa8b0d2f2bace2e760bd1620e03 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 4 Apr 2018 13:33:12 -0700 Subject: [PATCH 92/97] Merge issues --- appveyor.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index bdf7d4ea2c51..8b4185ffface 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,11 +56,11 @@ build: off test_script: - yarn run clean - yarn run vscode:prepublish - - if [%DEBUGGER_TEST%]==[true] ( + - yarn run testDebugger --silent - yarn run testDebugger --silent) + - yarn run testSingleWorkspace --silent - - if [%SINGLE_WORKSPACE_TEST%]==[true] ( + - yarn run testMultiWorkspace --silent - yarn run testSingleWorkspace --silent) + # - yarn run testAnalysisEngine --silent - - if [%MULTIROOT_WORKSPACE_TEST%]==[true] ( + - yarn run testMultiWorkspace --silent) + - # - if [%ANALYSIS_TEST%]==[true] ( + - if [%DEBUGGER_TEST%]==[true] ( + yarn run testDebugger --silent) + - if [%SINGLE_WORKSPACE_TEST%]==[true] ( + yarn run testSingleWorkspace --silent) + - if [%MULTIROOT_WORKSPACE_TEST%]==[true] ( + yarn run testMultiWorkspace --silent) + # - if [%ANALYSIS_TEST%]==[true] ( # yarn run testAnalysisEngine --silent) \ No newline at end of file From 0a0e66ccb8c80cb5ae978d369aeafecbad226e6f Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 4 Apr 2018 13:34:33 -0700 Subject: [PATCH 93/97] Merge issues --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 8b4185ffface..99a980c431cb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -63,4 +63,4 @@ test_script: - if [%MULTIROOT_WORKSPACE_TEST%]==[true] ( yarn run testMultiWorkspace --silent) # - if [%ANALYSIS_TEST%]==[true] ( - # yarn run testAnalysisEngine --silent) \ No newline at end of file + # yarn run testAnalysisEngine --silent) From d972ff343f44a5d5fb18aaff8ea4a1a2a239a8cc Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 4 Apr 2018 14:14:43 -0700 Subject: [PATCH 94/97] Check search paths only if using cache --- src/client/activation/interpreterDataService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/activation/interpreterDataService.ts b/src/client/activation/interpreterDataService.ts index 48e773b2e19a..7cc9eb6a54f2 100644 --- a/src/client/activation/interpreterDataService.ts +++ b/src/client/activation/interpreterDataService.ts @@ -55,11 +55,11 @@ export class InterpreterDataService { if (interpreterChanged || !interpreterData) { interpreterData = await this.getInterpreterDataFromPython(execService, interpreterPath); this.context.globalState.update(interpreterPath, interpreterData); + } else { + // Make sure we verify that search paths did not change. This must be done + // completely async so we don't delay Python language server startup. + this.verifySearchPathsAsync(interpreterData.searchPaths, interpreterPath, execService); } - - // Make sure we verify that search paths did not change. This must be done - // completely async so we don't delay Python language server startup. - this.verifySearchPathsAsync(interpreterData.searchPaths, interpreterPath, execService); return interpreterData; } From ccf8eafd73e52e39cf9568ec14e5da44152fbc0c Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 4 Apr 2018 15:53:26 -0700 Subject: [PATCH 95/97] Undo change --- src/client/common/configuration/service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/common/configuration/service.ts b/src/client/common/configuration/service.ts index 26802e5ab924..fe84a3de9d5c 100644 --- a/src/client/common/configuration/service.ts +++ b/src/client/common/configuration/service.ts @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { injectable } from 'inversify'; import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from 'vscode'; import { PythonSettings } from '../configSettings'; -import { IProcessService } from '../process/types'; import { IConfigurationService, IPythonSettings } from '../types'; +@injectable() export class ConfigurationService implements IConfigurationService { public getSettings(resource?: Uri): IPythonSettings { return PythonSettings.getInstance(resource); From 5a21ea94ba34d7a005667076f3a32edb5becb52e Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 4 Apr 2018 21:18:45 -0700 Subject: [PATCH 96/97] PR feedback --- src/client/activation/analysis.ts | 30 ++++++++++--------- .../activation/interpreterDataService.ts | 7 +++-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index ac281ba7a2db..f2371824fbbf 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -60,16 +60,17 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (!await this.fs.fileExistsAsync(mscorlib)) { // Depends on .NET Runtime or SDK this.languageClient = this.createSimpleLanguageClient(context, clientOptions); - const e = await this.tryStartLanguageClient(context, this.languageClient); - if (!e) { + try { + await this.tryStartLanguageClient(context, this.languageClient); return true; + } catch (ex) { + if (await this.isDotNetInstalled()) { + this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${ex}`); + return false; + } + // No .NET Runtime, no mscorlib - need to download self-contained package. + downloadPackage = true; } - if (await this.isDotNetInstalled()) { - this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${e}`); - return false; - } - // No .NET Runtime, no mscorlib - need to download self-contained package. - downloadPackage = true; } if (downloadPackage) { @@ -80,15 +81,16 @@ export class AnalysisExtensionActivator implements IExtensionActivator { const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineExecutableName()); // Now try to start self-contained app this.languageClient = this.createSelfContainedLanguageClient(context, serverModule, clientOptions); - const error = await this.tryStartLanguageClient(context, this.languageClient); - if (!error) { + try { + await this.tryStartLanguageClient(context, this.languageClient); return true; + } catch (ex) { + this.appShell.showErrorMessage(`Language server failed to start. Error ${ex}`); + return false; } - this.appShell.showErrorMessage(`Language server failed to start. Error ${error}`); - return false; } - private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { + private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { let disposable: Disposable | undefined; try { disposable = lc.start(); @@ -98,7 +100,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } catch (ex) { if (disposable) { disposable.dispose(); - return ex; + throw ex; } } } diff --git a/src/client/activation/interpreterDataService.ts b/src/client/activation/interpreterDataService.ts index 7cc9eb6a54f2..45cf9749e6cf 100644 --- a/src/client/activation/interpreterDataService.ts +++ b/src/client/activation/interpreterDataService.ts @@ -40,7 +40,8 @@ export class InterpreterDataService { return; } - let interpreterData = this.context.globalState.get(interpreterPath) as InterpreterData; + const cacheKey = `InterpreterData-${interpreterPath}`; + let interpreterData = this.context.globalState.get(cacheKey) as InterpreterData; let interpreterChanged = false; if (interpreterData) { // Check if interpreter executable changed @@ -58,7 +59,7 @@ export class InterpreterDataService { } else { // Make sure we verify that search paths did not change. This must be done // completely async so we don't delay Python language server startup. - this.verifySearchPathsAsync(interpreterData.searchPaths, interpreterPath, execService); + this.verifySearchPaths(interpreterData.searchPaths, interpreterPath, execService); } return interpreterData; } @@ -132,7 +133,7 @@ export class InterpreterDataService { return s.length > 0 && s[0] !== '['; } - private verifySearchPathsAsync(currentPaths: string, interpreterPath: string, execService: IPythonExecutionService): void { + private verifySearchPaths(currentPaths: string, interpreterPath: string, execService: IPythonExecutionService): void { this.getSearchPaths(execService) .then(async paths => { if (paths !== currentPaths) { From 03b0bfd829d0c84e61cce23ddbc042f98c2f9c6e Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Thu, 5 Apr 2018 09:46:48 -0700 Subject: [PATCH 97/97] Add async startup to PTVS --- src/client/activation/analysis.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index ac281ba7a2db..c31e2c510357 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -178,7 +178,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { maxDocumentationLineLength: 0, trimDocumentationText: false, maxDocumentationTextLength: 0 - } + }, + asyncStartup: true } }; }