Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2 Fixes/5682.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The extension will now prompt to auto install jupyter in case its not found.
2 changes: 2 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@
"Linter.enableLinter": "Enable {0}",
"Linter.enablePylint": "You have a pylintrc file in your workspace. Do you want to enable pylint?",
"Linter.replaceWithSelectedLinter": "Multiple linters are enabled in settings. Replace with '{0}'?",
"DataScience.libraryNotInstalled": "Data Science library {0} is not installed. Install?",
"DataScience.jupyterInstall": "Install",
"DataScience.jupyterSelectURILaunchLocal": "Launch a local Jupyter server when needed",
"DataScience.jupyterSelectURISpecifyURI": "Type in the URI to connect to a running Jupyter server",
"DataScience.jupyterSelectURIPrompt": "Enter the URI of a Jupyter server",
Expand Down
14 changes: 13 additions & 1 deletion src/client/common/installer/productInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { inject, injectable, named } from 'inversify';
import * as os from 'os';
import { OutputChannel, Uri } from 'vscode';
import '../../common/extensions';
import * as localize from '../../common/utils/localize';
import { IServiceContainer } from '../../ioc/types';
import { LinterId } from '../../linters/types';
import { sendTelemetryEvent } from '../../telemetry';
Expand Down Expand Up @@ -76,7 +77,7 @@ export abstract class BaseInstaller {
}

public async isInstalled(product: Product, resource?: Uri): Promise<boolean | undefined> {
if (product === Product.unittest) {
if (product === Product.unittest || product === Product.jupyter) {
return true;
}
// User may have customized the module name or provided the fully qualified path.
Expand Down Expand Up @@ -271,6 +272,14 @@ export class RefactoringLibraryInstaller extends BaseInstaller {
}
}

export class DataScienceInstaller extends BaseInstaller {
protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise<InstallerResponse> {
const productName = ProductNames.get(product)!;
const item = await this.appShell.showErrorMessage(localize.DataScience.libraryNotInstalled().format(productName), 'Yes', 'No');
return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore;
}
}

@injectable()
export class ProductInstaller implements IInstaller {
private readonly productService: IProductService;
Expand Down Expand Up @@ -307,6 +316,8 @@ export class ProductInstaller implements IInstaller {
return new TestFrameworkInstaller(this.serviceContainer, this.outputChannel);
case ProductType.RefactoringLibrary:
return new RefactoringLibraryInstaller(this.serviceContainer, this.outputChannel);
case ProductType.DataScience:
return new DataScienceInstaller(this.serviceContainer, this.outputChannel);
default:
break;
}
Expand All @@ -333,6 +344,7 @@ function translateProductToModule(product: Product, purpose: ModuleNamePurpose):
case Product.unittest: return 'unittest';
case Product.rope: return 'rope';
case Product.bandit: return 'bandit';
case Product.jupyter: return 'jupyter';
default: {
throw new Error(`Product ${product} cannot be installed as a Python Module.`);
}
Expand Down
1 change: 1 addition & 0 deletions src/client/common/installer/productNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ ProductNames.set(Product.pylint, 'pylint');
ProductNames.set(Product.pytest, 'pytest');
ProductNames.set(Product.yapf, 'yapf');
ProductNames.set(Product.rope, 'rope');
ProductNames.set(Product.jupyter, 'jupyter');
10 changes: 10 additions & 0 deletions src/client/common/installer/productPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,13 @@ export class RefactoringLibraryProductPathService extends BaseProductPathsServic
return this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run);
}
}

