UNPKG

atom-nuclide

Version:

A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.

508 lines (416 loc) 17.4 kB
Object.defineProperty(exports, '__esModule', { value: true }); /* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var getInstance = _asyncToGenerator(function* (file) { if (merlinProcessInstance && merlinProcessInstance.isRunning()) { return merlinProcessInstance; } var merlinPath = getPathToMerlin(); var flags = getMerlinFlags(); if (!(yield isInstalled(merlinPath))) { return null; } var dotMerlinPath = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.findNearestFile('.merlin', file); var options = { cwd: dotMerlinPath ? (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.dirname(dotMerlinPath) : '.' }; logger.info('Spawning new ocamlmerlin process'); var process = yield (0, (_commonsNodeProcess2 || _commonsNodeProcess()).safeSpawn)(merlinPath, flags, options); var version = yield getVersion(process); switch (version) { case '2.5.0': merlinProcessInstance = new MerlinProcessV2_5(process); break; case '2.3.1': merlinProcessInstance = new MerlinProcessV2_3_1(process); break; default: logger.error('Unsupported merlin version: ' + version); return null; } if (dotMerlinPath) { // TODO(pieter) add support for multiple .dotmerlin files yield merlinProcessInstance.pushDotMerlinPath(dotMerlinPath); logger.debug('Added .merlin path: ' + dotMerlinPath); } return merlinProcessInstance; }); exports.getInstance = getInstance; var getVersion = _asyncToGenerator(function* (proc) { try { // TODO: Support version 3 var result = yield _runSingleCommand(proc, ['protocol', 'version', 2]); // default to version 2 var match = result.merlin.match(/^The Merlin toolkit version (\d+(\.\d)*),/); return match != null && match[1] != null ? match[1] : '2.3.1'; } catch (e) { // version 2.3.1 doesn't have a 'protocol' command and will throw return '2.3.1'; } } /** * @return The path to ocamlmerlin on the user's machine. It is recommended not to cache the result * of this function in case the user updates his or her preferences in Atom, in which case the * return value will be stale. */ ); var isInstalled = _asyncToGenerator(function* (merlinPath) { if (isInstalledCache == null) { var result = yield (0, (_commonsNodeProcess2 || _commonsNodeProcess()).asyncExecute)('which', [merlinPath]); isInstalledCache = result.exitCode === 0; if (!isInstalledCache) { logger.info('ocamlmerlin not installed'); } } return isInstalledCache; } /** * Run a command; parse the json output, return an object. This assumes * that merlin's protocol is line-based (results are json objects rendered * on a single line). */ ); function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; } function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _commonsNodeNuclideUri2; function _commonsNodeNuclideUri() { return _commonsNodeNuclideUri2 = _interopRequireDefault(require('../../commons-node/nuclideUri')); } var _readline2; function _readline() { return _readline2 = _interopRequireDefault(require('readline')); } var _commonsNodeFsPromise2; function _commonsNodeFsPromise() { return _commonsNodeFsPromise2 = _interopRequireDefault(require('../../commons-node/fsPromise')); } var _commonsNodeProcess2; function _commonsNodeProcess() { return _commonsNodeProcess2 = require('../../commons-node/process'); } var _commonsNodePromiseExecutors2; function _commonsNodePromiseExecutors() { return _commonsNodePromiseExecutors2 = require('../../commons-node/promise-executors'); } var _nuclideLogging2; function _nuclideLogging() { return _nuclideLogging2 = require('../../nuclide-logging'); } var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)(); var ERROR_RESPONSES = new Set(['failure', 'error', 'exception']); /** * Wraps an ocamlmerlin process; provides api access to * ocamlmerlin's json-over-stdin/stdout protocol. * Derived classes spec which version of the protocol to speak. */ var MerlinProcessBase = (function () { function MerlinProcessBase(proc) { var _this = this; _classCallCheck(this, MerlinProcessBase); this._proc = proc; this._promiseQueue = new (_commonsNodePromiseExecutors2 || _commonsNodePromiseExecutors()).PromiseQueue(); this._running = true; this._proc.on('exit', function (code, signal) { _this._running = false; }); } /** * Wraps an ocamlmerlin process which talks v1 protocol; provides api access to * ocamlmerlin's json-over-stdin/stdout protocol. * * This is based on the protocol description at: * https://github.com/the-lambda-church/merlin/blob/merlin1/PROTOCOL.md * https://github.com/the-lambda-church/merlin/tree/master/src/frontend */ _createClass(MerlinProcessBase, [{ key: 'isRunning', value: function isRunning() { return this._running; } }, { key: 'runSingleCommand', value: function runSingleCommand(command) { return _runSingleCommand(this._proc, command); } }, { key: 'dispose', value: function dispose() { this._proc.kill(); } }]); return MerlinProcessBase; })(); var MerlinProcessV2_3_1 = (function (_MerlinProcessBase) { _inherits(MerlinProcessV2_3_1, _MerlinProcessBase); function MerlinProcessV2_3_1(proc) { _classCallCheck(this, MerlinProcessV2_3_1); _get(Object.getPrototypeOf(MerlinProcessV2_3_1.prototype), 'constructor', this).call(this, proc); } /** * Wraps an ocamlmerlin process which talks v2 protocol; provides api access to * ocamlmerlin's json-over-stdin/stdout protocol. * * This is based on the protocol description at: * https://github.com/the-lambda-church/merlin/blob/master/doc/dev/PROTOCOL.md * https://github.com/the-lambda-church/merlin/tree/master/src/frontend */ _createClass(MerlinProcessV2_3_1, [{ key: 'pushDotMerlinPath', value: _asyncToGenerator(function* (file) { var _this2 = this; return yield this._promiseQueue.submit(_asyncToGenerator(function* (resolve, reject) { var result = yield _this2.runSingleCommand(['reset', 'dot_merlin', [file], 'auto']); resolve(result); })); }) }, { key: 'pushNewBuffer', value: _asyncToGenerator(function* (name, content) { var _this3 = this; return yield this._promiseQueue.submit(_asyncToGenerator(function* (resolve, reject) { yield _this3.runSingleCommand(['reset', 'auto', // one of {ml, mli, auto} name]); // Clear the buffer. yield _this3.runSingleCommand(['seek', 'exact', { line: 1, col: 0 }]); yield _this3.runSingleCommand(['drop']); var result = yield _this3.runSingleCommand(['tell', 'source-eof', content]); resolve(result); })); }) }, { key: 'locate', value: _asyncToGenerator(function* (file, line, col, kind) { var _this4 = this; return yield this._promiseQueue.submit(_asyncToGenerator(function* (resolve, reject) { var location = yield _this4.runSingleCommand(['locate', /* identifier name */'', kind, 'at', { line: line + 1, col: col }]); if (typeof location === 'string') { return reject(Error(location)); } // Ocamlmerlin doesn't include a `file` field at all if the destination is // in the same file. if (!location.file) { location.file = file; } resolve(location); })); }) }, { key: 'enclosingType', value: _asyncToGenerator(function* (file, line, col) { var _this5 = this; return yield this._promiseQueue.submit(function (resolve, reject) { _this5.runSingleCommand(['type', 'enclosing', 'at', { line: line + 1, col: col }]).then(resolve).catch(reject); }); }) }, { key: 'complete', value: _asyncToGenerator(function* (file, line, col, prefix) { var _this6 = this; return yield this._promiseQueue.submit(function (resolve, reject) { _this6.runSingleCommand(['complete', 'prefix', prefix, 'at', { line: line + 1, col: col + 1 }]).then(resolve).catch(reject); }); }) }, { key: 'errors', value: _asyncToGenerator(function* () { var _this7 = this; return yield this._promiseQueue.submit(function (resolve, reject) { _this7.runSingleCommand(['errors']).then(resolve).catch(reject); }); }) }, { key: 'outline', value: _asyncToGenerator(function* (path) { var _this8 = this; return yield this._promiseQueue.submit(function (resolve, reject) { _this8.runSingleCommand(['outline']).then(resolve).catch(reject); }); }) }]); return MerlinProcessV2_3_1; })(MerlinProcessBase); exports.MerlinProcessV2_3_1 = MerlinProcessV2_3_1; var MerlinProcessV2_5 = (function (_MerlinProcessBase2) { _inherits(MerlinProcessV2_5, _MerlinProcessBase2); function MerlinProcessV2_5(proc) { _classCallCheck(this, MerlinProcessV2_5); _get(Object.getPrototypeOf(MerlinProcessV2_5.prototype), 'constructor', this).call(this, proc); } _createClass(MerlinProcessV2_5, [{ key: 'pushDotMerlinPath', value: _asyncToGenerator(function* (file) { var _this9 = this; return yield this._promiseQueue.submit(_asyncToGenerator(function* (resolve, reject) { var result = yield _this9.runSingleCommand(['reset', 'dot_merlin', [file], 'auto']); resolve(result); })); }) /** * Set the buffer content to query against. Merlin uses an internal * buffer (name + content) that is independent from file content on * disk. * * @return on success: a cursor position pointed at the end of the buffer */ }, { key: 'pushNewBuffer', value: _asyncToGenerator(function* (name, content) { var _this10 = this; return yield this._promiseQueue.submit(_asyncToGenerator(function* (resolve, reject) { var result = yield _this10.runSingleCommand(['tell', 'start', 'end', content]); resolve(result); })); }) /** * Find definition * * `kind` is one of 'ml' or 'mli' * * Note: ocamlmerlin line numbers are 1-based. * @return null if nothing was found; a position of the form * {"file": "somepath", "pos": {"line": 41, "col": 5}}. */ }, { key: 'locate', value: _asyncToGenerator(function* (file, line, col, kind) { var _this11 = this; return yield this._promiseQueue.submit(_asyncToGenerator(function* (resolve, reject) { var location = yield _this11.runSingleCommand(['locate', /* identifier name */'', kind, 'at', { line: line + 1, col: col }]); if (typeof location === 'string') { return reject(Error(location)); } // Ocamlmerlin doesn't include a `file` field at all if the destination is // in the same file. if (!location.file) { location.file = file; } resolve(location); })); }) }, { key: 'enclosingType', value: _asyncToGenerator(function* (file, line, col) { var _this12 = this; return yield this._promiseQueue.submit(function (resolve, reject) { _this12.runSingleCommand(['type', 'enclosing', 'at', { line: line + 1, col: col }]).then(resolve).catch(reject); }); }) }, { key: 'complete', value: _asyncToGenerator(function* (file, line, col, prefix) { var _this13 = this; return yield this._promiseQueue.submit(function (resolve, reject) { _this13.runSingleCommand(['complete', 'prefix', prefix, 'at', { line: line + 1, col: col + 1 }]).then(resolve).catch(reject); }); }) }, { key: 'errors', value: _asyncToGenerator(function* () { var _this14 = this; return yield this._promiseQueue.submit(function (resolve, reject) { _this14.runSingleCommand(['errors']).then(resolve).catch(reject); }); }) }, { key: 'outline', value: _asyncToGenerator(function* (path) { var _this15 = this; return yield this._promiseQueue.submit(function (resolve, reject) { _this15.runSingleCommand(['outline']).then(resolve).catch(reject); }); }) }]); return MerlinProcessV2_5; })(MerlinProcessBase); exports.MerlinProcessV2_5 = MerlinProcessV2_5; var merlinProcessInstance = undefined; function getPathToMerlin() { return global.atom && global.atom.config.get('nuclide.nuclide-ocaml.pathToMerlin') || 'ocamlmerlin'; } /** * @return The set of arguments to pass to ocamlmerlin. */ function getMerlinFlags() { var configVal = global.atom && global.atom.config.get('nuclide.nuclide-ocaml.merlinFlags'); // To split while stripping out any leading/trailing space, we match on all // *non*-whitespace. var configItems = configVal && configVal.match(/\S+/g); return configItems || []; } var isInstalledCache = null; function _runSingleCommand(process, command) { var commandString = JSON.stringify(command); var stdin = process.stdin; var stdout = process.stdout; return new Promise(function (resolve, reject) { var reader = (_readline2 || _readline()).default.createInterface({ input: stdout, terminal: false }); reader.on('line', function (line) { reader.close(); var response = undefined; try { response = JSON.parse(line); } catch (err) { response = null; } if (!response || !Array.isArray(response) || response.length !== 2) { logger.error('Unexpected response from ocamlmerlin: ${line}'); reject(Error('Unexpected ocamlmerlin output format')); return; } var status = response[0]; var content = response[1]; if (ERROR_RESPONSES.has(status)) { logger.error('Ocamlmerlin raised an error: ' + line); reject(Error('Ocamlmerlin returned an error')); return; } resolve(content); }); stdin.write(commandString); }); } /** * Tell merlin where to find its per-repo .merlin config file. * * Configuration file format description: * https://github.com/the-lambda-church/merlin/wiki/project-configuration * * @return a dummy cursor position on success */ /** * Set the buffer content to query against. Merlin uses an internal * buffer (name + content) that is independent from file content on * disk. * * @return on success: a cursor position pointed at the end of the buffer */ /** * Find definition * * `kind` is one of 'ml' or 'mli' * * Note: ocamlmerlin line numbers are 1-based. * @return null if nothing was found; a position of the form * {"file": "somepath", "pos": {"line": 41, "col": 5}}. */ /** * Run a command; parse the json output, return an object. This assumes * that merlin's protocol is line-based (results are json objects rendered * on a single line). */