Skip to content

Commit

Permalink
Add progress display
Browse files Browse the repository at this point in the history
  • Loading branch information
DonJayamanne committed Nov 1, 2018
1 parent 97bce59 commit 299909c
Show file tree
Hide file tree
Showing 20 changed files with 218 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/client/activation/interpreterDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<InterpreterData>(cacheKey);
let interpreterChanged = false;
if (interpreterData) {
// Check if interpreter executable changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<FolderVersionPair[]> {
const fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
Expand Down
6 changes: 4 additions & 2 deletions src/client/common/application/applicationShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -67,5 +67,7 @@ export class ApplicationShell implements IApplicationShell {
public showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable<WorkspaceFolder | undefined> {
return window.showWorkspaceFolderPick(options);
}

public withProgress<R>(options: ProgressOptions, task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable<R>): Thenable<R> {
return window.withProgress<R>(options, task);
}
}
27 changes: 24 additions & 3 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -248,6 +248,27 @@ export interface IApplicationShell {
* @return A promise that resolves to the workspace folder or `undefined`.
*/
showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable<WorkspaceFolder | undefined>;

/**
* 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<R>(options: ProgressOptions, task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable<R>): Thenable<R>;
}

export const ICommandManager = Symbol('ICommandManager');
Expand Down
2 changes: 1 addition & 1 deletion src/client/common/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ function getTextEditsInternal(before: string, diffs: [number, string][], startLi
let line = startLine;
let character = 0;
if (line > 0) {
const beforeLines = <string[]>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[] = [];
Expand Down
8 changes: 5 additions & 3 deletions src/client/common/envFileParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
4 changes: 2 additions & 2 deletions src/client/common/installer/channelManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ export class InstallationChannelManager implements IInstallationChannelManager {
}

public async getInstallationChannels(resource?: Uri): Promise<IModuleInstaller[]> {
let installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
const installers = this.serviceContainer.getAll<IModuleInstaller>(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) {
Expand Down
9 changes: 9 additions & 0 deletions src/client/common/utils/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,12 @@ class DeferredImpl<T> implements Deferred<T> {
export function createDeferred<T>(scope: any = null): Deferred<T> {
return new DeferredImpl<T>(scope);
}

export function createDeferredFrom<T>(...promises: Promise<T>[]): Deferred<T> {
const deferred = createDeferred<T>();
Promise.all(promises)
.then(deferred.resolve.bind(deferred))
.catch(deferred.reject.bind(deferred));

return deferred;
}
4 changes: 4 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', '<html><body><h1>{0} is not a valid file name</h1></body></html>');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
13 changes: 9 additions & 4 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -102,7 +102,8 @@ export async function activate(context: ExtensionContext): Promise<IExtensionApi
serviceManager.get<ICodeExecutionManager>(ICodeExecutionManager).registerCommands();
sendStartupTelemetry(activationDeferred.promise, serviceContainer).ignoreErrors();

interpreterManager.refresh()
const workspaceService = serviceContainer.get<IWorkspaceService>(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');
Expand Down Expand Up @@ -214,6 +215,9 @@ function initializeServices(context: ExtensionContext, serviceManager: ServiceMa
const disposables = serviceManager.get<IDisposableRegistry>(IDisposableRegistry);
const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables);
dispatcher.registerEventHandlers();

serviceManager.get<InterpreterLocatorProgressHandler>(InterpreterLocatorProgressHandler).register();
serviceManager.get<IInterpreterLocatorProgressService>(IInterpreterLocatorProgressService).register();
}

async function sendStartupTelemetry(activatedPromise: Promise<void>, serviceContainer: IServiceContainer) {
Expand All @@ -224,12 +228,13 @@ async function sendStartupTelemetry(activatedPromise: Promise<void>, serviceCont
const terminalShellType = terminalHelper.identifyTerminalShell(terminalHelper.getTerminalShellPath());
const condaLocator = serviceContainer.get<ICondaService>(ICondaService);
const interpreterService = serviceContainer.get<IInterpreterService>(IInterpreterService);
const workspaceService = serviceContainer.get<IWorkspaceService>(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<string>(() => ''),
interpreterService.getActiveInterpreter().catch<PythonInterpreter | undefined>(() => undefined),
interpreterService.getInterpreters().catch<PythonInterpreter[]>(() => [])
interpreterService.getInterpreters(mainWorkspaceUri).catch<PythonInterpreter[]>(() => [])
]);
const workspaceService = serviceContainer.get<IWorkspaceService>(IWorkspaceService);
const workspaceFolderCount = workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders!.length : 0;
const pythonVersion = interpreter ? interpreter.version_info.join('.') : undefined;
const interpreterType = interpreter ? interpreter.type : undefined;
Expand Down
15 changes: 14 additions & 1 deletion src/client/interpreter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface IVirtualEnvironmentsSearchPathProvider {
export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService');

export interface IInterpreterLocatorService extends Disposable {
readonly onLocating: Event<Promise<PythonInterpreter[]>>;
getInterpreters(resource?: Uri): Promise<PythonInterpreter[]>;
}

Expand Down Expand Up @@ -83,7 +84,7 @@ export interface IInterpreterService {
autoSetInterpreter(): Promise<void>;
getActiveInterpreter(resource?: Uri): Promise<PythonInterpreter | undefined>;
getInterpreterDetails(pythonPath: string, resoure?: Uri): Promise<undefined | PythonInterpreter>;
refresh(): Promise<void>;
refresh(resource: Uri | undefined): Promise<void>;
initialize(): void;
getDisplayName(interpreter: Partial<PythonInterpreter>): Promise<string>;
shouldAutoSetInterpreter(): Promise<boolean>;
Expand Down Expand Up @@ -126,3 +127,15 @@ export const IInterpreterWatcherBuilder = Symbol('IInterpreterWatcherBuilder');
export interface IInterpreterWatcherBuilder {
getWorkspaceVirtualEnvInterpreterWatcher(resource: Uri | undefined): Promise<IInterpreterWatcher>;
}

export const InterpreterLocatorProgressHandler = Symbol('InterpreterLocatorProgressHandler');
export interface InterpreterLocatorProgressHandler {
register(): void;
}

export const IInterpreterLocatorProgressService = Symbol('IInterpreterLocatorProgressService');
export interface IInterpreterLocatorProgressService {
readonly onRefreshing: Event<void>;
readonly onRefreshed: Event<void>;
register(): void;
}
46 changes: 46 additions & 0 deletions src/client/interpreter/display/progressDisplay.ts
Original file line number Diff line number Diff line change
@@ -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<void> | 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;
});
}
}
3 changes: 2 additions & 1 deletion src/client/interpreter/interpreterService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
14 changes: 12 additions & 2 deletions src/client/interpreter/locators/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -26,14 +26,24 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
private readonly disposables: Disposable[] = [];
private readonly platform: IPlatformService;
private readonly interpreterLocatorHelper: IInterpreterLocatorHelper;

constructor(
@inject(IServiceContainer) private serviceContainer: IServiceContainer
) {
serviceContainer.get<Disposable[]>(IDisposableRegistry).push(this);
this.platform = serviceContainer.get<IPlatformService>(IPlatformService);
this.interpreterLocatorHelper = serviceContainer.get<IInterpreterLocatorHelper>(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<Promise<PythonInterpreter[]>>}
* @memberof PythonInterpreterLocatorService
*/
public get onLocating(): Event<Promise<PythonInterpreter[]>> {
return new EventEmitter<Promise<PythonInterpreter[]>>().event;
}

/**
* Release any held resources.
Expand Down
Loading

0 comments on commit 299909c

Please sign in to comment.