Skip to content

Commit

Permalink
API for extending the connections pane
Browse files Browse the repository at this point in the history
  • Loading branch information
dfalbel committed Dec 17, 2024
1 parent 1df88e1 commit dd93e10
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 64 deletions.
60 changes: 60 additions & 0 deletions src/positron-dts/positron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,59 @@ declare module 'positron' {
pasteText(text: string): void;
}

export enum ConnectionsInputType {
String = 'string',
Number = 'number',
Option = 'option',
}

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: ConnectionsInputType;
// 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 +1440,11 @@ declare module 'positron' {


}

/**
* Refers to methods related to the connections pane
*/
namespace connections {
export function registerConnectionDriver(driver: ConnectionsDriver): vscode.Disposable;
}
}
72 changes: 72 additions & 0 deletions src/vs/workbench/api/browser/positron/mainThreadConnections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/


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

@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;
}
}
get checkDependencies() {
if (!this.availableMethods.checkDependencies) {
return undefined;
}
}
get installDependencies() {
if (!this.availableMethods.installDependencies) {
return undefined;
}
}
}
10 changes: 10 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,12 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce
},
};

const connections: typeof positron.connections = {
registerConnectionDriver(driver: positron.ConnectionsDriver): vscode.Disposable {
return extHostConnections.registerConnectionDriver(driver);
}
};

// --- End Positron ---

return <typeof positron>{
Expand All @@ -201,6 +209,7 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce
window,
languages,
methods,
connections,
PositronOutputLocation: extHostTypes.PositronOutputLocation,
RuntimeClientType: extHostTypes.RuntimeClientType,
RuntimeClientState: extHostTypes.RuntimeClientState,
Expand All @@ -216,6 +225,7 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce
RuntimeOnlineState: extHostTypes.RuntimeOnlineState,
RuntimeState: extHostTypes.RuntimeState,
RuntimeCodeFragmentStatus: extHostTypes.RuntimeCodeFragmentStatus,
ConnectionsInputType: extHostTypes.ConnectionsInputType,
};
};
}
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/browser/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'),
};
90 changes: 90 additions & 0 deletions src/vs/workbench/api/common/positron/extHostConnections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import { Disposable } from 'vscode';
import * as positron from 'positron';
import * as extHostProtocol from './extHost.positron.protocol.js';
import { Input, InputType } from '../../../services/positronConnections/browser/interfaces/positronConnectionsDriver.js';

export class ExtHostConnections implements extHostProtocol.ExtHostConnectionsShape {

private readonly _proxy: extHostProtocol.MainThreadConnectionsShape;
private _drivers: positron.ConnectionsDriver[] = [];

constructor(
mainContext: extHostProtocol.IMainPositronContext,
) {
// Trigger creation of the proxy
this._proxy = mainContext.getProxy(extHostProtocol.MainPositronContext.MainThreadConnections);
}

public registerConnectionDriver(driver: positron.ConnectionsDriver): Disposable {
// Check if the driver is already registered, and if not push, otherwise replace
const existingDriverIndex = this._drivers.findIndex(d => d.driverId === driver.driverId);
if (existingDriverIndex !== -1) {
this._drivers[existingDriverIndex] = driver;
} else {
this._drivers.push(driver);
}

const metadata = {
...driver.metadata,
inputs: driver.metadata.inputs.map(i => extHost2MainThreadInput(i))
}

this._proxy.$registerConnectionDriver(
driver.driverId,
metadata,
{
generateCode: driver.generateCode ? true : false,
connect: driver.connect ? true : false,
checkDependencies: driver.checkDependencies ? true : false,
installDependencies: driver.installDependencies ? true : false
}
);
return new Disposable(() => { });
}

public async $driverGenerateCode(driverId: string, inputs: Input[]) {
const driver = this._drivers.find(d => d.driverId === driverId);
if (!driver || !driver.generateCode) {
throw new Error(`Driver ${driverId} does not support code generation`);
}
return driver.generateCode(inputs.map(
i => ({ ...i, type: i.type as string as positron.ConnectionsInputType })
));
}

public $driverConnect(driverId: string, code: string): Promise<void> {
const driver = this._drivers.find(d => d.driverId === driverId);
if (!driver || !driver.connect) {
throw new Error(`Driver ${driverId} does not support connecting`);
}
return driver.connect(code);
}

public $driverCheckDependencies(driverId: string): Promise<boolean> {
const driver = this._drivers.find(d => d.driverId === driverId);
if (!driver || !driver.checkDependencies) {
throw new Error(`Driver ${driverId} does not support checking dependencies`);
}
return driver.checkDependencies();
}

public $driverInstallDependencies(driverId: string): Promise<boolean> {
const driver = this._drivers.find(d => d.driverId === driverId);
if (!driver || !driver.installDependencies) {
throw new Error(`Driver ${driverId} does not support installing dependencies`);
}
return driver.installDependencies();
}
}

function extHost2MainThreadInput(input: positron.ConnectionsInput): Input {
return {
...input,
type: input.type as string as InputType
}
}
6 changes: 6 additions & 0 deletions src/vs/workbench/api/common/positron/extHostTypes.positron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,9 @@ export enum LanguageRuntimeSessionLocation {
*/
Browser = 'browser',
}

export enum ConnectionsInputType {
String = 'string',
Number = 'number',
Option = 'option',
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const NewConnectionModalDialog = (props: PropsWithChildren<NewConnectionModalDia

const backHandler = () => {
// When hitting back, reset the language ID to the previously selected language id
setLanguageId(selectedDriver?.languageId);
setLanguageId(selectedDriver?.metadata.languageId);
setSelectedDriver(undefined);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,19 @@ interface CreateConnectionProps {

export const CreateConnection = (props: PropsWithChildren<CreateConnectionProps>) => {

const { name, languageId, generateCode } = props.selectedDriver;
const { generateCode, metadata } = props.selectedDriver;
const { name, languageId } = metadata;
const { onBack, onCancel, services } = props;
const editorRef = useRef<SimpleCodeEditorWidget>(undefined!);

const [inputs, setInputs] = useState<Array<Input>>(props.selectedDriver.inputs);
const [code, setCode] = useState<string | undefined>(props.selectedDriver.generateCode?.(props.selectedDriver.inputs));
const [inputs, setInputs] = useState<Array<Input>>(metadata.inputs);
const [code, setCode] = useState<string | undefined>(undefined);

useEffect(() => {
// Debounce the code generation to avoid unnecessary re-renders
const timeoutId = setTimeout(() => {
const timeoutId = setTimeout(async () => {
if (generateCode) {
const code = generateCode(inputs);
const code = await generateCode(inputs);
setCode(code);
}
}, 200);
Expand All @@ -57,7 +58,7 @@ export const CreateConnection = (props: PropsWithChildren<CreateConnectionProps>
message: localize(
'positron.newConnectionModalDialog.createConnection.connecting',
"Connecting to data source ({0})...",
props.selectedDriver.name
name
),
severity: Severity.Info
});
Expand Down Expand Up @@ -96,7 +97,7 @@ export const CreateConnection = (props: PropsWithChildren<CreateConnectionProps>
</h1>
</div>

<Form inputs={props.selectedDriver.inputs} onInputsChange={setInputs}></Form>
<Form inputs={metadata.inputs} onInputsChange={setInputs}></Form>

<div className='create-connection-code-title'>
{(() => localize('positron.newConnectionModalDialog.createConnection.code', "Connection Code"))()}
Expand Down
Loading

0 comments on commit dd93e10

Please sign in to comment.