UNPKG

ibm-streams

Version:
323 lines (300 loc) 9.51 kB
import { Registry, ToolkitUtils } from '@ibmstreams/common'; import * as childProcess from 'child_process'; import * as net from 'net'; import * as os from 'os'; import * as path from 'path'; import { ExtensionContext, ProgressLocation, window, workspace } from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, State, StreamInfo } from 'vscode-languageclient'; import { Configuration, EXTENSION_ID, EXTENSION_NAME, LANGUAGE_SERVER, LANGUAGE_SPL, Logger, TOOLKITS_CACHE_DIR, Settings } from '../utils'; import { checkUserPort } from './utils'; let client: LanguageClient = null; let clientState: State = null; let isClientReady = false; let serverProcess: childProcess.ChildProcess = null; /** * Language client that connects to the SPL LSP server */ export default class SplLanguageClient { private static _context: ExtensionContext; /** * Initialize the SPL language client and server * @param context the extension context */ public static async initialize(context: ExtensionContext): Promise<any> { this._context = context; await this.start(); } /** * Restart the SPL language client and server */ public static async restart(): Promise<any> { try { // Stop language server await this.cleanUp(); // Start language server await this.start(); } catch (err) { const errorMsg = `An error occurred while restarting the ${LANGUAGE_SERVER}.\n${err.toString()}`; Registry.getDefaultMessageHandler().logError(errorMsg, { showOutputChannel: true, showNotification: true }); } } /** * Start the SPL language client and server */ public static async start(): Promise<any> { // Configure language server options let serverOptions: ServerOptions; let launcher: string; let command: string; const getCommand = (launcherPath: string): string => os.platform() === 'win32' ? launcherPath : `./${launcherPath}`; const cwd = this._context.asAbsolutePath( path.join('node_modules', '@ibmstreams', 'spl-lsp', 'bin') ); const processOptions = { cwd, shell: true, env: { ...process.env } }; const mode = Configuration.getSetting(Settings.SERVER_MODE); let port: number; if (mode === Settings.SERVER_MODE_VALUE.EMBEDDED) { launcher = os.platform() === 'win32' ? 'startSplLspServer.bat' : 'startSplLspServer'; command = getCommand(launcher); serverOptions = { run: { command, options: processOptions }, debug: { command, options: processOptions } }; } else { // Spawn a process for starting the language server in socket mode port = Configuration.getSetting(Settings.SERVER_PORT); // Check if the port is available port = await checkUserPort(port); if (!port) { return; } launcher = os.platform() === 'win32' ? 'startSplLspServerSocket.bat' : 'startSplLspServerSocket'; command = getCommand(launcher); try { serverProcess = childProcess.spawn(command, [`port=${port}`], { ...processOptions, windowsHide: true }); if (serverProcess.stderr) { serverProcess.stderr.on('data', (data) => { if (data) { const errorMsg = 'An error occurred while starting the SPL language server in socket mode.\n'; Registry.getDefaultMessageHandler().logError( `${errorMsg}${data.toString()}`, { showOutputChannel: true, showNotification: true } ); } }); } serverProcess.on('error', (err) => { if (err) { const errorMsg = 'Failed to start the SPL language server in socket mode. '; Registry.getDefaultMessageHandler().logError( `${errorMsg}${err.toString()}`, { showOutputChannel: true, showNotification: true } ); } }); serverProcess.on('close', (code) => { Registry.getDefaultMessageHandler().logInfo( `The SPL language server child process exited with code ${code}.` ); }); } catch (err) { const errorMsg = 'Failed to start the SPL language server in socket mode.'; Registry.getDefaultMessageHandler().logError( `${errorMsg} ${err.toString()}`, { showOutputChannel: true, showNotification: true } ); } const connectionInfo = { port }; serverOptions = (): Promise<StreamInfo> => { // Connect to the language server via socket const socket = net.connect(connectionInfo); const result: StreamInfo = { writer: socket, reader: socket }; return Promise.resolve(result); }; } // Configure language client options const toolkitsOption = ToolkitUtils.getLangServerOptionForInitToolkits( TOOLKITS_CACHE_DIR, Configuration.getSetting(Settings.ENV_TOOLKIT_PATHS) ); const clientOptions: LanguageClientOptions = { outputChannel: Logger.languageServerOutputChannel, documentSelector: [{ scheme: 'file', language: LANGUAGE_SPL }], synchronize: { fileEvents: workspace.createFileSystemWatcher('**/*.*') }, initializationOptions: () => toolkitsOption, initializationFailedHandler: (err) => { if (err) { Logger.languageServerOutputChannel.appendLine(err.toString()); } Registry.getDefaultMessageHandler().logError( `Failed to initialize the ${LANGUAGE_SERVER}.`, { showOutputChannel: true, showNotification: true } ); return false; } }; // Create the language client and start it client = new LanguageClient( EXTENSION_ID, EXTENSION_NAME, serverOptions, clientOptions ); // Show progress in the status bar const progress = window.withProgress( { location: ProgressLocation.Window, title: 'Initializing SPL language features' }, () => new Promise((resolve, reject) => { client.onReady().then( () => { isClientReady = true; resolve(); }, () => { const errorMsg = 'An error occurred while initializing SPL language features.'; Registry.getDefaultMessageHandler().logError(errorMsg, { showOutputChannel: true, showNotification: true }); isClientReady = false; reject(); } ); }) ); // Monitor language client state client.onDidChangeState((event) => { const { newState } = event; clientState = newState; let status: string; switch (newState) { case State.Stopped: status = 'has stopped'; break; case State.Starting: status = `is starting${port ? ` on port ${port}` : ''}`; break; case State.Running: status = `is running${port ? ` on port ${port}` : ''}`; break; default: break; } Registry.getDefaultMessageHandler().logInfo( `The ${LANGUAGE_SERVER} ${status}.`, { showOutputChannel: true } ); }); // Start the client (and launch the server) if (mode === Settings.SERVER_MODE_VALUE.SOCKET) { // Allow time for the server process to start setTimeout(() => { client.start(); }, 5000); } else { client.start(); this._checkClientReady(); } return progress; } /** * Clean up the SPL language server and client */ public static cleanUp(): Promise<void> { if (!client) { return Promise.resolve(); } // Kill child server process if (serverProcess) { serverProcess.kill(); serverProcess = null; } // Stop the client if (clientState && clientState !== State.Stopped) { client.stop(); client = null; clientState = null; isClientReady = false; } return Promise.resolve(); } public static getClient(): LanguageClient { return client; } public static createDebugEnv(): any { return { JAVA_OPTS: '-Xdebug -Xrunjdwp:transport=dt_socket,address=8998,server=n,suspend=y', ...process.env }; } private static _checkClientReady(): void { // If client is not ready within 60 seconds, then ask the user if they want to switch to socket mode setTimeout(async () => { if (!isClientReady) { const result = await window.showWarningMessage( 'The SPL language server is taking a while to start. Do you want to try running the server in socket mode?', ...['Yes', 'No'] ); if (result === 'Yes') { await Configuration.setSetting( Settings.SERVER_MODE, Settings.SERVER_MODE_VALUE.SOCKET ); } } }, 60000); } } /** * Wait for the SPL language client to be ready * @param callbackFn the callback function to execute */ export function waitForLanguageClientReady(callbackFn: Function): void { if (isClientReady) { callbackFn(); } else { // Try again in 2 seconds setTimeout(() => { waitForLanguageClientReady(callbackFn); }, 2000); } }