Skip to content

Commit 00ad77b

Browse files
authored
Display a progress message when searching interpreters with support for jupyter (microsoft#7720)
* Better cancellations * Add a progress message when searching interpreters * Display progress when searching paths as well * News
1 parent 45f744d commit 00ad77b

File tree

8 files changed

+363
-212
lines changed

8 files changed

+363
-212
lines changed

news/1 Enhancements/7262.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Provide user feedback when searching for a jupyter server to use and allow the user to cancel this process.

package.nls.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,9 @@
374374
"DataScience.untitledNotebookMessage": "Your changes will be lost if you don't save them.",
375375
"DataScience.untitledNotebookYes": "Save",
376376
"DataScience.untitledNotebookNo": "Cancel",
377-
"DataScience.noInterpreter": "No python selected",
378-
"DataScience.notebookNotFound": "python -m jupyter notebook --version is not running"
377+
"DataScience.noInterpreter" : "No python selected",
378+
"DataScience.notebookNotFound" : "python -m jupyter notebook --version is not running",
379+
"DataScience.findJupyterCommandProgress" : "Active interpreter does not support {0}. Searching for the best available interpreter.",
380+
"DataScience.findJupyterCommandProgressCheckInterpreter": "Checking {0}.",
381+
"DataScience.findJupyterCommandProgressSearchCurrentPath": "Searching current path."
379382
}

src/client/common/cancellation.ts

