/* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ 'use strict'; function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } Object.defineProperty(exports, "__esModule", { value: true }); const cp = require("child_process"); const fs = require("fs"); const client_1 = require("./client"); const vscode_1 = require("vscode"); const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); const colorProvider_1 = require("./colorProvider"); const configuration_1 = require("./configuration"); const implementation_1 = require("./implementation"); const typeDefinition_1 = require("./typeDefinition"); const workspaceFolders_1 = require("./workspaceFolders"); const foldingRange_1 = require("./foldingRange"); const Is = require("./utils/is"); const electron = require("./utils/electron"); const processes_1 = require("./utils/processes"); __export(require("./client")); var Executable; (function (Executable) { function is(value) { return Is.string(value.command); } Executable.is = is; })(Executable || (Executable = {})); var TransportKind; (function (TransportKind) { TransportKind[TransportKind["stdio"] = 0] = "stdio"; TransportKind[TransportKind["ipc"] = 1] = "ipc"; TransportKind[TransportKind["pipe"] = 2] = "pipe"; TransportKind[TransportKind["socket"] = 3] = "socket"; })(TransportKind = exports.TransportKind || (exports.TransportKind = {})); var Transport; (function (Transport) { function isSocket(value) { let candidate = value; return candidate && candidate.kind === TransportKind.socket && Is.number(candidate.port); } Transport.isSocket = isSocket; })(Transport || (Transport = {})); var NodeModule; (function (NodeModule) { function is(value) { return Is.string(value.module); } NodeModule.is = is; })(NodeModule || (NodeModule = {})); var StreamInfo; (function (StreamInfo) { function is(value) { let candidate = value; return candidate && candidate.writer !== void 0 && candidate.reader !== void 0; } StreamInfo.is = is; })(StreamInfo || (StreamInfo = {})); var ChildProcessInfo; (function (ChildProcessInfo) { function is(value) { let candidate = value; return candidate && candidate.process !== void 0 && typeof candidate.detached === 'boolean'; } ChildProcessInfo.is = is; })(ChildProcessInfo || (ChildProcessInfo = {})); class LanguageClient extends client_1.BaseLanguageClient { constructor(arg1, arg2, arg3, arg4, arg5) { let id; let name; let serverOptions; let clientOptions; let forceDebug; if (Is.string(arg2)) { id = arg1; name = arg2; serverOptions = arg3; clientOptions = arg4; forceDebug = !!arg5; } else { id = arg1.toLowerCase(); name = arg1; serverOptions = arg2; clientOptions = arg3; forceDebug = arg4; } if (forceDebug === void 0) { forceDebug = false; } super(id, name, clientOptions); this._serverOptions = serverOptions; this._forceDebug = forceDebug; } stop() { return super.stop().then(() => { if (this._serverProcess) { let toCheck = this._serverProcess; this._serverProcess = undefined; if (this._isDetached === void 0 || !this._isDetached) { this.checkProcessDied(toCheck); } this._isDetached = undefined; } }); } checkProcessDied(childProcess) { if (!childProcess) { return; } setTimeout(() => { // Test if the process is still alive. Throws an exception if not try { process.kill(childProcess.pid, 0); processes_1.terminate(childProcess); } catch (error) { // All is fine. } }, 2000); } handleConnectionClosed() { this._serverProcess = undefined; super.handleConnectionClosed(); } createMessageTransports(encoding) { function getEnvironment(env) { if (!env) { return process.env; } let result = Object.create(null); Object.keys(process.env).forEach(key => result[key] = process.env[key]); Object.keys(env).forEach(key => result[key] = env[key]); return result; } function startedInDebugMode() { let args = process.execArgv; if (args) { return args.some((arg) => /^--debug=?/.test(arg) || /^--debug-brk=?/.test(arg) || /^--inspect=?/.test(arg) || /^--inspect-brk=?/.test(arg)); } ; return false; } let server = this._serverOptions; // We got a function. if (Is.func(server)) { return server().then((result) => { if (client_1.MessageTransports.is(result)) { this._isDetached = !!result.detached; return result; } else if (StreamInfo.is(result)) { this._isDetached = !!result.detached; return { reader: new vscode_languageserver_protocol_1.StreamMessageReader(result.reader), writer: new vscode_languageserver_protocol_1.StreamMessageWriter(result.writer) }; } else { let cp; if (ChildProcessInfo.is(result)) { cp = result.process; this._isDetached = result.detached; } else { cp = result; this._isDetached = false; } cp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); return { reader: new vscode_languageserver_protocol_1.StreamMessageReader(cp.stdout), writer: new vscode_languageserver_protocol_1.StreamMessageWriter(cp.stdin) }; } }); } let json; let runDebug = server; if (runDebug.run || runDebug.debug) { // We are under debugging. So use debug as well. if (typeof v8debug === 'object' || this._forceDebug || startedInDebugMode()) { json = runDebug.debug; } else { json = runDebug.run; } } else { json = server; } return this._getServerWorkingDir(json.options).then(serverWorkingDir => { if (NodeModule.is(json) && json.module) { let node = json; let transport = node.transport || TransportKind.stdio; if (node.runtime) { let args = []; let options = node.options || Object.create(null); if (options.execArgv) { options.execArgv.forEach(element => args.push(element)); } args.push(node.module); if (node.args) { node.args.forEach(element => args.push(element)); } let execOptions = Object.create(null); execOptions.cwd = serverWorkingDir; execOptions.env = getEnvironment(options.env); let pipeName = undefined; if (transport === TransportKind.ipc) { // exec options not correctly typed in lib execOptions.stdio = [null, null, null, 'ipc']; args.push('--node-ipc'); } else if (transport === TransportKind.stdio) { args.push('--stdio'); } else if (transport === TransportKind.pipe) { pipeName = vscode_languageserver_protocol_1.generateRandomPipeName(); args.push(`--pipe=${pipeName}`); } else if (Transport.isSocket(transport)) { args.push(`--socket=${transport.port}`); } args.push(`--clientProcessId=${process.pid.toString()}`); if (transport === TransportKind.ipc || transport === TransportKind.stdio) { let serverProcess = cp.spawn(node.runtime, args, execOptions); if (!serverProcess || !serverProcess.pid) { return Promise.reject(`Launching server using runtime ${node.runtime} failed.`); } this._serverProcess = serverProcess; serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); if (transport === TransportKind.ipc) { serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); return Promise.resolve({ reader: new vscode_languageserver_protocol_1.IPCMessageReader(serverProcess), writer: new vscode_languageserver_protocol_1.IPCMessageWriter(serverProcess) }); } else { return Promise.resolve({ reader: new vscode_languageserver_protocol_1.StreamMessageReader(serverProcess.stdout), writer: new vscode_languageserver_protocol_1.StreamMessageWriter(serverProcess.stdin) }); } } else if (transport == TransportKind.pipe) { return vscode_languageserver_protocol_1.createClientPipeTransport(pipeName).then((transport) => { let process = cp.spawn(node.runtime, args, execOptions); if (!process || !process.pid) { return Promise.reject(`Launching server using runtime ${node.runtime} failed.`); } this._serverProcess = process; process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); return transport.onConnected().then((protocol) => { return { reader: protocol[0], writer: protocol[1] }; }); }); } else if (Transport.isSocket(transport)) { return vscode_languageserver_protocol_1.createClientSocketTransport(transport.port).then((transport) => { let process = cp.spawn(node.runtime, args, execOptions); if (!process || !process.pid) { return Promise.reject(`Launching server using runtime ${node.runtime} failed.`); } this._serverProcess = process; process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); return transport.onConnected().then((protocol) => { return { reader: protocol[0], writer: protocol[1] }; }); }); } } else { let pipeName = undefined; return new Promise((resolve, reject) => { let args = node.args && node.args.slice() || []; if (transport === TransportKind.ipc) { args.push('--node-ipc'); } else if (transport === TransportKind.stdio) { args.push('--stdio'); } else if (transport === TransportKind.pipe) { pipeName = vscode_languageserver_protocol_1.generateRandomPipeName(); args.push(`--pipe=${pipeName}`); } else if (Transport.isSocket(transport)) { args.push(`--socket=${transport.port}`); } args.push(`--clientProcessId=${process.pid.toString()}`); let options = node.options || Object.create(null); options.execArgv = options.execArgv || []; options.cwd = serverWorkingDir; if (transport === TransportKind.ipc || transport === TransportKind.stdio) { electron.fork(node.module, args || [], options, (error, serverProcess) => { if (error || !serverProcess) { reject(error); } else { this._serverProcess = serverProcess; serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); if (transport === TransportKind.ipc) { serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); resolve({ reader: new vscode_languageserver_protocol_1.IPCMessageReader(this._serverProcess), writer: new vscode_languageserver_protocol_1.IPCMessageWriter(this._serverProcess) }); } else { resolve({ reader: new vscode_languageserver_protocol_1.StreamMessageReader(serverProcess.stdout), writer: new vscode_languageserver_protocol_1.StreamMessageWriter(serverProcess.stdin) }); } } }); } else if (transport === TransportKind.pipe) { vscode_languageserver_protocol_1.createClientPipeTransport(pipeName).then((transport) => { electron.fork(node.module, args || [], options, (error, cp) => { if (error || !cp) { reject(error); } else { this._serverProcess = cp; cp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); cp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); transport.onConnected().then((protocol) => { resolve({ reader: protocol[0], writer: protocol[1] }); }); } }); }); } else if (Transport.isSocket(transport)) { vscode_languageserver_protocol_1.createClientSocketTransport(transport.port).then((transport) => { electron.fork(node.module, args || [], options, (error, cp) => { if (error || !cp) { reject(error); } else { this._serverProcess = cp; cp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); cp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); transport.onConnected().then((protocol) => { resolve({ reader: protocol[0], writer: protocol[1] }); }); } }); }); } }); } } else if (Executable.is(json) && json.command) { let command = json; let args = command.args || []; let options = Object.assign({}, command.options); options.cwd = options.cwd || serverWorkingDir; let serverProcess = cp.spawn(command.command, args, options); if (!serverProcess || !serverProcess.pid) { return Promise.reject(`Launching server using command ${command.command} failed.`); } serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding))); this._serverProcess = serverProcess; this._isDetached = !!options.detached; return Promise.resolve({ reader: new vscode_languageserver_protocol_1.StreamMessageReader(serverProcess.stdout), writer: new vscode_languageserver_protocol_1.StreamMessageWriter(serverProcess.stdin) }); } return Promise.reject(new Error(`Unsupported server configuration ` + JSON.stringify(server, null, 4))); }); } registerProposedFeatures() { this.registerFeatures(ProposedFeatures.createAll(this)); } registerBuiltinFeatures() { super.registerBuiltinFeatures(); this.registerFeature(new configuration_1.ConfigurationFeature(this)); this.registerFeature(new typeDefinition_1.TypeDefinitionFeature(this)); this.registerFeature(new implementation_1.ImplementationFeature(this)); this.registerFeature(new colorProvider_1.ColorProviderFeature(this)); this.registerFeature(new workspaceFolders_1.WorkspaceFoldersFeature(this)); this.registerFeature(new foldingRange_1.FoldingRangeFeature(this)); } _mainGetRootPath() { let folders = vscode_1.workspace.workspaceFolders; if (!folders || folders.length === 0) { return undefined; } let folder = folders[0]; if (folder.uri.scheme === 'file') { return folder.uri.fsPath; } return undefined; } _getServerWorkingDir(options) { let cwd = options && options.cwd; if (!cwd) { cwd = this.clientOptions.workspaceFolder ? this.clientOptions.workspaceFolder.uri.fsPath : this._mainGetRootPath(); } if (cwd) { // make sure the folder exists otherwise creating the process will fail return new Promise(s => { fs.lstat(cwd, (err, stats) => { s(!err && stats.isDirectory() ? cwd : undefined); }); }); } return Promise.resolve(undefined); } } exports.LanguageClient = LanguageClient; class SettingMonitor { constructor(_client, _setting) { this._client = _client; this._setting = _setting; this._listeners = []; } start() { vscode_1.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this._listeners); this.onDidChangeConfiguration(); return new vscode_1.Disposable(() => { if (this._client.needsStop()) { this._client.stop(); } }); } onDidChangeConfiguration() { let index = this._setting.indexOf('.'); let primary = index >= 0 ? this._setting.substr(0, index) : this._setting; let rest = index >= 0 ? this._setting.substr(index + 1) : undefined; let enabled = rest ? vscode_1.workspace.getConfiguration(primary).get(rest, false) : vscode_1.workspace.getConfiguration(primary); if (enabled && this._client.needsStart()) { this._client.start(); } else if (!enabled && this._client.needsStop()) { this._client.stop(); } } } exports.SettingMonitor = SettingMonitor; // Exporting proposed protocol. var ProposedFeatures; (function (ProposedFeatures) { function createAll(_client) { let result = []; return result; } ProposedFeatures.createAll = createAll; })(ProposedFeatures = exports.ProposedFeatures || (exports.ProposedFeatures = {}));