diff --git a/package.json b/package.json index c4be011f6f9f..887c63a47f5b 100644 --- a/package.json +++ b/package.json @@ -1626,13 +1626,13 @@ "vscode-languageserver": "^3.1.0", "winreg": "^1.2.4", "xml2js": "^0.4.17", - "vscode": "^1.0.3" + "vscode": "^1.1.5" }, "devDependencies": { "@types/fs-extra": "^4.0.2", "@types/jquery": "^1.10.31", "@types/lodash": "^4.14.74", - "@types/mocha": "^2.2.32", + "@types/mocha": "^2.2.43", "@types/node": "^6.0.40", "@types/rx": "^2.5.33", "@types/semver": "^5.4.0", @@ -1660,7 +1660,6 @@ "tslint-microsoft-contrib": "^5.0.1", "typescript": "^2.5.2", "typescript-formatter": "^6.0.0", - "vscode": "^1.1.5", "webpack": "^1.13.2" } } diff --git a/src/client/common/contextKey.ts b/src/client/common/contextKey.ts new file mode 100644 index 000000000000..87fe57f07d34 --- /dev/null +++ b/src/client/common/contextKey.ts @@ -0,0 +1,15 @@ +import { commands } from 'vscode'; + +export class ContextKey { + private lastValue: boolean; + + constructor(private name: string) { } + + public async set(value: boolean): Promise { + if (this.lastValue === value) { + return; + } + this.lastValue = value; + await commands.executeCommand('setContext', this.name, this.lastValue); + } +} diff --git a/src/client/common/installer.ts b/src/client/common/installer.ts index 6cdd8a67608f..fb8042454455 100644 --- a/src/client/common/installer.ts +++ b/src/client/common/installer.ts @@ -1,28 +1,29 @@ -import { error } from './logger'; -import * as vscode from 'vscode'; -import * as settings from './configSettings'; import * as os from 'os'; +import * as vscode from 'vscode'; import { commands, ConfigurationTarget, Disposable, OutputChannel, Terminal, Uri, window, workspace } from 'vscode'; +import * as settings from './configSettings'; import { isNotInstalledError } from './helpers'; +import { error } from './logger'; import { execPythonFile, getFullyQualifiedPythonInterpreterPath } from './utils'; export enum Product { - pytest, - nosetest, - pylint, - flake8, - pep8, - pylama, - prospector, - pydocstyle, - yapf, - autopep8, - mypy, - unittest, - ctags, - rope + pytest = 1, + nosetest = 2, + pylint = 3, + flake8 = 4, + pep8 = 5, + pylama = 6, + prospector = 7, + pydocstyle = 8, + yapf = 9, + autopep8 = 10, + mypy = 11, + unittest = 12, + ctags = 13, + rope = 14 } +// tslint:disable-next-line:variable-name const ProductInstallScripts = new Map(); ProductInstallScripts.set(Product.autopep8, ['-m', 'pip', 'install', 'autopep8']); ProductInstallScripts.set(Product.flake8, ['-m', 'pip', 'install', 'flake8']); @@ -37,6 +38,7 @@ ProductInstallScripts.set(Product.pytest, ['-m', 'pip', 'install', '-U', 'pytest ProductInstallScripts.set(Product.yapf, ['-m', 'pip', 'install', 'yapf']); ProductInstallScripts.set(Product.rope, ['-m', 'pip', 'install', 'rope']); +// tslint:disable-next-line:variable-name const ProductUninstallScripts = new Map(); ProductUninstallScripts.set(Product.autopep8, ['-m', 'pip', 'uninstall', 'autopep8', '--yes']); ProductUninstallScripts.set(Product.flake8, ['-m', 'pip', 'uninstall', 'flake8', '--yes']); @@ -51,6 +53,7 @@ ProductUninstallScripts.set(Product.pytest, ['-m', 'pip', 'uninstall', 'pytest', ProductUninstallScripts.set(Product.yapf, ['-m', 'pip', 'uninstall', 'yapf', '--yes']); ProductUninstallScripts.set(Product.rope, ['-m', 'pip', 'uninstall', 'rope', '--yes']); +// tslint:disable-next-line:variable-name export const ProductExecutableAndArgs = new Map(); ProductExecutableAndArgs.set(Product.mypy, { executable: 'python', args: ['-m', 'mypy'] }); ProductExecutableAndArgs.set(Product.nosetest, { executable: 'python', args: ['-m', 'nose'] }); @@ -77,6 +80,7 @@ switch (os.platform()) { } } +// tslint:disable-next-line:variable-name export const Linters: Product[] = [ Product.flake8, Product.pep8, @@ -87,6 +91,7 @@ export const Linters: Product[] = [ Product.pydocstyle ]; +// tslint:disable-next-line:variable-name const ProductNames = new Map(); ProductNames.set(Product.autopep8, 'autopep8'); ProductNames.set(Product.flake8, 'flake8'); @@ -101,6 +106,7 @@ ProductNames.set(Product.pytest, 'py.test'); ProductNames.set(Product.yapf, 'yapf'); ProductNames.set(Product.rope, 'rope'); +// tslint:disable-next-line:variable-name export const SettingToDisableProduct = new Map(); SettingToDisableProduct.set(Product.flake8, 'linting.flake8Enabled'); SettingToDisableProduct.set(Product.mypy, 'linting.mypyEnabled'); @@ -112,6 +118,7 @@ SettingToDisableProduct.set(Product.pydocstyle, 'linting.pydocstyleEnabled'); SettingToDisableProduct.set(Product.pylint, 'linting.pylintEnabled'); SettingToDisableProduct.set(Product.pytest, 'unitTest.pyTestEnabled'); +// tslint:disable-next-line:variable-name const ProductInstallationPrompt = new Map(); ProductInstallationPrompt.set(Product.ctags, 'Install CTags to enable Python workspace symbols'); @@ -123,6 +130,7 @@ enum ProductType { WorkspaceSymbols } +// tslint:disable-next-line:variable-name const ProductTypeNames = new Map(); ProductTypeNames.set(ProductType.Formatter, 'Formatter'); ProductTypeNames.set(ProductType.Linter, 'Linter'); @@ -130,6 +138,7 @@ ProductTypeNames.set(ProductType.RefactoringLibrary, 'Refactoring library'); ProductTypeNames.set(ProductType.TestFramework, 'Test Framework'); ProductTypeNames.set(ProductType.WorkspaceSymbols, 'Workspace Symbols'); +// tslint:disable-next-line:variable-name const ProductTypes = new Map(); ProductTypes.set(Product.flake8, ProductType.Linter); ProductTypes.set(Product.mypy, ProductType.Linter); @@ -165,23 +174,26 @@ export class Installer implements vscode.Disposable { this.disposables.forEach(d => d.dispose()); } private shouldDisplayPrompt(product: Product) { + // tslint:disable-next-line:no-non-null-assertion const productName = ProductNames.get(product)!; const pythonConfig = workspace.getConfiguration('python'); const disablePromptForFeatures = pythonConfig.get('disablePromptForFeatures', [] as string[]); return disablePromptForFeatures.indexOf(productName) === -1; } + // tslint:disable-next-line:member-ordering public async promptToInstall(product: Product, resource?: Uri): Promise { + // tslint:disable-next-line:no-non-null-assertion const productType = ProductTypes.get(product)!; const productTypeName = ProductTypeNames.get(productType); + // tslint:disable-next-line:no-non-null-assertion const productName = ProductNames.get(product)!; if (!this.shouldDisplayPrompt(product)) { const message = `${productTypeName} '${productName}' not installed.`; if (this.outputChannel) { this.outputChannel.appendLine(message); - } - else { + } else { console.warn(message); } return InstallerResponse.Ignore; @@ -229,6 +241,7 @@ export class Installer implements vscode.Disposable { } } } + // tslint:disable-next-line:member-ordering public async install(product: Product, resource?: Uri): Promise { if (!this.outputChannel && !Installer.terminal) { Installer.terminal = window.createTerminal('Python Installer'); diff --git a/src/client/extension.ts b/src/client/extension.ts index dc7c850595e6..bb5cf5252048 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -1,41 +1,41 @@ 'use strict'; +import * as fs from 'fs'; +import * as os from 'os'; +import { workspace } from 'vscode'; import * as vscode from 'vscode'; -import { JediFactory } from './languageServices/jediProxyFactory'; +import * as settings from './common/configSettings'; +import { Commands } from './common/constants'; import { createDeferred } from './common/helpers'; +import * as telemetryHelper from './common/telemetry'; +import * as telemetryContracts from './common/telemetryContracts'; +import { SimpleConfigurationProvider } from './debugger'; +import { HelpProvider } from './helpProvider'; +import { InterpreterManager } from './interpreter'; +import { SetInterpreterProvider } from './interpreter/configuration/setInterpreterProvider'; +import { ShebangCodeLensProvider } from './interpreter/display/shebangCodeLensProvider'; +import * as jup from './jupyter/main'; +import { JupyterProvider } from './jupyter/provider'; +import { JediFactory } from './languageServices/jediProxyFactory'; import { PythonCompletionItemProvider } from './providers/completionProvider'; -import { PythonHoverProvider } from './providers/hoverProvider'; import { PythonDefinitionProvider } from './providers/definitionProvider'; -import { PythonReferenceProvider } from './providers/referenceProvider'; -import { PythonRenameProvider } from './providers/renameProvider'; +import { activateExecInTerminalProvider } from './providers/execInTerminalProvider'; +import { activateFormatOnSaveProvider } from './providers/formatOnSaveProvider'; import { PythonFormattingEditProvider } from './providers/formatProvider'; -import { ShebangCodeLensProvider } from './providers/shebangCodeLensProvider' -import * as sortImports from './sortImports'; +import { PythonHoverProvider } from './providers/hoverProvider'; import { LintProvider } from './providers/lintProvider'; -import { PythonSymbolProvider } from './providers/symbolProvider'; +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 * as settings from './common/configSettings'; -import * as telemetryHelper from './common/telemetry'; -import * as telemetryContracts from './common/telemetryContracts'; import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider'; -import { SetInterpreterProvider } from './providers/setInterpreterProvider'; -import { activateExecInTerminalProvider } from './providers/execInTerminalProvider'; -import { Commands } from './common/constants'; -import * as tests from './unittests/main'; -import * as jup from './jupyter/main'; -import { HelpProvider } from './helpProvider'; +import { PythonSymbolProvider } from './providers/symbolProvider'; import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider'; -import { activateFormatOnSaveProvider } from './providers/formatOnSaveProvider'; -import { WorkspaceSymbols } from './workspaceSymbols/main'; +import * as sortImports from './sortImports'; import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; -import * as os from 'os'; -import * as fs from 'fs'; -import { JupyterProvider } from './jupyter/provider'; -import { activateGoToObjectDefinitionProvider } from './providers/objectDefinitionProvider'; -import { InterpreterManager } from './interpreter'; -import { SimpleConfigurationProvider } from './debugger'; -import { ReplProvider } from './providers/replProvider'; -import { workspace } from 'vscode'; +import * as tests from './unittests/main'; +import { WorkspaceSymbols } from './workspaceSymbols/main'; const PYTHON: vscode.DocumentFilter = { language: 'python' }; let unitTestOutChannel: vscode.OutputChannel; @@ -44,10 +44,9 @@ let lintingOutChannel: vscode.OutputChannel; let jupMain: jup.Jupyter; const activationDeferred = createDeferred(); export const activated = activationDeferred.promise; +// tslint:disable-next-line:max-func-body-length export async function activate(context: vscode.ExtensionContext) { const pythonSettings = settings.PythonSettings.getInstance(); - const pythonExt = new PythonExt(); - context.subscriptions.push(pythonExt); sendStartupTelemetry(); lintingOutChannel = vscode.window.createOutputChannel(pythonSettings.linting.outputWindow); formatOutChannel = lintingOutChannel; @@ -85,11 +84,11 @@ export async function activate(context: vscode.ExtensionContext) { { beforeText: /^ *#.*$/, afterText: /.+$/, - action: { indentAction: vscode.IndentAction.None, appendText: '# ' }, + action: { indentAction: vscode.IndentAction.None, appendText: '# ' } }, { beforeText: /^\s+(continue|break|return)\b.*$/, - action: { indentAction: vscode.IndentAction.Outdent }, + action: { indentAction: vscode.IndentAction.Outdent } } ] }); @@ -101,7 +100,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.languages.registerHoverProvider(PYTHON, new PythonHoverProvider(jediFactory))); context.subscriptions.push(vscode.languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(jediFactory))); context.subscriptions.push(vscode.languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(jediFactory), '.')); - context.subscriptions.push(vscode.languages.registerCodeLensProvider(PYTHON, new ShebangCodeLensProvider())) + context.subscriptions.push(vscode.languages.registerCodeLensProvider(PYTHON, new ShebangCodeLensProvider())); const symbolProvider = new PythonSymbolProvider(jediFactory); context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(PYTHON, symbolProvider)); @@ -115,7 +114,7 @@ export async function activate(context: vscode.ExtensionContext) { } const jupyterExtInstalled = vscode.extensions.getExtension('donjayamanne.jupyter'); - let linterProvider = new LintProvider(context, lintingOutChannel, (a, b) => Promise.resolve(false)); + const linterProvider = new LintProvider(context, lintingOutChannel, (a, b) => Promise.resolve(false)); context.subscriptions.push(); if (jupyterExtInstalled) { if (jupyterExtInstalled.isActive) { @@ -127,8 +126,7 @@ export async function activate(context: vscode.ExtensionContext) { jupyterExtInstalled.exports.registerLanguageProvider(PYTHON.language, new JupyterProvider()); linterProvider.documentHasJupyterCodeCells = jupyterExtInstalled.exports.hasCodeCells; }); - } - else { + } else { jupMain = new jup.Jupyter(lintingOutChannel); const documentHasJupyterCodeCells = jupMain.hasCodeCells.bind(jupMain); jupMain.activate(); @@ -151,43 +149,6 @@ export async function activate(context: vscode.ExtensionContext) { activationDeferred.resolve(); } -class PythonExt implements vscode.Disposable { - - private isDjangoProject: ContextKey; - - constructor() { - this.isDjangoProject = new ContextKey('python.isDjangoProject'); - this.ensureState(); - } - public dispose() { - this.isDjangoProject = null; - } - private ensureState(): void { - // context: python.isDjangoProject - if (typeof vscode.workspace.rootPath === 'string') { - this.isDjangoProject.set(fs.existsSync(vscode.workspace.rootPath.concat("/manage.py"))); - } - else { - this.isDjangoProject.set(false); - } - } -} - -class ContextKey { - private lastValue: boolean; - - constructor(private name: string) { - } - - public set(value: boolean): void { - if (this.lastValue === value) { - return; - } - this.lastValue = value; - vscode.commands.executeCommand('setContext', this.name, this.lastValue); - } -} - function sendStartupTelemetry() { telemetryHelper.sendTelemetryEvent(telemetryContracts.EVENT_LOAD); } diff --git a/src/client/interpreter/configuration/pythonPathUpdaterService.ts b/src/client/interpreter/configuration/pythonPathUpdaterService.ts new file mode 100644 index 000000000000..b81963f9d8fe --- /dev/null +++ b/src/client/interpreter/configuration/pythonPathUpdaterService.ts @@ -0,0 +1,41 @@ +import * as path from 'path'; +import { ConfigurationTarget, Uri, window } from 'vscode'; +import { WorkspacePythonPath } from '../contracts'; +import { IPythonPathUpdaterService, IPythonPathUpdaterServiceFactory } from './types'; + +export class PythonPathUpdaterService { + constructor(private pythonPathSettingsUpdaterFactory: IPythonPathUpdaterServiceFactory) { } + public async updatePythonPath(pythonPath: string, configTarget: ConfigurationTarget, wkspace?: Uri): Promise { + const pythonPathUpdater = this.getPythonUpdaterService(configTarget, wkspace); + + try { + await pythonPathUpdater.updatePythonPath(path.normalize(pythonPath)); + } catch (reason) { + // tslint:disable-next-line:no-unsafe-any prefer-type-cast + const message = reason && typeof reason.message === 'string' ? reason.message as string : ''; + window.showErrorMessage(`Failed to set 'pythonPath'. Error: ${message}`); + console.error(reason); + } + } + private getPythonUpdaterService(configTarget: ConfigurationTarget, wkspace?: Uri) { + switch (configTarget) { + case ConfigurationTarget.Global: { + return this.pythonPathSettingsUpdaterFactory.getGlobalPythonPathConfigurationService(); + } + case ConfigurationTarget.Workspace: { + if (!wkspace) { + throw new Error('Workspace Uri not defined'); + } + // tslint:disable-next-line:no-non-null-assertion + return this.pythonPathSettingsUpdaterFactory.getWorkspacePythonPathConfigurationService(wkspace!); + } + default: { + if (!wkspace) { + throw new Error('Workspace Uri not defined'); + } + // tslint:disable-next-line:no-non-null-assertion + return this.pythonPathSettingsUpdaterFactory.getWorkspaceFolderPythonPathConfigurationService(wkspace!); + } + } + } +} diff --git a/src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts b/src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts new file mode 100644 index 000000000000..a46c18275d35 --- /dev/null +++ b/src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts @@ -0,0 +1,18 @@ +import { Uri } from 'vscode'; +import { InterpreterManager } from '../index'; +import { GlobalPythonPathUpdaterService } from './services/globalUpdaterService'; +import { WorkspaceFolderPythonPathUpdaterService } from './services/workspaceFolderUpdaterService'; +import { WorkspacePythonPathUpdaterService } from './services/workspaceUpdaterService'; +import { IPythonPathUpdaterService, IPythonPathUpdaterServiceFactory } from './types'; + +export class PythonPathUpdaterServiceFactory implements IPythonPathUpdaterServiceFactory { + public getGlobalPythonPathConfigurationService(): IPythonPathUpdaterService { + return new GlobalPythonPathUpdaterService(); + } + public getWorkspacePythonPathConfigurationService(wkspace: Uri): IPythonPathUpdaterService { + return new WorkspacePythonPathUpdaterService(wkspace); + } + public getWorkspaceFolderPythonPathConfigurationService(workspaceFolder: Uri): IPythonPathUpdaterService { + return new WorkspaceFolderPythonPathUpdaterService(workspaceFolder); + } +} diff --git a/src/client/interpreter/configuration/services/globalUpdaterService.ts b/src/client/interpreter/configuration/services/globalUpdaterService.ts new file mode 100644 index 000000000000..4b775adf0b71 --- /dev/null +++ b/src/client/interpreter/configuration/services/globalUpdaterService.ts @@ -0,0 +1,16 @@ +import { ConfigurationTarget, Uri, workspace } from 'vscode'; +import { InterpreterManager } from '../..'; +import { WorkspacePythonPath } from '../../contracts'; +import { IPythonPathUpdaterService } from '../types'; + +export class GlobalPythonPathUpdaterService implements IPythonPathUpdaterService { + public async updatePythonPath(pythonPath: string): Promise { + const pythonConfig = workspace.getConfiguration('python'); + const pythonPathValue = pythonConfig.inspect('pythonPath'); + + if (pythonPathValue && pythonPathValue.globalValue === pythonPath) { + return; + } + await pythonConfig.update('pythonPath', pythonPath, true); + } +} diff --git a/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts b/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts new file mode 100644 index 000000000000..d39f9a831f93 --- /dev/null +++ b/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts @@ -0,0 +1,23 @@ +import * as path from 'path'; +import { ConfigurationTarget, Uri, workspace } from 'vscode'; +import { InterpreterManager } from '../..'; +import { WorkspacePythonPath } from '../../contracts'; +import { IPythonPathUpdaterService } from '../types'; + +export class WorkspaceFolderPythonPathUpdaterService implements IPythonPathUpdaterService { + constructor(private workspaceFolder: Uri) { + } + public async updatePythonPath(pythonPath: string): Promise { + const pythonConfig = workspace.getConfiguration('python', this.workspaceFolder); + const pythonPathValue = pythonConfig.inspect('pythonPath'); + + if (pythonPathValue && pythonPathValue.workspaceFolderValue === pythonPath) { + return; + } + if (pythonPath.startsWith(this.workspaceFolder.fsPath)) { + // tslint:disable-next-line:no-invalid-template-strings + pythonPath = path.join('${workspaceRoot}', path.relative(this.workspaceFolder.fsPath, pythonPath)); + } + await pythonConfig.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder); + } +} diff --git a/src/client/interpreter/configuration/services/workspaceUpdaterService.ts b/src/client/interpreter/configuration/services/workspaceUpdaterService.ts new file mode 100644 index 000000000000..b0da1b168345 --- /dev/null +++ b/src/client/interpreter/configuration/services/workspaceUpdaterService.ts @@ -0,0 +1,23 @@ +import * as path from 'path'; +import { ConfigurationTarget, Uri, workspace } from 'vscode'; +import { InterpreterManager } from '../..'; +import { WorkspacePythonPath } from '../../contracts'; +import { IPythonPathUpdaterService } from '../types'; + +export class WorkspacePythonPathUpdaterService implements IPythonPathUpdaterService { + constructor(private wkspace: Uri) { + } + public async updatePythonPath(pythonPath: string): Promise { + const pythonConfig = workspace.getConfiguration('python', this.wkspace); + const pythonPathValue = pythonConfig.inspect('pythonPath'); + + if (pythonPathValue && pythonPathValue.workspaceValue === pythonPath) { + return; + } + if (pythonPath.startsWith(this.wkspace.fsPath)) { + // tslint:disable-next-line:no-invalid-template-strings + pythonPath = path.join('${workspaceRoot}', path.relative(this.wkspace.fsPath, pythonPath)); + } + await pythonConfig.update('pythonPath', pythonPath, false); + } +} diff --git a/src/client/providers/setInterpreterProvider.ts b/src/client/interpreter/configuration/setInterpreterProvider.ts similarity index 53% rename from src/client/providers/setInterpreterProvider.ts rename to src/client/interpreter/configuration/setInterpreterProvider.ts index 47cdc95de966..b155d7caa049 100644 --- a/src/client/providers/setInterpreterProvider.ts +++ b/src/client/interpreter/configuration/setInterpreterProvider.ts @@ -1,11 +1,13 @@ 'use strict'; import * as path from 'path'; import { commands, ConfigurationTarget, Disposable, QuickPickItem, QuickPickOptions, Uri, window, workspace } from 'vscode'; -import { InterpreterManager } from '../interpreter'; -import { PythonInterpreter, WorkspacePythonPath } from '../interpreter/contracts'; -import { getInterpretersForEachFolderAndWorkspace } from '../interpreter/helpers'; -import * as settings from './../common/configSettings'; -import { ShebangCodeLensProvider } from './shebangCodeLensProvider'; +import { InterpreterManager } from '../'; +import * as settings from '../../common/configSettings'; +import { PythonInterpreter, WorkspacePythonPath } from '../contracts'; +import { ShebangCodeLensProvider } from '../display/shebangCodeLensProvider'; +import { PythonPathUpdaterService } from './pythonPathUpdaterService'; +import { PythonPathUpdaterServiceFactory } from './pythonPathUpdaterServiceFactory'; +import { IPythonPathUpdaterServiceFactory } from './types'; // tslint:disable-next-line:interface-name interface PythonPathQuickPickItem extends QuickPickItem { @@ -14,27 +16,22 @@ interface PythonPathQuickPickItem extends QuickPickItem { export class SetInterpreterProvider implements Disposable { private disposables: Disposable[] = []; + private pythonPathUpdaterService: PythonPathUpdaterService; constructor(private interpreterManager: InterpreterManager) { this.disposables.push(commands.registerCommand('python.setInterpreter', this.setInterpreter.bind(this))); this.disposables.push(commands.registerCommand('python.setShebangInterpreter', this.setShebangInterpreter.bind(this))); + this.pythonPathUpdaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory()); } public dispose() { this.disposables.forEach(disposable => disposable.dispose()); } - private async getWorkspacePythonPath(): Promise { + private async getWorkspaceToSetPythonPath(): Promise { if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { return undefined; } if (workspace.workspaceFolders.length === 1) { return { folderUri: workspace.workspaceFolders[0].uri, configTarget: ConfigurationTarget.Workspace }; } - // We could have each workspace folder with different python paths. - // Or, we could the workspace with a pythonPath and one of the workspace folders with different python paths. - // Lets just find how many different setups we have. - const configs = getInterpretersForEachFolderAndWorkspace(); - if (configs.length === 1) { - return configs[0]; - } // Ok we have multiple interpreters, get the user to pick a folder. // tslint:disable-next-line:no-any prefer-type-cast @@ -54,63 +51,64 @@ export class SetInterpreterProvider implements Disposable { path: suggestion.path }; } - private async presentQuickPick() { - const targetConfig = await this.getWorkspacePythonPath(); - const resourceUri = targetConfig ? targetConfig.folderUri : undefined; - const suggestions = await this.getSuggestions(resourceUri); + + private async getSuggestions(resourceUri?: Uri) { + const interpreters = await this.interpreterManager.getInterpreters(resourceUri); + // tslint:disable-next-line:no-non-null-assertion + interpreters.sort((a, b) => a.displayName! > b.displayName! ? 1 : -1); + return Promise.all(interpreters.map(item => this.suggestionToQuickPickItem(item, resourceUri))); + } + + private async setInterpreter() { + const setInterpreterGlobally = !Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0; + let configTarget = ConfigurationTarget.Global; + let wkspace: Uri; + if (!setInterpreterGlobally) { + const targetConfig = await this.getWorkspaceToSetPythonPath(); + if (!targetConfig) { + return; + } + configTarget = targetConfig.configTarget; + wkspace = targetConfig.folderUri; + } + + const suggestions = await this.getSuggestions(wkspace); let currentPythonPath = settings.PythonSettings.getInstance().pythonPath; - if (workspace.rootPath && currentPythonPath.startsWith(workspace.rootPath)) { - currentPythonPath = `.${path.sep}${path.relative(workspace.rootPath, currentPythonPath)}`; + if (wkspace && currentPythonPath.startsWith(wkspace.fsPath)) { + currentPythonPath = `.${path.sep}${path.relative(wkspace.fsPath, currentPythonPath)}`; } const quickPickOptions: QuickPickOptions = { matchOnDetail: true, matchOnDescription: true, placeHolder: `current: ${currentPythonPath}` }; + const selection = await window.showQuickPick(suggestions, quickPickOptions); if (selection !== undefined) { - this.interpreterManager.setPythonPath(selection.path, targetConfig); + await this.pythonPathUpdaterService.updatePythonPath(selection.path, configTarget, wkspace); } } - private async getSuggestions(resourceUri?: Uri) { - const interpreters = await this.interpreterManager.getInterpreters(resourceUri); - // tslint:disable-next-line:no-non-null-assertion - interpreters.sort((a, b) => a.displayName! > b.displayName! ? 1 : -1); - return Promise.all(interpreters.map(item => this.suggestionToQuickPickItem(item, resourceUri))); - } - - private setInterpreter() { - this.presentQuickPick(); - } - private async setShebangInterpreter(): Promise { const shebang = await ShebangCodeLensProvider.detectShebang(window.activeTextEditor.document); if (!shebang) { return; } - const existingConfigs = getInterpretersForEachFolderAndWorkspace(); - const hasFoldersWithPythonPathSet = existingConfigs.filter(item => item.configTarget === ConfigurationTarget.WorkspaceFolder).length > 0; - + const isGlobalChange = !Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0; const workspaceFolder = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri); - // tslint:disable-next-line:no-backbone-get-set-outside-model - const value = workspace.getConfiguration('python', window.activeTextEditor.document.uri).inspect('pythonPath'); - const currentValueSetInWorkspaceFolder = value && typeof value.workspaceFolderValue === 'string'; + const isWorkspaceChange = Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length === 1; - const setPythonPathInSpecificFolder = hasFoldersWithPythonPathSet || currentValueSetInWorkspaceFolder; - if (setPythonPathInSpecificFolder) { - const configTarget = workspaceFolder ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; - const workspaceTarget: WorkspacePythonPath = { folderUri: workspaceFolder.uri, configTarget: configTarget }; - return this.interpreterManager.setPythonPath(shebang, workspaceTarget); + if (isGlobalChange) { + await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Global); + return; } - const setPythonPathInRootWorkspace = Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0; - if (setPythonPathInRootWorkspace) { - const configTarget: WorkspacePythonPath = { folderUri: workspace.workspaceFolders[0].uri, configTarget: ConfigurationTarget.Workspace }; - return this.interpreterManager.setPythonPath(shebang, configTarget); + if (isWorkspaceChange || !workspaceFolder) { + await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Workspace, workspace.workspaceFolders[0].uri); + return; } - return this.interpreterManager.setPythonPath(shebang); + await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.WorkspaceFolder, workspaceFolder.uri); } } diff --git a/src/client/interpreter/configuration/types.ts b/src/client/interpreter/configuration/types.ts new file mode 100644 index 000000000000..05825416f3bc --- /dev/null +++ b/src/client/interpreter/configuration/types.ts @@ -0,0 +1,12 @@ +import { Uri } from 'vscode'; +import { IPythonPathUpdaterService } from './types'; + +export interface IPythonPathUpdaterService { + updatePythonPath(pythonPath: string): Promise; +} + +export interface IPythonPathUpdaterServiceFactory { + getGlobalPythonPathConfigurationService(): IPythonPathUpdaterService; + getWorkspacePythonPathConfigurationService(wkspace: Uri): IPythonPathUpdaterService; + getWorkspaceFolderPythonPathConfigurationService(workspaceFolder: Uri): IPythonPathUpdaterService; +} diff --git a/src/client/providers/shebangCodeLensProvider.ts b/src/client/interpreter/display/shebangCodeLensProvider.ts similarity index 79% rename from src/client/providers/shebangCodeLensProvider.ts rename to src/client/interpreter/display/shebangCodeLensProvider.ts index 8988d6161644..a45f4a4db431 100644 --- a/src/client/providers/shebangCodeLensProvider.ts +++ b/src/client/interpreter/display/shebangCodeLensProvider.ts @@ -1,40 +1,16 @@ -"use strict"; -import { IS_WINDOWS } from '../common/utils'; -import * as vscode from 'vscode'; +'use strict'; import * as child_process from 'child_process'; -import * as settings from '../common/configSettings'; -import { TextDocument, CodeLens, CancellationToken } from 'vscode'; -import { getFirstNonEmptyLineFromMultilineString } from '../interpreter/helpers'; -export class ShebangCodeLensProvider implements vscode.CodeLensProvider { - onDidChangeCodeLenses: vscode.Event = vscode.workspace.onDidChangeConfiguration; - - public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise { - const codeLenses = await this.createShebangCodeLens(document); - return Promise.resolve(codeLenses); - } - - private async createShebangCodeLens(document: TextDocument) { - const shebang = await ShebangCodeLensProvider.detectShebang(document); - if (!shebang || shebang === settings.PythonSettings.getInstance(document.uri).pythonPath) { - return []; - } - - const firstLine = document.lineAt(0); - const startOfShebang = new vscode.Position(0, 0); - const endOfShebang = new vscode.Position(0, firstLine.text.length - 1); - const shebangRange = new vscode.Range(startOfShebang, endOfShebang); - - const cmd: vscode.Command = { - command: 'python.setShebangInterpreter', - title: 'Set as interpreter' - }; - - const codeLenses = [(new CodeLens(shebangRange, cmd))]; - return codeLenses; - } +import * as vscode from 'vscode'; +import { CancellationToken, CodeLens, TextDocument } from 'vscode'; +import * as settings from '../../common/configSettings'; +import { IS_WINDOWS } from '../../common/utils'; +import { getFirstNonEmptyLineFromMultilineString } from '../../interpreter/helpers'; +export class ShebangCodeLensProvider implements vscode.CodeLensProvider { + public onDidChangeCodeLenses: vscode.Event = vscode.workspace.onDidChangeConfiguration; + // tslint:disable-next-line:function-name public static async detectShebang(document: TextDocument): Promise { - let firstLine = document.lineAt(0); + const firstLine = document.lineAt(0); if (firstLine.isEmptyOrWhitespace) { return; } @@ -49,7 +25,7 @@ export class ShebangCodeLensProvider implements vscode.CodeLensProvider { } private static async getFullyQualifiedPathToInterpreter(pythonPath: string) { if (pythonPath.indexOf('bin/env ') >= 0 && !IS_WINDOWS) { - // In case we have pythonPath as '/usr/bin/env python' + // In case we have pythonPath as '/usr/bin/env python' return new Promise(resolve => { const command = child_process.exec(`${pythonPath} -c 'import sys;print(sys.executable)'`); let result = ''; @@ -60,13 +36,36 @@ export class ShebangCodeLensProvider implements vscode.CodeLensProvider { resolve(getFirstNonEmptyLineFromMultilineString(result)); }); }); - } - else { + } else { return new Promise(resolve => { - child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => { + child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); }); } } + + public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise { + const codeLenses = await this.createShebangCodeLens(document); + return Promise.resolve(codeLenses); + } + + private async createShebangCodeLens(document: TextDocument) { + const shebang = await ShebangCodeLensProvider.detectShebang(document); + if (!shebang || shebang === settings.PythonSettings.getInstance(document.uri).pythonPath) { + return []; + } + + const firstLine = document.lineAt(0); + const startOfShebang = new vscode.Position(0, 0); + const endOfShebang = new vscode.Position(0, firstLine.text.length - 1); + const shebangRange = new vscode.Range(startOfShebang, endOfShebang); + + const cmd: vscode.Command = { + command: 'python.setShebangInterpreter', + title: 'Set as interpreter' + }; + + return [(new CodeLens(shebangRange, cmd))]; + } } diff --git a/src/client/interpreter/helpers.ts b/src/client/interpreter/helpers.ts index f1371ca40922..dc8308344d2f 100644 --- a/src/client/interpreter/helpers.ts +++ b/src/client/interpreter/helpers.ts @@ -23,50 +23,3 @@ export function getActiveWorkspaceUri(): WorkspacePythonPath | undefined { } return undefined; } - -export function getInterpretersForEachFolderAndWorkspace(): WorkspacePythonPath[] { - if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { - return []; - } - const value = workspace.getConfiguration('python').inspect('pythonPath'); - const workspacePythonPath = value && typeof value.workspaceValue === 'string' ? value.workspaceValue : undefined; - - if (workspace.workspaceFolders.length === 1) { - if (workspacePythonPath) { - return [{ - folderUri: workspace.workspaceFolders[0].uri, - pytonPath: workspacePythonPath, - configTarget: ConfigurationTarget.Workspace - }]; - } - else { - return []; - } - } - - const workspaceConfig: WorkspacePythonPath[] = workspacePythonPath ? [{ - folderUri: workspace.workspaceFolders[0].uri, - pytonPath: workspacePythonPath, - configTarget: ConfigurationTarget.Workspace - }] : []; - - return workspace.workspaceFolders.reduce((accumulator, folder) => { - // tslint:disable-next-line:no-backbone-get-set-outside-model - const folderValue = workspace.getConfiguration('python', folder.uri).inspect('pythonPath'); - - if (folderValue && typeof folderValue.workspaceFolderValue === 'string' && - folderValue.workspaceFolderValue !== workspacePythonPath && - accumulator.findIndex(item => item.pytonPath === folderValue.workspaceFolderValue) === -1) { - - const info: WorkspacePythonPath = { - folderUri: folder.uri, - pytonPath: folderValue.workspaceFolderValue, - configTarget: ConfigurationTarget.WorkspaceFolder - }; - - accumulator.push(info); - } - - return accumulator; - }, workspaceConfig); -} diff --git a/src/client/interpreter/index.ts b/src/client/interpreter/index.ts index e6ebea67f7a9..a90c2592fcb0 100644 --- a/src/client/interpreter/index.ts +++ b/src/client/interpreter/index.ts @@ -3,6 +3,8 @@ import * as path from 'path'; import { ConfigurationTarget, Disposable, StatusBarAlignment, Uri, window, workspace } from 'vscode'; import { PythonSettings } from '../common/configSettings'; import { IS_WINDOWS } from '../common/utils'; +import { PythonPathUpdaterService } from './configuration/pythonPathUpdaterService'; +import { PythonPathUpdaterServiceFactory } from './configuration/pythonPathUpdaterServiceFactory'; import { WorkspacePythonPath } from './contracts'; import { InterpreterDisplay } from './display'; import { getActiveWorkspaceUri } from './helpers'; @@ -16,12 +18,14 @@ export class InterpreterManager implements Disposable { private disposables: Disposable[] = []; private display: InterpreterDisplay | null | undefined; private interpreterProvider: PythonInterpreterLocatorService; + private pythonPathUpdaterService: PythonPathUpdaterService; constructor() { const virtualEnvMgr = new VirtualEnvironmentManager([new VEnv(), new VirtualEnv()]); const statusBar = window.createStatusBarItem(StatusBarAlignment.Left); this.interpreterProvider = new PythonInterpreterLocatorService(virtualEnvMgr); const versionService = new InterpreterVersionService(); this.display = new InterpreterDisplay(statusBar, this.interpreterProvider, virtualEnvMgr, versionService); + this.pythonPathUpdaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory()); PythonSettings.getInstance().addListener('change', () => this.onConfigChanged()); this.disposables.push(window.onDidChangeActiveTextEditor(() => this.refresh())); this.disposables.push(statusBar); @@ -54,33 +58,7 @@ export class InterpreterManager implements Disposable { const pythonPath = interpretersInWorkspace[0].path; const relativePath = path.dirname(pythonPath).substring(activeWorkspace.folderUri.fsPath.length); if (relativePath.split(path.sep).filter(l => l.length > 0).length === 2) { - await this.setPythonPath(pythonPath, activeWorkspace); - } - } - - /** - * Sets the python path in the settings. - * @param {string} pythonPath - * @param {WorkspacePythonPath} [workspacePythonPath] If this is not passed, then user setting will be updated - * @returns {Promise} - * @memberof InterpreterManager - */ - public async setPythonPath(pythonPath: string, workspacePythonPath?: WorkspacePythonPath): Promise { - pythonPath = IS_WINDOWS ? pythonPath.replace(/\\/g, '/') : pythonPath; - const isMultiRootWorkspace = Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 1; - try { - if (!workspacePythonPath) { - return await this.setPythonPathInUserSettings(pythonPath); - } - if (!isMultiRootWorkspace) { - return await this.setPythonPathInSingleWorkspace(pythonPath); - } - await this.setPythonPathInWorkspace(pythonPath, workspacePythonPath.configTarget, workspacePythonPath.folderUri); - } catch (reason) { - // tslint:disable-next-line:no-unsafe-any prefer-type-cast - const message = reason && typeof reason.message === 'string' ? reason.message as string : ''; - window.showErrorMessage(`Failed to set 'pythonPath'. Error: ${message}`); - console.error(reason); + await this.pythonPathUpdaterService.updatePythonPath(pythonPath, activeWorkspace.configTarget, activeWorkspace.folderUri); } } public dispose(): void { @@ -89,35 +67,13 @@ export class InterpreterManager implements Disposable { this.display = null; this.interpreterProvider.dispose(); } - private async setPythonPathInUserSettings(pythonPath) { - const pythonConfig = workspace.getConfiguration('python'); - return pythonConfig.update('pythonPath', pythonPath, true); - } - private async setPythonPathInSingleWorkspace(pythonPath: string) { - const pythonConfig = workspace.getConfiguration('python'); - // tslint:disable-next-line:no-non-null-assertion - const workspacePath = workspace.workspaceFolders![0].uri.fsPath; - if (pythonPath.toUpperCase().startsWith(workspacePath.toUpperCase())) { - // tslint:disable-next-line:no-invalid-template-strings - pythonPath = path.join('${workspaceRoot}', path.relative(workspacePath, pythonPath)); - } - return pythonConfig.update('pythonPath', pythonPath, false); - } - private async setPythonPathInWorkspace(pythonPath, configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder, resource?: Uri) { - const pythonConfig = workspace.getConfiguration('python', resource); - if (configTarget === ConfigurationTarget.WorkspaceFolder && resource && pythonPath.toUpperCase().startsWith(resource.fsPath.toUpperCase())) { - // tslint:disable-next-line:no-invalid-template-strings - pythonPath = path.join('${workspaceRoot}', path.relative(resource.fsPath, pythonPath)); - } - return pythonConfig.update('pythonPath', pythonPath, configTarget); - } private shouldAutoSetInterpreter() { const activeWorkspace = getActiveWorkspaceUri(); if (!activeWorkspace) { return false; } const pythonConfig = workspace.getConfiguration('python'); - const pythonPathInConfig = pythonConfig.get('pythonPath', 'python'); + const pythonPathInConfig = pythonConfig.get('pythonPath', 'python'); return path.basename(pythonPathInConfig) === pythonPathInConfig; } private onConfigChanged() { diff --git a/src/client/interpreter/locators/services/virtualEnvService.ts b/src/client/interpreter/locators/services/virtualEnvService.ts index a10818f1c3ec..d7430dad07ad 100644 --- a/src/client/interpreter/locators/services/virtualEnvService.ts +++ b/src/client/interpreter/locators/services/virtualEnvService.ts @@ -74,8 +74,8 @@ export function getKnownSearchPathsForVirtualEnvs(resource?: Uri): string[] { if (venvPath) { paths.push(untildify(venvPath)); } - if (workspace.rootPath) { - paths.push(workspace.rootPath); + if (Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length === 0) { + paths.push(workspace.workspaceFolders[0].uri.fsPath); } return paths; } diff --git a/src/client/providers/execInTerminalProvider.ts b/src/client/providers/execInTerminalProvider.ts index 15a2277f4525..c25d784dc5d2 100644 --- a/src/client/providers/execInTerminalProvider.ts +++ b/src/client/providers/execInTerminalProvider.ts @@ -1,12 +1,15 @@ 'use strict'; +import * as fs from 'fs-extra'; +import { EOL } from 'os'; +import * as path from 'path'; import * as vscode from 'vscode'; +import { Disposable, workspace } from 'vscode'; import * as settings from '../common/configSettings'; import { Commands, PythonLanguage } from '../common/constants'; -import { EOL } from 'os'; -let path = require('path'); -let terminal: vscode.Terminal; +import { ContextKey } from '../common/contextKey'; import { IS_WINDOWS } from '../common/utils'; +let terminal: vscode.Terminal; export function activateExecInTerminalProvider(): vscode.Disposable[] { const disposables: vscode.Disposable[] = []; disposables.push(vscode.commands.registerCommand(Commands.Exec_In_Terminal, execInTerminal)); @@ -17,13 +20,14 @@ export function activateExecInTerminalProvider(): vscode.Disposable[] { terminal = null; } })); + disposables.push(new DjangoContextInitializer()); return disposables; } function removeBlankLines(code: string): string { - let codeLines = code.split(/\r?\n/g); - let codeLinesWithoutEmptyLines = codeLines.filter(line => line.trim().length > 0); - let lastLineIsEmpty = codeLines.length > 0 && codeLines[codeLines.length - 1].trim().length === 0; + const codeLines = code.split(/\r?\n/g); + const codeLinesWithoutEmptyLines = codeLines.filter(line => line.trim().length > 0); + const lastLineIsEmpty = codeLines.length > 0 && codeLines[codeLines.length - 1].trim().length === 0; if (lastLineIsEmpty) { codeLinesWithoutEmptyLines.unshift(''); } @@ -31,9 +35,10 @@ function removeBlankLines(code: string): string { } function execInTerminal(fileUri?: vscode.Uri) { const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell'); + // tslint:disable-next-line:no-backbone-get-set-outside-model const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get('windows')); - let pythonSettings = settings.PythonSettings.getInstance(fileUri); + const pythonSettings = settings.PythonSettings.getInstance(fileUri); let filePath: string; let currentPythonPath = pythonSettings.pythonPath; @@ -67,26 +72,25 @@ function execInTerminal(fileUri?: vscode.Uri) { filePath = `"${filePath}"`; } - terminal = terminal ? terminal : vscode.window.createTerminal(`Python`); + terminal = terminal ? terminal : vscode.window.createTerminal('Python'); if (pythonSettings.terminal && pythonSettings.terminal.executeInFileDir) { const fileDirPath = path.dirname(filePath); - if (fileDirPath !== vscode.workspace.rootPath && fileDirPath.substring(1) !== vscode.workspace.rootPath) { + const wkspace = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath)); + if (wkspace && fileDirPath !== wkspace.uri.fsPath && fileDirPath.substring(1) !== wkspace.uri.fsPath) { terminal.sendText(`cd "${fileDirPath}"`); } } const launchArgs = settings.PythonSettings.getInstance(fileUri).terminal.launchArgs; - const launchArgsString = launchArgs.length > 0 ? " ".concat(launchArgs.join(" ")) : ""; + const launchArgsString = launchArgs.length > 0 ? ' '.concat(launchArgs.join(' ')) : ''; const command = `${currentPythonPath}${launchArgsString} ${filePath}`; if (IS_WINDOWS) { - const commandWin = command.replace(/\\/g, "/"); + const commandWin = command.replace(/\\/g, '/'); if (IS_POWERSHELL) { terminal.sendText(`& ${commandWin}`); - } - else { + } else { terminal.sendText(commandWin); } - } - else { + } else { terminal.sendText(command); } terminal.show(); @@ -99,6 +103,7 @@ function execSelectionInTerminal() { } const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell'); + // tslint:disable-next-line:no-backbone-get-set-outside-model const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get('windows')); let currentPythonPath = settings.PythonSettings.getInstance(activeEditor.document.uri).pythonPath; @@ -110,9 +115,8 @@ function execSelectionInTerminal() { let code: string; if (selection.isEmpty) { code = vscode.window.activeTextEditor.document.lineAt(selection.start.line).text; - } - else { - let textRange = new vscode.Range(selection.start, selection.end); + } else { + const textRange = new vscode.Range(selection.start, selection.end); code = vscode.window.activeTextEditor.document.getText(textRange); } if (code.length === 0) { @@ -120,28 +124,26 @@ function execSelectionInTerminal() { } code = removeBlankLines(code); const launchArgs = settings.PythonSettings.getInstance(activeEditor.document.uri).terminal.launchArgs; - const launchArgsString = launchArgs.length > 0 ? " ".concat(launchArgs.join(" ")) : ""; + const launchArgsString = launchArgs.length > 0 ? ' '.concat(launchArgs.join(' ')) : ''; const command = `${currentPythonPath}${launchArgsString}`; if (!terminal) { - terminal = vscode.window.createTerminal(`Python`); + terminal = vscode.window.createTerminal('Python'); if (IS_WINDOWS) { - const commandWin = command.replace(/\\/g, "/"); + const commandWin = command.replace(/\\/g, '/'); if (IS_POWERSHELL) { terminal.sendText(`& ${commandWin}`); - } - else { + } else { terminal.sendText(commandWin); } - } - else { + } else { terminal.sendText(command); } } - const unix_code = code.replace(/\r\n/g, "\n"); + // tslint:disable-next-line:variable-name + const unix_code = code.replace(/\r\n/g, '\n'); if (IS_WINDOWS) { - terminal.sendText(unix_code.replace(/\n/g, "\r\n")); - } - else { + terminal.sendText(unix_code.replace(/\n/g, '\r\n')); + } else { terminal.sendText(unix_code); } terminal.show(); @@ -154,6 +156,7 @@ function execSelectionInDjangoShell() { } const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell'); + // tslint:disable-next-line:no-backbone-get-set-outside-model const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get('windows')); let currentPythonPath = settings.PythonSettings.getInstance(activeEditor.document.uri).pythonPath; @@ -161,44 +164,99 @@ function execSelectionInDjangoShell() { currentPythonPath = `"${currentPythonPath}"`; } - const workspaceRoot = vscode.workspace.rootPath; - const djangoShellCmd = `"${workspaceRoot}/manage.py" shell`; + const workspaceUri = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri); + const defaultWorkspace = Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0 ? vscode.workspace.workspaceFolders[0].uri.fsPath : ''; + const workspaceRoot = workspaceUri ? workspaceUri.uri.fsPath : defaultWorkspace; + const djangoShellCmd = `"${path.join(workspaceRoot, 'manage.py')}" shell`; const selection = vscode.window.activeTextEditor.selection; let code: string; if (selection.isEmpty) { code = vscode.window.activeTextEditor.document.lineAt(selection.start.line).text; - } - else { - let textRange = new vscode.Range(selection.start, selection.end); + } else { + const textRange = new vscode.Range(selection.start, selection.end); code = vscode.window.activeTextEditor.document.getText(textRange); } if (code.length === 0) { return; } const launchArgs = settings.PythonSettings.getInstance(activeEditor.document.uri).terminal.launchArgs; - const launchArgsString = launchArgs.length > 0 ? " ".concat(launchArgs.join(" ")) : ""; + const launchArgsString = launchArgs.length > 0 ? ' '.concat(launchArgs.join(' ')) : ''; const command = `${currentPythonPath}${launchArgsString} ${djangoShellCmd}`; if (!terminal) { - terminal = vscode.window.createTerminal(`Django Shell`); + terminal = vscode.window.createTerminal('Django Shell'); if (IS_WINDOWS) { - const commandWin = command.replace(/\\/g, "/"); + const commandWin = command.replace(/\\/g, '/'); if (IS_POWERSHELL) { terminal.sendText(`& ${commandWin}`); - } - else { + } else { terminal.sendText(commandWin); } - } - else { + } else { terminal.sendText(command); } } - const unix_code = code.replace(/\r\n/g, "\n"); + // tslint:disable-next-line:variable-name + const unix_code = code.replace(/\r\n/g, '\n'); if (IS_WINDOWS) { - terminal.sendText(unix_code.replace(/\n/g, "\r\n")); - } - else { + terminal.sendText(unix_code.replace(/\n/g, '\r\n')); + } else { terminal.sendText(unix_code); } terminal.show(); } + +class DjangoContextInitializer implements vscode.Disposable { + private isDjangoProject: ContextKey; + private monitoringActiveTextEditor: boolean; + private workspaceContextKeyValues = new Map(); + private lastCheckedWorkspace: string; + private disposables: Disposable[] = []; + constructor() { + this.isDjangoProject = new ContextKey('python.isDjangoProject'); + this.ensureState(); + this.disposables.push(vscode.workspace.onDidChangeWorkspaceFolders(() => this.updateContextKeyBasedOnActiveWorkspace())); + } + + public dispose() { + this.isDjangoProject = null; + this.disposables.forEach(disposable => disposable.dispose()); + } + private updateContextKeyBasedOnActiveWorkspace() { + if (this.monitoringActiveTextEditor) { + return; + } + this.monitoringActiveTextEditor = true; + this.disposables.push(vscode.window.onDidChangeActiveTextEditor(() => this.ensureState())); + } + private getActiveWorkspace(): string | undefined { + if (!Array.isArray(workspace.workspaceFolders || workspace.workspaceFolders.length === 0)) { + return undefined; + } + if (workspace.workspaceFolders.length === 1) { + return workspace.workspaceFolders[0].uri.fsPath; + } + const activeEditor = vscode.window.activeTextEditor; + if (!activeEditor) { + return undefined; + } + const workspaceFolder = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri); + return workspaceFolder ? workspaceFolder.uri.fsPath : undefined; + } + private async ensureState(): Promise { + const activeWorkspace = this.getActiveWorkspace(); + if (!activeWorkspace) { + return await this.isDjangoProject.set(false); + } + if (this.lastCheckedWorkspace === activeWorkspace) { + return; + } + if (this.workspaceContextKeyValues.has(activeWorkspace)) { + await this.isDjangoProject.set(this.workspaceContextKeyValues.get(activeWorkspace)); + } else { + const exists = await fs.pathExists(path.join(activeWorkspace, 'manage.py')); + await this.isDjangoProject.set(exists); + this.workspaceContextKeyValues.set(activeWorkspace, exists); + this.lastCheckedWorkspace = activeWorkspace; + } + } +} diff --git a/src/test/common.ts b/src/test/common.ts index 486293b24015..00ef1caa14f2 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -82,6 +82,16 @@ async function setPythonPathInWorkspace(resource: string | Uri | undefined, conf PythonSettings.dispose(); } } +async function restoreGlobalPythonPathSetting(): Promise { + const pythonConfig = workspace.getConfiguration('python'); + const currentGlobalPythonPathSetting = pythonConfig.inspect('pythonPath').globalValue; + if (globalPythonPathSetting !== currentGlobalPythonPathSetting) { + await pythonConfig.update('pythonPath', undefined, true); + } + PythonSettings.dispose(); +} +const globalPythonPathSetting = workspace.getConfiguration('python').inspect('pythonPath').globalValue; export const clearPythonPathInWorkspaceFolder = async (resource: string | Uri) => retryAsync(setPythonPathInWorkspace)(resource, ConfigurationTarget.WorkspaceFolder); export const setPythonPathInWorkspaceRoot = async (pythonPath: string) => retryAsync(setPythonPathInWorkspace)(undefined, ConfigurationTarget.Workspace, pythonPath); +export const resetGlobalPythonPathSetting = async () => retryAsync(restoreGlobalPythonPathSetting)(); diff --git a/src/test/index.ts b/src/test/index.ts index 077d65e200e8..64de9103f775 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -7,6 +7,7 @@ process.env['VSC_PYTHON_CI_TEST'] = '1'; testRunner.configure({ ui: 'tdd', useColors: true, - timeout: 25000 + timeout: 25000, + retries: 3 }); module.exports = testRunner; diff --git a/src/test/initialize.ts b/src/test/initialize.ts index f67afc30b8ec..67193c2da0bd 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -4,9 +4,11 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { PythonSettings } from '../client/common/configSettings'; import { activated } from '../client/extension'; -import { clearPythonPathInWorkspaceFolder, setPythonPathInWorkspaceRoot } from './common'; +import { clearPythonPathInWorkspaceFolder, resetGlobalPythonPathSetting, setPythonPathInWorkspaceRoot } from './common'; const dummyPythonFile = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); +const multirootPath = path.join(__dirname, '..', '..', 'src', 'testMultiRootWkspc'); +const workspace3Uri = vscode.Uri.file(path.join(multirootPath, 'workspace3')); //First thing to be executed. // tslint:disable-next-line:no-string-literal @@ -20,7 +22,9 @@ export const IS_MULTI_ROOT_TEST = isMultitrootTest(); // Ability to use custom python environments for testing export async function initializePython() { + await resetGlobalPythonPathSetting(); await clearPythonPathInWorkspaceFolder(dummyPythonFile); + await clearPythonPathInWorkspaceFolder(workspace3Uri); await setPythonPathInWorkspaceRoot(PYTHON_PATH); } diff --git a/src/test/interpreters/pythonPathUpdater.multiroot.test.ts b/src/test/interpreters/pythonPathUpdater.multiroot.test.ts new file mode 100644 index 000000000000..7f7c1040c68b --- /dev/null +++ b/src/test/interpreters/pythonPathUpdater.multiroot.test.ts @@ -0,0 +1,75 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import { ConfigurationTarget, Uri, workspace } from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; +import { PythonPathUpdaterService } from '../../client/interpreter/configuration/pythonPathUpdaterService'; +import { PythonPathUpdaterServiceFactory } from '../../client/interpreter/configuration/pythonPathUpdaterServiceFactory'; +import { GlobalPythonPathUpdaterService } from '../../client/interpreter/configuration/services/globalUpdaterService'; +import { WorkspaceFolderPythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceFolderUpdaterService'; +import { WorkspacePythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceUpdaterService'; +import { WorkspacePythonPath } from '../../client/interpreter/contracts'; +import { clearPythonPathInWorkspaceFolder } from '../common'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; + +const workspaceRoot = path.join(__dirname, '..', '..', '..', 'src', 'test'); +const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); +const workspace3Uri = Uri.file(path.join(multirootPath, 'workspace3')); + +// tslint:disable-next-line:max-func-body-length +suite('Multiroot Python Path Settings Updater', () => { + suiteSetup(async function () { + if (!IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + setup(initializeTest); + suiteTeardown(async () => { + await closeActiveWindows(); + await initializeTest(); + }); + teardown(async () => { + await closeActiveWindows(); + await initializeTest(); + }); + + test('Updating Workspace Folder Python Path should work', async () => { + const workspaceUri = workspace3Uri; + const workspaceUpdater = new WorkspaceFolderPythonPathUpdaterService(workspace.getWorkspaceFolder(workspaceUri).uri); + const pythonPath = `xWorkspacePythonPath${new Date().getMilliseconds()}`; + await workspaceUpdater.updatePythonPath(pythonPath); + const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath').workspaceFolderValue; + assert.equal(folderValue, pythonPath, 'Workspace Python Path not updated'); + }); + + test('Updating Workspace Folder Python Path using the factor service should work', async () => { + const workspaceUri = workspace3Uri; + const factory = new PythonPathUpdaterServiceFactory(); + const workspaceUpdater = factory.getWorkspaceFolderPythonPathConfigurationService(workspace.getWorkspaceFolder(workspaceUri).uri); + const pythonPath = `xWorkspacePythonPathFromFactory${new Date().getMilliseconds()}`; + await workspaceUpdater.updatePythonPath(pythonPath); + const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath').workspaceFolderValue; + assert.equal(folderValue, pythonPath, 'Workspace Python Path not updated'); + }); + + test('Updating Workspace Python Path using the PythonPathUpdaterService should work', async () => { + const workspaceUri = workspace3Uri; + const updaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory()); + const pythonPath = `xWorkspacePythonPathFromUpdater${new Date().getMilliseconds()}`; + await updaterService.updatePythonPath(pythonPath, ConfigurationTarget.WorkspaceFolder, workspace.getWorkspaceFolder(workspaceUri).uri); + const folderValue = workspace.getConfiguration('python', workspace3Uri).inspect('pythonPath').workspaceFolderValue; + assert.equal(folderValue, pythonPath, 'Workspace Python Path not updated'); + }); + + test('Python Path should be relative to workspace', async () => { + const workspaceUri = workspace.getWorkspaceFolder(workspace3Uri).uri; + const pythonInterpreter = `xWorkspacePythonPath${new Date().getMilliseconds()}`; + const pythonPath = path.join(workspaceUri.fsPath, 'x', 'y', 'z', pythonInterpreter); + const workspaceUpdater = new WorkspacePythonPathUpdaterService(workspaceUri); + await workspaceUpdater.updatePythonPath(pythonPath); + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + // tslint:disable-next-line:no-invalid-template-strings + assert.equal(workspaceValue, path.join('${workspaceRoot}', 'x', 'y', 'z', pythonInterpreter), 'Workspace Python Path not updated'); + }); +}); diff --git a/src/test/interpreters/pythonPathUpdater.test.ts b/src/test/interpreters/pythonPathUpdater.test.ts new file mode 100644 index 000000000000..4b11c96ebd60 --- /dev/null +++ b/src/test/interpreters/pythonPathUpdater.test.ts @@ -0,0 +1,92 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import { ConfigurationTarget, Uri, workspace } from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; +import { PythonPathUpdaterService } from '../../client/interpreter/configuration/pythonPathUpdaterService'; +import { PythonPathUpdaterServiceFactory } from '../../client/interpreter/configuration/pythonPathUpdaterServiceFactory'; +import { GlobalPythonPathUpdaterService } from '../../client/interpreter/configuration/services/globalUpdaterService'; +import { WorkspacePythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceUpdaterService'; +import { WorkspacePythonPath } from '../../client/interpreter/contracts'; +import { clearPythonPathInWorkspaceFolder } from '../common'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; + +const workspaceRoot = path.join(__dirname, '..', '..', '..', 'src', 'test'); + +// tslint:disable-next-line:max-func-body-length +suite('Python Path Settings Updater', () => { + suiteSetup(initialize); + setup(initializeTest); + suiteTeardown(async () => { + await closeActiveWindows(); + await initializeTest(); + }); + teardown(async () => { + await closeActiveWindows(); + await initializeTest(); + }); + + // Create Github issue VS Code bug (global changes not reflected immediately) + + // test('Updating Global Python Path should work', async () => { + // const globalUpdater = new GlobalPythonPathUpdaterService(); + // const pythonPath = `xGlobalPythonPath${new Date().getMilliseconds()}`; + // await globalUpdater.updatePythonPath(pythonPath); + // const globalPythonValue = workspace.getConfiguration('python').inspect('pythonPath').globalValue; + // assert.equal(globalPythonValue, pythonPath, 'Global Python Path not updated'); + // }); + + // test('Updating Global Python Path using the factory service should work', async () => { + // const globalUpdater = new PythonPathUpdaterServiceFactory().getGlobalPythonPathConfigurationService(); + // const pythonPath = `xGlobalPythonPathFromFactory${new Date().getMilliseconds()}`; + // await globalUpdater.updatePythonPath(pythonPath); + // const globalPythonValue = workspace.getConfiguration('python').inspect('pythonPath').globalValue; + // assert.equal(globalPythonValue, pythonPath, 'Global Python Path not updated'); + // }); + + // test('Updating Global Python Path using the PythonPathUpdaterService should work', async () => { + // const updaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory()); + // const pythonPath = `xGlobalPythonPathFromUpdater${new Date().getMilliseconds()}`; + // await updaterService.updatePythonPath(pythonPath, ConfigurationTarget.Global); + // const globalPythonValue = workspace.getConfiguration('python').inspect('pythonPath').globalValue; + // assert.equal(globalPythonValue, pythonPath, 'Global Python Path not updated'); + // }); + + test('Updating Workspace Python Path should work', async () => { + const workspaceUri = Uri.file(workspaceRoot); + const workspaceUpdater = new WorkspacePythonPathUpdaterService(workspace.getWorkspaceFolder(workspaceUri).uri); + const pythonPath = `xWorkspacePythonPath${new Date().getMilliseconds()}`; + await workspaceUpdater.updatePythonPath(pythonPath); + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + assert.equal(workspaceValue, pythonPath, 'Workspace Python Path not updated'); + }); + + test('Updating Workspace Python Path using the factor service should work', async () => { + const workspaceUri = Uri.file(workspaceRoot); + const factory = new PythonPathUpdaterServiceFactory(); + const workspaceUpdater = factory.getWorkspacePythonPathConfigurationService(workspace.getWorkspaceFolder(workspaceUri).uri); + const pythonPath = `xWorkspacePythonPathFromFactory${new Date().getMilliseconds()}`; + await workspaceUpdater.updatePythonPath(pythonPath); + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + assert.equal(workspaceValue, pythonPath, 'Workspace Python Path not updated'); + }); + + test('Updating Workspace Python Path using the PythonPathUpdaterService should work', async () => { + const workspaceUri = Uri.file(workspaceRoot); + const updaterService = new PythonPathUpdaterService(new PythonPathUpdaterServiceFactory()); + const pythonPath = `xWorkspacePythonPathFromUpdater${new Date().getMilliseconds()}`; + await updaterService.updatePythonPath(pythonPath, ConfigurationTarget.Workspace, workspace.getWorkspaceFolder(workspaceUri).uri); + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + assert.equal(workspaceValue, pythonPath, 'Workspace Python Path not updated'); + }); + + test('Python Path should be relative to workspace', async () => { + const workspaceUri = workspace.getWorkspaceFolder(Uri.file(workspaceRoot)).uri; + const pythonInterpreter = `xWorkspacePythonPath${new Date().getMilliseconds()}`; + const pythonPath = path.join(workspaceUri.fsPath, 'x', 'y', 'z', pythonInterpreter); + const workspaceUpdater = new WorkspacePythonPathUpdaterService(workspaceUri); + await workspaceUpdater.updatePythonPath(pythonPath); + const workspaceValue = workspace.getConfiguration('python').inspect('pythonPath').workspaceValue; + // tslint:disable-next-line:no-invalid-template-strings + assert.equal(workspaceValue, path.join('${workspaceRoot}', 'x', 'y', 'z', pythonInterpreter), 'Workspace Python Path not updated'); + }); +}); diff --git a/src/test/providers/shebangCodeLenseProvider.test.ts b/src/test/providers/shebangCodeLenseProvider.test.ts index c8e914f40afd..23616ebb3e70 100644 --- a/src/test/providers/shebangCodeLenseProvider.test.ts +++ b/src/test/providers/shebangCodeLenseProvider.test.ts @@ -1,13 +1,13 @@ import * as assert from 'assert'; +import * as child_process from 'child_process'; import * as path from 'path'; import * as vscode from 'vscode'; -import * as child_process from 'child_process'; +import { ConfigurationTarget } from 'vscode'; import { IS_WINDOWS, PythonSettings } from '../../client/common/configSettings'; +import { ShebangCodeLensProvider } from '../../client/interpreter/display/shebangCodeLensProvider'; +import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; import { rootWorkspaceUri, updateSetting } from '../common'; -import { ShebangCodeLensProvider } from '../../client/providers/shebangCodeLensProvider'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; -import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; -import { ConfigurationTarget } from 'vscode'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'shebang'); const fileShebang = path.join(autoCompPath, 'shebang.py'); @@ -16,12 +16,12 @@ const fileShebangInvalid = path.join(autoCompPath, 'shebangInvalid.py'); const filePlain = path.join(autoCompPath, 'plain.py'); suite('Shebang detection', () => { - suiteSetup(() => initialize()); + suiteSetup(initialize); suiteTeardown(async () => { await initialize(); await closeActiveWindows(); }); - setup(() => initializeTest()); + setup(initializeTest); test('A code lens will appear when sheban python and python in settings are different', async () => { const pythonPath = 'someUnknownInterpreter'; @@ -30,7 +30,7 @@ suite('Shebang detection', () => { const codeLenses = await setupCodeLens(editor); assert.equal(codeLenses.length, 1, 'No CodeLens available'); - let codeLens = codeLenses[0]; + const codeLens = codeLenses[0]; assert(codeLens.range.isSingleLine, 'Invalid CodeLens Range'); assert.equal(codeLens.command.command, 'python.setShebangInterpreter'); }); @@ -58,7 +58,7 @@ suite('Shebang detection', () => { const codeLenses = await setupCodeLens(editor); assert.equal(codeLenses.length, 1, 'No CodeLens available'); - let codeLens = codeLenses[0]; + const codeLens = codeLenses[0]; assert(codeLens.range.isSingleLine, 'Invalid CodeLens Range'); assert.equal(codeLens.command.command, 'python.setShebangInterpreter'); @@ -96,7 +96,6 @@ suite('Shebang detection', () => { async function setupCodeLens(editor: vscode.TextEditor) { const document = editor.document; const codeLensProvider = new ShebangCodeLensProvider(); - const codeLenses = await codeLensProvider.provideCodeLenses(document, null); - return codeLenses; + return await codeLensProvider.provideCodeLenses(document, null); } }); diff --git a/src/test/unittests/nosetest.test.ts b/src/test/unittests/nosetest.test.ts index ddf743277aaa..d61e4e0d9944 100644 --- a/src/test/unittests/nosetest.test.ts +++ b/src/test/unittests/nosetest.test.ts @@ -54,9 +54,7 @@ suite('Unit Tests (nosetest)', () => { testManager = new nose.TestManager(rootDir, outChannel); } - test('Discover Tests (single test file)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (single test file)', async () => { createTestManager(UNITTEST_SINGLE_TEST_FILE_PATH); const tests = await testManager.discoverTests(true, true); assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); @@ -65,9 +63,7 @@ suite('Unit Tests (nosetest)', () => { assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_one.py') && t.nameToRun === t.name), true, 'Test File not found'); }); - test('Check that nameToRun in testSuits has class name after : (single test file)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Check that nameToRun in testSuits has class name after : (single test file)', async () => { createTestManager(UNITTEST_SINGLE_TEST_FILE_PATH); const tests = await testManager.discoverTests(true, true); assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); @@ -80,9 +76,7 @@ suite('Unit Tests (nosetest)', () => { const found = tests.testFiles.some(t => t.name === testFile && t.nameToRun === t.name); assert.equal(found, true, `Test File not found '${testFile}'`); } - test('Discover Tests (-m=test)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (-m=test)', async () => { await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -96,9 +90,7 @@ suite('Unit Tests (nosetest)', () => { lookForTestFile(tests, 'test_root.py'); }); - test('Discover Tests (-w=specific -m=tst)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (-w=specific -m=tst)', async () => { await updateSetting('unitTest.nosetestArgs', ['-w', 'specific', '-m', 'tst'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -109,9 +101,7 @@ suite('Unit Tests (nosetest)', () => { lookForTestFile(tests, path.join('specific', 'tst_unittest_two.py')); }); - test('Discover Tests (-m=test_)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (-m=test_)', async () => { await updateSetting('unitTest.nosetestArgs', ['-m', 'test_'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -121,9 +111,7 @@ suite('Unit Tests (nosetest)', () => { lookForTestFile(tests, 'test_root.py'); }); - test('Run Tests', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Tests', async () => { await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const results = await testManager.runTest(); @@ -133,9 +121,7 @@ suite('Unit Tests (nosetest)', () => { assert.equal(results.summary.skipped, 2, 'skipped'); }); - test('Run Failed Tests', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Failed Tests', async () => { await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); let results = await testManager.runTest(); @@ -151,9 +137,7 @@ suite('Unit Tests (nosetest)', () => { assert.equal(results.summary.skipped, 0, 'skipped again'); }); - test('Run Specific Test File', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Specific Test File', async () => { await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -168,9 +152,7 @@ suite('Unit Tests (nosetest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Specific Test Suite', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Specific Test Suite', async () => { await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -185,9 +167,7 @@ suite('Unit Tests (nosetest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Specific Test Function', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Specific Test Function', async () => { await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); diff --git a/src/test/unittests/pytest.test.ts b/src/test/unittests/pytest.test.ts index 77086d86808f..c0fb8dc64540 100644 --- a/src/test/unittests/pytest.test.ts +++ b/src/test/unittests/pytest.test.ts @@ -40,9 +40,7 @@ suite('Unit Tests (PyTest)', () => { testManager = new pytest.TestManager(rootDir, outChannel); } - test('Discover Tests (single test file)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (single test file)', async () => { testManager = new pytest.TestManager(UNITTEST_SINGLE_TEST_FILE_PATH, outChannel); const tests = await testManager.discoverTests(true, true); assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); @@ -52,9 +50,7 @@ suite('Unit Tests (PyTest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'test_root.py' && t.nameToRun === t.name), true, 'Test File not found'); }); - test('Discover Tests (pattern = test_)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (pattern = test_)', async () => { await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -69,9 +65,7 @@ suite('Unit Tests (PyTest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'test_root.py' && t.nameToRun === t.name), true, 'Test File not found'); }); - test('Discover Tests (pattern = _test)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (pattern = _test)', async () => { await updateSetting('unitTest.pyTestArgs', ['-k=_test.py'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -81,9 +75,7 @@ suite('Unit Tests (PyTest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'tests/unittest_three_test.py' && t.nameToRun === t.name), true, 'Test File not found'); }); - test('Discover Tests (with config)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (with config)', async () => { await updateSetting('unitTest.pyTestArgs', [], rootWorkspaceUri, configTarget); rootDirectory = UNITTEST_TEST_FILES_PATH_WITH_CONFIGS; createTestManager(); @@ -95,9 +87,7 @@ suite('Unit Tests (PyTest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'other/test_pytest.py' && t.nameToRun === t.name), true, 'Test File not found'); }); - test('Run Tests', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Tests', async () => { await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); const results = await testManager.runTest(); @@ -107,9 +97,7 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 3, 'skipped'); }); - test('Run Failed Tests', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Failed Tests', async () => { await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); let results = await testManager.runTest(); @@ -125,9 +113,7 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 0, 'Failed skipped'); }); - test('Run Specific Test File', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Specific Test File', async () => { await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); await testManager.discoverTests(true, true); @@ -148,9 +134,7 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 0, 'skipped'); }); - test('Run Specific Test Suite', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Specific Test Suite', async () => { await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -162,9 +146,7 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Specific Test Function', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Specific Test Function', async () => { await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -176,9 +158,7 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 0, 'skipped'); }); - test('Setting cwd should return tests', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Setting cwd should return tests', async () => { await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(unitTestTestFilesCwdPath); diff --git a/src/test/unittests/unittest.test.ts b/src/test/unittests/unittest.test.ts index be12df3af776..478111c1d620 100644 --- a/src/test/unittests/unittest.test.ts +++ b/src/test/unittests/unittest.test.ts @@ -51,9 +51,7 @@ suite('Unit Tests (unittest)', () => { testManager = new unittest.TestManager(rootDir, outChannel); } - test('Discover Tests (single test file)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (single test file)', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); testManager = new unittest.TestManager(UNITTEST_SINGLE_TEST_FILE_PATH, outChannel); const tests = await testManager.discoverTests(true, true); @@ -63,9 +61,7 @@ suite('Unit Tests (unittest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'test_one.py' && t.nameToRun === 'Test_test1.test_A'), true, 'Test File not found'); }); - test('Discover Tests', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -76,9 +72,7 @@ suite('Unit Tests (unittest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'test_unittest_two.py' && t.nameToRun === 'Test_test2.test_A2'), true, 'Test File not found'); }); - test('Discover Tests (pattern = *_test_*.py)', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Discover Tests (pattern = *_test_*.py)', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=*_test*.py'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -88,9 +82,7 @@ suite('Unit Tests (unittest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'unittest_three_test.py' && t.nameToRun === 'Test_test3.test_A'), true, 'Test File not found'); }); - test('Run Tests', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Tests', async () => { await updateSetting('unitTest.unittestArgs', ['-v', '-s', './tests', '-p', 'test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(); const results = await testManager.runTest(); @@ -100,9 +92,7 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Failed Tests', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Failed Tests', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(); let results = await testManager.runTest(); @@ -118,9 +108,7 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 0, 'Failed skipped'); }); - test('Run Specific Test File', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Specific Test File', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(unitTestSpecificTestFilesPath); const tests = await testManager.discoverTests(true, true); @@ -136,9 +124,7 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Specific Test Suite', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Specific Test Suite', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(unitTestSpecificTestFilesPath); const tests = await testManager.discoverTests(true, true); @@ -154,9 +140,7 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Specific Test Function', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Run Specific Test Function', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -168,9 +152,7 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 0, 'skipped'); }); - test('Setting cwd should return tests', async function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); + test('Setting cwd should return tests', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); createTestManager(unitTestTestFilesCwdPath);