diff --git a/src/client/api.ts b/src/client/api.ts index 7bc3fc81373b..7cf580d9f78f 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -10,7 +10,7 @@ import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient import { LanguageClient } from 'vscode-languageclient/node'; import { PYLANCE_NAME } from './activation/node/languageClientFactory'; import { ILanguageServerOutputChannel } from './activation/types'; -import { IExtensionApi } from './apiTypes'; +import { PythonExtension } from './api/main'; import { isTestExecution, PYTHON_LANGUAGE } from './common/constants'; import { IConfigurationService, Resource } from './common/types'; import { getDebugpyLauncherArgs, getDebugpyPackagePath } from './debugger/extension/adapter/remoteLaunchers'; @@ -29,14 +29,14 @@ export function buildApi( serviceManager: IServiceManager, serviceContainer: IServiceContainer, discoveryApi: IDiscoveryAPI, -): IExtensionApi { +): PythonExtension { const configurationService = serviceContainer.get(IConfigurationService); const interpreterService = serviceContainer.get(IInterpreterService); serviceManager.addSingleton(JupyterExtensionIntegration, JupyterExtensionIntegration); const jupyterIntegration = serviceContainer.get(JupyterExtensionIntegration); const outputChannel = serviceContainer.get(ILanguageServerOutputChannel); - const api: IExtensionApi & { + const api: PythonExtension & { /** * @deprecated Temporarily exposed for Pylance until we expose this API generally. Will be removed in an * iteration or two. @@ -44,7 +44,7 @@ export function buildApi( pylance: ApiForPylance; } & { /** - * @deprecated Use IExtensionApi.environments API instead. + * @deprecated Use PythonExtension.environments API instead. * * Return internal settings within the extension which are stored in VSCode storage */ diff --git a/src/client/apiTypes.ts b/src/client/api/main.ts similarity index 87% rename from src/client/apiTypes.ts rename to src/client/api/main.ts index d30a81582a7e..b9266a732826 100644 --- a/src/client/apiTypes.ts +++ b/src/client/api/main.ts @@ -1,19 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { CancellationToken, Event, Uri, WorkspaceFolder } from 'vscode'; -import { IDataViewerDataProvider, IJupyterUriProvider } from './jupyter/types'; +import { CancellationToken, Event, Uri, WorkspaceFolder, QuickPickItem, extensions } from 'vscode'; /* * Do not introduce any breaking changes to this API. * This is the public API for other extensions to interact with this extension. */ - -export interface IExtensionApi { +export interface PythonExtension { /** * Promise indicating whether all parts of the extension have completed loading or not. * @type {Promise} - * @memberof IExtensionApi */ ready: Promise; jupyter: { @@ -128,6 +125,47 @@ export interface IExtensionApi { }; } +interface IJupyterServerUri { + baseUrl: string; + token: string; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + authorizationHeader: any; // JSON object for authorization header. + expiration?: Date; // Date/time when header expires and should be refreshed. + displayName: string; +} + +type JupyterServerUriHandle = string; + +export interface IJupyterUriProvider { + readonly id: string; // Should be a unique string (like a guid) + getQuickPickEntryItems(): QuickPickItem[]; + handleQuickPick(item: QuickPickItem, backEnabled: boolean): Promise; + getServerUri(handle: JupyterServerUriHandle): Promise; +} + +interface IDataFrameInfo { + columns?: { key: string; type: ColumnType }[]; + indexColumn?: string; + rowCount?: number; +} + +export interface IDataViewerDataProvider { + dispose(): void; + getDataFrameInfo(): Promise; + getAllRows(): Promise; + getRows(start: number, end: number): Promise; +} + +enum ColumnType { + String = 'string', + Number = 'number', + Bool = 'bool', +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type IRowsResponse = any[]; + export type RefreshOptions = { /** * When `true`, force trigger a refresh regardless of whether a refresh was already triggered. Note this can be expensive so @@ -349,3 +387,20 @@ export type EnvironmentVariablesChangeEvent = { */ readonly env: EnvironmentVariables; }; + +export const PVSC_EXTENSION_ID = 'ms-python.python'; + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace PythonExtension { + export async function api(): Promise { + const extension = extensions.getExtension(PVSC_EXTENSION_ID); + if (extension === undefined) { + throw new Error(`Python extension is not installed or is disabled`); + } + if (!extension.isActive) { + await extension.activate(); + } + const pythonApi: PythonExtension = extension.exports; + return pythonApi; + } +} diff --git a/src/client/api/package-lock.json b/src/client/api/package-lock.json new file mode 100644 index 000000000000..137262c6523b --- /dev/null +++ b/src/client/api/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "@vscode/python-extension", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@vscode/python-extension", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/vscode": "^1.78.0" + }, + "devDependencies": { + "@types/node": "^16.11.7", + "typescript": "^4.7.2" + } + }, + "node_modules/@types/node": { + "version": "16.18.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.27.tgz", + "integrity": "sha512-GFfndd/RINWD19W+xNJ9Qh/sOZ5ieTiOSagA86ER/12i/l+MEnQxsbldGRF23azWjRfe7zUlAldyrwN84a1E5w==", + "dev": true + }, + "node_modules/@types/vscode": { + "version": "1.78.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.78.0.tgz", + "integrity": "sha512-LJZIJpPvKJ0HVQDqfOy6W4sNKUBBwyDu1Bs8chHBZOe9MNuKTJtidgZ2bqjhmmWpUb0TIIqv47BFUcVmAsgaVA==" + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "@types/node": { + "version": "16.18.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.27.tgz", + "integrity": "sha512-GFfndd/RINWD19W+xNJ9Qh/sOZ5ieTiOSagA86ER/12i/l+MEnQxsbldGRF23azWjRfe7zUlAldyrwN84a1E5w==", + "dev": true + }, + "@types/vscode": { + "version": "1.78.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.78.0.tgz", + "integrity": "sha512-LJZIJpPvKJ0HVQDqfOy6W4sNKUBBwyDu1Bs8chHBZOe9MNuKTJtidgZ2bqjhmmWpUb0TIIqv47BFUcVmAsgaVA==" + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } + } +} diff --git a/src/client/api/package.json b/src/client/api/package.json new file mode 100644 index 000000000000..901ab3f13cfd --- /dev/null +++ b/src/client/api/package.json @@ -0,0 +1,26 @@ +{ + "name": "@vscode/python-extension", + "description": "VSCode Python extension's public API", + "version": "1.0.0", + "publisher": "ms-python", + "author": { + "name": "Microsoft Corporation" + }, + "types": "./index.d.ts", + "license": "MIT", + "homepage": "https://github.com/Microsoft/vscode-python", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-python" + }, + "bugs": { + "url": "https://github.com/Microsoft/vscode-python/issues" + }, + "dependencies": { + "@types/vscode": "^1.78.0" + }, + "devDependencies": { + "@types/node": "^16.11.7", + "typescript": "^4.7.2" + } +} diff --git a/src/client/deprecatedProposedApiTypes.ts b/src/client/deprecatedProposedApiTypes.ts index 14cabe1d09ae..407cb1dab394 100644 --- a/src/client/deprecatedProposedApiTypes.ts +++ b/src/client/deprecatedProposedApiTypes.ts @@ -4,7 +4,7 @@ import { Uri, Event } from 'vscode'; import { PythonEnvKind, EnvPathType } from './pythonEnvironments/base/info'; import { ProgressNotificationEvent, GetRefreshEnvironmentsOptions } from './pythonEnvironments/base/locator'; -import { Resource } from './apiTypes'; +import { Resource } from './api/main'; export interface EnvironmentDetailsOptions { useCache: boolean; diff --git a/src/client/environmentApi.ts b/src/client/environmentApi.ts index 3e846eb2772f..ee0b28ae2ff4 100644 --- a/src/client/environmentApi.ts +++ b/src/client/environmentApi.ts @@ -26,11 +26,11 @@ import { EnvironmentTools, EnvironmentType, EnvironmentVariablesChangeEvent, - IExtensionApi, + PythonExtension, RefreshOptions, ResolvedEnvironment, Resource, -} from './apiTypes'; +} from './api/main'; import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi'; type ActiveEnvironmentChangeEvent = { @@ -114,7 +114,7 @@ function filterUsingVSCodeContext(e: PythonEnvInfo) { export function buildEnvironmentApi( discoveryApi: IDiscoveryAPI, serviceContainer: IServiceContainer, -): IExtensionApi['environments'] { +): PythonExtension['environments'] { const interpreterPathService = serviceContainer.get(IInterpreterPathService); const configService = serviceContainer.get(IConfigurationService); const disposables = serviceContainer.get(IDisposableRegistry); @@ -180,7 +180,7 @@ export function buildEnvironmentApi( onEnvironmentVariablesChanged, ); - const environmentApi: IExtensionApi['environments'] = { + const environmentApi: PythonExtension['environments'] = { getEnvironmentVariables: (resource?: Resource) => { sendApiTelemetry('getEnvironmentVariables'); resource = resource && 'uri' in resource ? resource.uri : resource; diff --git a/src/client/extension.ts b/src/client/extension.ts index 5fcb63e2d322..89649d377c74 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -41,7 +41,7 @@ import { sendErrorTelemetry, sendStartupTelemetry } from './startupTelemetry'; import { IStartupDurations } from './types'; import { runAfterActivation } from './common/utils/runAfterActivation'; import { IInterpreterService } from './interpreter/contracts'; -import { IExtensionApi } from './apiTypes'; +import { PythonExtension } from './api/main'; import { WorkspaceService } from './common/application/workspace'; import { disposeAll } from './common/utils/resourceLifecycle'; import { ProposedExtensionAPI } from './proposedApiTypes'; @@ -58,8 +58,8 @@ let activatedServiceContainer: IServiceContainer | undefined; ///////////////////////////// // public functions -export async function activate(context: IExtensionContext): Promise { - let api: IExtensionApi; +export async function activate(context: IExtensionContext): Promise { + let api: PythonExtension; let ready: Promise; let serviceContainer: IServiceContainer; try { @@ -103,7 +103,7 @@ async function activateUnsafe( context: IExtensionContext, startupStopWatch: StopWatch, startupDurations: IStartupDurations, -): Promise<[IExtensionApi & ProposedExtensionAPI, Promise, IServiceContainer]> { +): Promise<[PythonExtension & ProposedExtensionAPI, Promise, IServiceContainer]> { // Add anything that we got from initializing logs to dispose. context.subscriptions.push(...logDispose); diff --git a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts index 52209a5a31d0..e0e79134fc56 100644 --- a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts +++ b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License import { Event, Disposable, WorkspaceFolder } from 'vscode'; -import { EnvironmentTools } from '../../apiTypes'; +import { EnvironmentTools } from '../../api/main'; export type CreateEnvironmentUserActions = 'Back' | 'Cancel'; export type EnvironmentProviderId = string; diff --git a/src/test/api.test.ts b/src/test/api.test.ts index 24eb78c11bf0..488ce79073c2 100644 --- a/src/test/api.test.ts +++ b/src/test/api.test.ts @@ -2,12 +2,12 @@ // Licensed under the MIT License. import { expect } from 'chai'; -import { IExtensionApi } from '../client/apiTypes'; +import { PythonExtension } from '../client/api/main'; import { ProposedExtensionAPI } from '../client/proposedApiTypes'; import { initialize } from './initialize'; suite('Python API tests', () => { - let api: IExtensionApi & ProposedExtensionAPI; + let api: PythonExtension & ProposedExtensionAPI; suiteSetup(async () => { api = await initialize(); }); diff --git a/src/test/common.ts b/src/test/common.ts index 0a76c495830a..0168ee47fc37 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -10,7 +10,7 @@ import * as glob from 'glob'; import * as path from 'path'; import { coerce, SemVer } from 'semver'; import { ConfigurationTarget, Event, TextDocument, Uri } from 'vscode'; -import type { IExtensionApi } from '../client/apiTypes'; +import type { PythonExtension } from '../client/api/main'; import { IProcessService } from '../client/common/process/types'; import { IDisposable } from '../client/common/types'; import { IServiceContainer, IServiceManager } from '../client/ioc/types'; @@ -438,7 +438,7 @@ export async function isPythonVersion(...versions: string[]): Promise { } } -export interface IExtensionTestApi extends IExtensionApi, ProposedExtensionAPI { +export interface IExtensionTestApi extends PythonExtension, ProposedExtensionAPI { serviceContainer: IServiceContainer; serviceManager: IServiceManager; } diff --git a/src/test/environmentApi.unit.test.ts b/src/test/environmentApi.unit.test.ts index a4ea73fb6c92..76441247db49 100644 --- a/src/test/environmentApi.unit.test.ts +++ b/src/test/environmentApi.unit.test.ts @@ -36,8 +36,8 @@ import { ActiveEnvironmentPathChangeEvent, EnvironmentVariablesChangeEvent, EnvironmentsChangeEvent, - IExtensionApi, -} from '../client/apiTypes'; + PythonExtension, +} from '../client/api/main'; suite('Python Environment API', () => { const workspacePath = 'path/to/workspace'; @@ -57,7 +57,7 @@ suite('Python Environment API', () => { let onDidChangeEnvironments: EventEmitter; let onDidChangeEnvironmentVariables: EventEmitter; - let environmentApi: IExtensionApi['environments']; + let environmentApi: PythonExtension['environments']; setup(() => { serviceContainer = typemoq.Mock.ofType(); diff --git a/src/test/initialize.ts b/src/test/initialize.ts index f4f37204da85..8a7abca0d91c 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import * as vscode from 'vscode'; -import type { IExtensionApi } from '../client/apiTypes'; +import type { PythonExtension } from '../client/api/main'; import { clearPythonPathInWorkspaceFolder, IExtensionTestApi, @@ -42,7 +42,7 @@ export async function initialize(): Promise { return (api as any) as IExtensionTestApi; } export async function activateExtension() { - const extension = vscode.extensions.getExtension(PVSC_EXTENSION_ID_FOR_TESTS)!; + const extension = vscode.extensions.getExtension(PVSC_EXTENSION_ID_FOR_TESTS)!; const api = await extension.activate(); // Wait until its ready to use. await api.ready;