]>
git.immae.eu Git - github/fretlink/purs-loader.git/blob - src/PscIde.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')('purs-loader');
17 const dargs
= require('./dargs');
19 const Psc
= require('./Psc');
21 const PsModuleMap
= require('./PsModuleMap');
23 function connect(psModule
) {
24 const options
= psModule
.options
25 const cache
= psModule
.cache
27 if (cache
.ideServer
) return Promise
.resolve(psModule
)
29 cache
.ideServer
= true
31 const connect_
= () => new Promise((resolve
, reject
) => {
32 const args
= dargs(options
.pscIdeArgs
)
34 debug('attempting to connect to psc-ide-server', args
)
36 const ideClient
= spawn('psc-ide-client', args
)
38 ideClient
.stderr
.on('data', data
=> {
39 debug(data
.toString())
40 cache
.ideServer
= false
41 reject(new Error('psc-ide-client failed'))
43 ideClient
.stdout
.once('data', data
=> {
44 debug(data
.toString())
45 if (data
.toString()[0] === '{') {
46 const res
= JSON
.parse(data
.toString())
47 if (res
.resultType
=== 'success') {
48 cache
.ideServer
= ideServer
51 cache
.ideServer
= ideServer
52 reject(new Error('psc-ide-client failed'))
55 cache
.ideServer
= false
56 reject(new Error('psc-ide-client failed'))
59 ideClient
.stdin
.resume()
60 ideClient
.stdin
.write(JSON
.stringify({ command: 'load' }))
61 ideClient
.stdin
.write('\n')
64 const serverArgs
= dargs(Object
.assign({
65 outputDirectory: options
.output
,
67 }, options
.pscIdeServerArgs
))
69 debug('attempting to start psc-ide-server', serverArgs
)
71 const ideServer
= cache
.ideServer
= spawn('psc-ide-server', serverArgs
)
73 ideServer
.stdout
.on('data', data
=> {
74 debug('psc-ide-server stdout: %s', data
.toString());
77 ideServer
.stderr
.on('data', data
=> {
78 debug('psc-ide-server stderr: %s', data
.toString());
81 ideServer
.on('error', error
=> {
82 debug('psc-ide-server error: %o', error
);
85 ideServer
.on('close', (code
, signal
) => {
86 debug('psc-ide-server close: %s %s', code
, signal
);
89 return retryPromise((retry
, number
) => {
90 return connect_().catch(error
=> {
91 if (!cache
.ideServer
&& number
=== 9) {
94 console
.warn('Failed to connect to or start psc-ide-server. A full compilation will occur on rebuild');
96 return Promise
.resolve(psModule
)
108 module
.exports
.connect
= connect
;
110 function rebuild(psModule
) {
111 const options
= psModule
.options
112 const cache
= psModule
.cache
114 debug('attempting rebuild with psc-ide-client %s', psModule
.srcPath
)
116 const request
= (body
) => new Promise((resolve
, reject
) => {
117 const args
= dargs(options
.pscIdeArgs
)
118 const ideClient
= spawn('psc-ide-client', args
)
123 ideClient
.stdout
.on('data', data
=> {
124 stdout
= stdout
+ data
.toString()
127 ideClient
.stderr
.on('data', data
=> {
128 stderr
= stderr
+ data
.toString()
131 ideClient
.on('close', code
=> {
133 const error
= stderr
=== '' ? 'Failed to spawn psc-ide-client' : stderr
134 return reject(new Error(error
))
140 res
= JSON
.parse(stdout
.toString())
146 if (res
&& !Array
.isArray(res
.result
)) {
147 return resolve(psModule
);
150 Promise
.map(res
.result
, (item
, i
) => {
152 return formatIdeResult(item
, options
, i
, res
.result
.length
)
154 .then(compileMessages
=> {
155 if (res
.resultType
=== 'error') {
156 if (res
.result
.some(item
=> {
157 const isUnknownModule
= item
.errorCode
=== 'UnknownModule';
159 const isUnknownModuleImport
= item
.errorCode
=== 'UnknownName' && /Unknown module
/.test(item
.message
);
161 return isUnknownModule
|| isUnknownModuleImport
;
163 debug('unknown module, attempting full recompile')
164 return Psc
.compile(psModule
)
165 .then(() => PsModuleMap
.makeMap(options
.src
).then(map
=> {
166 debug('rebuilt module map after unknown module forced a recompile');
167 cache
.psModuleMap
= map
;
169 .then(() => request({ command: 'load' }))
171 .catch(() => resolve(psModule
))
173 const errorMessage
= compileMessages
.join('\n');
174 if (errorMessage
.length
) {
175 psModule
.emitError(errorMessage
);
179 const warningMessage
= compileMessages
.join('\n');
180 if (options
.warnings
&& warningMessage
.length
) {
181 psModule
.emitWarning(warningMessage
);
188 debug('psc-ide-client stdin: %o', body
);
190 ideClient
.stdin
.write(JSON
.stringify(body
))
191 ideClient
.stdin
.write('\n')
197 file: psModule
.srcPath
,
201 module
.exports
.rebuild
= rebuild
;
203 function formatIdeResult(result
, options
, index
, length
) {
204 let numAndErr
= `[${index+1}/${length} ${result.errorCode}]`
205 numAndErr
= options
.pscIdeColors
? colors
.yellow(numAndErr
) : numAndErr
207 function makeResult() {
208 return Promise
.resolve(`\n${numAndErr} ${result.message}`)
211 function makeResultSnippet(filename
, pos
) {
212 const srcPath
= path
.relative(options
.context
, filename
);
213 const fileAndPos
= `${srcPath}:${pos.startLine}:${pos.startColumn}`
215 return fs
.readFileAsync(filename
, 'utf8').then(source
=> {
216 const lines
= source
.split('\n').slice(pos
.startLine
- 1, pos
.endLine
)
217 const endsOnNewline
= pos
.endColumn
=== 1 && pos
.startLine
!== pos
.endLine
218 const up
= options
.pscIdeColors
? colors
.red('^') : '^'
219 const down
= options
.pscIdeColors
? colors
.red('v') : 'v'
220 let trimmed
= lines
.slice(0)
223 lines
.splice(lines
.length
- 1, 1)
224 pos
.endLine
= pos
.endLine
- 1
225 pos
.endColumn
= lines
[lines
.length
- 1].length
|| 1
228 // strip newlines at the end
230 trimmed
= lines
.reverse().reduce((trimmed
, line
, i
) => {
231 if (i
=== 0 && line
=== '') trimmed
.trimming
= true
232 if (!trimmed
.trimming
) trimmed
.push(line
)
233 if (trimmed
.trimming
&& line
!== '') {
234 trimmed
.trimming
= false
239 pos
.endLine
= pos
.endLine
- (lines
.length
- trimmed
.length
)
240 pos
.endColumn
= trimmed
[trimmed
.length
- 1].length
|| 1
243 const spaces
= ' '.repeat(String(pos
.endLine
).length
)
244 let snippet
= trimmed
.map((line
, i
) => {
245 return ` ${pos.startLine + i} ${line}`
248 if (trimmed
.length
=== 1) {
249 snippet
+= `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}`
251 snippet
= ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}`
252 snippet
+= `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}`
255 return Promise
.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`)
259 return result
.filename
&& result
.position
? makeResultSnippet(result
.filename
, result
.position
) : makeResult();