]>
git.immae.eu Git - github/fretlink/purs-loader.git/blob - lib/ide.js
3 var path
= require('path');
5 var Promise
= require('bluebird');
7 var fs
= Promise
.promisifyAll(require('fs'));
9 var retryPromise
= require('promise-retry');
11 var spawn
= require('cross-spawn');
13 var colors
= require('chalk');
15 var debug_
= require('debug');
17 var debug
= debug_('purs-loader');
19 var debugVerbose
= debug_('purs-loader:verbose');
21 var dargs
= require('./dargs');
23 var compile
= require('./compile');
25 var PsModuleMap
= require('./purs-module-map');
27 function UnknownModuleError() {
28 this.name
= 'UnknownModuleError';
29 this.stack
= new Error().stack
;
32 UnknownModuleError
.prototype = Object
.create(Error
.prototype);
34 UnknownModuleError
.prototype.constructor = UnknownModuleError
;
36 module
.exports
.UnknownModuleError
= UnknownModuleError
;
38 function spawnIdeClient(body
, options
) {
39 var ideClientCommand
= options
.pscIdeClient
|| 'purs';
41 var ideClientArgs
= (options
.pscIdeClient
? [] : ['ide', 'client']).concat(dargs(options
.pscIdeClientArgs
));
47 debug('ide client %s %o %O', ideClientCommand
, ideClientArgs
, body
);
49 return new Promise(function (resolve
, reject
) {
50 var ideClient
= spawn(ideClientCommand
, ideClientArgs
);
52 ideClient
.stderr
.on('data', function (data
) {
53 stderr
.push(data
.toString());
56 ideClient
.stdout
.on('data', function (data
) {
57 stdout
.push(data
.toString());
60 ideClient
.on('close', function (code
) {
62 var errorMessage
= stderr
.join('');
64 reject(new Error('ide client failed: ' + errorMessage
));
66 var result
= stdout
.join('');
72 ideClient
.stdin
.resume();
74 ideClient
.stdin
.write(JSON
.stringify(body
));
76 ideClient
.stdin
.write('\n');
80 function formatIdeResult(result
, options
, index
, length
) {
81 var numAndErr
= '[' + (index
+ 1) + '/' + length
+ ' ' + result
.errorCode
+ ']';
82 numAndErr
= options
.pscIdeColors
? colors
.yellow(numAndErr
) : numAndErr
;
84 function makeResult() {
85 return Promise
.resolve('\n' + numAndErr
+ ' ' + result
.message
);
88 function makeResultSnippet(filename
, pos
) {
89 var srcPath
= path
.relative(options
.context
, filename
);
90 var fileAndPos
= srcPath
+ ':' + pos
.startLine
+ ':' + pos
.startColumn
;
92 return fs
.readFileAsync(filename
, 'utf8').then(function (source
) {
93 var lines
= source
.split('\n').slice(pos
.startLine
- 1, pos
.endLine
);
94 var endsOnNewline
= pos
.endColumn
=== 1 && pos
.startLine
!== pos
.endLine
;
95 var up
= options
.pscIdeColors
? colors
.red('^') : '^';
96 var down
= options
.pscIdeColors
? colors
.red('v') : 'v';
97 var trimmed
= lines
.slice(0);
100 lines
.splice(lines
.length
- 1, 1);
101 pos
.endLine
= pos
.endLine
- 1;
102 pos
.endColumn
= lines
[lines
.length
- 1].length
|| 1;
105 // strip newlines at the end
107 trimmed
= lines
.reverse().reduce(function (trimmed
, line
, i
) {
108 if (i
=== 0 && line
=== '') trimmed
.trimming
= true;
109 if (!trimmed
.trimming
) trimmed
.push(line
);
110 if (trimmed
.trimming
&& line
!== '') {
111 trimmed
.trimming
= false;
116 pos
.endLine
= pos
.endLine
- (lines
.length
- trimmed
.length
);
117 pos
.endColumn
= trimmed
[trimmed
.length
- 1].length
|| 1;
120 var spaces
= ' '.repeat(String(pos
.endLine
).length
);
121 var snippet
= trimmed
.map(function (line
, i
) {
122 return ' ' + (pos
.startLine
+ i
) + ' ' + line
;
125 if (trimmed
.length
=== 1) {
126 snippet
+= '\n ' + spaces
+ ' ' + ' '.repeat(pos
.startColumn
- 1) + up
.repeat(pos
.endColumn
- pos
.startColumn
+ 1);
128 snippet
= ' ' + spaces
+ ' ' + ' '.repeat(pos
.startColumn
- 1) + down
+ '\n' + snippet
;
129 snippet
+= '\n ' + spaces
+ ' ' + ' '.repeat(pos
.endColumn
- 1) + up
;
132 return Promise
.resolve('\n' + numAndErr
+ ' ' + fileAndPos
+ '\n\n' + snippet
+ '\n\n' + result
.message
);
133 }).catch(function (error
) {
134 debug('failed to format ide result: %o', error
);
136 return Promise
.resolve('');
140 return result
.filename
&& result
.position
? makeResultSnippet(result
.filename
, result
.position
) : makeResult();
143 module
.exports
.connect
= function connect(psModule
) {
144 var options
= psModule
.options
;
146 var serverCommand
= options
.pscIdeServer
|| 'purs';
148 var serverArgs
= (options
.pscIdeServer
? [] : ['ide', 'server']).concat(dargs(Object
.assign({
149 outputDirectory: options
.output
,
151 }, options
.pscIdeServerArgs
)));
153 debug('ide server: %s %o', serverCommand
, serverArgs
);
155 var ideServer
= spawn(serverCommand
, serverArgs
);
157 ideServer
.stdout
.on('data', function (data
) {
158 debugVerbose('ide server stdout: %s', data
.toString());
161 ideServer
.stderr
.on('data', function (data
) {
162 debugVerbose('ide server stderr: %s', data
.toString());
165 ideServer
.on('error', function (error
) {
166 debugVerbose('ide server error: %o', error
);
169 ideServer
.on('close', function (code
, signal
) {
170 debugVerbose('ide server close: %s %s', code
, signal
);
173 return Promise
.resolve(ideServer
);
176 module
.exports
.load
= function load(psModule
) {
177 var options
= psModule
.options
;
179 var body
= { command: 'load' };
181 return spawnIdeClient(body
, options
);
184 module
.exports
.loadWithRetry
= function loadWithRetry(psModule
) {
187 return retryPromise(function (retry
, number
) {
188 debugVerbose('attempting to load modules (%d out of %d attempts)', number
, retries
);
190 return module
.exports
.load(psModule
).catch(retry
);
196 }).then(function () {
201 module
.exports
.rebuild
= function rebuild(psModule
) {
202 var options
= psModule
.options
;
207 file: psModule
.srcPath
211 var parseResponse
= function parseResponse(response
) {
213 var parsed
= JSON
.parse(response
);
215 debugVerbose('parsed JSON response: %O', parsed
);
217 return Promise
.resolve(parsed
);
219 return Promise
.reject(error
);
223 var formatResponse
= function formatResponse(parsed
) {
224 var result
= Array
.isArray(parsed
.result
) ? parsed
.result : [];
226 return Promise
.map(result
, function (item
, i
) {
227 debugVerbose('formatting result %O', item
);
229 return formatIdeResult(item
, options
, i
, result
.length
);
230 }).then(function (formatted
) {
233 formatted: formatted
,
234 formattedMessage: formatted
.join('\n')
239 return spawnIdeClient(body
, options
).then(parseResponse
).then(formatResponse
).then(function (_ref
) {
240 var parsed
= _ref
.parsed
,
241 formatted
= _ref
.formatted
,
242 formattedMessage
= _ref
.formattedMessage
;
244 if (parsed
.resultType
=== 'success') {
245 if (options
.warnings
&& formattedMessage
.length
) {
246 psModule
.emitWarning(formattedMessage
);
250 } else if ((parsed
.result
|| []).some(function (item
) {
251 var isModuleNotFound
= item
.errorCode
=== 'ModuleNotFound';
253 var isUnknownModule
= item
.errorCode
=== 'UnknownModule';
255 var isUnknownModuleImport
= item
.errorCode
=== 'UnknownName' && /Unknown module
/.test(item
.message
);
257 return isModuleNotFound
|| isUnknownModule
|| isUnknownModuleImport
;
259 debug('module %s was not rebuilt because the module is unknown', psModule
.name
);
261 return Promise
.reject(new UnknownModuleError());
263 if (formattedMessage
.length
) {
264 psModule
.emitError(formattedMessage
);