Skip to content

Commit 35ab321

Browse files
authored
Progress indicator when loading interpreters and speed up detection of new virtual envs in workspace (#3079)
For #656 When extension is refreshing the list of interpreters, we'll display a progress message Refreshing Python Interpreters Add a progress label for this.
1 parent 2510061 commit 35ab321

30 files changed

Lines changed: 513 additions & 70 deletions

package.nls.json

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,24 +59,26 @@
5959
"LanguageServiceSurveyBanner.bannerMessage": "Can you please take 2 minutes to tell us how the Python Language Server is working for you?",
6060
"LanguageServiceSurveyBanner.bannerLabelYes": "Yes, take survey now",
6161
"LanguageServiceSurveyBanner.bannerLabelNo": "No, thanks",
62-
"DataScience.unknownMimeType" : "Unknown mime type for data",
63-
"DataScience.historyTitle" : "Python Interactive",
62+
"DataScience.unknownMimeType": "Unknown mime type for data",
63+
"DataScience.historyTitle": "Python Interactive",
6464
"DataScience.badWebPanelFormatString": "<html><body><h1>{0} is not a valid file name</h1></body></html>",
65-
"DataScience.sessionDisposed" : "Cannot execute code, session has been disposed.",
66-
"DataScience.exportDialogTitle" : "Export to Jupyter Notebook",
67-
"DataScience.exportDialogFilter" : "Jupyter Notebooks",
68-
"DataScience.exportDialogComplete" : "Notebook written to {0}",
69-
"DataScience.exportDialogFailed" : "Failed to export notebook. {0}",
70-
"DataScience.exportOpenQuestion" : "Open in browser",
71-
"DataScience.collapseInputTooltip" : "Collapse input block",
72-
"DataScience.importDialogTitle" : "Import Jupyter Notebook",
73-
"DataScience.importDialogFilter" : "Jupyter Notebooks",
74-
"DataScience.notebookCheckForImportYes" : "Import",
75-
"DataScience.notebookCheckForImportNo" : "Later",
76-
"DataScience.notebookCheckForImportDontAskAgain" : "Don't Ask Again",
77-
"DataScience.notebookCheckForImportTitle" : "Do you want to import the Jupyter Notebook into Python code?",
78-
"DataScience.jupyterNotSupported" : "Jupyter is not installed",
79-
"DataScience.jupyterNbConvertNotSupported" : "Jupyter nbconvert is not installed",
80-
"DataScience.importingFormat" : "Importing {0}",
81-
"DataScience.startingJupyter" : "Starting Jupyter backend"
65+
"DataScience.sessionDisposed": "Cannot execute code, session has been disposed.",
66+
"DataScience.exportDialogTitle": "Export to Jupyter Notebook",
67+
"DataScience.exportDialogFilter": "Jupyter Notebooks",
68+
"DataScience.exportDialogComplete": "Notebook written to {0}",
69+
"DataScience.exportDialogFailed": "Failed to export notebook. {0}",
70+
"DataScience.exportOpenQuestion": "Open in browser",
71+
"DataScience.collapseInputTooltip": "Collapse input block",
72+
"DataScience.importDialogTitle": "Import Jupyter Notebook",
73+
"DataScience.importDialogFilter": "Jupyter Notebooks",
74+
"DataScience.notebookCheckForImportYes": "Import",
75+
"DataScience.notebookCheckForImportNo": "Later",
76+
"DataScience.notebookCheckForImportDontAskAgain": "Don't Ask Again",
77+
"DataScience.notebookCheckForImportTitle": "Do you want to import the Jupyter Notebook into Python code?",
78+
"DataScience.jupyterNotSupported": "Jupyter is not installed",
79+
"DataScience.jupyterNbConvertNotSupported": "Jupyter nbconvert is not installed",
80+
"DataScience.importingFormat": "Importing {0}",
81+
"DataScience.startingJupyter": "Starting Jupyter backend",
82+
"Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters",
83+
"Interpreters.LoadingInterpreters": "Loading Python Interpreters"
8284
}

src/client/activation/interpreterDataService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class InterpreterDataService {
4040
}
4141

