diff --git a/.eslintrc.js b/.eslintrc.js index 042d30e1293..3e189b9bd51 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -222,6 +222,15 @@ module.exports = { rules: { '@typescript-eslint/no-explicit-any': 'off' } + }, + { + files: ['src/*.d.ts'], + rules: { + // Keep the *.d.ts files clean of any linting suppressions. + // These files will be distributed as is as part of the npm package. + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + } } ], settings: { diff --git a/src/api.d.ts b/src/api.d.ts index 5d7c0d5d0c9..dedb7879c12 100644 --- a/src/api.d.ts +++ b/src/api.d.ts @@ -1,6 +1,201 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -/* eslint-disable @typescript-eslint/no-explicit-any */ +import { CancellationToken, ProviderResult, CancellationError } from 'vscode'; +import { Event, Uri } from 'vscode'; -export interface JupyterAPI {} +export interface JupyterAPI { + /** + * Creates a Jupyter Server Collection that can be displayed in the Notebook Kernel Picker. + * + * The ideal time to invoke this method would be when a Notebook Document has been opened. + * Calling this during activation of the extension might not be ideal, as this would result in + * unnecessarily activating the Jupyter extension as well. + * + * Extensions can use this API to provide a list of Jupyter Servers to VS Code users with custom authentication schemes. + * E.g. one could provide a list of Jupyter Servers that require Kerberos authentication or other. + */ + createJupyterServerCollection(id: string, label: string): JupyterServerCollection; +} + +/** + * Provides information required to connect to a Jupyter Server. + */ +export interface JupyterServerConnectionInformation { + /** + * Base Url of the Jupyter Server. + * E.g. http://localhost:8888 or http://remoteServer.com/hub/user/, etc. + */ + readonly baseUrl: Uri; + /** + * Jupyter Authentication Token. + * When starting Jupyter Notebook/Lab, this can be provided using the --NotebookApp.token= argument. + * Also when starting Jupyter Notebook/Lab in CLI the token is part of the query string, see here: http://localhost:8888/lab?token= + */ + readonly token?: string; + /** + * HTTP header to be used when connecting to the server. + * If a {@link token token} is not provided, then headers will be used to connect to the server. + */ + readonly headers?: Record; + /** + * The local directory that maps to the remote directory of the Jupyter Server. + * E.g. assume you start Jupyter Notebook on a remote machine with --notebook-dir=/foo/bar, + * and you have a file named /foo/bar/sample.ipynb, /foo/bar/sample2.ipynb and the like. + * Next assume you have local directory named /users/xyz/remoteServer with the files with the same names, sample.ipynb and sample2.ipynb + * + * + * Using this setting one can map the local directory to the remote directory. + * With the previous example in mind, the value of this property would be Uri.file('/users/xyz/remoteServer'). + * + * This results in Jupyter Session names being generated in a way thats is consistent with Jupyter Notebook/Lab. + * I.e. the session names map to the relative path of the notebook file. + * Taking the previous example into account, if one were to start a Remote Kernel for the local file `/users/xyz/remoteServer/sample2.ipynb`, + * then the name of the remote Jupyter Session would be `sample2.ipynb`. + * + * This is useful in the context where the remote Jupyter Server is running on the same machine as VS Code, but the files are mapped on different directories. + */ + readonly mappedRemote?: Uri; + /** + * Returns the sub-protocols to be used. See details of `protocols` here https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket + * Useful if there is a custom authentication scheme that needs to be used for WebSocket connections. + * Note: The client side npm package @jupyterlab/services uses WebSockets to connect to remote Kernels. + * + * This is useful in the context of vscode.dev or github.dev or the like where the remote Jupyter Server is unable to read the cookies/headers sent from client as part of {@link JupyterServerConnectionInformation.headers headers}. + */ + readonly webSocketProtocols?: string[]; +} + +/** + * Represents a Jupyter Server displayed in the list of Servers. + * Each server can have its own authentication scheme (token based, username/password or other). + * See {@link JupyterServerProvider.resolveJupyterServer} for more information. + */ +export interface JupyterServer { + /** + * Unique identifier for this server. + */ + readonly id: string; + /** + * A human-readable string representing the name of the Server. + */ + readonly label: string; + /** + * Information required to Connect to the Jupyter Server. + * This can be eagerly provided by the extension or lazily provided by the extension. + * For instance of the authentication mechanism is straight forward (e.g. token based), then the extension can provide this information eagerly. + * Else then information required to connect to the server will be retrieved via {@link JupyterServerProvider.resolveJupyterServer}. + */ + readonly connectionInformation?: JupyterServerConnectionInformation; +} + +/** + * Represents a Jupyter Server with certain information that cannot be `undefined`. + * For instance the {@link connectionInformation} cannot be `undefined` as this is required to connect to the server. + */ +export interface ResolvedJupyterServer { + /** + * Unique identifier for this server. + */ + readonly id: string; + /** + * A human-readable string representing the name of the Server. + */ + readonly label: string; + /** + * Information required to Connect to the Jupyter Server. + */ + readonly connectionInformation: JupyterServerConnectionInformation; +} + +/** + * Provider of Jupyter Servers. + */ +export interface JupyterServerProvider { + /** + * Event fired when the list of servers change. + * Note: The method {@link provideJupyterServers} will not be called unless changes are detected. + */ + onDidChangeServers?: Event; + /** + * Returns the list of {@link JupyterServer Jupyter Servers} or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideJupyterServers(token: CancellationToken): ProviderResult; + /** + * Returns the connection information for the Jupyter server. + * It is OK to return the given `server`. When no result is returned, the given `server` will be used. + */ + resolveJupyterServer(server: JupyterServer, token: CancellationToken): ProviderResult; +} + +/** + * Represents a reference to a Jupyter Server command. + * Each command allows the user to perform an action, such as starting a new Jupyter Server. + */ +export interface JupyterServerCommand { + /** + * A human-readable string which is rendered prominent. + */ + label: string; + /** + * A human-readable string which is rendered less prominent on the same line. + */ + description?: string; +} + +/** + * Provider of {@link JupyterServerCommand Jupyter Server Commands}. + * Each command allows the user to perform an action, such as starting a new Jupyter Server. + */ +export interface JupyterServerCommandProvider { + /** + * Returns a list of commands to be displayed to the user. + * @param value The value entered by the user in the quick pick. + */ + provideCommands(value: string | undefined, token: CancellationToken): Promise; + /** + * Invoked when a {@link JupyterServerCommand command} has been selected. + * @param command The {@link JupyterServerCommand command} selected by the user. + * @returns The {@link JupyterServer Jupyter Server} or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + * + * Returning `undefined` or `null` will result in the previous UI being displayed, this will most likely be the Notebook Kernel Picker. + * Thus extensions can implement a back button like behavior in their UI by returning `undefined` or `null` from this method. + * If however users exit the UI or workflow (if any provided by 3rd party extension) by selecting a close button or hitting the `ESC` key or the like, + * extensions are then expected to throw a {@link CancellationError}, else the previous UI will be once again, which might not be desirable. + */ + handleCommand(command: JupyterServerCommand, token: CancellationToken): ProviderResult; +} + +/** + * Represents a logical collection of {@link JupyterServer Jupyter Servers}. + * Each collection is represented as a separate entry in the Notebook Kernel Picker. + * Extensions can contribute multiple collections, each with one or more {@link JupyterServer Jupyter Servers}. + */ +export interface JupyterServerCollection { + /** + * Unique identifier of the Server Collection. + */ + readonly id: string; + /** + * A human-readable string representing the collection of the Servers. This can be read and updated by the extension. + */ + label: string; + /** + * A link to a resource containing more information. This can be read and updated by the extension. + */ + documentation?: Uri; + /** + * Provider of {@link JupyterServer Jupyter Servers}. This can be read and updated by the extension. + */ + serverProvider?: JupyterServerProvider; + /** + * Provider of {@link JupyterServerCommand Commands}. This can be read and updated by the extension. + */ + commandProvider?: JupyterServerCommandProvider; + /** + * Removes this Server Collection. + */ + dispose(): void; +} diff --git a/src/api.deprecated.d.ts b/src/api.deprecated.d.ts index 8ce17f7eb37..dee6318012f 100644 --- a/src/api.deprecated.d.ts +++ b/src/api.deprecated.d.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// eslint-disable-next-line @typescript-eslint/no-unused-vars import { Uri } from 'vscode'; declare module './api' { @@ -18,9 +17,5 @@ declare module './api' { * @deprecated Use `label` instead. */ title?: string; - /** - * @deprecated Use `description` instead. - */ - detail?: string; } } diff --git a/src/api.internal.d.ts b/src/api.internal.d.ts index a6126ed7ab2..8cefa4529f0 100644 --- a/src/api.internal.d.ts +++ b/src/api.internal.d.ts @@ -18,16 +18,6 @@ declare module './api' { */ onDidChangeProvider: Event; } - export interface JupyterServerProvider { - /** - * Display a `trash` icon next to each server in the quick pick. - * Allowing the user to remove this server. - * Currently used only by the Jupyter Extension. - * A better more generic way to deal with this would be via commands. - */ - removeJupyterServer?(server: JupyterServer): Promise; - } - export interface IJupyterUriProvider { /** * Internal cache of the Jupyter Servers, providing sync access to the servers. diff --git a/src/api.proposed.d.ts b/src/api.proposed.d.ts deleted file mode 100644 index c550c7fa1a3..00000000000 --- a/src/api.proposed.d.ts +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// API & types defined in this file are proposed and subject to change. -// To use these types please reach out to the Jupyter Extension team (file an issue on the Jupyter Extension GitHub repo). -// Or please wait for these to be finalized and released. - -import { CancellationToken, ProviderResult } from 'vscode'; -import { Event, Uri } from 'vscode'; - -declare module './api' { - /** - * Provides information required to connect to a Jupyter Server. - */ - export interface JupyterServerConnectionInformation { - /** - * Base Url of the Jupyter Server. - * E.g. http://localhost:8888 or http://remoteServer.com/hub/user/, etc. - */ - readonly baseUrl: Uri; - /** - * Jupyter auth Token. - */ - readonly token?: string; - /** - * HTTP header to be used when connecting to the server. - */ - readonly headers?: Record; - /** - * The local directory that maps to the remote directory of the Jupyter Server. - * E.g. assume you start Jupyter Notebook with --notebook-dir=/foo/bar, - * and you have a file named /foo/bar/sample.ipynb, /foo/bar/sample2.ipynb and the like. - * Then assume the mapped local directory will be /users/xyz/remoteServer and the files sample.ipynb and sample2.ipynb - * are in the above local directory. - * - * Using this setting one can map the local directory to the remote directory. - * In this case the value of this property would be /users/xyz/remoteServer. - * - * Note: A side effect of providing this value is the session names are generated the way they are in Jupyter Notebook/Lab. - * I.e. the session names map to the relative path of the notebook file. - * As a result when attempting to create a new session for a notebook/file, Jupyter will - * first check if a session already exists for the same file and same kernel, and if so, will re-use that session. - */ - readonly mappedRemoteNotebookDir?: Uri; - /** - * Returns the sub-protocols to be used. See details of `protocols` here https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket - * Useful if there is a custom authentication scheme that needs to be used for WebSocket connections. - * Note: The client side npm package @jupyterlab/services uses WebSockets to connect to remote Kernels. - */ - readonly webSocketProtocols?: string[]; - } - - /** - * Represents a Jupyter Server displayed in the list of Servers. - */ - export interface JupyterServer { - /** - * Unique identifier for this server. - */ - readonly id: string; - /** - * A human-readable string representing the name of the Server. - */ - readonly label: string; - /** - * Information required to Connect to the Jupyter Server. - */ - readonly connectionInformation?: JupyterServerConnectionInformation; - } - /** - * Represents a Jupyter Server with certain information that cannot be `undefined`. - */ - export interface ResolvedJupyterServer { - /** - * Unique identifier for this server. - */ - readonly id: string; - /** - * A human-readable string representing the name of the Server. - */ - readonly label: string; - /** - * Information required to Connect to the Jupyter Server. - */ - readonly connectionInformation: JupyterServerConnectionInformation; - } - - /** - * Provider of Jupyter Servers. - */ - export interface JupyterServerProvider { - /** - * Event fired when the list of servers changes. - * Note: the provideJupyterServers method will not be called unless changes are detected. - */ - onDidChangeServers?: Event; - /** - * Returns the list of servers. - */ - provideJupyterServers(token: CancellationToken): ProviderResult; - /** - * Returns the connection information for the Jupyter server. - * It is OK to return the given `server`. When no result is returned, the given `server` will be used. - */ - resolveJupyterServer(server: JupyterServer, token: CancellationToken): ProviderResult; - } - /** - * Represents a reference to a Jupyter Server command. - */ - export interface JupyterServerCommand { - /** - * A human-readable string which is rendered prominent. - */ - label: string; - /** - * A human-readable string which is rendered less prominent on the same line. - */ - description?: string; - } - /** - * Provider of Jupyter Server Commands. - * Each command allows the user to perform an action. - */ - export interface JupyterServerCommandProvider { - /** - * Returns a list of commands to be displayed to the user. - * @param value The value entered by the user in the quick pick. - */ - provideCommands(value: string | undefined, token: CancellationToken): Promise; - /** - * Invoked when a command has been selected. - * @param command The command selected by the user. - * @returns The Jupyter Server or a thenable that resolves to such. The lack of a result can be - * signaled by returning `undefined` or `null`. - */ - handleCommand(command: JupyterServerCommand, token: CancellationToken): ProviderResult; - } - export interface JupyterServerCollection { - /** - * Unique identifier of the Server Collection. - */ - readonly id: string; - /** - * A human-readable string representing the collection of the Servers. This can be read and updated by the extension. - */ - label: string; - /** - * A link to a resource containing more information. This can be read and updated by the extension. - */ - documentation?: Uri; - /** - * Provider of Jupyter Servers. This can be read and updated by the extension. - */ - serverProvider?: JupyterServerProvider; - /** - * Provider of Commands. This can be read and updated by the extension. - */ - commandProvider?: JupyterServerCommandProvider; - /** - * Removes this Server Collection. - */ - dispose(): void; - } - export interface JupyterAPI { - /** - * Creates a Jupyter Server Collection that can be displayed in the Notebook Kernel Picker. - * - * The ideal time to invoke this method would be when a Notebook Document has been opened. - * Calling this during activation of the extension might not be ideal, as this would result in - * unnecessarily activating the Jupyter extension as well. - */ - createJupyterServerCollection(id: string, label: string): JupyterServerCollection; - } -} diff --git a/src/api.proposed.kernelStarupHook.d.ts b/src/api.proposed.kernelStarupHook.d.ts index 76874fb3ccb..cce67b496b4 100644 --- a/src/api.proposed.kernelStarupHook.d.ts +++ b/src/api.proposed.kernelStarupHook.d.ts @@ -12,7 +12,7 @@ declare module './api' { * Invoked after a kernel has been started, allowing the contributing extension to perform startup * actions on the kernel. * This is only invoked for kernels - * - That belong to JupyterServers contributed by this provider. + * - That belong to {@link JupyterServer JupyterServers} contributed by this provider. * - That have been started, not for connecting to already started (active) kernels * * Note: This operation affects the over all startup time of a kernel, which could adversely impact the UX. @@ -20,7 +20,7 @@ declare module './api' { * * @param uri The Uri of the resource associated with the kernel. * In the case of Jupyter Notebooks and Interactive Window, this is the Uri of the Notebook. - * @session The Session Connection for the Kernel. Use this to communicate with the backend kernel. + * @session The {@link Session.ISessionConnection Session Connection} for the Kernel. Use this to communicate with the backend kernel. */ onStartKernel?( data: { uri: Uri; server: JupyterServer; session: Session.ISessionConnection }, diff --git a/src/api.proposed.removeJupyterServer.d.ts b/src/api.proposed.removeJupyterServer.d.ts new file mode 100644 index 00000000000..688137663a8 --- /dev/null +++ b/src/api.proposed.removeJupyterServer.d.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { Uri } from 'vscode'; + +// Ability to remove a Jupyter server is internal to the Jupyter Extension & Jupyter Hub extension. + +declare module './api' { + export interface JupyterServerProvider { + /** + * Display a `trash` icon next to each server in the quick pick. + * Allowing the user to remove this server. + * Currently used only by the Jupyter Extension. + * A better more generic way to deal with this would be via commands. + */ + removeJupyterServer?(server: JupyterServer): Promise; + } +} diff --git a/src/api.unstable.d.ts b/src/api.unstable.d.ts index f58d3e074b0..503e6225f91 100644 --- a/src/api.unstable.d.ts +++ b/src/api.unstable.d.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -/* eslint-disable @typescript-eslint/no-explicit-any */ - import { CancellationToken, Disposable, Event, NotebookController, NotebookDocument, QuickPickItem, Uri } from 'vscode'; import type { Kernel } from '@jupyterlab/services/lib/kernel'; import type { Session } from '@jupyterlab/services'; diff --git a/src/kernels/jupyter/connection/jupyterServerProviderRegistry.ts b/src/kernels/jupyter/connection/jupyterServerProviderRegistry.ts index b4b85a9fa01..e5979314b51 100644 --- a/src/kernels/jupyter/connection/jupyterServerProviderRegistry.ts +++ b/src/kernels/jupyter/connection/jupyterServerProviderRegistry.ts @@ -132,7 +132,7 @@ class JupyterUriProviderAdaptor extends Disposables implements IJupyterUriProvid return items.map((c) => { return { label: stripCodicons(c.label || c.title), - detail: stripCodicons(c.description || c.detail), + description: stripCodicons(c.description), command: c }; }); @@ -196,7 +196,7 @@ class JupyterUriProviderAdaptor extends Disposables implements IJupyterUriProvid displayName: server.label, token: info.token || '', authorizationHeader: info.headers, - mappedRemoteNotebookDir: info.mappedRemoteNotebookDir?.toString(), + mappedRemoteNotebookDir: info.mappedRemote?.toString(), webSocketProtocols: info.webSocketProtocols }; } @@ -215,7 +215,7 @@ class JupyterUriProviderAdaptor extends Disposables implements IJupyterUriProvid displayName: server.label, token: info.token || '', authorizationHeader: info.headers, - mappedRemoteNotebookDir: info.mappedRemoteNotebookDir?.toString(), + mappedRemoteNotebookDir: info.mappedRemote?.toString(), webSocketProtocols: info.webSocketProtocols }; } diff --git a/src/kernels/jupyter/connection/jupyterUriProviderRegistration.ts b/src/kernels/jupyter/connection/jupyterUriProviderRegistration.ts index 3505c9f1e35..c31be0ff51a 100644 --- a/src/kernels/jupyter/connection/jupyterUriProviderRegistration.ts +++ b/src/kernels/jupyter/connection/jupyterUriProviderRegistration.ts @@ -6,7 +6,6 @@ import { Disposable, EventEmitter, Memento, QuickPickItem } from 'vscode'; import { JVSC_EXTENSION_ID, Telemetry } from '../../../platform/common/constants'; import { GLOBAL_MEMENTO, IDisposableRegistry, IExtensions, IMemento } from '../../../platform/common/types'; import { swallowExceptions } from '../../../platform/common/utils/decorators'; -import * as localize from '../../../platform/common/utils/localize'; import { noop } from '../../../platform/common/utils/misc'; import { IInternalJupyterUriProvider, IJupyterUriProviderRegistration, JupyterServerProviderHandle } from '../types'; import { sendTelemetryEvent } from '../../../telemetry'; @@ -229,11 +228,10 @@ class JupyterUriProviderWrapper extends Disposables implements IInternalJupyterU } return (await this.provider.getQuickPickEntryItems(value)).map((q) => { q.label = stripCodicons(q.label); - q.detail = stripCodicons(q.detail); + q.description = stripCodicons(q.description || q.detail); // We only support description, not detail. return { ...q, - // Add the package name onto the description - description: localize.DataScience.uriProviderDescriptionFormat(q.description || '', this.extensionId), + detail: undefined, original: q }; }); diff --git a/src/kernels/jupyter/connection/jupyterUriProviderRegistration.unit.test.ts b/src/kernels/jupyter/connection/jupyterUriProviderRegistration.unit.test.ts index 3ae4c595927..c5bd800a40f 100644 --- a/src/kernels/jupyter/connection/jupyterUriProviderRegistration.unit.test.ts +++ b/src/kernels/jupyter/connection/jupyterUriProviderRegistration.unit.test.ts @@ -17,7 +17,6 @@ import { IServiceContainer } from '../../../platform/ioc/types'; import { Disposable, EventEmitter, Memento, QuickPickItem } from 'vscode'; import { createEventHandler } from '../../../test/common'; import { resolvableInstance } from '../../../test/datascience/helpers'; -import { DataScience } from '../../../platform/common/utils/localize'; use(chaiAsPromised); suite('Uri Provider Registration', () => { @@ -228,7 +227,7 @@ suite('Uri Provider Registration', () => { quickPickItemsForHandle1.map((item) => { return { ...item, - description: DataScience.uriProviderDescriptionFormat(item.description || '', 'a'), + detail: undefined, original: item }; }) @@ -238,7 +237,7 @@ suite('Uri Provider Registration', () => { quickPickItemsForHandle2.map((item) => { return { ...item, - description: DataScience.uriProviderDescriptionFormat(item.description || '', 'ext2'), + detail: undefined, original: item }; }) diff --git a/src/notebooks/controllers/kernelSource/remoteNotebookKernelSourceSelector.ts b/src/notebooks/controllers/kernelSource/remoteNotebookKernelSourceSelector.ts index 751cf397803..ff1b687ee63 100644 --- a/src/notebooks/controllers/kernelSource/remoteNotebookKernelSourceSelector.ts +++ b/src/notebooks/controllers/kernelSource/remoteNotebookKernelSourceSelector.ts @@ -313,7 +313,6 @@ export class RemoteNotebookKernelSourceSelector implements IRemoteNotebookKernel ...i, provider: provider, type: KernelFinderEntityQuickPickType.UriProviderQuickPick, - description: undefined, originalItem: i }; } @@ -421,7 +420,6 @@ export class RemoteNotebookKernelSourceSelector implements IRemoteNotebookKernel ...i, provider: provider, type: KernelFinderEntityQuickPickType.UriProviderQuickPick, - description: undefined, originalItem: i }; }); diff --git a/src/platform/common/utils/localize.ts b/src/platform/common/utils/localize.ts index 26c54e9e3cb..174b0193fe5 100644 --- a/src/platform/common/utils/localize.ts +++ b/src/platform/common/utils/localize.ts @@ -28,7 +28,7 @@ export namespace Common { export const reload = l10n.t('Reload'); export const moreInfo = l10n.t('More Info'); export const learnMore = l10n.t('Learn more'); - export const labelForQuickPickSeparatorIndicatingThereIsAnotherGroupOfMoreItems = l10n.t('more'); + export const labelForQuickPickSeparatorIndicatingThereIsAnotherGroupOfMoreItems = l10n.t('More'); export const and = l10n.t('and'); export const reportThisIssue = l10n.t('Report this issue'); export const clickHereForMoreInfoWithHtml = (link: string) => @@ -82,8 +82,6 @@ export namespace DataScience { export const pythonExtensionInstalled = l10n.t( 'Python Extension is now installed. Some features might not be available until a notebook or interactive window session is restarted.' ); - export const uriProviderDescriptionFormat = (description: string, extensionId: string) => - l10n.t('{0} (From {1} extension)', description, extensionId); export const unknownPackage = l10n.t('unknown'); export const interactiveWindowTitleFormat = (ownerFileName: string) => l10n.t('Interactive - {0}', ownerFileName);