Skip to content

Commit

Permalink
feat: installer for the runner (#1038)
Browse files Browse the repository at this point in the history
### Summary of Changes

Add a command "Install the Safe-DS Runner" to automatically install the
runner and update the settings accordingly. It also gets suggested in
the error message if the runner could not be started.
  • Loading branch information
lars-reimann authored Apr 13, 2024
1 parent dbe96e3 commit 2bde594
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 34 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SafeDsServices } from '../safe-ds-module.js';
import { Connection } from 'vscode-languageserver';
import { SafeDsServices } from '../language/safe-ds-module.js';
import { Connection, MessageActionItem } from 'vscode-languageserver';
import { Disposable } from 'vscode-languageserver-protocol';

/* c8 ignore start */
Expand All @@ -11,6 +11,7 @@ export class SafeDsMessagingProvider {
private readonly connection: Connection | undefined;
private logger: Logger | undefined = undefined;
private userMessageProvider: UserMessageProvider | undefined = undefined;
private messageBroker: MessageBroker | undefined = undefined;

constructor(services: SafeDsServices) {
this.connection = services.shared.lsp.Connection;
Expand Down Expand Up @@ -86,11 +87,18 @@ export class SafeDsMessagingProvider {
* Depending on the client this might be a modal dialog with a confirmation button or a notification in a
* notification center.
*/
showInformationMessage(message: string): void {
showInformationMessage(message: string): void;
async showInformationMessage<T extends MessageActionItem>(message: string, ...actions: T[]): Promise<T | undefined>;
async showInformationMessage<T extends MessageActionItem>(
message: string,
...actions: T[]
): Promise<T | undefined> {
if (this.userMessageProvider?.showInformationMessage) {
this.userMessageProvider.showInformationMessage(message);
return this.userMessageProvider.showInformationMessage(message, ...actions);
} else if (this.connection) {
this.connection.window.showInformationMessage(message);
return this.connection.window.showInformationMessage(message, ...actions);
} else {
return undefined;
}
}

Expand All @@ -100,11 +108,15 @@ export class SafeDsMessagingProvider {
* Depending on the client this might be a modal dialog with a confirmation button or a notification in a
* notification center.
*/
showWarningMessage(message: string): void {
showWarningMessage(message: string): void;
async showWarningMessage<T extends MessageActionItem>(message: string, ...actions: T[]): Promise<T | undefined>;
async showWarningMessage<T extends MessageActionItem>(message: string, ...actions: T[]): Promise<T | undefined> {
if (this.userMessageProvider?.showWarningMessage) {
this.userMessageProvider.showWarningMessage(message);
return this.userMessageProvider.showWarningMessage(message, ...actions);
} else if (this.connection) {
this.connection.window.showWarningMessage(message);
return this.connection.window.showWarningMessage(message, ...actions);
} else {
return undefined;
}
}

Expand All @@ -114,11 +126,15 @@ export class SafeDsMessagingProvider {
* Depending on the client this might be a modal dialog with a confirmation button or a notification in a
* notification center.
*/
showErrorMessage(message: string): void {
showErrorMessage(message: string): void;
async showErrorMessage<T extends MessageActionItem>(message: string, ...actions: T[]): Promise<T | undefined>;
async showErrorMessage<T extends MessageActionItem>(message: string, ...actions: T[]): Promise<T | undefined> {
if (this.userMessageProvider?.showErrorMessage) {
this.userMessageProvider.showErrorMessage(message);
return this.userMessageProvider.showErrorMessage(message, ...actions);
} else if (this.connection) {
this.connection.window.showErrorMessage(message);
return this.connection.window.showErrorMessage(message, ...actions);
} else {
return undefined;
}
}

Expand All @@ -128,25 +144,29 @@ export class SafeDsMessagingProvider {
* @param method The method to register a request handler for.
* @param handler The handler to install.
*/
onNotification(method: string, handler: (...params: any[]) => void): Disposable {
onNotification(method: string, handler: (...args: any[]) => void): Disposable {
if (this.connection) {
return this.connection.onNotification(method, handler);
} else if (this.messageBroker?.onNotification) {
return this.messageBroker.onNotification(method, handler);
} else {
return {
dispose() {},
};
}

return {
dispose() {},
};
}

/**
* Send a notification to the client.
*
* @param method The method to invoke on the client.
* @param params The notification's parameters.
* @param args The notification's parameters.
*/
async sendNotification(method: string, ...params: any): Promise<void> {
async sendNotification(method: string, ...args: any): Promise<void> {
if (this.connection) {
await this.connection.sendNotification(method, params);
await this.connection.sendNotification(method, args);
} else if (this.messageBroker?.sendNotification) {
await this.messageBroker.sendNotification(method, ...args);
}
}

Expand All @@ -163,6 +183,13 @@ export class SafeDsMessagingProvider {
setUserMessageProvider(userMessageProvider: UserMessageProvider) {
this.userMessageProvider = userMessageProvider;
}

/**
* Set the message broker to use for communicating with the client.
*/
setMessageBroker(messageBroker: MessageBroker) {
this.messageBroker = messageBroker;
}
}

/* c8 ignore stop */
Expand Down Expand Up @@ -204,15 +231,36 @@ export interface UserMessageProvider {
/**
* Prominently show an information message. The message should be short and human-readable.
*/
showInformationMessage?: (message: string) => void;
showInformationMessage?: <T extends MessageActionItem>(message: string, ...actions: T[]) => Thenable<T | undefined>;

/**
* Prominently show a warning message. The message should be short and human-readable.
*/
showWarningMessage?: (message: string) => void;
showWarningMessage?: <T extends MessageActionItem>(message: string, ...actions: T[]) => Thenable<T | undefined>;

/**
* Prominently show an error message. The message should be short and human-readable.
*/
showErrorMessage?: (message: string) => void;
showErrorMessage?: <T extends MessageActionItem>(message: string, ...actions: T[]) => Thenable<T | undefined>;
}

/**
* A message broker for communicating with the client.
*/
export interface MessageBroker {
/**
* Installs a notification handler for the given method.
*
* @param method The method to register a request handler for.
* @param handler The handler to install.
*/
onNotification?: (method: string, handler: (...args: any[]) => void) => Disposable;

/**
* Send a notification to the client.
*
* @param method The method to invoke on the client.
* @param args The notification's parameters.
*/
sendNotification?: (method: string, ...args: any[]) => Promise<void>;
}
10 changes: 9 additions & 1 deletion packages/safe-ds-lang/src/language/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RPC_RUNNER_STARTED } from './runner/safe-ds-runner.js';
import { pipVersionRange, RPC_RUNNER_INSTALL, RPC_RUNNER_STARTED } from './runner/safe-ds-runner.js';

// Services
export type { SafeDsServices } from './safe-ds-module.js';
Expand All @@ -22,5 +22,13 @@ export * as messages from './runner/messages.js';

// Remote procedure calls
export const rpc = {
runnerInstall: RPC_RUNNER_INSTALL,
runnerStarted: RPC_RUNNER_STARTED,
};

// Dependencies
export const dependencies = {
'safe-ds-runner': {
pipVersionRange,
},
};
17 changes: 11 additions & 6 deletions packages/safe-ds-lang/src/language/runner/safe-ds-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ import { SafeDsAnnotations } from '../builtins/safe-ds-annotations.js';
import { SafeDsPythonGenerator } from '../generation/safe-ds-python-generator.js';
import { isSdsModule } from '../generated/ast.js';
import semver from 'semver';
import { SafeDsMessagingProvider } from '../lsp/safe-ds-messaging-provider.js';
import { SafeDsMessagingProvider } from '../../communication/safe-ds-messaging-provider.js';

// Most of the functionality cannot be tested automatically as a functioning runner setup would always be required

export const RPC_RUNNER_INSTALL = 'runner/install';
export const RPC_RUNNER_STARTED = 'runner/started';

const LOWEST_SUPPORTED_VERSION = '0.10.0';
const LOWEST_UNSUPPORTED_VERSION = '0.11.0';
const npmVersionRange = `>=${LOWEST_SUPPORTED_VERSION} <${LOWEST_UNSUPPORTED_VERSION}`;
const pipVersionRange = `>=${LOWEST_SUPPORTED_VERSION},<${LOWEST_UNSUPPORTED_VERSION}`;
const LOWEST_SUPPORTED_RUNNER_VERSION = '0.10.0';
const LOWEST_UNSUPPORTED_RUNNER_VERSION = '0.11.0';
const npmVersionRange = `>=${LOWEST_SUPPORTED_RUNNER_VERSION} <${LOWEST_UNSUPPORTED_RUNNER_VERSION}`;
export const pipVersionRange = `>=${LOWEST_SUPPORTED_RUNNER_VERSION},<${LOWEST_UNSUPPORTED_RUNNER_VERSION}`;

const RUNNER_TAG = 'Runner';

Expand Down Expand Up @@ -175,9 +176,13 @@ export class SafeDsRunner {
}
} catch (error) {
this.error(`Could not start runner: ${error instanceof Error ? error.message : error}`);
this.messaging.showErrorMessage(
const action = await this.messaging.showErrorMessage(
`The runner process could not be started: ${error instanceof Error ? error.message : error}`,
{ title: 'Install runner' },
);
if (action?.title === 'Install runner') {
await this.messaging.sendNotification(RPC_RUNNER_INSTALL);
}
return;
}
// Start the runner at the specified port
Expand Down
19 changes: 17 additions & 2 deletions packages/safe-ds-lang/src/language/safe-ds-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ import { SafeDsTypeFactory } from './typing/safe-ds-type-factory.js';
import { SafeDsMarkdownGenerator } from './generation/safe-ds-markdown-generator.js';
import { SafeDsCompletionProvider } from './lsp/safe-ds-completion-provider.js';
import { SafeDsFuzzyMatcher } from './lsp/safe-ds-fuzzy-matcher.js';
import { type Logger, SafeDsMessagingProvider, type UserMessageProvider } from './lsp/safe-ds-messaging-provider.js';
import {
type Logger,
MessageBroker,
SafeDsMessagingProvider,
type UserMessageProvider,
} from '../communication/safe-ds-messaging-provider.js';
import { SafeDsConfigurationProvider } from './workspace/safe-ds-configuration-provider.js';
import { SafeDsCodeLensProvider } from './lsp/safe-ds-code-lens-provider.js';

Expand Down Expand Up @@ -239,12 +244,16 @@ export const createSafeDsServices = async function (
/* c8 ignore next 2 */
SafeDs.lsp.MessagingProvider.setLogger(options.logger);
}
if (options?.messageBroker) {
/* c8 ignore next 2 */
SafeDs.lsp.MessagingProvider.setMessageBroker(options.messageBroker);
}
if (!options?.omitBuiltins) {
await shared.workspace.WorkspaceManager.initializeWorkspace([]);
}
if (options?.runnerCommand) {
/* c8 ignore next 2 */
SafeDs.runtime.Runner.updateRunnerCommand(options.runnerCommand);
await SafeDs.runtime.Runner.updateRunnerCommand(options.runnerCommand);
}
if (options?.userMessageProvider) {
/* c8 ignore next 2 */
Expand All @@ -269,6 +278,12 @@ export interface ModuleOptions {
*/
omitBuiltins?: boolean;

/**
* A message broker for communicating with the client. If the broker lacks a capability, we fall back to the
* language server connection, if available.
*/
messageBroker?: MessageBroker;

/**
* Command to start the runner.
*/
Expand Down
6 changes: 6 additions & 0 deletions packages/safe-ds-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@
"title": "Dump Diagnostics to JSON",
"category": "Safe-DS"
},
{
"command": "safe-ds.installRunner",
"title": "Install the Safe-DS Runner",
"category": "Safe-DS"
},
{
"command": "safe-ds.openDiagnosticsDumps",
"title": "Open Diagnostics Dumps in New VS Code Window",
Expand Down Expand Up @@ -224,6 +229,7 @@
"deploy": "vsce publish"
},
"dependencies": {
"semver": "^7.6.0",
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit 2bde594

Please sign in to comment.