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

Merged
merged 9 commits into from
Dec 20, 2024
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
102 changes: 102 additions & 0 deletions extensions/positron-connections/src/drivers.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;
}
}

3 changes: 3 additions & 0 deletions extensions/positron-connections/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import * as vscode from 'vscode';
import * as positron from 'positron';
import { ConnectionItem, ConnectionItemsProvider, isActiveConnectionItem, DatabaseConnectionItem, DisconnectedConnectionItem } from './connection';
import { PositronConnectionsComm } from './comms/ConnectionsComms';
import { registerConnectionDrivers } from './drivers';


export function activate(context: vscode.ExtensionContext) {
// We always register the drivers.
registerConnectionDrivers(context);

const config = vscode.workspace.getConfiguration('positron');
const enabled = !config.get<boolean>('connections', false);
Expand Down
100 changes: 100 additions & 0 deletions src/positron-dts/positron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,92 @@ declare module 'positron' {
pasteText(text: string): void;
}

/**
* ConnectionsInput interface defines the structure for connection inputs.
*/
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;
}

/**
* ConnectionsDriverMetadata interface defines the structure for connection driver metadata.
*/
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 +1473,18 @@ declare module 'positron' {


}

/**
* Refers to methods related to the connections pane
*/
namespace connections {
/**
* Registers a new connection driver with Positron allowing extensions to contribute
* to the 'New Connection' dialog.
*
* @param driver The connection driver to register
* @returns A disposable that unregisters the driver when disposed
*/
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
76 changes: 76 additions & 0 deletions src/vs/workbench/api/browser/positron/mainThreadConnections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* 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';
import { DisposableStore } from '../../../../base/common/lifecycle.js';

@extHostNamedCustomer(MainPositronContext.MainThreadConnections)
export class MainThreadConnections implements MainThreadConnectionsShape {
private readonly _proxy: ExtHostConnectionsShape;
private readonly _disposables = new DisposableStore();
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 {
this._disposables.dispose();
}
}

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
Loading
Loading