diff --git a/packages/safe-ds-lang/src/language/index.ts b/packages/safe-ds-lang/src/language/index.ts index db48f57a0..c93a14e87 100644 --- a/packages/safe-ds-lang/src/language/index.ts +++ b/packages/safe-ds-lang/src/language/index.ts @@ -1,4 +1,4 @@ -import { pipVersionRange, RPC_RUNNER_INSTALL, RPC_RUNNER_STARTED } from './runner/safe-ds-runner.js'; +import { pipVersionRange, RPC_RUNNER_INSTALL, RPC_RUNNER_START, RPC_RUNNER_STARTED } from './runner/safe-ds-runner.js'; // Services export type { SafeDsServices } from './safe-ds-module.js'; @@ -23,6 +23,7 @@ export * as messages from './runner/messages.js'; // Remote procedure calls export const rpc = { runnerInstall: RPC_RUNNER_INSTALL, + runnerStart: RPC_RUNNER_START, runnerStarted: RPC_RUNNER_STARTED, }; diff --git a/packages/safe-ds-lang/src/language/runner/safe-ds-runner.ts b/packages/safe-ds-lang/src/language/runner/safe-ds-runner.ts index dc3955496..5c2ee5748 100644 --- a/packages/safe-ds-lang/src/language/runner/safe-ds-runner.ts +++ b/packages/safe-ds-lang/src/language/runner/safe-ds-runner.ts @@ -23,6 +23,7 @@ import { SafeDsMessagingProvider } from '../../communication/safe-ds-messaging-p // 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_START = 'runner/start'; export const RPC_RUNNER_STARTED = 'runner/started'; const LOWEST_SUPPORTED_RUNNER_VERSION = '0.10.0'; @@ -59,7 +60,7 @@ export class SafeDsRunner { constructor(services: SafeDsServices) { this.annotations = services.builtins.Annotations; this.generator = services.generation.PythonGenerator; - this.messaging = services.lsp.MessagingProvider; + this.messaging = services.communication.MessagingProvider; // Register listeners this.registerMessageLoggingCallbacks(); @@ -68,6 +69,12 @@ export class SafeDsRunner { await this.updateRunnerCommand(newValue); }); + this.messaging.onNotification(RPC_RUNNER_START, async () => { + if (!this.isPythonServerAvailable()) { + await this.startPythonServer(); + } + }); + services.shared.lsp.Connection?.onShutdown(async () => { await this.stopPythonServer(); }); diff --git a/packages/safe-ds-lang/src/language/safe-ds-module.ts b/packages/safe-ds-lang/src/language/safe-ds-module.ts index 769670d06..c6efb54a2 100644 --- a/packages/safe-ds-lang/src/language/safe-ds-module.ts +++ b/packages/safe-ds-lang/src/language/safe-ds-module.ts @@ -64,6 +64,9 @@ export type SafeDsAddedServices = { Enums: SafeDsEnums; ImpurityReasons: SafeDsImpurityReasons; }; + communication: { + MessagingProvider: SafeDsMessagingProvider; + }; documentation: { DocumentationProvider: SafeDsDocumentationProvider; }; @@ -81,7 +84,6 @@ export type SafeDsAddedServices = { NodeMapper: SafeDsNodeMapper; }; lsp: { - MessagingProvider: SafeDsMessagingProvider; NodeInfoProvider: SafeDsNodeInfoProvider; }; purity: { @@ -132,6 +134,9 @@ export const SafeDsModule: Module new SafeDsEnums(services), ImpurityReasons: (services) => new SafeDsImpurityReasons(services), }, + communication: { + MessagingProvider: (services) => new SafeDsMessagingProvider(services), + }, documentation: { CommentProvider: (services) => new SafeDsCommentProvider(services), DocumentationProvider: (services) => new SafeDsDocumentationProvider(services), @@ -156,7 +161,6 @@ export const SafeDsModule: Module new SafeDsDocumentSymbolProvider(services), Formatter: () => new SafeDsFormatter(), InlayHintProvider: (services) => new SafeDsInlayHintProvider(services), - MessagingProvider: (services) => new SafeDsMessagingProvider(services), NodeInfoProvider: (services) => new SafeDsNodeInfoProvider(services), RenameProvider: (services) => new SafeDsRenameProvider(services), SemanticTokenProvider: (services) => new SafeDsSemanticTokenProvider(services), @@ -242,11 +246,11 @@ export const createSafeDsServices = async function ( // Apply options if (options?.logger) { /* c8 ignore next 2 */ - SafeDs.lsp.MessagingProvider.setLogger(options.logger); + SafeDs.communication.MessagingProvider.setLogger(options.logger); } if (options?.messageBroker) { /* c8 ignore next 2 */ - SafeDs.lsp.MessagingProvider.setMessageBroker(options.messageBroker); + SafeDs.communication.MessagingProvider.setMessageBroker(options.messageBroker); } if (!options?.omitBuiltins) { await shared.workspace.WorkspaceManager.initializeWorkspace([]); @@ -257,7 +261,7 @@ export const createSafeDsServices = async function ( } if (options?.userMessageProvider) { /* c8 ignore next 2 */ - SafeDs.lsp.MessagingProvider.setUserMessageProvider(options.userMessageProvider); + SafeDs.communication.MessagingProvider.setUserMessageProvider(options.userMessageProvider); } return { shared, SafeDs }; diff --git a/packages/safe-ds-vscode/src/extension/commands/installRunner.ts b/packages/safe-ds-vscode/src/extension/commands/installRunner.ts index 7180df13c..621fcc1c9 100644 --- a/packages/safe-ds-vscode/src/extension/commands/installRunner.ts +++ b/packages/safe-ds-vscode/src/extension/commands/installRunner.ts @@ -1,9 +1,10 @@ import vscode, { ExtensionContext, Uri } from 'vscode'; import child_process from 'node:child_process'; import semver from 'semver'; -import { dependencies, SafeDsServices } from '@safe-ds/lang'; +import { dependencies, rpc, SafeDsServices } from '@safe-ds/lang'; import { logError, printOutputMessage } from '../output.js'; import fs from 'node:fs'; +import { LanguageClient } from 'vscode-languageclient/node.js'; const pythonCommandCandidates = ['python3', 'python', 'py']; @@ -11,27 +12,33 @@ const LOWEST_SUPPORTED_PYTHON_VERSION = '3.11.0'; const LOWEST_UNSUPPORTED_PYTHON_VERSION = '3.13.0'; const npmVersionRange = `>=${LOWEST_SUPPORTED_PYTHON_VERSION} <${LOWEST_UNSUPPORTED_PYTHON_VERSION}`; -export const installRunner = (context: ExtensionContext, services: SafeDsServices) => async () => { - // If the runner is already started, do nothing - if (services.runtime.Runner.isPythonServerAvailable()) { - vscode.window.showInformationMessage('The runner is already installed and running.'); - return; - } - - // Install the runner if it is not already installed - if (!fs.existsSync(getRunnerCommand(context))) { - const success = await doInstallRunner(context); - if (!success) { +export const installRunner = (context: ExtensionContext, client: LanguageClient, services: SafeDsServices) => { + return async () => { + // If the runner is already started, do nothing + if (services.runtime.Runner.isPythonServerAvailable()) { + vscode.window.showInformationMessage('The runner is already installed and running.'); return; } - } - // Set the runner command in the configuration - await vscode.workspace - .getConfiguration() - .update('safe-ds.runner.command', getRunnerCommand(context), vscode.ConfigurationTarget.Global); + // Install the runner if it is not already installed + if (!fs.existsSync(getRunnerCommand(context))) { + const success = await doInstallRunner(context); + if (!success) { + return; + } + } + + // Set the runner command in the configuration + await vscode.workspace + .getConfiguration() + .update('safe-ds.runner.command', getRunnerCommand(context), vscode.ConfigurationTarget.Global); + + // Start the runner (needed if the configuration did not change, so no event is fired) + await client.sendNotification(rpc.runnerStart); - vscode.window.showInformationMessage('The runner has been installed successfully.'); + // Inform the user + vscode.window.showInformationMessage('The runner has been installed successfully.'); + }; }; /** diff --git a/packages/safe-ds-vscode/src/extension/mainClient.ts b/packages/safe-ds-vscode/src/extension/mainClient.ts index 18cf75db1..6d57a4108 100644 --- a/packages/safe-ds-vscode/src/extension/mainClient.ts +++ b/packages/safe-ds-vscode/src/extension/mainClient.ts @@ -47,7 +47,7 @@ export const activate = async function (context: vscode.ExtensionContext) { const registerNotificationListeners = function (context: vscode.ExtensionContext) { client.onNotification(rpc.runnerInstall, async () => { - await installRunner(context, services)(); + await installRunner(context, client, services)(); }); client.onNotification(rpc.runnerStarted, async (port: number) => { await services.runtime.Runner.connectToPort(port); @@ -112,7 +112,7 @@ const registerVSCodeCommands = function (context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('safe-ds.dumpDiagnostics', dumpDiagnostics(context))); context.subscriptions.push( - vscode.commands.registerCommand('safe-ds.installRunner', installRunner(context, services)), + vscode.commands.registerCommand('safe-ds.installRunner', installRunner(context, client, services)), ); context.subscriptions.push( vscode.commands.registerCommand('safe-ds.openDiagnosticsDumps', openDiagnosticsDumps(context)),