Skip to content

Commit 8bb3ddf

Browse files
authored
Add package name to output (#12683)
1 parent a6db5c0 commit 8bb3ddf

5 files changed

Lines changed: 111 additions & 9 deletions

File tree

package.nls.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,5 +550,7 @@
550550
"DataScienceRendererExtension.installationCompleteMessage": "complete.",
551551
"DataScienceRendererExtension.startingDownloadOutputMessage": "Starting download of Notebook Renderers extension.",
552552
"DataScienceRendererExtension.downloadingMessage": "Downloading Notebook Renderers Extension...",
553-
"DataScienceRendererExtension.downloadCompletedOutputMessage": "Notebook Renderers extension download complete."
553+
"DataScienceRendererExtension.downloadCompletedOutputMessage": "Notebook Renderers extension download complete.",
554+
"DataScience.uriProviderDescriptionFormat": "{0} (From {1} extension)",
555+
"DataScience.unknownPackage": "unknown"
554556
}

src/client/common/utils/localize.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,11 @@ export namespace DataScience {
316316
'DataScience.unknownServerUri',
317317
'Server URI cannot be used. Did you uninstall an extension that provided a Jupyter server connection?'
318318
);
319+
export const uriProviderDescriptionFormat = localize(
320+
'DataScience.uriProviderDescriptionFormat',
321+
'{0} (From {1} extension)'
322+
);
323+
export const unknownPackage = localize('DataScience.unknownPackage', 'unknown');
319324
export const historyTitle = localize('DataScience.historyTitle', 'Python Interactive');
320325
export const dataExplorerTitle = localize('DataScience.dataExplorerTitle', 'Data Viewer');
321326
export const badWebPanelFormatString = localize(

src/client/datascience/jupyterUriProviderRegistration.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33
import { inject, injectable } from 'inversify';
4+
import * as path from 'path';
5+
import { IFileSystem } from '../common/platform/types';
46
import { IExtensions } from '../common/types';
57
import * as localize from '../common/utils/localize';
8+
import { EXTENSION_ROOT_DIR } from '../constants';
9+
import { JupyterUriProviderWrapper } from './jupyterUriProviderWrapper';
610
import {
711
IJupyterServerUri,
812
IJupyterUriProvider,
@@ -13,20 +17,23 @@ import {
1317
@injectable()
1418
export class JupyterUriProviderRegistration implements IJupyterUriProviderRegistration {
1519
private loadedOtherExtensionsPromise: Promise<void> | undefined;
16-
private providers = new Map<string, IJupyterUriProvider>();
20+
private providers = new Map<string, Promise<IJupyterUriProvider>>();
1721

18-
constructor(@inject(IExtensions) private readonly extensions: IExtensions) {}
22+
constructor(
23+
@inject(IExtensions) private readonly extensions: IExtensions,
24+
@inject(IFileSystem) private readonly fileSystem: IFileSystem
25+
) {}
1926

2027
public async getProviders(): Promise<ReadonlyArray<IJupyterUriProvider>> {
2128
await this.checkOtherExtensions();
2229

2330
// Other extensions should have registered in their activate callback
24-
return [...this.providers.values()];
31+
return Promise.all([...this.providers.values()]);
2532
}
2633

2734
public registerProvider(provider: IJupyterUriProvider) {
2835
if (!this.providers.has(provider.id)) {
29-
this.providers.set(provider.id, provider);
36+
this.providers.set(provider.id, this.createProvider(provider));
3037
} else {
3138
throw new Error(`IJupyterUriProvider already exists with id ${provider.id}`);
3239
}
@@ -35,8 +42,9 @@ export class JupyterUriProviderRegistration implements IJupyterUriProviderRegist
3542
public async getJupyterServerUri(id: string, handle: JupyterServerUriHandle): Promise<IJupyterServerUri> {
3643
await this.checkOtherExtensions();
3744

38-
const provider = this.providers.get(id);
39-
if (provider) {
45+
const providerPromise = this.providers.get(id);
46+
if (providerPromise) {
47+
const provider = await providerPromise;
4048
return provider.getServerUri(handle);
4149
}
4250
throw new Error(localize.DataScience.unknownServerUri());
@@ -55,4 +63,44 @@ export class JupyterUriProviderRegistration implements IJupyterUriProviderRegist
5563
.map((e) => (e.isActive ? Promise.resolve() : e.activate()));
5664
await Promise.all(list);
5765
}
66+
67+
private async createProvider(provider: IJupyterUriProvider): Promise<IJupyterUriProvider> {
68+
const packageName = await this.determineExtensionFromCallstack();
69+
return new JupyterUriProviderWrapper(provider, packageName);
70+
}
71+
72+
private async determineExtensionFromCallstack(): Promise<string> {
73+
const stack = new Error().stack;
74+
if (stack) {
75+
const root = EXTENSION_ROOT_DIR.toLowerCase();
76+
const frames = stack.split('\n').map((f) => {
77+
const result = /\((.*)\)/.exec(f);
78+
if (result) {
79+
return result[1];
80+
}
81+
});
82+
for (const frame of frames) {
83+
if (frame && !frame.startsWith(root)) {
84+
// This file is from a different extension. Try to find its package.json
85+
let dirName = path.dirname(frame);
86+
let last = frame;
87+
while (dirName && dirName.length < last.length) {
88+
const possiblePackageJson = path.join(dirName, 'package.json');
89+
if (await this.fileSystem.fileExists(possiblePackageJson)) {
90+
const text = await this.fileSystem.readFile(possiblePackageJson);
91+
try {
92+
const json = JSON.parse(text);
93+
return `${json.publisher}.${json.name}`;
94+
} catch {
95+
// If parse fails, then not the extension
96+
}
97+
}
98+
last = dirName;
99+
dirName = path.dirname(dirName);
100+
}
101+
}
102+
}
103+
}
104+
return localize.DataScience.unknownPackage();
105+
}
58106
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
import * as vscode from 'vscode';
4+
import * as localize from '../common/utils/localize';
5+
import { IJupyterServerUri, IJupyterUriProvider, JupyterServerUriHandle } from './types';
6+
7+
/**
8+
* This class wraps an IJupyterUriProvider provided by another extension. It allows us to show
9+
* extra data on the other extension's UI.
10+
*/
11+
export class JupyterUriProviderWrapper implements IJupyterUriProvider {
12+
constructor(private readonly provider: IJupyterUriProvider, private packageName: string) {}
13+
public get id() {
14+
return this.provider.id;
15+
}
16+
public getQuickPickEntryItems(): vscode.QuickPickItem[] {
17+
return this.provider.getQuickPickEntryItems().map((q) => {
18+
return {
19+
...q,
20+
// Add the package name onto the description
21+
description: localize.DataScience.uriProviderDescriptionFormat().format(
22+
q.description || '',
23+
this.packageName
24+
),
25+
original: q
26+
};
27+
});
28+
}
29+
public handleQuickPick(
30+
item: vscode.QuickPickItem,
31+
back: boolean
32+
): Promise<JupyterServerUriHandle | 'back' | undefined> {
33+
// tslint:disable-next-line: no-any
34+
if ((item as any).original) {
35+
// tslint:disable-next-line: no-any
36+
return this.provider.handleQuickPick((item as any).original, back);
37+
}
38+
return this.provider.handleQuickPick(item, back);
39+
}
40+
41+
public getServerUri(handle: JupyterServerUriHandle): Promise<IJupyterServerUri> {
42+
return this.provider.getServerUri(handle);
43+
}
44+
}

src/test/datascience/jupyterUriProviderRegistration.unit.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
'use strict';
44

55
import { assert } from 'chai';
6-
import { instance, mock, when } from 'ts-mockito';
6+
import { anything, instance, mock, when } from 'ts-mockito';
77
import * as TypeMoq from 'typemoq';
88
import * as vscode from 'vscode';
9+
import { FileSystem } from '../../client/common/platform/fileSystem';
910
import { JupyterUriProviderRegistration } from '../../client/datascience/jupyterUriProviderRegistration';
1011
import { IJupyterServerUri, IJupyterUriProvider, JupyterServerUriHandle } from '../../client/datascience/types';
1112
import { MockExtensions } from './mockExtensions';
@@ -47,6 +48,8 @@ suite('DataScience URI Picker', () => {
4748
let registration: JupyterUriProviderRegistration | undefined;
4849
const extensions = mock(MockExtensions);
4950
const extensionList: vscode.Extension<any>[] = [];
51+
const fileSystem = mock(FileSystem);
52+
when(fileSystem.fileExists(anything())).thenResolve(false);
5053
providerIds.forEach((id) => {
5154
const extension = TypeMoq.Mock.ofType<vscode.Extension<any>>();
5255
const packageJson = TypeMoq.Mock.ofType<any>();
@@ -64,7 +67,7 @@ suite('DataScience URI Picker', () => {
6467
extensionList.push(extension.object);
6568
});
6669
when(extensions.all).thenReturn(extensionList);
67-
registration = new JupyterUriProviderRegistration(instance(extensions));
70+
registration = new JupyterUriProviderRegistration(instance(extensions), instance(fileSystem));
6871
return registration;
6972
}
7073

0 commit comments

Comments
 (0)