import * as path from 'path'; import {getPort} from 'portfinder'; import {Server, createServer, connect} from 'net'; import {AtomEnvironment, Disposable} from 'atom'; import {HighlightParams, ProgressParams, CursorMovementParams, StsAdapter} from './sts-adapter'; import {ActiveServer, LanguageServerProcess} from 'atom-languageclient'; import {AutoLanguageClient} from 'atom-languageclient'; import {findJdk, findJvm, JVM} from '@pivotal-tools/jvm-launch-utils'; import {Writable} from 'stream'; export class JavaProcessLanguageClient extends AutoLanguageClient { DEBUG = false; private server: Server; constructor(protected serverHome: string, protected serverLauncherJar: string) { super(); } getServerJar(): string { return path.resolve(this.serverHome, this.serverLauncherJar); } showErrorMessage(detail: string, desc?: string): Promise { const atomEnv: AtomEnvironment = atom; const notification = atomEnv.notifications.addError('Cannot start Language Server', { dismissable: true, detail: detail, description: desc, buttons: [{ text: 'OK', onDidClick: () => { notification.dismiss() }, }] }); return Promise.reject(new Error(detail)); } protected startServerProcess(projectPath: string): LanguageServerProcess | Promise { // TODO: Remove when debugging is over const atomEnv: AtomEnvironment = atom; atomEnv.config.set('core.debugLSP', true); let childProcess: LanguageServerProcess; if (this.DEBUG) { return this.connectToLS(); } return new Promise((resolve, reject) => { let basePort = Math.floor(Math.random() * 10000) + 40000; getPort({port: basePort}, (err, port) => { this.server = createServer(socket => { this.socket = socket; resolve(childProcess); }); this.server.listen(port, 'localhost', () => { this.launchProcess(port).then(p => childProcess = p); }); }); }); } private connectToLS(): LanguageServerProcess | Promise { return new Promise(resolve => { this.socket = connect({ port: 5007 }); const stdout: any = { setEncoding: () => { return stdout; }, on: () => stdout }; const process: LanguageServerProcess = { stdin: {}, stdout: stdout, stderr: stdout, pid: -1, kill: () => { console.log('fake shutdown'); }, on: () => process, addListener: () => process, prependListener: () => process, once: () => process, prependOnceListener: () => process, removeListener: () => process, removeAllListeners: () => process, setMaxListeners: () => process, getMaxListeners: () => 0, listeners: () => [], emit: () => false, eventNames: () => [], listenerCount: () => 0 }; resolve(process); }); } // Start adapters that are not shared between servers protected postInitialization(server: ActiveServer): void { const stsAdapter = this.createStsAdapter() || new StsAdapter(); (server.connection)._onRequest({method: 'sts/moveCursor'}, (params: CursorMovementParams) => stsAdapter.onMoveCursor(params)); server.connection.onCustom('sts/progress', (params: ProgressParams) => stsAdapter.onProgress(params)); server.connection.onCustom('sts/highlight', (params: HighlightParams) => stsAdapter.onHighlight(params)); server.disposable.add(new Disposable(() => { if (this.server) { this.server.close() } })); } preferJdk(): boolean { return false; } findJvm(): Promise { const options = this.getJavaOptions(); const javaHome = options ? this.getJavaOptions().home : ''; return this.preferJdk() ? findJdk(javaHome) : findJvm(javaHome); } private launchProcess(port: number): Promise { return this.findJvm() .catch(error => { return this.showErrorMessage("Error trying to find JVM", ""+error); }) .then(jvm => { if (!jvm) { return this.showErrorMessage("Couldn't locate java in $JAVA_HOME or $PATH"); } let version = jvm.getMajorVersion(); if (version<8) { return this.showErrorMessage( 'No compatible Java Runtime Environment found', 'The Java Runtime Environment is either below version "1.8" or is missing from the system' ); } return this.launchVmArgs(jvm).then(args => { args.push(`-Dserver.port=${port}`); return this.doLaunchProcess( jvm, this.getServerJar(), args ); }); }); } protected launchVmArgs(jvm: JVM): Promise { return Promise.resolve([]); } private doLaunchProcess(jvm: JVM, launcher: string, args: string[] =[]): LanguageServerProcess { let vmArgs = args.concat([ '-Dsts.lsp.client=atom', // '-Dlsp.completions.indentation.enable=true', // Looks like Atom has magic indents same like VSCode - comment it out '-Dlsp.yaml.completions.errors.disable=true', ]); const javaOptions = this.getJavaOptions(); if (javaOptions && javaOptions.vmargs) { vmArgs.push(...javaOptions.vmargs); } this.logger.debug(`starting "${jvm.getJavaExecutable()} ${vmArgs.join('\n')}\n-jar ${launcher}"`); return jvm.jarLaunch(launcher, vmArgs, { cwd: this.serverHome }); } createStsAdapter(): StsAdapter | null { return null; } protected getJavaOptions(): JavaOptions { return {}; } } export interface JavaOptions { home?: string; vmargs?: string[]; }