diff --git a/src/client/activation/interpreterDataService.ts b/src/client/activation/interpreterDataService.ts index daa595ebf383..1e082bb171d5 100644 --- a/src/client/activation/interpreterDataService.ts +++ b/src/client/activation/interpreterDataService.ts @@ -40,7 +40,7 @@ export class InterpreterDataService { } const cacheKey = `InterpreterData-${interpreterPath}`; - let interpreterData = this.context.globalState.get(cacheKey) as InterpreterData; + let interpreterData = this.context.globalState.get(cacheKey); let interpreterChanged = false; if (interpreterData) { // Check if interpreter executable changed diff --git a/src/client/activation/languageServer/languageServerFolderService.ts b/src/client/activation/languageServer/languageServerFolderService.ts index 20e88669462d..57be87a36c50 100644 --- a/src/client/activation/languageServer/languageServerFolderService.ts +++ b/src/client/activation/languageServer/languageServerFolderService.ts @@ -65,8 +65,8 @@ export class LanguageServerFolderService implements ILanguageServerFolderService if (dirs.length === 0) { return; } - const sortedDirs = dirs.sort((a, b) => a.version.compare(b.version)); - return sortedDirs[sortedDirs.length - 1]; + dirs.sort((a, b) => a.version.compare(b.version)); + return dirs[dirs.length - 1]; } public async getExistingLanguageServerDirectories(): Promise { const fs = this.serviceContainer.get(IFileSystem); diff --git a/src/client/common/application/applicationShell.ts b/src/client/common/application/applicationShell.ts index e956becd2204..a66e5c4e9594 100644 --- a/src/client/common/application/applicationShell.ts +++ b/src/client/common/application/applicationShell.ts @@ -6,7 +6,7 @@ const opn = require('opn'); import { injectable } from 'inversify'; -import { CancellationToken, Disposable, InputBoxOptions, MessageItem, MessageOptions, OpenDialogOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions, StatusBarAlignment, StatusBarItem, Uri, window, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; +import { CancellationToken, Disposable, InputBoxOptions, MessageItem, MessageOptions, OpenDialogOptions, Progress, ProgressOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions, StatusBarAlignment, StatusBarItem, Uri, window, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; import { IApplicationShell } from './types'; @injectable() @@ -67,5 +67,7 @@ export class ApplicationShell implements IApplicationShell { public showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable { return window.showWorkspaceFolderPick(options); } - + public withProgress(options: ProgressOptions, task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable): Thenable { + return window.withProgress(options, task); + } } diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index 1e045842ed99..070c4a6e0f67 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -7,9 +7,9 @@ import { Breakpoint, BreakpointsChangeEvent, CancellationToken, ConfigurationChangeEvent, DebugConfiguration, DebugConfigurationProvider, DebugConsole, DebugSession, DebugSessionCustomEvent, Disposable, Event, FileSystemWatcher, GlobPattern, InputBoxOptions, MessageItem, - MessageOptions, OpenDialogOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions, StatusBarAlignment, StatusBarItem, - Terminal, TerminalOptions, TextDocument, TextDocumentShowOptions, TextEditor, TextEditorEdit, TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, - TextEditorViewColumnChangeEvent, Uri, ViewColumn, WorkspaceConfiguration, WorkspaceEdit, WorkspaceFolder, WorkspaceFolderPickOptions, WorkspaceFoldersChangeEvent + MessageOptions, OpenDialogOptions, Progress, ProgressOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions, + StatusBarAlignment, StatusBarItem, Terminal, TerminalOptions, TextDocument, TextDocumentShowOptions, TextEditor, TextEditorEdit, + TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, TextEditorViewColumnChangeEvent, Uri, ViewColumn, WorkspaceConfiguration, WorkspaceEdit, WorkspaceFolder, WorkspaceFolderPickOptions, WorkspaceFoldersChangeEvent } from 'vscode'; export const IApplicationShell = Symbol('IApplicationShell'); @@ -248,6 +248,27 @@ export interface IApplicationShell { * @return A promise that resolves to the workspace folder or `undefined`. */ showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable; + + /** + * Show progress in the editor. Progress is shown while running the given callback + * and while the promise it returned isn't resolved nor rejected. The location at which + * progress should show (and other details) is defined via the passed [`ProgressOptions`](#ProgressOptions). + * + * @param task A callback returning a promise. Progress state can be reported with + * the provided [progress](#Progress)-object. + * + * To report discrete progress, use `increment` to indicate how much work has been completed. Each call with + * a `increment` value will be summed up and reflected as overall progress until 100% is reached (a value of + * e.g. `10` accounts for `10%` of work done). + * Note that currently only `ProgressLocation.Notification` is capable of showing discrete progress. + * + * To monitor if the operation has been cancelled by the user, use the provided [`CancellationToken`](#CancellationToken). + * Note that currently only `ProgressLocation.Notification` is supporting to show a cancel button to cancel the + * long running operation. + * + * @return The thenable the task-callback returned. + */ + withProgress(options: ProgressOptions, task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable): Thenable; } export const ICommandManager = Symbol('ICommandManager'); diff --git a/src/client/common/editor.ts b/src/client/common/editor.ts index 8f70a2714f48..696b3b90fe63 100644 --- a/src/client/common/editor.ts +++ b/src/client/common/editor.ts @@ -158,7 +158,7 @@ function getTextEditsInternal(before: string, diffs: [number, string][], startLi let line = startLine; let character = 0; if (line > 0) { - const beforeLines = before.split(/\r?\n/g); + const beforeLines = before.split(/\r?\n/g); beforeLines.filter((l, i) => i < line).forEach(l => character += l.length + NEW_LINE_LENGTH); } const edits: Edit[] = []; diff --git a/src/client/common/envFileParser.ts b/src/client/common/envFileParser.ts index 18ac1655ee51..52d2234c21b8 100644 --- a/src/client/common/envFileParser.ts +++ b/src/client/common/envFileParser.ts @@ -9,7 +9,7 @@ function parseEnvironmentVariables(contents: string): EnvironmentVariables | und return undefined; } - const env = {} as EnvironmentVariables; + const env: EnvironmentVariables = {}; contents.split('\n').forEach(line => { const match = line.match(/^\s*([\w\.\-]+)\s*=\s*(.*)?\s*$/); if (match !== null) { @@ -40,7 +40,9 @@ export function parseEnvFile(envFile: string, mergeWithProcessEnvVars: boolean = export function mergeEnvVariables(targetEnvVars: EnvironmentVariables, sourceEnvVars: EnvironmentVariables = process.env): EnvironmentVariables { const service = new EnvironmentVariablesService(new PathUtils(IS_WINDOWS)); service.mergeVariables(sourceEnvVars, targetEnvVars); - service.appendPythonPath(targetEnvVars, sourceEnvVars.PYTHONPATH); + if (sourceEnvVars.PYTHONPATH) { + service.appendPythonPath(targetEnvVars, sourceEnvVars.PYTHONPATH); + } return targetEnvVars; } @@ -57,6 +59,6 @@ export function mergePythonPath(env: EnvironmentVariables, currentPythonPath: st return env; } const service = new EnvironmentVariablesService(new PathUtils(IS_WINDOWS)); - service.appendPythonPath(env, currentPythonPath!); + service.appendPythonPath(env, currentPythonPath); return env; } diff --git a/src/client/common/installer/channelManager.ts b/src/client/common/installer/channelManager.ts index 7c5780d9152e..d145f6216b10 100644 --- a/src/client/common/installer/channelManager.ts +++ b/src/client/common/installer/channelManager.ts @@ -41,13 +41,13 @@ export class InstallationChannelManager implements IInstallationChannelManager { } public async getInstallationChannels(resource?: Uri): Promise { - let installers = this.serviceContainer.getAll(IModuleInstaller); + const installers = this.serviceContainer.getAll(IModuleInstaller); const supportedInstallers: IModuleInstaller[] = []; if (installers.length === 0) { return []; } // group by priority and pick supported from the highest priority - installers = installers.sort((a, b) => b.priority - a.priority); + installers.sort((a, b) => b.priority - a.priority); let currentPri = installers[0].priority; for (const mi of installers) { if (mi.priority !== currentPri) { diff --git a/src/client/common/utils/async.ts b/src/client/common/utils/async.ts index ed7d6b4869cf..9c5c53dc7892 100644 --- a/src/client/common/utils/async.ts +++ b/src/client/common/utils/async.ts @@ -64,3 +64,12 @@ class DeferredImpl implements Deferred { export function createDeferred(scope: any = null): Deferred { return new DeferredImpl(scope); } + +export function createDeferredFrom(...promises: Promise[]): Deferred { + const deferred = createDeferred(); + Promise.all(promises) + .then(deferred.resolve.bind(deferred)) + .catch(deferred.reject.bind(deferred)); + + return deferred; +} diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 107a1c0a0d51..b3a5a5bad690 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -14,6 +14,10 @@ export namespace LanguageServiceSurveyBanner { export const bannerLabelNo = localize('LanguageServiceSurveyBanner.bannerLabelNo', 'No, thanks'); } +export namespace Interpreters { + export const refreshing = localize('Interpreters.RefreshingInterpreters', 'Refreshing Python Interpreters'); +} + export namespace DataScience { export const historyTitle = localize('DataScience.historyTitle', 'Python Interactive'); export const badWebPanelFormatString = localize('DataScience.badWebPanelFormatString', '

{0} is not a valid file name

'); diff --git a/src/client/debugger/extension/configProviders/baseProvider.ts b/src/client/debugger/extension/configProviders/baseProvider.ts index 04d34d0a15fc..ad7a65769a07 100644 --- a/src/client/debugger/extension/configProviders/baseProvider.ts +++ b/src/client/debugger/extension/configProviders/baseProvider.ts @@ -68,7 +68,7 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro debugConfiguration.cwd = workspaceFolder.fsPath; } if (typeof debugConfiguration.envFile !== 'string' && workspaceFolder) { - const envFile = workspaceFolder ? path.join(workspaceFolder.fsPath, '.env') : ''; + const envFile = path.join(workspaceFolder.fsPath, '.env'); debugConfiguration.envFile = envFile; } if (typeof debugConfiguration.stopOnEntry !== 'boolean') { diff --git a/src/client/extension.ts b/src/client/extension.ts index 1a609546f089..084608a018aa 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -41,7 +41,7 @@ import { registerTypes as debugConfigurationRegisterTypes } from './debugger/ext import { IDebugConfigurationProvider, IDebuggerBanner } from './debugger/extension/types'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; import { IInterpreterSelector } from './interpreter/configuration/types'; -import { ICondaService, IInterpreterService, PythonInterpreter } from './interpreter/contracts'; +import { ICondaService, IInterpreterLocatorProgressService, IInterpreterService, InterpreterLocatorProgressHandler, PythonInterpreter } from './interpreter/contracts'; import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; import { ServiceContainer } from './ioc/container'; import { ServiceManager } from './ioc/serviceManager'; @@ -102,7 +102,8 @@ export async function activate(context: ExtensionContext): Promise(ICodeExecutionManager).registerCommands(); sendStartupTelemetry(activationDeferred.promise, serviceContainer).ignoreErrors(); - interpreterManager.refresh() + const workspaceService = serviceContainer.get(IWorkspaceService); + interpreterManager.refresh(workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders![0].uri : undefined) .catch(ex => console.error('Python Extension: interpreterManager.refresh', ex)); const jupyterExtension = extensions.getExtension('donjayamanne.jupyter'); @@ -214,6 +215,9 @@ function initializeServices(context: ExtensionContext, serviceManager: ServiceMa const disposables = serviceManager.get(IDisposableRegistry); const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables); dispatcher.registerEventHandlers(); + + serviceManager.get(InterpreterLocatorProgressHandler).register(); + serviceManager.get(IInterpreterLocatorProgressService).register(); } async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { @@ -224,12 +228,13 @@ async function sendStartupTelemetry(activatedPromise: Promise, serviceCont const terminalShellType = terminalHelper.identifyTerminalShell(terminalHelper.getTerminalShellPath()); const condaLocator = serviceContainer.get(ICondaService); const interpreterService = serviceContainer.get(IInterpreterService); + const workspaceService = serviceContainer.get(IWorkspaceService); + const mainWorkspaceUri = workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders![0].uri : undefined; const [condaVersion, interpreter, interpreters] = await Promise.all([ condaLocator.getCondaVersion().then(ver => ver ? ver.raw : '').catch(() => ''), interpreterService.getActiveInterpreter().catch(() => undefined), - interpreterService.getInterpreters().catch(() => []) + interpreterService.getInterpreters(mainWorkspaceUri).catch(() => []) ]); - const workspaceService = serviceContainer.get(IWorkspaceService); const workspaceFolderCount = workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders!.length : 0; const pythonVersion = interpreter ? interpreter.version_info.join('.') : undefined; const interpreterType = interpreter ? interpreter.type : undefined; diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index e687ab557bd2..932b3abceb4c 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -28,6 +28,7 @@ export interface IVirtualEnvironmentsSearchPathProvider { export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService'); export interface IInterpreterLocatorService extends Disposable { + readonly onLocating: Event>; getInterpreters(resource?: Uri): Promise; } @@ -83,7 +84,7 @@ export interface IInterpreterService { autoSetInterpreter(): Promise; getActiveInterpreter(resource?: Uri): Promise; getInterpreterDetails(pythonPath: string, resoure?: Uri): Promise; - refresh(): Promise; + refresh(resource: Uri | undefined): Promise; initialize(): void; getDisplayName(interpreter: Partial): Promise; shouldAutoSetInterpreter(): Promise; @@ -126,3 +127,15 @@ export const IInterpreterWatcherBuilder = Symbol('IInterpreterWatcherBuilder'); export interface IInterpreterWatcherBuilder { getWorkspaceVirtualEnvInterpreterWatcher(resource: Uri | undefined): Promise; } + +export const InterpreterLocatorProgressHandler = Symbol('InterpreterLocatorProgressHandler'); +export interface InterpreterLocatorProgressHandler { + register(): void; +} + +export const IInterpreterLocatorProgressService = Symbol('IInterpreterLocatorProgressService'); +export interface IInterpreterLocatorProgressService { + readonly onRefreshing: Event; + readonly onRefreshed: Event; + register(): void; +} diff --git a/src/client/interpreter/display/progressDisplay.ts b/src/client/interpreter/display/progressDisplay.ts new file mode 100644 index 000000000000..07d0a1da6ddb --- /dev/null +++ b/src/client/interpreter/display/progressDisplay.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { Disposable, ProgressLocation, ProgressOptions } from 'vscode'; +import { IApplicationShell } from '../../common/application/types'; +import { traceVerbose } from '../../common/logger'; +import { IDisposableRegistry } from '../../common/types'; +import { createDeferred, Deferred } from '../../common/utils/async'; +import { Interpreters } from '../../common/utils/localize'; +import { IInterpreterLocatorProgressService, InterpreterLocatorProgressHandler } from '../contracts'; + +const progressOptions: ProgressOptions = { location: ProgressLocation.Window, title: Interpreters.refreshing() }; + +@injectable() +export class InterpreterLocatorProgressStatubarHandler implements InterpreterLocatorProgressHandler { + private deferred: Deferred | undefined; + constructor(@inject(IApplicationShell) private readonly shell: IApplicationShell, + @inject(IInterpreterLocatorProgressService) private readonly progressService: IInterpreterLocatorProgressService, + @inject(IDisposableRegistry) private readonly disposables: Disposable[]) { } + public register() { + this.progressService.onRefreshing(() => this.showProgress(), this, this.disposables); + this.progressService.onRefreshed(() => this.hideProgress(), this, this.disposables); + } + @traceVerbose('Display locator refreshing progress') + private showProgress(): void { + if (!this.deferred) { + this.createProgress(); + } + } + @traceVerbose('Hide locator refreshing progress') + private hideProgress(): void { + if (this.deferred) { + this.deferred.resolve(); + this.deferred = undefined; + } + } + private createProgress() { + this.shell.withProgress(progressOptions, () => { + this.deferred = createDeferred(); + return this.deferred.promise; + }); + } +} diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 11e1ebeeff65..430e1c4140a4 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -81,7 +81,8 @@ export class InterpreterService implements Disposable, IInterpreterService { return; } // Always pick the highest version by default. - const pythonPath = interpretersInWorkspace.sort((a, b) => a.version! > b.version! ? 1 : -1)[0].path; + interpretersInWorkspace.sort((a, b) => a.version! > b.version! ? 1 : -1); + const pythonPath = interpretersInWorkspace[0].path; // Ensure this new environment is at the same level as the current workspace. // In windows the interpreter is under scripts/python.exe on linux it is under bin/python. // Meaning the sub directory must be either scripts, bin or other (but only one level deep). diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index 521b099a588a..b922b1376252 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -1,6 +1,6 @@ import { inject, injectable } from 'inversify'; import * as _ from 'lodash'; -import { Disposable, Uri } from 'vscode'; +import { Disposable, Event, EventEmitter, Uri } from 'vscode'; import { IPlatformService } from '../../common/platform/types'; import { IDisposableRegistry } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; @@ -26,7 +26,6 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi private readonly disposables: Disposable[] = []; private readonly platform: IPlatformService; private readonly interpreterLocatorHelper: IInterpreterLocatorHelper; - constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer ) { @@ -34,6 +33,17 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi this.platform = serviceContainer.get(IPlatformService); this.interpreterLocatorHelper = serviceContainer.get(IInterpreterLocatorHelper); } + /** + * This class should never emit events when we're locating. + * The events will be fired by the indivitual locators retrieved in `getLocators`. + * + * @readonly + * @type {Event>} + * @memberof PythonInterpreterLocatorService + */ + public get onLocating(): Event> { + return new EventEmitter>().event; + } /** * Release any held resources. diff --git a/src/client/interpreter/locators/progressService.ts b/src/client/interpreter/locators/progressService.ts new file mode 100644 index 000000000000..cddec2f665c3 --- /dev/null +++ b/src/client/interpreter/locators/progressService.ts @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { Disposable, Event, EventEmitter } from 'vscode'; +import { traceVerbose } from '../../common/logger'; +import { IDisposableRegistry } from '../../common/types'; +import { createDeferredFrom, Deferred } from '../../common/utils/async'; +import { noop } from '../../common/utils/misc'; +import { IServiceContainer } from '../../ioc/types'; +import { IInterpreterLocatorProgressService, IInterpreterLocatorService, PythonInterpreter } from '../contracts'; + +@injectable() +export class InterpreterLocatorProgressService implements IInterpreterLocatorProgressService { + private deferreds: Deferred[] = []; + private readonly refreshing = new EventEmitter(); + private readonly refreshed = new EventEmitter(); + private readonly locators: IInterpreterLocatorService[] = []; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IDisposableRegistry) private readonly disposables: Disposable[]) { + this.locators = serviceContainer.getAll(IInterpreterLocatorService); + } + + public get onRefreshing(): Event { + return this.refreshing.event; + } + public get onRefreshed(): Event { + return this.refreshed.event; + } + public register(): void { + this.locators.forEach(locator => { + locator.onLocating(this.handleProgress, this, this.disposables); + }); + } + @traceVerbose('Detected refreshing of Interpreters') + private handleProgress(promise: Promise) { + this.deferreds.push(createDeferredFrom(promise)); + this.notifyRefreshing(); + this.checkProgress(); + } + @traceVerbose('All locators have completed locating') + private notifyCompleted() { + this.refreshed.fire(); + } + @traceVerbose('Notify locators are locating') + private notifyRefreshing() { + this.refreshing.fire(); + } + @traceVerbose('Checking whether locactors have completed locating') + private checkProgress() { + if (this.areAllItemsCcomplete()) { + return this.notifyCompleted(); + } + Promise.all(this.deferreds.map(item => item.promise)) + .catch(noop) + .then(() => this.checkProgress()) + .ignoreErrors(); + } + private areAllItemsCcomplete() { + this.deferreds = this.deferreds.filter(item => !item.completed); + return this.deferreds.length === 0; + } +} diff --git a/src/client/interpreter/locators/services/cacheableLocatorService.ts b/src/client/interpreter/locators/services/cacheableLocatorService.ts index ecac28ee6d33..7e2d8bed685b 100644 --- a/src/client/interpreter/locators/services/cacheableLocatorService.ts +++ b/src/client/interpreter/locators/services/cacheableLocatorService.ts @@ -5,7 +5,7 @@ import { injectable, unmanaged } from 'inversify'; import * as md5 from 'md5'; -import { Disposable, Uri } from 'vscode'; +import { Disposable, Event, EventEmitter, Uri } from 'vscode'; import { IWorkspaceService } from '../../../common/application/types'; import '../../../common/extensions'; import { Logger } from '../../../common/logger'; @@ -19,11 +19,15 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ private readonly promisesPerResource = new Map>(); private readonly handlersAddedToResource = new Set(); private readonly cacheKeyPrefix: string; + private readonly locating = new EventEmitter>(); constructor(@unmanaged() name: string, @unmanaged() protected readonly serviceContainer: IServiceContainer, @unmanaged() private cachePerWorkspace: boolean = false) { this.cacheKeyPrefix = `INTERPRETERS_CACHE_v2_${name}`; } + public get onLocating(): Event> { + return this.locating.event; + } public abstract dispose(); public async getInterpreters(resource?: Uri): Promise { const cacheKey = this.getCacheKey(resource); @@ -42,6 +46,8 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ deferred!.resolve(items); }) .catch(ex => deferred!.reject(ex)); + + this.locating.fire(deferred.promise); } if (deferred.completed) { return deferred.promise; diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index e3828d174dd2..12ea7771f6bd 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -17,6 +17,7 @@ import { IInterpreterDisplay, IInterpreterHelper, IInterpreterLocatorHelper, + IInterpreterLocatorProgressService, IInterpreterLocatorService, IInterpreterService, IInterpreterVersionService, @@ -24,6 +25,7 @@ import { IInterpreterWatcherBuilder, IKnownSearchPathsForInterpreters, INTERPRETER_LOCATOR_SERVICE, + InterpreterLocatorProgressHandler, IPipEnvService, IShebangCodeLensProvider, IVirtualEnvironmentsSearchPathProvider, @@ -33,12 +35,14 @@ import { WORKSPACE_VIRTUAL_ENV_SERVICE } from './contracts'; import { InterpreterDisplay } from './display'; +import { InterpreterLocatorProgressStatubarHandler } from './display/progressDisplay'; import { ShebangCodeLensProvider } from './display/shebangCodeLensProvider'; import { InterpreterHelper } from './helpers'; import { InterpreterService } from './interpreterService'; import { InterpreterVersionService } from './interpreterVersion'; import { InterpreterLocatorHelper } from './locators/helpers'; import { PythonInterpreterLocatorService } from './locators/index'; +import { InterpreterLocatorProgressService } from './locators/progressService'; import { CondaEnvFileService } from './locators/services/condaEnvFileService'; import { CondaEnvService } from './locators/services/condaEnvService'; import { CondaService } from './locators/services/condaService'; @@ -90,4 +94,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IInterpreterHelper, InterpreterHelper); serviceManager.addSingleton(IInterpreterLocatorHelper, InterpreterLocatorHelper); serviceManager.addSingleton(IInterpreterComparer, InterpreterComparer); + + serviceManager.addSingleton(InterpreterLocatorProgressHandler, InterpreterLocatorProgressStatubarHandler); + serviceManager.addSingleton(IInterpreterLocatorProgressService, InterpreterLocatorProgressService); } diff --git a/src/client/typeFormatters/dispatcher.ts b/src/client/typeFormatters/dispatcher.ts index 0470b28c902d..86c22bc60d47 100644 --- a/src/client/typeFormatters/dispatcher.ts +++ b/src/client/typeFormatters/dispatcher.ts @@ -23,8 +23,8 @@ export class OnTypeFormattingDispatcher implements OnTypeFormattingEditProvider } public getTriggerCharacters(): { first: string; more: string[] } | undefined { - let keys = Object.keys(this.providers); - keys = keys.sort(); // Make output deterministic + const keys = Object.keys(this.providers); + keys.sort(); // Make output deterministic const first = keys.shift(); diff --git a/src/client/unittests/unittest/helper.ts b/src/client/unittests/unittest/helper.ts index a97aed3102cf..f89c4c287b39 100644 --- a/src/client/unittests/unittest/helper.ts +++ b/src/client/unittests/unittest/helper.ts @@ -30,7 +30,7 @@ export class UnitTestHelper implements IUnitTestHelper { const testIds: string[] = []; if (testsToRun && testsToRun.testFolder) { // Get test ids of files in these folders. - testsToRun.testFolder.map(folder => { + testsToRun.testFolder.forEach(folder => { tests.testFiles.forEach(f => { if (f.fullPath.startsWith(folder.name)) { testIds.push(f.nameToRun);