]>
git.immae.eu Git - github/fretlink/purs-loader.git/blob - src/ide.js
3 const path
= require('path');
5 const Promise
= require('bluebird');
7 const fs
= Promise
.promisifyAll(require('fs'));
9 const retryPromise
= require('promise-retry');
11 const spawn
= require('cross-spawn');
13 const colors
= require('chalk');
15 const debug_
= require('debug');
17 const debug
= debug_('purs-loader');
19 const debugVerbose
= debug_('purs-loader:verbose');
21 const dargs
= require('./dargs');
23 const compile
= require('./compile');
25 const 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 const ideClientCommand
= options
.pscIdeClient
|| 'purs';
41 const ideClientArgs
= (options
.pscIdeClient
? [] : ['ide', 'client']).concat(dargs(options
.pscIdeClientArgs
));
47 debug('ide client %s %o %O', ideClientCommand
, ideClientArgs
, body
);
49 return new Promise((resolve
, reject
) => {
50 const ideClient
= spawn(ideClientCommand
, ideClientArgs
);
52 ideClient
.stderr
.on('data', data
=> {
53 stderr
.push(data
.toString());
56 ideClient
.stdout
.on('data', data
=> {
57 stdout
.push(data
.toString());
60 ideClient
.on('close', code
=> {
62 const errorMessage
= stderr
.join('');
64 reject(new Error(`ide client failed: ${errorMessage}`));
67 const result
= stdout
.join('');
73 ideClient
.stdin
.resume();
75 ideClient
.stdin
.write(JSON
.stringify(body
));
77 ideClient
.stdin
.write('\n');
81 function formatIdeResult(result
, options
, index
, length
) {
82 let numAndErr
= `[${index+1}/${length} ${result.errorCode}]`
83 numAndErr
= options
.pscIdeColors
? colors
.yellow(numAndErr
) : numAndErr
85 function makeResult() {
86 return Promise
.resolve(`\n${numAndErr} ${result.message}`)
89 function makeResultSnippet(filename
, pos
) {
90 const srcPath
= path
.relative(options
.context
, filename
);
91 const fileAndPos
= `${srcPath}:${pos.startLine}:${pos.startColumn}`
93 return fs
.readFileAsync(filename
, 'utf8').then(source
=> {
94 const lines
= source
.split('\n').slice(pos
.startLine
- 1, pos
.endLine
)
95 const endsOnNewline
= pos
.endColumn
=== 1 && pos
.startLine
!== pos
.endLine
96 const up
= options
.pscIdeColors
? colors
.red('^') : '^'
97 const down
= options
.pscIdeColors
? colors
.red('v') : 'v'
98 let trimmed
= lines
.slice(0)
101 lines
.splice(lines
.length
- 1, 1)
102 pos
.endLine
= pos
.endLine
- 1
103 pos
.endColumn
= lines
[lines
.length
- 1].length
|| 1
106 // strip newlines at the end
108 trimmed
= lines
.reverse().reduce((trimmed
, line
, i
) => {
109 if (i
=== 0 && line
=== '') trimmed
.trimming
= true
110 if (!trimmed
.trimming
) trimmed
.push(line
)
111 if (trimmed
.trimming
&& line
!== '') {
112 trimmed
.trimming
= false
117 pos
.endLine
= pos
.endLine
- (lines
.length
- trimmed
.length
)
118 pos
.endColumn
= trimmed
[trimmed
.length
- 1].length
|| 1
121 const spaces
= ' '.repeat(String(pos
.endLine
).length
)
122 let snippet
= trimmed
.map((line
, i
) => {
123 return ` ${pos.startLine + i} ${line}`
126 if (trimmed
.length
=== 1) {
127 snippet
+= `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}`
129 snippet
= ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}`
130 snippet
+= `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}`
133 return Promise
.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`)
135 debug('failed to format ide result: %o', error
);
137 return Promise
.resolve('');
141 return result
.filename
&& result
.position
? makeResultSnippet(result
.filename
, result
.position
) : makeResult();
144 module
.exports
.connect
= function connect(psModule
) {
145 const options
= psModule
.options
147 const serverCommand
= options
.pscIdeServer
|| 'purs';
149 const serverArgs
= (options
.pscIdeServer
? [] : ['ide', 'server']).concat(dargs(Object
.assign({
150 outputDirectory: options
.output
,
152 }, options
.pscIdeServerArgs
)));
154 debug('ide server: %s %o', serverCommand
, serverArgs
);
156 const ideServer
= spawn(serverCommand
, serverArgs
);
158 ideServer
.stdout
.on('data', data
=> {
159 debugVerbose('ide server stdout: %s', data
.toString());
162 ideServer
.stderr
.on('data', data
=> {
163 debugVerbose('ide server stderr: %s', data
.toString());
166 ideServer
.on('error', error
=> {
167 debugVerbose('ide server error: %o', error
);
170 ideServer
.on('close', (code
, signal
) => {
171 debugVerbose('ide server close: %s %s', code
, signal
);
174 return Promise
.resolve(ideServer
);
177 module
.exports
.load
= function load(psModule
) {
178 const options
= psModule
.options
180 const body
= {command: 'load'};
182 return spawnIdeClient(body
, options
);
185 module
.exports
.loadWithRetry
= function loadWithRetry(psModule
) {
188 return retryPromise((retry
, number
) => {
189 debugVerbose('attempting to load modules (%d out of %d attempts)', number
, retries
);
191 return module
.exports
.load(psModule
).catch(retry
);
197 }).then(() => psModule
);
200 module
.exports
.rebuild
= function rebuild(psModule
) {
201 const options
= psModule
.options
;
206 file: psModule
.srcPath
,
210 const parseResponse
= response
=> {
212 const parsed
= JSON
.parse(response
);
214 debugVerbose('parsed JSON response: %O', parsed
);
216 return Promise
.resolve(parsed
);
219 return Promise
.reject(error
);
223 const formatResponse
= parsed
=> {
224 const result
= Array
.isArray(parsed
.result
) ? parsed
.result : [];
226 return Promise
.map(result
, (item
, i
) => {
227 debugVerbose('formatting result %O', item
);
229 return formatIdeResult(item
, options
, i
, result
.length
);
230 }).then(formatted
=> ({
232 formatted: formatted
,
233 formattedMessage: formatted
.join('\n')
237 return spawnIdeClient(body
, options
)
239 .then(formatResponse
)
240 .then(({ parsed
, formatted
, formattedMessage
}) => {
241 if (parsed
.resultType
=== 'success') {
242 if (options
.warnings
&& formattedMessage
.length
) {
243 psModule
.emitWarning(formattedMessage
);
248 else if ((parsed
.result
|| []).some(item
=> {
249 const isModuleNotFound
= item
.errorCode
=== 'ModuleNotFound';
251 const isUnknownModule
= item
.errorCode
=== 'UnknownModule';
253 const isUnknownModuleImport
= item
.errorCode
=== 'UnknownName' && /Unknown module
/.test(item
.message
);
255 return isModuleNotFound
|| isUnknownModule
|| isUnknownModuleImport
;
257 debug('module %s was not rebuilt because the module is unknown', psModule
.name
);
259 return Promise
.reject(new UnknownModuleError());
262 if (formattedMessage
.length
) {
263 psModule
.emitError(formattedMessage
);