439 lines
21 KiB
JavaScript
439 lines
21 KiB
JavaScript
|
/* --------------------------------------------------------------------------------------------
|
||
|
* 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 = {}));
|