4242
const cacheKey = `InterpreterData-${interpreterPath}`;
43-
let interpreterData = this.context.globalState.get(cacheKey) as InterpreterData;
43+
let interpreterData = this.context.globalState.get<InterpreterData>(cacheKey);
4444
let interpreterChanged = false;
4545
if (interpreterData) {
4646
// Check if interpreter executable changed

src/client/activation/languageServer/languageServerFolderService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ export class LanguageServerFolderService implements ILanguageServerFolderService
6565
if (dirs.length === 0) {
6666
return;
6767
}
68-
const sortedDirs = dirs.sort((a, b) => a.version.compare(b.version));
69-
return sortedDirs[sortedDirs.length - 1];
68+
dirs.sort((a, b) => a.version.compare(b.version));
69+
return dirs[dirs.length - 1];
7070
}
7171
public async getExistingLanguageServerDirectories(): Promise<FolderVersionPair[]> {
7272
const fs = this.serviceContainer.get<IFileSystem>(IFileSystem);

src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { DiagnosticCodes } from '../constants';
1616
import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler';
1717
import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService, IInvalidPythonPathInDebuggerService } from '../types';
1818

19-
const InvalidPythonPathInDebuggerMessage = 'You need to select a Python interpreter before you start debugging.\n\nTip: click on "Select Python Environment" in the status bar.';
19+
const InvalidPythonPathInDebuggerMessage = 'You need to select a Python interpreter before you start debugging.\n\nTip: click on "Select Python Interpreter" in the status bar.';
2020