Lines changed: 130 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,130 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
2-
// Licensed under the MIT License.
3-
'use strict';
4-
import { CancellationToken } from 'vscode';
5-
6-
import { createDeferred } from './utils/async';
7-
import * as localize from './utils/localize';
8-
9-
/**
10-
* Error type thrown when canceling.
11-
*/
12-
export class CancellationError extends Error {
13-
constructor() {
14-
super(localize.Common.canceled());
15-
}
16-
}
17-
/**
18-
* Create a promise that will either resolve with a default value or reject when the token is cancelled.
19-
*
20-
* @export
21-
* @template T
22-
* @param {({ defaultValue: T; token: CancellationToken; cancelAction: 'reject' | 'resolve' })} options
23-
* @returns {Promise<T>}
24-
*/
25-
export function createPromiseFromCancellation<T>(options: { defaultValue: T; token?: CancellationToken; cancelAction: 'reject' | 'resolve' }): Promise<T> {
26-
return new Promise<T>((resolve, reject) => {
27-
// Never resolve.
28-
if (!options.token) {
29-
return;
30-
}
31-
const complete = () => {
32-
if (options.token!.isCancellationRequested) {
33-
if (options.cancelAction === 'resolve') {
34-
return resolve(options.defaultValue);
35-
}
36-
if (options.cancelAction === 'reject') {
37-
return reject(new CancellationError());
38-
}
39-
}
40-
};
41-
42-
options.token.onCancellationRequested(complete);
43-
});
44-
}
45-
46-
export namespace Cancellation {
47-
/**
48-
* Races a promise and cancellation. Promise can take a cancellation token too in order to listen to cancellation.
49-
* @param work function returning a promise to race
50-
* @param token token used for cancellation
51-
*/
52-
export function race<T>(work: (token?: CancellationToken) => Promise<T>, token?: CancellationToken): Promise<T> {
53-
if (token) {
54-
// Use a deferred promise. Resolves when the work finishes
55-
const deferred = createDeferred<T>();
56-
57-
// Cancel the deferred promise when the cancellation happens
58-
token.onCancellationRequested(() => {
59-
if (!deferred.completed) {
60-
deferred.reject(new CancellationError());
61-
}
62-
});
63-
64-
// Might already be canceled
65-
if (token.isCancellationRequested) {
66-
// Just start out as rejected
67-
deferred.reject(new CancellationError());
68-
} else {
69-
// Not canceled yet. When the work finishes
70-
// either resolve our promise or cancel.
71-
work(token)
72-
.then(v => {
73-
if (!deferred.completed) {
74-
deferred.resolve(v);
75-
}
76-
})
77-
.catch(e => {
78-
if (!deferred.completed) {
79-
deferred.reject(e);
80-
}
81-
});
82-
}
83-
84-
return deferred.promise;
85-
} else {
86-
// No actual token, just do the original work.
87-
return work();
88-
}
89-
}
90-
91-
/**
92-
* isCanceled returns a boolean indicating if the cancel token has been canceled.
93-
* @param cancelToken
94-
*/
95-
export function isCanceled(cancelToken?: CancellationToken): boolean {
96-
return cancelToken ? cancelToken.isCancellationRequested : false;
97-
}
98-
99-
/**
100-
* throws a CancellationError if the token is canceled.
101-
* @param cancelToken
102-
*/
103-
export function throwIfCanceled(cancelToken?: CancellationToken): void {
104-
if (isCanceled(cancelToken)) {
105-
throw new CancellationError();
106-
}
107-
}
108-
}
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
5+
import { CancellationToken, CancellationTokenSource } from 'vscode';
6+
import { createDeferred } from './utils/async';
7+
import * as localize from './utils/localize';
8+
9+
/**
10+
* Error type thrown when canceling.
11+
*/
12+
export class CancellationError extends Error {
13+
constructor() {
14+
super(localize.Common.canceled());
15+
}
16+
}
17+
/**
18+
* Create a promise that will either resolve with a default value or reject when the token is cancelled.
19+
*
20+
* @export
21+
* @template T
22+
* @param {({ defaultValue: T; token: CancellationToken; cancelAction: 'reject' | 'resolve' })} options
23+
* @returns {Promise<T>}
24+
*/
25+
export function createPromiseFromCancellation<T>(options: { defaultValue: T; token?: CancellationToken; cancelAction: 'reject' | 'resolve' }): Promise<T> {
26+
return new Promise<T>((resolve, reject) => {
27+
// Never resolve.
28+
if (!options.token) {
29+
return;
30+
}
31+
const complete = () => {
32+
if (options.token!.isCancellationRequested) {
33+
if (options.cancelAction === 'resolve') {
34+
return resolve(options.defaultValue);
35+
}
36+
if (options.cancelAction === 'reject') {
37+
return reject(new CancellationError());
38+
}
39+
}
40+
};
41+
42+
options.token.onCancellationRequested(complete);
43+
});
44+
}
45+
46+
/**
47+
* Create a single unified cancellation token that wraps multiple cancellation tokens.
48+
*
49+
* @export
50+
* @param {(...(CancellationToken | undefined)[])} tokens
51+
* @returns {CancellationToken}
52+
*/
53+
export function wrapCancellationTokens(...tokens: (CancellationToken | undefined)[]): CancellationToken {
54+
const wrappedCancellantionToken = new CancellationTokenSource();
55+
for (const token of tokens) {
56+
if (!token) {
57+
continue;
58+
}
59+
if (token.isCancellationRequested) {
60+
return token;
61+
}
62+
token.onCancellationRequested(() => wrappedCancellantionToken.cancel());
63+
}
64+
65+
return wrappedCancellantionToken.token;
66+
}
67+
68+
export namespace Cancellation {
69+
/**
70+
* Races a promise and cancellation. Promise can take a cancellation token too in order to listen to cancellation.
71+
* @param work function returning a promise to race
72+
* @param token token used for cancellation
73+
*/
74+
export function race<T>(work: (token?: CancellationToken) => Promise<T>, token?: CancellationToken): Promise<T> {
75+
if (token) {
76+
// Use a deferred promise. Resolves when the work finishes
77+
const deferred = createDeferred<T>();
78+
79+
// Cancel the deferred promise when the cancellation happens
80+
token.onCancellationRequested(() => {
81+
if (!deferred.completed) {
82+
deferred.reject(new CancellationError());
83+
}
84+
});
85+
86+
// Might already be canceled
87+
if (token.isCancellationRequested) {
88+
// Just start out as rejected
89+
deferred.reject(new CancellationError());
90+
} else {
91+
// Not canceled yet. When the work finishes
92+
// either resolve our promise or cancel.
93+
work(token)
94+
.then(v => {
95+
if (!deferred.completed) {
96+
deferred.resolve(v);
97+
}
98+
})
99+
.catch(e => {
100+
if (!deferred.completed) {
101+
deferred.reject(e);
102+
}
103+
});
104+
}
105+
106+
return deferred.promise;
107+
} else {
108+
// No actual token, just do the original work.
109+
return work();
110+
}
111+
}
112+
113+
/**
114+
* isCanceled returns a boolean indicating if the cancel token has been canceled.
115+
* @param cancelToken
116+
*/
117+
export function isCanceled(cancelToken?: CancellationToken): boolean {
118+
return cancelToken ? cancelToken.isCancellationRequested : false;
119+
}
120+
121+
/**
122+
* throws a CancellationError if the token is canceled.
123+
* @param cancelToken
124+
*/
125+
export function throwIfCanceled(cancelToken?: CancellationToken): void {
126+
if (isCanceled(cancelToken)) {
127+
throw new CancellationError();
128+
}
129+
}
130+
}

src/client/common/utils/localize.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ export namespace DataScience {
291291
export const convertingToPythonFile = localize('DataScience.convertingToPythonFile', 'Converting ipynb to python file');
292292
export const noInterpreter = localize('DataScience.noInterpreter', 'No python selected');
293293
export const notebookNotFound = localize('DataScience.notebookNotFound', 'python -m jupyter notebook --version is not running');
294+
export const findJupyterCommandProgress = localize('DataScience.findJupyterCommandProgress', 'Active interpreter does not support {0}. Searching for the best available interpreter.');
295+
export const findJupyterCommandProgressCheckInterpreter = localize('DataScience.findJupyterCommandProgressCheckInterpreter', 'Checking {0}.');
296+
export const findJupyterCommandProgressSearchCurrentPath = localize('DataScience.findJupyterCommandProgressSearchCurrentPath', 'Searching current path.');
294297
}
295298

296299
export namespace DebugConfigStrings {

0 commit comments

Comments
 (0)