diff --git a/packages/safe-ds-lang/src/language/index.ts b/packages/safe-ds-lang/src/language/index.ts index c4ad95b27..1bdf16c6c 100644 --- a/packages/safe-ds-lang/src/language/index.ts +++ b/packages/safe-ds-lang/src/language/index.ts @@ -1,3 +1,5 @@ +import { RPC_RUNNER_STARTED } from './runner/safe-ds-runner.js'; + // Services export type { SafeDsServices } from './safe-ds-module.js'; export { createSafeDsServices } from './safe-ds-module.js'; @@ -17,3 +19,8 @@ export { locationToString, positionToString, rangeToString } from '../helpers/lo // Messages export * as messages from './runner/messages.js'; + +// Remote procedure calls +export const rpc = { + 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 299604342..3e9c362aa 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 @@ -22,6 +22,8 @@ import { SafeDsMessagingProvider } from '../lsp/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_STARTED = 'runner/started'; + const LOWEST_SUPPORTED_VERSION = '0.8.0'; const LOWEST_UNSUPPORTED_VERSION = '0.9.0'; const npmVersionRange = `>=${LOWEST_SUPPORTED_VERSION} <${LOWEST_UNSUPPORTED_VERSION}`; @@ -62,8 +64,7 @@ export class SafeDsRunner { this.registerMessageLoggingCallbacks(); services.workspace.SettingsProvider.onRunnerCommandUpdate(async (newValue) => { - this.updateRunnerCommand(newValue); - await this.startPythonServer(); + await this.updateRunnerCommand(newValue); }); services.shared.lsp.Connection?.onShutdown(async () => { @@ -117,17 +118,32 @@ export class SafeDsRunner { ); }, 'runtime_error'); } - /* c8 ignore stop */ + + async connectToPort(port: number): Promise { + if (this.isPythonServerAvailable()) { + return; + } + + this.port = port; + try { + await this.connectToWebSocket(); + } catch (error) { + await this.stopPythonServer(); + } + } /** * Change the command to start the runner process. This will not cause the runner process to restart, if it is already running. * * @param command New Runner Command. */ - /* c8 ignore start */ - public updateRunnerCommand(command: string | undefined): void { + public async updateRunnerCommand(command: string | undefined): Promise { if (command) { this.runnerCommand = command; + await this.startPythonServer(); + if (this.isPythonServerAvailable()) { + await this.messaging.sendNotification(RPC_RUNNER_STARTED, this.port); + } } } @@ -136,6 +152,11 @@ export class SafeDsRunner { * Uses the 'safe-ds.runner.command' setting to execute the process. */ public async startPythonServer(): Promise { + if (this.isPythonServerAvailable()) { + this.info('As the Safe-DS Runner is currently successfully running, no attempt to start it will be made'); + return; + } + this.acceptsConnections = false; const runnerCommandParts = this.runnerCommand.split(/\s/u); const runnerCommand = runnerCommandParts.shift()!; // After shift, only the actual args are left diff --git a/packages/safe-ds-vscode/src/extension/mainClient.ts b/packages/safe-ds-vscode/src/extension/mainClient.ts index 559eecf47..0cf3202c0 100644 --- a/packages/safe-ds-vscode/src/extension/mainClient.ts +++ b/packages/safe-ds-vscode/src/extension/mainClient.ts @@ -2,7 +2,7 @@ import * as path from 'node:path'; import * as vscode from 'vscode'; import type { LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node.js'; import { LanguageClient, TransportKind } from 'vscode-languageclient/node.js'; -import { ast, createSafeDsServices, getModuleMembers, messages, SafeDsServices } from '@safe-ds/lang'; +import { ast, createSafeDsServices, getModuleMembers, messages, rpc, SafeDsServices } from '@safe-ds/lang'; import { NodeFileSystem } from 'langium/node'; import { initializeLog, logError, logOutput, printOutputMessage } from './output.js'; import crypto from 'crypto'; @@ -23,22 +23,25 @@ let lastSuccessfulPipelineNode: ast.SdsPipeline | undefined; // This function is called when the extension is activated. export const activate = async function (context: vscode.ExtensionContext) { initializeLog(); - client = startLanguageClient(context); - const runnerCommandSetting = vscode.workspace.getConfiguration('safe-ds.runner').get('command')!; // Default is set services = ( await createSafeDsServices(NodeFileSystem, { logger: { info: logOutput, error: logError, }, - runnerCommand: runnerCommandSetting, userMessageProvider: { showErrorMessage: vscode.window.showErrorMessage, }, }) ).SafeDs; - await services.runtime.Runner.startPythonServer(); - acceptRunRequests(context); + + client = createLanguageClient(context); + client.onNotification(rpc.runnerStarted, async (port: number) => { + await services.runtime.Runner.connectToPort(port); + }); + await client.start(); + + registerVSCodeCommands(context); }; // This function is called when the extension is deactivated. @@ -50,7 +53,7 @@ export const deactivate = async function (): Promise { return; }; -const startLanguageClient = function (context: vscode.ExtensionContext): LanguageClient { +const createLanguageClient = function (context: vscode.ExtensionContext): LanguageClient { const serverModule = context.asAbsolutePath(path.join('dist', 'extension', 'mainServer.cjs')); // The debug options for the server // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging. @@ -82,19 +85,8 @@ const startLanguageClient = function (context: vscode.ExtensionContext): Languag outputChannel: vscode.window.createOutputChannel('Safe-DS Language Client', 'log'), }; - // Create the language client and start the client. - const result = new LanguageClient('safe-ds', 'Safe-DS', serverOptions, clientOptions); - - // Start the client. This will also launch the server - result.start(); - return result; -}; - -const acceptRunRequests = async function (context: vscode.ExtensionContext) { - // Register VS Code Entry Points - registerVSCodeCommands(context); - // Register watchers - registerVSCodeWatchers(); + // Create the language client + return new LanguageClient('safe-ds', 'Safe-DS', serverOptions, clientOptions); }; const registerVSCodeCommands = function (context: vscode.ExtensionContext) { @@ -420,25 +412,6 @@ const validateDocuments = async function ( return undefined; }; -const registerVSCodeWatchers = function () { - vscode.workspace.onDidChangeConfiguration((event) => { - if (event.affectsConfiguration('safe-ds.runner.command')) { - // Try starting runner - logOutput('Safe-DS Runner Command was updated'); - services.runtime.Runner.updateRunnerCommand( - vscode.workspace.getConfiguration('safe-ds.runner').get('command')!, - ); - if (!services.runtime.Runner.isPythonServerAvailable()) { - services.runtime.Runner.startPythonServer(); - } else { - logOutput( - 'As the Safe-DS Runner is currently successfully running, no attempt to start it will be made', - ); - } - } - }); -}; - const isRangeEqual = function (lhs: Range, rhs: Range): boolean { return ( lhs.start.character === rhs.start.character &&