2121
export class InvalidPythonPathInDebuggerDiagnostic extends BaseDiagnostic {
2222
constructor() {

src/client/common/application/applicationShell.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
const opn = require('opn');
77

88
import { injectable } from 'inversify';
9-
import { CancellationToken, Disposable, InputBoxOptions, MessageItem, MessageOptions, OpenDialogOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions, StatusBarAlignment, StatusBarItem, Uri, window, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode';
9+
import { CancellationToken, Disposable, InputBoxOptions, MessageItem, MessageOptions, OpenDialogOptions, Progress, ProgressOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions, StatusBarAlignment, StatusBarItem, Uri, window, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode';
1010
import { IApplicationShell } from './types';
1111

1212
@injectable()
@@ -67,5 +67,7 @@ export class ApplicationShell implements IApplicationShell {
6767
public showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable<WorkspaceFolder | undefined> {
6868
return window.showWorkspaceFolderPick(options);
6969
}
70-
70+
public withProgress<R>(options: ProgressOptions, task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable<R>): Thenable<R> {
71+
return window.withProgress<R>(options, task);
72+
}
7173
}

src/client/common/application/types.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import {
88
Breakpoint, BreakpointsChangeEvent, CancellationToken, ConfigurationChangeEvent, DebugConfiguration, DebugConfigurationProvider, DebugConsole, DebugSession, DebugSessionCustomEvent, Disposable,
99
Event, FileSystemWatcher, GlobPattern, InputBoxOptions, MessageItem,
10-
MessageOptions, OpenDialogOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions, StatusBarAlignment, StatusBarItem,
11-
Terminal, TerminalOptions, TextDocument, TextDocumentShowOptions, TextEditor, TextEditorEdit, TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent,
12-
TextEditorViewColumnChangeEvent, Uri, ViewColumn, WorkspaceConfiguration, WorkspaceEdit, WorkspaceFolder, WorkspaceFolderPickOptions, WorkspaceFoldersChangeEvent
10+
MessageOptions, OpenDialogOptions, Progress, ProgressOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions,
11+
StatusBarAlignment, StatusBarItem, Terminal, TerminalOptions, TextDocument, TextDocumentShowOptions, TextEditor, TextEditorEdit,
12+
TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, TextEditorViewColumnChangeEvent, Uri, ViewColumn, WorkspaceConfiguration, WorkspaceEdit, WorkspaceFolder, WorkspaceFolderPickOptions, WorkspaceFoldersChangeEvent
1313
} from 'vscode';
1414

1515
export const IApplicationShell = Symbol('IApplicationShell');
@@ -248,6 +248,27 @@ export interface IApplicationShell {
248248
* @return A promise that resolves to the workspace folder or `undefined`.
249249
*/
250250
showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable<WorkspaceFolder | undefined>;
251+
252+
/**
253+
* Show progress in the editor. Progress is shown while running the given callback
254+
* and while the promise it returned isn't resolved nor rejected. The location at which
255+
* progress should show (and other details) is defined via the passed [`ProgressOptions`](#ProgressOptions).
256+
*
257+
* @param task A callback returning a promise. Progress state can be reported with
258+
* the provided [progress](#Progress)-object.
259+
*
260+
* To report discrete progress, use `increment` to indicate how much work has been completed. Each call with
261+
* a `increment` value will be summed up and reflected as overall progress until 100% is reached (a value of
262+
* e.g. `10` accounts for `10%` of work done).
263+
* Note that currently only `ProgressLocation.Notification` is capable of showing discrete progress.
264+
*
265+
* To monitor if the operation has been cancelled by the user, use the provided [`CancellationToken`](#CancellationToken).
266+
* Note that currently only `ProgressLocation.Notification` is supporting to show a cancel button to cancel the
267+
* long running operation.
268+
*
269+
* @return The thenable the task-callback returned.
270+
*/
271+
withProgress<R>(options: ProgressOptions, task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable<R>): Thenable<R>;
251272
}
252273

253274
export const ICommandManager = Symbol('ICommandManager');

src/client/common/editor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ function getTextEditsInternal(before: string, diffs: [number, string][], startLi
158158
let line = startLine;
159159
let character = 0;
160160
if (line > 0) {
161-
const beforeLines = <string[]>before.split(/\r?\n/g);
161+
const beforeLines = before.split(/\r?\n/g);
162162
beforeLines.filter((l, i) => i < line).forEach(l => character += l.length + NEW_LINE_LENGTH);
163163
}
164164
const edits: Edit[] = [];

src/client/common/envFileParser.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function parseEnvironmentVariables(contents: string): EnvironmentVariables | und
99
return undefined;
1010
}
1111

12-
const env = {} as EnvironmentVariables;
12+
const env: EnvironmentVariables = {};
1313
contents.split('\n').forEach(line => {
1414
const match = line.match(/^\s*([\w\.\-]+)\s*=\s*(.*)?\s*$/);
1515
if (match !== null) {
@@ -40,7 +40,9 @@ export function parseEnvFile(envFile: string, mergeWithProcessEnvVars: boolean =
4040
export function mergeEnvVariables(targetEnvVars: EnvironmentVariables, sourceEnvVars: EnvironmentVariables = process.env): EnvironmentVariables {
4141
const service = new EnvironmentVariablesService(new PathUtils(IS_WINDOWS));
4242
service.mergeVariables(sourceEnvVars, targetEnvVars);
43-
service.appendPythonPath(targetEnvVars, sourceEnvVars.PYTHONPATH);
43+
if (sourceEnvVars.PYTHONPATH) {
44+
service.appendPythonPath(targetEnvVars, sourceEnvVars.PYTHONPATH);
45+
}
4446
return targetEnvVars;
4547
}
4648

@@ -57,6 +59,6 @@ export function mergePythonPath(env: EnvironmentVariables, currentPythonPath: st
5759
return env;
5860
}
5961
const service = new EnvironmentVariablesService(new PathUtils(IS_WINDOWS));
60-
service.appendPythonPath(env, currentPythonPath!);
62+
service.appendPythonPath(env, currentPythonPath);
6163
return env;
6264
}

src/client/common/installer/channelManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ export class InstallationChannelManager implements IInstallationChannelManager {
4141
}
4242

4343
public async getInstallationChannels(resource?: Uri): Promise<IModuleInstaller[]> {
44-
let installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
44+
const installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
4545
const supportedInstallers: IModuleInstaller[] = [];
4646
if (installers.length === 0) {
4747
return [];
4848
}
4949
// group by priority and pick supported from the highest priority
50-
installers = installers.sort((a, b) => b.priority - a.priority);
50+
installers.sort((a, b) => b.priority - a.priority);
5151
let currentPri = installers[0].priority;
5252
for (const mi of installers) {
5353
if (mi.priority !== currentPri) {

src/client/common/utils/async.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,12 @@ class DeferredImpl<T> implements Deferred<T> {
6464
export function createDeferred<T>(scope: any = null): Deferred<T> {
6565
return new DeferredImpl<T>(scope);
6666
}
67+
68+
export function createDeferredFrom<T>(...promises: Promise<T>[]): Deferred<T> {
69+
const deferred = createDeferred<T>();
70+
Promise.all(promises)
71+
.then(deferred.resolve.bind(deferred))
72+
.catch(deferred.reject.bind(deferred));
73+
74+
return deferred;
75+
}

0 commit comments

Comments
 (0)