mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 17:50:22 +00:00
308 lines
9.0 KiB
ReasonML
308 lines
9.0 KiB
ReasonML
/*
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
*/
|
|
let startedMerlin: ref (option Js.Unsafe.any) = {contents: None};
|
|
|
|
let fixedEnv = Js.Unsafe.js_expr "require('../lib/fixedEnv')";
|
|
|
|
/* This and the subsequent big js blocks are copied over from Nuclide. More convenient for now. */
|
|
let findNearestMerlinFile' = Js.Unsafe.js_expr {|
|
|
function findNearestMerlinFile(beginAtFilePath) {
|
|
var path = require('path');
|
|
var fs = require('fs');
|
|
var fileDir = path.dirname(beginAtFilePath);
|
|
var currentPath = path.resolve(fileDir);
|
|
do {
|
|
var fileToFind = path.join(currentPath, '.merlin');
|
|
var hasFile = fs.existsSync(fileToFind);
|
|
if (hasFile) {
|
|
return path.dirname(currentPath);
|
|
}
|
|
|
|
if (path.dirname(currentPath) === currentPath) {
|
|
// Bail
|
|
return '.';
|
|
}
|
|
currentPath = path.dirname(currentPath);
|
|
} while (true);
|
|
}
|
|
|};
|
|
|
|
let findNearestMerlinFile beginAtFilePath::path => {
|
|
let result = Js.Unsafe.fun_call findNearestMerlinFile' [|Js.Unsafe.inject (Js.string path)|];
|
|
Js.to_string result
|
|
};
|
|
|
|
let createMerlinReaderFnOnce' = Js.Unsafe.js_expr {|
|
|
function(ocamlMerlinPath, ocamlMerlinFlags, dotMerlinDir, fixedEnv) {
|
|
var spawn = require('child_process').spawn;
|
|
// To split while stripping out any leading/trailing space, we match on all
|
|
// *non*-whitespace.
|
|
var items = ocamlMerlinFlags === '' ? [] : ocamlMerlinFlags.split(/\s+/);
|
|
var merlinProcess = spawn(ocamlMerlinPath, items, {cwd: dotMerlinDir});
|
|
merlinProcess.stderr.on('data', function(d) {
|
|
console.error('Ocamlmerlin: something wrong happened:');
|
|
console.error(d.toString());
|
|
});
|
|
|
|
merlinProcess.stdout.on('close', function(d) {
|
|
console.error('Ocamlmerlin: closed.');
|
|
});
|
|
|
|
var cmdQueue = [];
|
|
var hasStartedReading = false;
|
|
|
|
var readline = require('readline');
|
|
var reader = readline.createInterface({
|
|
input: merlinProcess.stdout,
|
|
terminal: false,
|
|
});
|
|
|
|
return function(cmd, resolve, reject) {
|
|
cmdQueue.push([resolve, reject]);
|
|
|
|
if (!hasStartedReading) {
|
|
hasStartedReading = true;
|
|
reader.on('line', function(line) {
|
|
var response;
|
|
try {
|
|
response = JSON.parse(line);
|
|
} catch (err) {
|
|
response = null;
|
|
}
|
|
var resolveReject = cmdQueue.shift();
|
|
var resolve = resolveReject[0];
|
|
var reject = resolveReject[1];
|
|
|
|
if (!response || !Array.isArray(response) || response.length !== 2) {
|
|
reject(new Error('Unexpected ocamlmerlin output format: ' + line));
|
|
return;
|
|
}
|
|
|
|
var status = response[0];
|
|
var content = response[1];
|
|
|
|
var errorResponses = {
|
|
'failure': true,
|
|
'error': true,
|
|
'exception': true,
|
|
};
|
|
|
|
if (errorResponses[status]) {
|
|
reject(new Error('Ocamlmerlin returned an error: ' + line));
|
|
return;
|
|
}
|
|
|
|
resolve(content);
|
|
});
|
|
}
|
|
|
|
merlinProcess.stdin.write(JSON.stringify(cmd));
|
|
};
|
|
}
|
|
|};
|
|
|
|
let createMerlinReaderFnOnce
|
|
pathToMerlin::pathToMerlin
|
|
merlinFlags::merlinFlags
|
|
dotMerlinPath::dotMerlinPath =>
|
|
Js.Unsafe.fun_call
|
|
createMerlinReaderFnOnce'
|
|
[|
|
|
Js.Unsafe.inject (Js.string pathToMerlin),
|
|
Js.Unsafe.inject (Js.string merlinFlags),
|
|
Js.Unsafe.inject (Js.string dotMerlinPath),
|
|
Js.Unsafe.inject fixedEnv
|
|
|];
|
|
|
|
let startMerlinProcess path::path =>
|
|
switch startedMerlin.contents {
|
|
| Some readerFn => ()
|
|
| None =>
|
|
let atomReasonPathToMerlin = Atom.Config.get "atom-reason.pathToMerlin";
|
|
let atomReasonMerlinFlags = Atom.Config.get "atom-reason.merlinFlags";
|
|
let atomReasonMerlinLogFile = Atom.Config.get "atom-reason.merlinLogFile";
|
|
switch atomReasonMerlinLogFile {
|
|
| JsonString "" => ()
|
|
| JsonString s => Atom.Env.setEnvVar "MERLIN_LOG" s
|
|
| _ => ()
|
|
};
|
|
let readerFn =
|
|
createMerlinReaderFnOnce
|
|
pathToMerlin::(Atom.JsonValue.unsafeExtractString atomReasonPathToMerlin)
|
|
merlinFlags::(Atom.JsonValue.unsafeExtractString atomReasonMerlinFlags)
|
|
dotMerlinPath::(findNearestMerlinFile beginAtFilePath::path);
|
|
startedMerlin.contents = Some readerFn
|
|
};
|
|
|
|
let readOneLine cmd::cmd resolve reject =>
|
|
switch startedMerlin.contents {
|
|
| None => raise Not_found
|
|
| Some readerFn =>
|
|
Js.Unsafe.fun_call
|
|
readerFn
|
|
[|
|
|
Js.Unsafe.inject cmd,
|
|
Js.Unsafe.inject (Js.wrap_callback resolve),
|
|
Js.Unsafe.inject (Js.wrap_callback reject)
|
|
|]
|
|
};
|
|
|
|
/* contextify is important for avoiding different buffers calling the backing merlin at the same time. */
|
|
/* https://github.com/the-lambda-church/merlin/blob/d98a08d318ca14d9c702bbd6eeadbb762d325ce7/doc/dev/PROTOCOL.md#contextual-commands */
|
|
let contextify query::query path::path => Js.Unsafe.obj [|
|
|
("query", Js.Unsafe.inject query),
|
|
("context", Js.Unsafe.inject (Js.array [|Js.string "auto", Js.string path|]))
|
|
|];
|
|
|
|
let prepareCommand text::text path::path query::query resolve reject => {
|
|
startMerlinProcess path;
|
|
/* These two commands should be run before every main command. */
|
|
readOneLine
|
|
cmd::(
|
|
contextify
|
|
/* The protocol command tells Merlin which API version we want to use. (2 for us) */
|
|
query::(
|
|
Js.array [|
|
|
Js.Unsafe.inject (Js.string "protocol"),
|
|
Js.Unsafe.inject (Js.string "version"),
|
|
Js.Unsafe.inject (Js.number_of_float 2.)
|
|
|]
|
|
)
|
|
path::path
|
|
)
|
|
(
|
|
fun _ =>
|
|
readOneLine
|
|
cmd::(
|
|
contextify
|
|
/* The tell command allows us to synchronize our text with Merlin's internal buffer. */
|
|
query::(
|
|
Js.array [|Js.string "tell", Js.string "start", Js.string "end", Js.string text|]
|
|
)
|
|
path::path
|
|
)
|
|
(fun _ => readOneLine cmd::(contextify query::query path::path) resolve reject)
|
|
reject
|
|
)
|
|
reject
|
|
};
|
|
|
|
let positionToJsMerlinPosition (line, col) => Js.Unsafe.obj [|
|
|
/* lines (rows) are 1-based for merlin, not 0-based, like for Atom */
|
|
("line", Js.Unsafe.inject (Js.number_of_float (float_of_int (line + 1)))),
|
|
("col", Js.Unsafe.inject (Js.number_of_float (float_of_int col)))
|
|
|];
|
|
|
|
/* Actual merlin commands we'll use. */
|
|
let getTypeHint path::path text::text position::position resolve reject =>
|
|
prepareCommand
|
|
text::text
|
|
path::path
|
|
query::(
|
|
Js.array [|
|
|
Js.Unsafe.inject (Js.string "type"),
|
|
Js.Unsafe.inject (Js.string "enclosing"),
|
|
Js.Unsafe.inject (Js.string "at"),
|
|
Js.Unsafe.inject (positionToJsMerlinPosition position)
|
|
|]
|
|
)
|
|
resolve
|
|
reject;
|
|
|
|
let getAutoCompleteSuggestions
|
|
path::path
|
|
text::text
|
|
position::position
|
|
prefix::prefix
|
|
resolve
|
|
reject =>
|
|
prepareCommand
|
|
text::text
|
|
path::path
|
|
query::(
|
|
Js.array [|
|
|
Js.Unsafe.inject (Js.string "complete"),
|
|
Js.Unsafe.inject (Js.string "prefix"),
|
|
Js.Unsafe.inject (Js.string prefix),
|
|
Js.Unsafe.inject (Js.string "at"),
|
|
Js.Unsafe.inject (positionToJsMerlinPosition position),
|
|
Js.Unsafe.inject (Js.string "with"),
|
|
Js.Unsafe.inject (Js.string "doc")
|
|
|]
|
|
)
|
|
resolve
|
|
reject;
|
|
|
|
let getDiagnostics path::path text::text resolve reject =>
|
|
prepareCommand
|
|
text::text
|
|
path::path
|
|
query::(Js.array [|Js.Unsafe.inject (Js.string "errors")|])
|
|
resolve
|
|
reject;
|
|
|
|
let locate path::path text::text extension::extension position::position resolve reject =>
|
|
prepareCommand
|
|
text::text
|
|
path::path
|
|
query::(
|
|
Js.array [|
|
|
Js.Unsafe.inject (Js.string "locate"),
|
|
Js.Unsafe.inject (Js.string ""),
|
|
Js.Unsafe.inject (Js.string extension),
|
|
Js.Unsafe.inject (Js.string "at"),
|
|
Js.Unsafe.inject (positionToJsMerlinPosition position)
|
|
|]
|
|
)
|
|
resolve
|
|
reject;
|
|
|
|
/* reject */
|
|
let getOccurrences path::path text::text position::position resolve reject =>
|
|
prepareCommand
|
|
text::text
|
|
path::path
|
|
query::(
|
|
Js.array [|
|
|
Js.Unsafe.inject (Js.string "occurrences"),
|
|
Js.Unsafe.inject (Js.string "ident"),
|
|
Js.Unsafe.inject (Js.string "at"),
|
|
Js.Unsafe.inject (positionToJsMerlinPosition position)
|
|
|]
|
|
)
|
|
resolve
|
|
reject;
|
|
|
|
let destruct
|
|
path::path
|
|
text::text
|
|
startPosition::startPosition
|
|
endPosition::endPosition
|
|
resolve
|
|
reject =>
|
|
prepareCommand
|
|
text::text
|
|
path::path
|
|
query::(
|
|
Js.array [|
|
|
Js.Unsafe.inject (Js.string "case"),
|
|
Js.Unsafe.inject (Js.string "analysis"),
|
|
Js.Unsafe.inject (Js.string "from"),
|
|
Js.Unsafe.inject (positionToJsMerlinPosition startPosition),
|
|
Js.Unsafe.inject (Js.string "to"),
|
|
Js.Unsafe.inject (positionToJsMerlinPosition endPosition)
|
|
|]
|
|
)
|
|
resolve
|
|
reject;
|
|
|
|
let getOutline path::path text::text resolve reject =>
|
|
prepareCommand
|
|
text::text
|
|
path::path
|
|
query::(Js.array [|Js.Unsafe.inject (Js.string "outline")|])
|
|
resolve
|
|
reject; |