Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API for extending the connections pane #5785

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
102 changes: 102 additions & 0 deletions extensions/positron-r/src/connections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import * as positron from 'positron';
import * as vscode from 'vscode';

export function registerConnectionDrivers(context: vscode.ExtensionContext) {
context.subscriptions.push(
positron.connections.registerConnectionDriver(new RPostgreSQLDriver())
);
}

class RPostgreSQLDriver implements positron.ConnectionsDriver {
driverId: string = 'postgres';
metadata: positron.ConnectionsDriverMetadata = {
languageId: 'r',
name: 'PostgresSQL',
inputs: [
{
'id': 'dbname',
'label': 'Database Name',
'type': 'string',
'value': 'localhost'
},
{
'id': 'host',
'label': 'Host',
'type': 'string',
'value': 'localhost'
},
{
'id': 'port',
'label': 'Port',
'type': 'number',
'value': '5432'
},
{
'id': 'user',
'label': 'User',
'type': 'string',
'value': 'postgres'
},
{
'id': 'password',
'label': 'Password',
'type': 'string',
'value': 'password'
},
{
'id': 'bigint',
'label': 'Integer representation',
'type': 'option',
'options': [
{ 'identifier': 'integer64', 'title': 'integer64' },
{ 'identifier': 'integer', 'title': 'integer' },
{ 'identifier': 'numeric', 'title': 'numeric' },
{ 'identifier': 'character', 'title': 'character' }
],
'value': 'integer64'
}
]
};

generateCode(inputs: positron.ConnectionsInput[]) {
const dbname = inputs.find(input => input.id === 'dbname')?.value;
const host = inputs.find(input => input.id === 'host')?.value;
const port = inputs.find(input => input.id === 'port')?.value;
const user = inputs.find(input => input.id === 'user')?.value;
const password = inputs.find(input => input.id === 'password')?.value;
const bigint = inputs.find(input => input.id === 'bigint')?.value;

return `library(DBI)
con <- dbConnect(
RPostgres::Postgres(),
dbname = '${dbname ?? ''}',
host = '${host ?? ''}',
port = ${port ?? ''},
user = '${user ?? ''}',
password = '${password ?? ''}',
bigint = '${bigint ?? ''}'
)
`;
}

async connect(code: string) {
const exec = await positron.runtime.executeCode(
'r',
code,
true,
false,
positron.RuntimeCodeExecutionMode.Interactive,
positron.RuntimeErrorBehavior.Continue
);
if (!exec) {
throw new Error('Failed to execute code');
}
return;
}
}

4 changes: 4 additions & 0 deletions extensions/positron-r/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { setContexts } from './contexts';
import { setupTestExplorer, refreshTestExplorer } from './testing/testing';
import { RRuntimeManager } from './runtime-manager';
import { registerUriHandler } from './uri-handler';
import { registerConnectionDrivers } from './connections';

export const LOGGER = vscode.window.createOutputChannel('Positron R Extension', { log: true });

Expand Down Expand Up @@ -47,4 +48,7 @@ export function activate(context: vscode.ExtensionContext) {
refreshTestExplorer(context);
}
});

// Register connection drivers.
registerConnectionDrivers(context);
}
54 changes: 54 additions & 0 deletions src/positron-dts/positron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,53 @@ declare module 'positron' {
pasteText(text: string): void;
}

export interface ConnectionsInput {
// The unique identifier for the input.
id: string;
// A human-readable label for the input.
label: string;
// The type of the input.
type: 'string' | 'number' | 'option';
// Options, if the input type is an option.
options?: { 'identifier': string; 'title': string }[];
// The default value for the input.
value?: string;
}

export interface ConnectionsDriverMetadata {
// The language identifier for the driver.
// Drivers are grouped by language, not by runtime.
languageId: string;
// A human-readable name for the driver.
name: string;
// The base64-encoded SVG icon for the driver.
base64EncodedIconSvg?: string;
// The inputs required to create a connection.
// For instance, a connection might require a username
// and password.
inputs: Array<ConnectionsInput>;
}

export interface ConnectionsDriver {
// The unique identifier for the driver.
driverId: string;

// The metadata for the driver.
metadata: ConnectionsDriverMetadata;

// Generates the connection code based on the inputs.
generateCode?: (inputs: Array<ConnectionsInput>) => string;
// Connect session
connect?: (code: string) => Promise<void>;
// Checks if the dependencies for the driver are installed
// and functioning.
checkDependencies?: () => Promise<boolean>;
// Installs the dependencies for the driver.
// For instance, R packages would install the required
// R packages, and or other dependencies.
installDependencies?: () => Promise<boolean>;
}

