From 482d4bebe5406c8510960647deaa29a8fe589868 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 26 Oct 2017 16:53:58 -0700 Subject: [PATCH 1/8] fixes to unit tests and forgotten multiroot --- package.json | 5 +- src/client/common/contextKey.ts | 15 ++ src/client/common/installer.ts | 51 +++--- src/client/extension.ts | 39 ----- .../locators/services/virtualEnvService.ts | 4 +- .../providers/execInTerminalProvider.ts | 148 ++++++++++++------ .../providers/setInterpreterProvider.ts | 4 +- .../common/configSettings.multiroot.test.ts | 4 +- src/test/unittests/nosetest.test.ts | 44 ++---- src/test/unittests/pytest.test.ts | 44 ++---- src/test/unittests/unittest.test.ts | 40 ++--- 11 files changed, 197 insertions(+), 201 deletions(-) create mode 100644 src/client/common/contextKey.ts 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..37c2b5bc940f 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -46,8 +46,6 @@ const activationDeferred = createDeferred(); export const activated = activationDeferred.promise; 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; @@ -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/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/client/providers/setInterpreterProvider.ts b/src/client/providers/setInterpreterProvider.ts index 47cdc95de966..29039fca8272 100644 --- a/src/client/providers/setInterpreterProvider.ts +++ b/src/client/providers/setInterpreterProvider.ts @@ -59,8 +59,8 @@ export class SetInterpreterProvider implements Disposable { const resourceUri = targetConfig ? targetConfig.folderUri : undefined; const suggestions = await this.getSuggestions(resourceUri); let currentPythonPath = settings.PythonSettings.getInstance().pythonPath; - if (workspace.rootPath && currentPythonPath.startsWith(workspace.rootPath)) { - currentPythonPath = `.${path.sep}${path.relative(workspace.rootPath, currentPythonPath)}`; + if (targetConfig.folderUri && currentPythonPath.startsWith(targetConfig.folderUri.fsPath)) { + currentPythonPath = `.${path.sep}${path.relative(targetConfig.folderUri.fsPath, currentPythonPath)}`; } const quickPickOptions: QuickPickOptions = { matchOnDetail: true, diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index dc7ef9590885..e7efd550c895 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -8,7 +8,9 @@ import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } fr const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); // tslint:disable-next-line:max-func-body-length -suite('Multiroot Config Settings', () => { +suite('Multiroot Config Settings', function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); suiteSetup(async function () { if (!IS_MULTI_ROOT_TEST) { // tslint:disable-next-line:no-invalid-this diff --git a/src/test/unittests/nosetest.test.ts b/src/test/unittests/nosetest.test.ts index ddf743277aaa..4f25d95bb620 100644 --- a/src/test/unittests/nosetest.test.ts +++ b/src/test/unittests/nosetest.test.ts @@ -17,7 +17,9 @@ const filesToDelete = [ ]; // tslint:disable-next-line:max-func-body-length -suite('Unit Tests (nosetest)', () => { +suite('Unit Tests (nosetest)', function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); const configTarget = IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace; const rootDirectory = UNITTEST_TEST_FILES_PATH; let testManager: nose.TestManager; @@ -54,9 +56,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 +65,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 +78,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 +92,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 +103,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 +113,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 +123,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 +139,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 +154,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 +169,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..123e72b03711 100644 --- a/src/test/unittests/pytest.test.ts +++ b/src/test/unittests/pytest.test.ts @@ -14,7 +14,9 @@ const UNITTEST_TEST_FILES_PATH_WITH_CONFIGS = path.join(__dirname, '..', '..', ' const unitTestTestFilesCwdPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'cwd', 'src'); // tslint:disable-next-line:max-func-body-length -suite('Unit Tests (PyTest)', () => { +suite('Unit Tests (PyTest)', function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); let rootDirectory = UNITTEST_TEST_FILES_PATH; let testManager: pytest.TestManager; let testResultDisplay: TestResultDisplay; @@ -40,9 +42,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 +52,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 +67,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 +77,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 +89,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 +99,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 +115,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 +136,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 +148,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 +160,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..de409344da8c 100644 --- a/src/test/unittests/unittest.test.ts +++ b/src/test/unittests/unittest.test.ts @@ -23,7 +23,9 @@ const defaultUnitTestArgs = [ ]; // tslint:disable-next-line:max-func-body-length -suite('Unit Tests (unittest)', () => { +suite('Unit Tests (unittest)', function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); let testManager: unittest.TestManager; let testResultDisplay: TestResultDisplay; let outChannel: MockOutputChannel; @@ -51,9 +53,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 +63,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 +74,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 +84,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 +94,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 +110,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 +126,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 +142,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 +154,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); From 62683ff9969b9872858870e72c172a39346b5747 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 26 Oct 2017 17:33:15 -0700 Subject: [PATCH 2/8] globally retry all tests 3 times --- src/test/common/configSettings.multiroot.test.ts | 4 +--- src/test/index.ts | 3 ++- src/test/unittests/nosetest.test.ts | 4 +--- src/test/unittests/pytest.test.ts | 4 +--- src/test/unittests/unittest.test.ts | 4 +--- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index e7efd550c895..dc7ef9590885 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -8,9 +8,7 @@ import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } fr const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); // tslint:disable-next-line:max-func-body-length -suite('Multiroot Config Settings', function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); +suite('Multiroot Config Settings', () => { suiteSetup(async function () { if (!IS_MULTI_ROOT_TEST) { // tslint:disable-next-line:no-invalid-this 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/unittests/nosetest.test.ts b/src/test/unittests/nosetest.test.ts index 4f25d95bb620..d61e4e0d9944 100644 --- a/src/test/unittests/nosetest.test.ts +++ b/src/test/unittests/nosetest.test.ts @@ -17,9 +17,7 @@ const filesToDelete = [ ]; // tslint:disable-next-line:max-func-body-length -suite('Unit Tests (nosetest)', function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); +suite('Unit Tests (nosetest)', () => { const configTarget = IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace; const rootDirectory = UNITTEST_TEST_FILES_PATH; let testManager: nose.TestManager; diff --git a/src/test/unittests/pytest.test.ts b/src/test/unittests/pytest.test.ts index 123e72b03711..c0fb8dc64540 100644 --- a/src/test/unittests/pytest.test.ts +++ b/src/test/unittests/pytest.test.ts @@ -14,9 +14,7 @@ const UNITTEST_TEST_FILES_PATH_WITH_CONFIGS = path.join(__dirname, '..', '..', ' const unitTestTestFilesCwdPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'cwd', 'src'); // tslint:disable-next-line:max-func-body-length -suite('Unit Tests (PyTest)', function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); +suite('Unit Tests (PyTest)', () => { let rootDirectory = UNITTEST_TEST_FILES_PATH; let testManager: pytest.TestManager; let testResultDisplay: TestResultDisplay; diff --git a/src/test/unittests/unittest.test.ts b/src/test/unittests/unittest.test.ts index de409344da8c..478111c1d620 100644 --- a/src/test/unittests/unittest.test.ts +++ b/src/test/unittests/unittest.test.ts @@ -23,9 +23,7 @@ const defaultUnitTestArgs = [ ]; // tslint:disable-next-line:max-func-body-length -suite('Unit Tests (unittest)', function () { - // tslint:disable-next-line:no-invalid-this - this.retries(3); +suite('Unit Tests (unittest)', () => { let testManager: unittest.TestManager; let testResultDisplay: TestResultDisplay; let outChannel: MockOutputChannel; From 75570ee259647c7177bb829e4d3c7bd242c5ab9d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 26 Oct 2017 20:02:09 -0700 Subject: [PATCH 3/8] refactor changing interpreters --- src/client/extension.ts | 64 +++--- .../configuration/pythonPathUpdaterService.ts | 42 ++++ .../pythonPathUpdaterServiceFactory.ts | 18 ++ .../services/globalUpdaterService.ts | 16 ++ .../services/workspaceFolderUpdaterService.ts | 23 ++ .../services/workspaceUpdaterService.ts | 23 ++ .../configuration/setInterpreterProvider.ts | 114 ++++++++++ src/client/interpreter/configuration/types.ts | 12 + .../display/shebangCodeLensProvider.ts | 71 ++++++ src/client/interpreter/helpers.ts | 47 ---- src/client/interpreter/index.ts | 56 +---- .../providers/setInterpreterProvider.ts | 210 +++++++++--------- .../providers/shebangCodeLensProvider.ts | 128 +++++------ .../shebangCodeLenseProvider.test.ts | 19 +- 14 files changed, 535 insertions(+), 308 deletions(-) create mode 100644 src/client/interpreter/configuration/pythonPathUpdaterService.ts create mode 100644 src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts create mode 100644 src/client/interpreter/configuration/services/globalUpdaterService.ts create mode 100644 src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts create mode 100644 src/client/interpreter/configuration/services/workspaceUpdaterService.ts create mode 100644 src/client/interpreter/configuration/setInterpreterProvider.ts create mode 100644 src/client/interpreter/configuration/types.ts create mode 100644 src/client/interpreter/display/shebangCodeLensProvider.ts diff --git a/src/client/extension.ts b/src/client/extension.ts index 37c2b5bc940f..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,6 +44,7 @@ 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(); sendStartupTelemetry(); @@ -83,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 } } ] }); @@ -99,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)); @@ -113,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) { @@ -125,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(); diff --git a/src/client/interpreter/configuration/pythonPathUpdaterService.ts b/src/client/interpreter/configuration/pythonPathUpdaterService.ts new file mode 100644 index 000000000000..adeca205553b --- /dev/null +++ b/src/client/interpreter/configuration/pythonPathUpdaterService.ts @@ -0,0 +1,42 @@ +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..68ad3b1b173f --- /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 pythonPathValue = workspace.getConfiguration('python').inspect('pythonPath'); + + if (pythonPathValue && pythonPathValue.globalValue === pythonPath) { + return; + } + const pythonConfig = workspace.getConfiguration('python'); + 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..5cdef095267f --- /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, false); + } +} 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/interpreter/configuration/setInterpreterProvider.ts b/src/client/interpreter/configuration/setInterpreterProvider.ts new file mode 100644 index 000000000000..b155d7caa049 --- /dev/null +++ b/src/client/interpreter/configuration/setInterpreterProvider.ts @@ -0,0 +1,114 @@ +'use strict'; +import * as path from 'path'; +import { commands, ConfigurationTarget, Disposable, QuickPickItem, QuickPickOptions, Uri, window, workspace } from 'vscode'; +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 { + path: string; +} + +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 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 }; + } + + // Ok we have multiple interpreters, get the user to pick a folder. + // tslint:disable-next-line:no-any prefer-type-cast + const workspaceFolder = await (window as any).showWorkspaceFolderPick({ placeHolder: 'Select a workspace' }); + return workspaceFolder ? { folderUri: workspaceFolder.uri, configTarget: ConfigurationTarget.WorkspaceFolder } : undefined; + } + private async suggestionToQuickPickItem(suggestion: PythonInterpreter, workspaceUri?: Uri): Promise { + let detail = suggestion.path; + if (workspaceUri && suggestion.path.startsWith(workspaceUri.fsPath)) { + detail = `.${path.sep}${path.relative(workspaceUri.fsPath, suggestion.path)}`; + } + return { + // tslint:disable-next-line:no-non-null-assertion + label: suggestion.displayName!, + description: suggestion.companyDisplayName || '', + detail: detail, + path: suggestion.path + }; + } + + 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 (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) { + await this.pythonPathUpdaterService.updatePythonPath(selection.path, configTarget, wkspace); + } + } + + private async setShebangInterpreter(): Promise { + const shebang = await ShebangCodeLensProvider.detectShebang(window.activeTextEditor.document); + if (!shebang) { + return; + } + + const isGlobalChange = !Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0; + const workspaceFolder = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri); + const isWorkspaceChange = Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length === 1; + + if (isGlobalChange) { + await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Global); + return; + } + + if (isWorkspaceChange || !workspaceFolder) { + await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Workspace, workspace.workspaceFolders[0].uri); + return; + } + + 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/interpreter/display/shebangCodeLensProvider.ts b/src/client/interpreter/display/shebangCodeLensProvider.ts new file mode 100644 index 000000000000..a45f4a4db431 --- /dev/null +++ b/src/client/interpreter/display/shebangCodeLensProvider.ts @@ -0,0 +1,71 @@ +'use strict'; +import * as child_process from 'child_process'; +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 { + const firstLine = document.lineAt(0); + if (firstLine.isEmptyOrWhitespace) { + return; + } + + if (!firstLine.text.startsWith('#!')) { + return; + } + + const shebang = firstLine.text.substr(2).trim(); + const pythonPath = await ShebangCodeLensProvider.getFullyQualifiedPathToInterpreter(shebang); + return typeof pythonPath === 'string' && pythonPath.length > 0 ? pythonPath : undefined; + } + private static async getFullyQualifiedPathToInterpreter(pythonPath: string) { + if (pythonPath.indexOf('bin/env ') >= 0 && !IS_WINDOWS) { + // 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 = ''; + command.stdout.on('data', (data) => { + result += data.toString(); + }); + command.on('close', () => { + resolve(getFirstNonEmptyLineFromMultilineString(result)); + }); + }); + } else { + return new Promise(resolve => { + 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/providers/setInterpreterProvider.ts b/src/client/providers/setInterpreterProvider.ts index 29039fca8272..a14070e34bc5 100644 --- a/src/client/providers/setInterpreterProvider.ts +++ b/src/client/providers/setInterpreterProvider.ts @@ -1,116 +1,116 @@ -'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'; +// '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 * as settings from './../common/configSettings'; +// import { ShebangCodeLensProvider } from './shebangCodeLensProvider'; -// tslint:disable-next-line:interface-name -interface PythonPathQuickPickItem extends QuickPickItem { - path: string; -} +// // tslint:disable-next-line:interface-name +// interface PythonPathQuickPickItem extends QuickPickItem { +// path: string; +// } -export class SetInterpreterProvider implements Disposable { - private disposables: Disposable[] = []; - 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))); - } - public dispose() { - this.disposables.forEach(disposable => disposable.dispose()); - } - private async getWorkspacePythonPath(): 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]; - } +// export class SetInterpreterProvider implements Disposable { +// private disposables: Disposable[] = []; +// 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))); +// } +// public dispose() { +// this.disposables.forEach(disposable => disposable.dispose()); +// } +// 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 }; +// } - // Ok we have multiple interpreters, get the user to pick a folder. - // tslint:disable-next-line:no-any prefer-type-cast - const workspaceFolder = await (window as any).showWorkspaceFolderPick({ placeHolder: 'Select a workspace' }); - return workspaceFolder ? { folderUri: workspaceFolder.uri, configTarget: ConfigurationTarget.WorkspaceFolder } : undefined; - } - private async suggestionToQuickPickItem(suggestion: PythonInterpreter, workspaceUri?: Uri): Promise { - let detail = suggestion.path; - if (workspaceUri && suggestion.path.startsWith(workspaceUri.fsPath)) { - detail = `.${path.sep}${path.relative(workspaceUri.fsPath, suggestion.path)}`; - } - return { - // tslint:disable-next-line:no-non-null-assertion - label: suggestion.displayName!, - description: suggestion.companyDisplayName || '', - detail: detail, - path: suggestion.path - }; - } - private async presentQuickPick() { - const targetConfig = await this.getWorkspacePythonPath(); - const resourceUri = targetConfig ? targetConfig.folderUri : undefined; - const suggestions = await this.getSuggestions(resourceUri); - let currentPythonPath = settings.PythonSettings.getInstance().pythonPath; - if (targetConfig.folderUri && currentPythonPath.startsWith(targetConfig.folderUri.fsPath)) { - currentPythonPath = `.${path.sep}${path.relative(targetConfig.folderUri.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); - } - } +// // Ok we have multiple interpreters, get the user to pick a folder. +// // tslint:disable-next-line:no-any prefer-type-cast +// const workspaceFolder = await (window as any).showWorkspaceFolderPick({ placeHolder: 'Select a workspace' }); +// return workspaceFolder ? { folderUri: workspaceFolder.uri, configTarget: ConfigurationTarget.WorkspaceFolder } : undefined; +// } +// private async suggestionToQuickPickItem(suggestion: PythonInterpreter, workspaceUri?: Uri): Promise { +// let detail = suggestion.path; +// if (workspaceUri && suggestion.path.startsWith(workspaceUri.fsPath)) { +// detail = `.${path.sep}${path.relative(workspaceUri.fsPath, suggestion.path)}`; +// } +// return { +// // tslint:disable-next-line:no-non-null-assertion +// label: suggestion.displayName!, +// description: suggestion.companyDisplayName || '', +// detail: detail, +// path: suggestion.path +// }; +// } - 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 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 setInterpreter() { +// const setInterpreterGlobally = !Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0; +// let targetConfig: WorkspacePythonPath; +// if (!setInterpreterGlobally) { +// targetConfig = await this.getWorkspaceToSetPythonPath(); +// if (!targetConfig) { +// return; +// } +// } - private async setShebangInterpreter(): Promise { - const shebang = await ShebangCodeLensProvider.detectShebang(window.activeTextEditor.document); - if (!shebang) { - return; - } +// const resourceUri = targetConfig ? targetConfig.folderUri : undefined; +// const suggestions = await this.getSuggestions(resourceUri); +// let currentPythonPath = settings.PythonSettings.getInstance().pythonPath; +// if (targetConfig && targetConfig.folderUri && currentPythonPath.startsWith(targetConfig.folderUri.fsPath)) { +// currentPythonPath = `.${path.sep}${path.relative(targetConfig.folderUri.fsPath, currentPythonPath)}`; +// } +// const quickPickOptions: QuickPickOptions = { +// matchOnDetail: true, +// matchOnDescription: true, +// placeHolder: `current: ${currentPythonPath}` +// }; - const existingConfigs = getInterpretersForEachFolderAndWorkspace(); - const hasFoldersWithPythonPathSet = existingConfigs.filter(item => item.configTarget === ConfigurationTarget.WorkspaceFolder).length > 0; +// const selection = await window.showQuickPick(suggestions, quickPickOptions); +// if (selection !== undefined) { +// await this.interpreterManager.setPythonPath(selection.path, targetConfig); +// } +// } - 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'; +// private async setShebangInterpreter(): Promise { +// const shebang = await ShebangCodeLensProvider.detectShebang(window.activeTextEditor.document); +// if (!shebang) { +// return; +// } - 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); - } +// const pythonPathValue = workspace.getConfiguration('python', window.activeTextEditor.document.uri).inspect('pythonPath'); +// const isGlobalChange = !Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0; +// const workspaceFolder = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri); +// const isWorkspaceChange = Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length === 1; - 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 (isGlobalChange) { +// if (!pythonPathValue || typeof pythonPathValue.globalValue !== 'string' || pythonPathValue.globalValue !== shebang) { +// await this.interpreterManager.setPythonPath(shebang); +// } +// return; +// } - return this.interpreterManager.setPythonPath(shebang); - } -} +// if (isWorkspaceChange || !workspaceFolder) { +// if (!pythonPathValue || typeof pythonPathValue.workspaceValue !== 'string' || pythonPathValue.workspaceValue !== shebang) { +// const targetInfo: WorkspacePythonPath = { configTarget: ConfigurationTarget.Workspace, folderUri: workspace.workspaceFolders[0].uri }; +// await this.interpreterManager.setPythonPath(shebang, targetInfo); +// } +// return; +// } + +// if (!pythonPathValue || typeof pythonPathValue.workspaceValue !== 'string' || pythonPathValue.workspaceValue !== shebang) { +// const targetInfo: WorkspacePythonPath = { configTarget: ConfigurationTarget.WorkspaceFolder, folderUri: workspaceFolder.uri }; +// await this.interpreterManager.setPythonPath(shebang, targetInfo); +// } +// } +// } diff --git a/src/client/providers/shebangCodeLensProvider.ts b/src/client/providers/shebangCodeLensProvider.ts index 8988d6161644..d792ef89dc1f 100644 --- a/src/client/providers/shebangCodeLensProvider.ts +++ b/src/client/providers/shebangCodeLensProvider.ts @@ -1,72 +1,72 @@ -"use strict"; -import { IS_WINDOWS } from '../common/utils'; -import * as vscode from 'vscode'; -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; +// "use strict"; +// import { IS_WINDOWS } from '../common/utils'; +// import * as vscode from 'vscode'; +// 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); - } +// 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 []; - } +// 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 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 cmd: vscode.Command = { +// command: 'python.setShebangInterpreter', +// title: 'Set as interpreter' +// }; - const codeLenses = [(new CodeLens(shebangRange, cmd))]; - return codeLenses; - } +// const codeLenses = [(new CodeLens(shebangRange, cmd))]; +// return codeLenses; +// } - public static async detectShebang(document: TextDocument): Promise { - let firstLine = document.lineAt(0); - if (firstLine.isEmptyOrWhitespace) { - return; - } +// public static async detectShebang(document: TextDocument): Promise { +// let firstLine = document.lineAt(0); +// if (firstLine.isEmptyOrWhitespace) { +// return; +// } - if (!firstLine.text.startsWith('#!')) { - return; - } +// if (!firstLine.text.startsWith('#!')) { +// return; +// } - const shebang = firstLine.text.substr(2).trim(); - const pythonPath = await ShebangCodeLensProvider.getFullyQualifiedPathToInterpreter(shebang); - return typeof pythonPath === 'string' && pythonPath.length > 0 ? pythonPath : undefined; - } - private static async getFullyQualifiedPathToInterpreter(pythonPath: string) { - if (pythonPath.indexOf('bin/env ') >= 0 && !IS_WINDOWS) { - // 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 = ''; - command.stdout.on('data', (data) => { - result += data.toString(); - }); - command.on('close', () => { - resolve(getFirstNonEmptyLineFromMultilineString(result)); - }); - }); - } - else { - return new Promise(resolve => { - child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => { - resolve(getFirstNonEmptyLineFromMultilineString(stdout)); - }); - }); - } - } -} +// const shebang = firstLine.text.substr(2).trim(); +// const pythonPath = await ShebangCodeLensProvider.getFullyQualifiedPathToInterpreter(shebang); +// return typeof pythonPath === 'string' && pythonPath.length > 0 ? pythonPath : undefined; +// } +// private static async getFullyQualifiedPathToInterpreter(pythonPath: string) { +// if (pythonPath.indexOf('bin/env ') >= 0 && !IS_WINDOWS) { +// // 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 = ''; +// command.stdout.on('data', (data) => { +// result += data.toString(); +// }); +// command.on('close', () => { +// resolve(getFirstNonEmptyLineFromMultilineString(result)); +// }); +// }); +// } +// else { +// return new Promise(resolve => { +// child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => { +// resolve(getFirstNonEmptyLineFromMultilineString(stdout)); +// }); +// }); +// } +// } +// } 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); } }); From e3374febe4ffdc27ebafd68c86511bac47ae7e2b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 26 Oct 2017 20:53:15 -0700 Subject: [PATCH 4/8] added tests --- .../services/globalUpdaterService.ts | 4 +- .../services/workspaceFolderUpdaterService.ts | 2 +- src/test/common.ts | 10 ++ src/test/initialize.ts | 6 +- .../pythonPathUpdater.multiroot.test.ts | 75 +++++++++++++++ .../interpreters/pythonPathUpdater.test.ts | 92 +++++++++++++++++++ 6 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 src/test/interpreters/pythonPathUpdater.multiroot.test.ts create mode 100644 src/test/interpreters/pythonPathUpdater.test.ts diff --git a/src/client/interpreter/configuration/services/globalUpdaterService.ts b/src/client/interpreter/configuration/services/globalUpdaterService.ts index 68ad3b1b173f..4b775adf0b71 100644 --- a/src/client/interpreter/configuration/services/globalUpdaterService.ts +++ b/src/client/interpreter/configuration/services/globalUpdaterService.ts @@ -5,12 +5,12 @@ import { IPythonPathUpdaterService } from '../types'; export class GlobalPythonPathUpdaterService implements IPythonPathUpdaterService { public async updatePythonPath(pythonPath: string): Promise { - const pythonPathValue = workspace.getConfiguration('python').inspect('pythonPath'); + const pythonConfig = workspace.getConfiguration('python'); + const pythonPathValue = pythonConfig.inspect('pythonPath'); if (pythonPathValue && pythonPathValue.globalValue === pythonPath) { return; } - const pythonConfig = workspace.getConfiguration('python'); await pythonConfig.update('pythonPath', pythonPath, true); } } diff --git a/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts b/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts index 5cdef095267f..d39f9a831f93 100644 --- a/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts +++ b/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts @@ -18,6 +18,6 @@ export class WorkspaceFolderPythonPathUpdaterService implements IPythonPathUpdat // tslint:disable-next-line:no-invalid-template-strings pythonPath = path.join('${workspaceRoot}', path.relative(this.workspaceFolder.fsPath, pythonPath)); } - await pythonConfig.update('pythonPath', pythonPath, false); + await pythonConfig.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder); } } 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/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..e05274fb14ae --- /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 { 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'; +import { WorkspaceFolderPythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceFolderUpdaterService'; + +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'); + }); +}); From 74985368de1b873bab68c5e8104bc2dd87f06142 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 26 Oct 2017 20:55:20 -0700 Subject: [PATCH 5/8] fixed linter --- src/test/interpreters/pythonPathUpdater.multiroot.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/interpreters/pythonPathUpdater.multiroot.test.ts b/src/test/interpreters/pythonPathUpdater.multiroot.test.ts index e05274fb14ae..7f7c1040c68b 100644 --- a/src/test/interpreters/pythonPathUpdater.multiroot.test.ts +++ b/src/test/interpreters/pythonPathUpdater.multiroot.test.ts @@ -5,11 +5,11 @@ 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'; -import { WorkspaceFolderPythonPathUpdaterService } from '../../client/interpreter/configuration/services/workspaceFolderUpdaterService'; const workspaceRoot = path.join(__dirname, '..', '..', '..', 'src', 'test'); const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); From 7debbcf7eec7c9cf10ea770e12d58485347f62bd Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 26 Oct 2017 21:21:01 -0700 Subject: [PATCH 6/8] removed redundant files --- .../providers/setInterpreterProvider.ts | 116 ------------------ .../providers/shebangCodeLensProvider.ts | 72 ----------- src/test/index.ts | 3 +- 3 files changed, 2 insertions(+), 189 deletions(-) delete mode 100644 src/client/providers/setInterpreterProvider.ts delete mode 100644 src/client/providers/shebangCodeLensProvider.ts diff --git a/src/client/providers/setInterpreterProvider.ts b/src/client/providers/setInterpreterProvider.ts deleted file mode 100644 index a14070e34bc5..000000000000 --- a/src/client/providers/setInterpreterProvider.ts +++ /dev/null @@ -1,116 +0,0 @@ -// '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 * as settings from './../common/configSettings'; -// import { ShebangCodeLensProvider } from './shebangCodeLensProvider'; - -// // tslint:disable-next-line:interface-name -// interface PythonPathQuickPickItem extends QuickPickItem { -// path: string; -// } - -// export class SetInterpreterProvider implements Disposable { -// private disposables: Disposable[] = []; -// 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))); -// } -// public dispose() { -// this.disposables.forEach(disposable => disposable.dispose()); -// } -// 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 }; -// } - -// // Ok we have multiple interpreters, get the user to pick a folder. -// // tslint:disable-next-line:no-any prefer-type-cast -// const workspaceFolder = await (window as any).showWorkspaceFolderPick({ placeHolder: 'Select a workspace' }); -// return workspaceFolder ? { folderUri: workspaceFolder.uri, configTarget: ConfigurationTarget.WorkspaceFolder } : undefined; -// } -// private async suggestionToQuickPickItem(suggestion: PythonInterpreter, workspaceUri?: Uri): Promise { -// let detail = suggestion.path; -// if (workspaceUri && suggestion.path.startsWith(workspaceUri.fsPath)) { -// detail = `.${path.sep}${path.relative(workspaceUri.fsPath, suggestion.path)}`; -// } -// return { -// // tslint:disable-next-line:no-non-null-assertion -// label: suggestion.displayName!, -// description: suggestion.companyDisplayName || '', -// detail: detail, -// path: suggestion.path -// }; -// } - -// 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 targetConfig: WorkspacePythonPath; -// if (!setInterpreterGlobally) { -// targetConfig = await this.getWorkspaceToSetPythonPath(); -// if (!targetConfig) { -// return; -// } -// } - -// const resourceUri = targetConfig ? targetConfig.folderUri : undefined; -// const suggestions = await this.getSuggestions(resourceUri); -// let currentPythonPath = settings.PythonSettings.getInstance().pythonPath; -// if (targetConfig && targetConfig.folderUri && currentPythonPath.startsWith(targetConfig.folderUri.fsPath)) { -// currentPythonPath = `.${path.sep}${path.relative(targetConfig.folderUri.fsPath, currentPythonPath)}`; -// } -// const quickPickOptions: QuickPickOptions = { -// matchOnDetail: true, -// matchOnDescription: true, -// placeHolder: `current: ${currentPythonPath}` -// }; - -// const selection = await window.showQuickPick(suggestions, quickPickOptions); -// if (selection !== undefined) { -// await this.interpreterManager.setPythonPath(selection.path, targetConfig); -// } -// } - -// private async setShebangInterpreter(): Promise { -// const shebang = await ShebangCodeLensProvider.detectShebang(window.activeTextEditor.document); -// if (!shebang) { -// return; -// } - -// const pythonPathValue = workspace.getConfiguration('python', window.activeTextEditor.document.uri).inspect('pythonPath'); -// const isGlobalChange = !Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0; -// const workspaceFolder = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri); -// const isWorkspaceChange = Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length === 1; - -// if (isGlobalChange) { -// if (!pythonPathValue || typeof pythonPathValue.globalValue !== 'string' || pythonPathValue.globalValue !== shebang) { -// await this.interpreterManager.setPythonPath(shebang); -// } -// return; -// } - -// if (isWorkspaceChange || !workspaceFolder) { -// if (!pythonPathValue || typeof pythonPathValue.workspaceValue !== 'string' || pythonPathValue.workspaceValue !== shebang) { -// const targetInfo: WorkspacePythonPath = { configTarget: ConfigurationTarget.Workspace, folderUri: workspace.workspaceFolders[0].uri }; -// await this.interpreterManager.setPythonPath(shebang, targetInfo); -// } -// return; -// } - -// if (!pythonPathValue || typeof pythonPathValue.workspaceValue !== 'string' || pythonPathValue.workspaceValue !== shebang) { -// const targetInfo: WorkspacePythonPath = { configTarget: ConfigurationTarget.WorkspaceFolder, folderUri: workspaceFolder.uri }; -// await this.interpreterManager.setPythonPath(shebang, targetInfo); -// } -// } -// } diff --git a/src/client/providers/shebangCodeLensProvider.ts b/src/client/providers/shebangCodeLensProvider.ts deleted file mode 100644 index d792ef89dc1f..000000000000 --- a/src/client/providers/shebangCodeLensProvider.ts +++ /dev/null @@ -1,72 +0,0 @@ -// "use strict"; -// import { IS_WINDOWS } from '../common/utils'; -// import * as vscode from 'vscode'; -// 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; -// } - -// public static async detectShebang(document: TextDocument): Promise { -// let firstLine = document.lineAt(0); -// if (firstLine.isEmptyOrWhitespace) { -// return; -// } - -// if (!firstLine.text.startsWith('#!')) { -// return; -// } - -// const shebang = firstLine.text.substr(2).trim(); -// const pythonPath = await ShebangCodeLensProvider.getFullyQualifiedPathToInterpreter(shebang); -// return typeof pythonPath === 'string' && pythonPath.length > 0 ? pythonPath : undefined; -// } -// private static async getFullyQualifiedPathToInterpreter(pythonPath: string) { -// if (pythonPath.indexOf('bin/env ') >= 0 && !IS_WINDOWS) { -// // 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 = ''; -// command.stdout.on('data', (data) => { -// result += data.toString(); -// }); -// command.on('close', () => { -// resolve(getFirstNonEmptyLineFromMultilineString(result)); -// }); -// }); -// } -// else { -// return new Promise(resolve => { -// child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => { -// resolve(getFirstNonEmptyLineFromMultilineString(stdout)); -// }); -// }); -// } -// } -// } diff --git a/src/test/index.ts b/src/test/index.ts index 64de9103f775..c704c5684564 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -8,6 +8,7 @@ testRunner.configure({ ui: 'tdd', useColors: true, timeout: 25000, - retries: 3 + retries: 3, + grep: 'Python Path Settings Updater' }); module.exports = testRunner; From 643d24b7602e943df5aa39c0224d4013dfce18ad Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Oct 2017 11:50:41 -0700 Subject: [PATCH 7/8] removed unwanted grep --- src/test/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/index.ts b/src/test/index.ts index c704c5684564..64de9103f775 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -8,7 +8,6 @@ testRunner.configure({ ui: 'tdd', useColors: true, timeout: 25000, - retries: 3, - grep: 'Python Path Settings Updater' + retries: 3 }); module.exports = testRunner; From 581be2464f773722fe39486cef62ee3792ae1890 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 30 Oct 2017 15:10:22 -0700 Subject: [PATCH 8/8] remove blank line --- src/client/interpreter/configuration/pythonPathUpdaterService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/interpreter/configuration/pythonPathUpdaterService.ts b/src/client/interpreter/configuration/pythonPathUpdaterService.ts index adeca205553b..b81963f9d8fe 100644 --- a/src/client/interpreter/configuration/pythonPathUpdaterService.ts +++ b/src/client/interpreter/configuration/pythonPathUpdaterService.ts @@ -9,7 +9,6 @@ export class PythonPathUpdaterService { 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