@injectable()
export class DataScienceProductPathService extends BaseProductPathsService {
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
super(serviceContainer);
}
public getExecutableNameFromSettings(product: Product, _?: Uri): string {
return this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run);
}
}
1 change: 1 addition & 0 deletions src/client/common/installer/productService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class ProductService implements IProductService {
this.ProductTypes.set(Product.black, ProductType.Formatter);
this.ProductTypes.set(Product.yapf, ProductType.Formatter);
this.ProductTypes.set(Product.rope, ProductType.RefactoringLibrary);
this.ProductTypes.set(Product.jupyter, ProductType.DataScience);
}
public getProductType(product: Product): ProductType {
return this.ProductTypes.get(product)!;
Expand Down
3 changes: 2 additions & 1 deletion src/client/common/installer/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CondaInstaller } from './condaInstaller';
import { PipEnvInstaller } from './pipEnvInstaller';
import { PipInstaller } from './pipInstaller';
import { PoetryInstaller } from './poetryInstaller';
import { CTagsProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from './productPath';
import { CTagsProductPathService, DataScienceProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from './productPath';
import { ProductService } from './productService';
import { IInstallationChannelManager, IModuleInstaller, IProductPathService, IProductService } from './types';

Expand All @@ -28,5 +28,6 @@ export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton<IProductPathService>(IProductPathService, LinterProductPathService, ProductType.Linter);
serviceManager.addSingleton<IProductPathService>(IProductPathService, TestFrameworkProductPathService, ProductType.TestFramework);
serviceManager.addSingleton<IProductPathService>(IProductPathService, RefactoringLibraryProductPathService, ProductType.RefactoringLibrary);
serviceManager.addSingleton<IProductPathService>(IProductPathService, DataScienceProductPathService, ProductType.DataScience);
serviceManager.addSingleton<IWebPanelProvider>(IWebPanelProvider, WebPanelProvider);
}
6 changes: 4 additions & 2 deletions src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ export enum ProductType {
Formatter = 'Formatter',
TestFramework = 'TestFramework',
RefactoringLibrary = 'RefactoringLibrary',
WorkspaceSymbols = 'WorkspaceSymbols'
WorkspaceSymbols = 'WorkspaceSymbols',
DataScience = 'DataScience'
}

export enum Product {
Expand All @@ -95,7 +96,8 @@ export enum Product {
rope = 14,
isort = 15,
black = 16,
bandit = 17
bandit = 17,
jupyter = 18
}

export enum ModuleNamePurpose {
Expand Down
2 changes: 2 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ export namespace DataScience {
export const notebookCheckForImportYes = localize('DataScience.notebookCheckForImportYes', 'Import');
export const notebookCheckForImportNo = localize('DataScience.notebookCheckForImportNo', 'Later');
export const notebookCheckForImportDontAskAgain = localize('DataScience.notebookCheckForImportDontAskAgain', 'Don\'t Ask Again');
export const libraryNotInstalled = localize('DataScience.libraryNotInstalled', 'Data Science library {0} is not installed. Install?');
export const jupyterInstall = localize('DataScience.jupyterInstall', 'Install');
export const jupyterNotSupported = localize('DataScience.jupyterNotSupported', 'Jupyter is not installed');
export const jupyterNotSupportedBecauseOfEnvironment = localize('DataScience.jupyterNotSupportedBecauseOfEnvironment', 'Activating {0} to run Jupyter failed with {1}');
export const jupyterNbConvertNotSupported = localize('DataScience.jupyterNbConvertNotSupported', 'Jupyter nbconvert is not installed');
Expand Down
76 changes: 27 additions & 49 deletions src/client/datascience/editor-integration/codewatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
import { inject, injectable } from 'inversify';
import { CodeLens, Position, Range, Selection, TextDocument, TextEditor, TextEditorRevealType } from 'vscode';

import { IApplicationShell, IDocumentManager } from '../../common/application/types';
import { IDocumentManager } from '../../common/application/types';
import { IFileSystem } from '../../common/platform/types';
import { IConfigurationService, IDataScienceSettings, ILogger } from '../../common/types';
import { noop } from '../../common/utils/misc';
import { IConfigurationService, IDataScienceSettings } from '../../common/types';
// import * as localize from '../../common/utils/localize';
import { StopWatch } from '../../common/utils/stopWatch';
import { IServiceContainer } from '../../ioc/types';
import { captureTelemetry } from '../../telemetry';
import { ICodeExecutionHelper } from '../../terminals/types';
import { Commands, Telemetry } from '../constants';
import { JupyterInstallError } from '../jupyter/jupyterInstallError';
import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError';
import { ICodeLensFactory, ICodeWatcher, IInteractiveWindowProvider } from '../types';
// import { JupyterInstallError } from '../jupyter/jupyterInstallError';
// import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError';
import { ICodeLensFactory, ICodeWatcher, IDataScienceErrorHandler, IInteractiveWindowProvider } from '../types';

@injectable()
export class CodeWatcher implements ICodeWatcher {
Expand All @@ -24,15 +25,15 @@ export class CodeWatcher implements ICodeWatcher {
private codeLenses: CodeLens[] = [];
private cachedSettings: IDataScienceSettings | undefined;

constructor(@inject(IApplicationShell) private applicationShell: IApplicationShell,
@inject(ILogger) private logger: ILogger,
@inject(IInteractiveWindowProvider) private interactiveWindowProvider : IInteractiveWindowProvider,
@inject(IFileSystem) private fileSystem: IFileSystem,
@inject(IConfigurationService) private configService: IConfigurationService,
@inject(IDocumentManager) private documentManager : IDocumentManager,
@inject(ICodeExecutionHelper) private executionHelper: ICodeExecutionHelper,
@inject(ICodeLensFactory) private codeLensFactory: ICodeLensFactory
) {
constructor(@inject(IInteractiveWindowProvider) private interactiveWindowProvider: IInteractiveWindowProvider,
@inject(IFileSystem) private fileSystem: IFileSystem,
@inject(IConfigurationService) private configService: IConfigurationService,
@inject(IDocumentManager) private documentManager: IDocumentManager,
@inject(ICodeExecutionHelper) private executionHelper: ICodeExecutionHelper,
@inject(IDataScienceErrorHandler) protected dataScienceErrorHandler: IDataScienceErrorHandler,
@inject(ICodeLensFactory) private codeLensFactory: ICodeLensFactory,
@inject(IServiceContainer) protected serviceContainer: IServiceContainer
) {
}

public setDocument(document: TextDocument) {
Expand All @@ -57,7 +58,7 @@ export class CodeWatcher implements ICodeWatcher {
return this.version;
}

public getCachedSettings() : IDataScienceSettings | undefined {
public getCachedSettings(): IDataScienceSettings | undefined {
return this.cachedSettings;
}

Expand Down Expand Up @@ -143,13 +144,13 @@ export class CodeWatcher implements ICodeWatcher {
}

@captureTelemetry(Telemetry.RunSelectionOrLine)
public async runSelectionOrLine(activeEditor : TextEditor | undefined) {
public async runSelectionOrLine(activeEditor: TextEditor | undefined) {
if (this.document && activeEditor &&
this.fileSystem.arePathsSame(activeEditor.document.fileName, this.document.fileName)) {
// Get just the text of the selection or the current line if none
const codeToExecute = await this.executionHelper.getSelectedTextToExecute(activeEditor);
if (!codeToExecute) {
return ;
return;
}
const normalizedCode = await this.executionHelper.normalizeLines(codeToExecute!);
if (!normalizedCode || normalizedCode.trim().length === 0) {
Expand Down Expand Up @@ -184,7 +185,7 @@ export class CodeWatcher implements ICodeWatcher {
}

@captureTelemetry(Telemetry.RunCell)
public runCell(range: Range) : Promise<void> {
public runCell(range: Range): Promise<void> {
if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) {
return Promise.resolve();
}
Expand All @@ -195,7 +196,7 @@ export class CodeWatcher implements ICodeWatcher {
}

@captureTelemetry(Telemetry.DebugCurrentCell)
public debugCell(range: Range) : Promise<void> {
public debugCell(range: Range): Promise<void> {
if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) {
return Promise.resolve();
}
Expand All @@ -205,7 +206,7 @@ export class CodeWatcher implements ICodeWatcher {
}

@captureTelemetry(Telemetry.RunCurrentCell)
public runCurrentCell() : Promise<void> {
public runCurrentCell(): Promise<void> {
if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) {
return Promise.resolve();
}
Expand All @@ -224,7 +225,7 @@ export class CodeWatcher implements ICodeWatcher {
return this.runMatchingCell(this.documentManager.activeTextEditor.selection, true);
}

public async addEmptyCellToBottom() : Promise<void> {
public async addEmptyCellToBottom(): Promise<void> {
const editor = this.documentManager.activeTextEditor;
if (editor) {
editor.edit((editBuilder) => {
Expand All @@ -236,7 +237,7 @@ export class CodeWatcher implements ICodeWatcher {
}
}

private async addCode(code: string, file: string, line: number, editor?: TextEditor, debug?: boolean) : Promise<void> {
private async addCode(code: string, file: string, line: number, editor?: TextEditor, debug?: boolean): Promise<void> {
try {
const stopWatch = new StopWatch();
const activeInteractiveWindow = await this.interactiveWindowProvider.getOrCreateActive();
Expand All @@ -246,7 +247,7 @@ export class CodeWatcher implements ICodeWatcher {
await activeInteractiveWindow.addCode(code, file, line, editor, stopWatch);
}
} catch (err) {
this.handleError(err);
this.dataScienceErrorHandler.handleError(err);
}
}

Expand Down Expand Up @@ -279,11 +280,11 @@ export class CodeWatcher implements ICodeWatcher {
}
}

private getCurrentCellLens(pos: Position) : CodeLens | undefined {
private getCurrentCellLens(pos: Position): CodeLens | undefined {
return this.codeLenses.find(l => l.range.contains(pos) && l.command !== undefined && l.command.command === Commands.RunCell);
}

private getNextCellLens(pos: Position) : CodeLens | undefined {
private getNextCellLens(pos: Position): CodeLens | undefined {
const currentIndex = this.codeLenses.findIndex(l => l.range.contains(pos) && l.command !== undefined && l.command.command === Commands.RunCell);
if (currentIndex >= 0) {
return this.codeLenses.find((l: CodeLens, i: number) => l.command !== undefined && l.command.command === Commands.RunCell && i > currentIndex);
Expand All @@ -298,29 +299,6 @@ export class CodeWatcher implements ICodeWatcher {
}
}

// tslint:disable-next-line:no-any
private handleError = (err : any) => {
if (err instanceof JupyterInstallError) {
const jupyterError = err as JupyterInstallError;

// This is a special error that shows a link to open for more help
this.applicationShell.showErrorMessage(jupyterError.message, jupyterError.actionTitle).then(v => {
// User clicked on the link, open it.
if (v === jupyterError.actionTitle) {
this.applicationShell.openUrl(jupyterError.action);
}
});
} else if (err instanceof JupyterSelfCertsError) {
// Don't show the message for self cert errors
noop();
} else if (err.message) {
this.applicationShell.showErrorMessage(err.message);
} else {
this.applicationShell.showErrorMessage(err.toString());
}
this.logger.logError(err);
}

// User has picked run and advance on the last cell of a document
// Create a new cell at the bottom and put their selection there, ready to type
private createNewCell(currentRange: Range): Range {
Expand Down
56 changes: 56 additions & 0 deletions src/client/datascience/errorHandler/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { inject, injectable } from 'inversify';
import { IApplicationShell } from '../../common/application/types';
import { IInstallationChannelManager } from '../../common/installer/types';
import { ILogger, Product } from '../../common/types';
import * as localize from '../../common/utils/localize';
import { noop } from '../../common/utils/misc';
import { JupyterInstallError } from '../jupyter/jupyterInstallError';
import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError';
import { IDataScienceErrorHandler } from '../types';

@injectable()
export class DataScienceErrorHandler implements IDataScienceErrorHandler {
constructor(@inject(IApplicationShell) private applicationShell: IApplicationShell,
@inject(ILogger) private logger: ILogger,
@inject(IInstallationChannelManager) protected channels: IInstallationChannelManager) {
}

public handleError(err: Error) {
if (err instanceof JupyterInstallError) {
this.applicationShell.showInformationMessage(
localize.DataScience.jupyterNotSupported(),
localize.DataScience.jupyterInstall(),
localize.DataScience.notebookCheckForImportNo())
.then(response => {
if (response === localize.DataScience.jupyterInstall()) {
return this.channels.getInstallationChannel(Product.jupyter);
} else {
const jupyterError = err as JupyterInstallError;

// This is a special error that shows a link to open for more help
this.applicationShell.showErrorMessage(jupyterError.message, jupyterError.actionTitle).then(v => {
// User clicked on the link, open it.
if (v === jupyterError.actionTitle) {
this.applicationShell.openUrl(jupyterError.action);
}
});
}
}).then(installer => {
if (installer) {
installer.installModule('jupyter')
.catch(e => this.applicationShell.showErrorMessage(e.message, localize.DataScience.pythonInteractiveHelpLink()));
}
});
} else if (err instanceof JupyterSelfCertsError) {
// Don't show the message for self cert errors
noop();
} else if (err.message) {
this.applicationShell.showErrorMessage(err.message);
} else {
this.applicationShell.showErrorMessage(err.toString());
}
this.logger.logError(err);
}
}
Loading