namespace languages {
/**
* Register a statement range provider.
Expand Down Expand Up @@ -1387,4 +1434,11 @@ declare module 'positron' {


}

/**
* Refers to methods related to the connections pane
*/
namespace connections {
export function registerConnectionDriver(driver: ConnectionsDriver): vscode.Disposable;
}
}
1 change: 1 addition & 0 deletions src/vs/workbench/api/browser/extensionHost.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ import './positron/mainThreadPreviewPanel.js';
import './positron/mainThreadModalDialogs.js';
import './positron/mainThreadConsoleService.js';
import './positron/mainThreadContextKeyService.js';
import './positron/mainThreadConnections.js';
// --- End Positron ---

export class ExtensionPoints implements IWorkbenchContribution {
Expand Down
74 changes: 74 additions & 0 deletions src/vs/workbench/api/browser/positron/mainThreadConnections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import { ExtHostConnectionsShape, ExtHostPositronContext, MainPositronContext, MainThreadConnectionsShape } from '../../common/positron/extHost.positron.protocol.js';
import { extHostNamedCustomer, IExtHostContext } from '../../../services/extensions/common/extHostCustomers.js';
import { IDriver, IDriverMetadata, Input } from '../../../services/positronConnections/common/interfaces/positronConnectionsDriver.js';
import { IPositronConnectionsService } from '../../../services/positronConnections/common/interfaces/positronConnectionsService.js';

@extHostNamedCustomer(MainPositronContext.MainThreadConnections)
export class MainThreadConnections implements MainThreadConnectionsShape {
private readonly _proxy: ExtHostConnectionsShape;
constructor(
extHostContext: IExtHostContext,
@IPositronConnectionsService private readonly _connectionsService: IPositronConnectionsService
) {
this._proxy = extHostContext.getProxy(ExtHostPositronContext.ExtHostConnections);
}

$registerConnectionDriver(driverId: string, metadata: IDriverMetadata, availableMethods: IAvailableDriverMethods): void {
this._connectionsService.driverManager.registerDriver(new MainThreadDriverAdapter(
driverId, metadata, availableMethods, this._proxy
));
}

$removeConnectionDriver(driverId: string): void {
this._connectionsService.driverManager.removeDriver(driverId);
}

dispose(): void {

}
}

export interface IAvailableDriverMethods {
generateCode: boolean,
connect: boolean,
checkDependencies: boolean,
installDependencies: boolean
}

class MainThreadDriverAdapter implements IDriver {
constructor(
readonly driverId: string,
readonly metadata: IDriverMetadata,
private readonly availableMethods: IAvailableDriverMethods,
private readonly _proxy: ExtHostConnectionsShape
) { }
get generateCode() {
if (!this.availableMethods.generateCode) {
return undefined;
}
return (inputs: Input[]) => this._proxy.$driverGenerateCode(this.driverId, inputs);
}
get connect() {
if (!this.availableMethods.connect) {
return undefined;
}
return (code: string) => this._proxy.$driverConnect(this.driverId, code);
}
get checkDependencies() {
if (!this.availableMethods.checkDependencies) {
return undefined;
}
return () => this._proxy.$driverCheckDependencies(this.driverId);
}
get installDependencies() {
if (!this.availableMethods.installDependencies) {
return undefined;
}
return () => this._proxy.$driverInstallDependencies(this.driverId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { IRuntimeStartupService, RuntimeStartupPhase } from '../../../services/r
import { SerializableObjectWithBuffers } from '../../../services/extensions/common/proxyIdentifier.js';
import { isWebviewReplayMessage } from '../../../contrib/positronWebviewPreloads/browser/utils.js';
import { IPositronWebviewPreloadService } from '../../../services/positronWebviewPreloads/browser/positronWebviewPreloadService.js';
import { IPositronConnectionsService } from '../../../services/positronConnections/browser/interfaces/positronConnectionsService.js';
import { IPositronConnectionsService } from '../../../services/positronConnections/common/interfaces/positronConnectionsService.js';

/**
* Represents a language runtime event (for example a message or state change)
Expand Down
15 changes: 15 additions & 0 deletions src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ExtHostConsoleService } from './extHostConsoleService.js';
import { ExtHostMethods } from './extHostMethods.js';
import { ExtHostEditors } from '../extHostTextEditors.js';
import { UiFrontendRequest } from '../../../services/languageRuntime/common/positronUiComm.js';
import { ExtHostConnections } from './extHostConnections.js';

/**
* Factory interface for creating an instance of the Positron API.
Expand Down Expand Up @@ -71,6 +72,7 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce
const extHostMethods = rpcProtocol.set(ExtHostPositronContext.ExtHostMethods,
new ExtHostMethods(rpcProtocol, extHostEditors, extHostDocuments, extHostModalDialogs,
extHostLanguageRuntime, extHostWorkspace, extHostCommands, extHostContextKeyService));
const extHostConnections = rpcProtocol.set(ExtHostPositronContext.ExtHostConnections, new ExtHostConnections(rpcProtocol));

return function (extension: IExtensionDescription, extensionInfo: IExtensionRegistries, configProvider: ExtHostConfigProvider): typeof positron {

Expand Down Expand Up @@ -192,6 +194,18 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce
},
};

const connections: typeof positron.connections = {
/**
* Register a connection driver that's used to generate code for connecting to a data source
* using the 'New Connection' dialog.
* @param driver The connection driver to register.
* @returns A disposable that can be used to unregister the driver.
*/
registerConnectionDriver(driver: positron.ConnectionsDriver): vscode.Disposable {
return extHostConnections.registerConnectionDriver(driver);
}
};

// --- End Positron ---

return <typeof positron>{
Expand All @@ -201,6 +215,7 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce
window,
languages,
methods,
connections,
PositronOutputLocation: extHostTypes.PositronOutputLocation,
RuntimeClientType: extHostTypes.RuntimeClientType,
RuntimeClientState: extHostTypes.RuntimeClientState,
Expand Down
16 changes: 16 additions & 0 deletions src/vs/workbench/api/common/positron/extHost.positron.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { URI, UriComponents } from '../../../../base/common/uri.js';
import { IEditorContext } from '../../../services/frontendMethods/common/editorContext.js';
import { RuntimeClientType } from './extHostTypes.positron.js';
import { LanguageRuntimeDynState, RuntimeSessionMetadata } from 'positron';
import { IDriverMetadata, Input } from '../../../services/positronConnections/common/interfaces/positronConnectionsDriver.js';
import { IAvailableDriverMethods } from '../../browser/positron/mainThreadConnections.js';

// NOTE: This check is really to ensure that extHost.protocol is included by the TypeScript compiler
// as a dependency of this module, and therefore that it's initialized first. This is to avoid a
Expand Down Expand Up @@ -107,6 +109,18 @@ export interface ExtHostMethodsShape {
showQuestion(title: string, message: string, okButtonTitle: string, cancelButtonTitle: string): Promise<boolean>;
}

export interface MainThreadConnectionsShape {
$registerConnectionDriver(driverId: string, metadata: IDriverMetadata, availableMethods: IAvailableDriverMethods): void;
$removeConnectionDriver(driverId: string): void;
}

export interface ExtHostConnectionsShape {
$driverGenerateCode(driverId: string, inputs: Input[]): Promise<string>;
$driverConnect(driverId: string, code: string): Promise<void>;
$driverCheckDependencies(driverId: string): Promise<boolean>;
$driverInstallDependencies(driverId: string): Promise<boolean>;
}

/**
* The view state of a preview in the Preview panel. Only one preview can be
* active at a time (the one currently loaded into the panel); the active
Expand Down Expand Up @@ -179,6 +193,7 @@ export const ExtHostPositronContext = {
ExtHostConsoleService: createProxyIdentifier<ExtHostConsoleServiceShape>('ExtHostConsoleService'),
ExtHostContextKeyService: createProxyIdentifier<ExtHostContextKeyServiceShape>('ExtHostContextKeyService'),
ExtHostMethods: createProxyIdentifier<ExtHostMethodsShape>('ExtHostMethods'),
ExtHostConnections: createProxyIdentifier<ExtHostConnectionsShape>('ExtHostConnections'),
};

export const MainPositronContext = {
Expand All @@ -188,4 +203,5 @@ export const MainPositronContext = {
MainThreadConsoleService: createProxyIdentifier<MainThreadConsoleServiceShape>('MainThreadConsoleService'),
MainThreadContextKeyService: createProxyIdentifier<MainThreadContextKeyServiceShape>('MainThreadContextKeyService'),
MainThreadMethods: createProxyIdentifier<MainThreadMethodsShape>('MainThreadMethods'),
MainThreadConnections: createProxyIdentifier<MainThreadConnectionsShape>('MainThreadConnections'),
};
Loading
Loading