]>
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
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
55 cache
.ideServer
= false
59 ideClient
.stdin
.resume()
60 ideClient
.stdin
.write(JSON
.stringify({ command: 'load' }))
61 ideClient
.stdin
.write('\n')
64 const args
= dargs(Object
.assign({
65 outputDirectory: options
.output
,
66 }, options
.pscIdeArgs
))
68 debug('attempting to start psc-ide-server', args
)
70 const ideServer
= cache
.ideServer
= spawn('psc-ide-server', [])
71 ideServer
.stderr
.on('data', data
=> {
72 debug(data
.toString())
75 return retryPromise((retry
, number
) => {
76 return connect_().catch(error
=> {
77 if (!cache
.ideServer
&& number
=== 9) {
81 'failed to connect to or start psc-ide-server, ' +
82 'full compilation will occur on rebuild'
85 return Promise
.resolve(psModule
)
97 module
.exports
.connect
= connect
;
99 function rebuild(psModule
) {
100 const options
= psModule
.options
101 const cache
= psModule
.cache
103 debug('attempting rebuild with psc-ide-client %s', psModule
.srcPath
)
105 const request
= (body
) => new Promise((resolve
, reject
) => {
106 const args
= dargs(options
.pscIdeArgs
)
107 const ideClient
= spawn('psc-ide-client', args
)
112 ideClient
.stdout
.on('data', data
=> {
113 stdout
= stdout
+ data
.toString()
116 ideClient
.stderr
.on('data', data
=> {
117 stderr
= stderr
+ data
.toString()
120 ideClient
.on('close', code
=> {
122 const error
= stderr
=== '' ? 'Failed to spawn psc-ide-client' : stderr
123 return reject(new Error(error
))
129 res
= JSON
.parse(stdout
.toString())
135 if (res
&& !Array
.isArray(res
.result
)) {
136 return res
.resultType
=== 'success'
138 : reject('psc-ide rebuild failed')
141 Promise
.map(res
.result
, (item
, i
) => {
143 return formatIdeResult(item
, options
, i
, res
.result
.length
)
145 .then(compileMessages
=> {
146 if (res
.resultType
=== 'error') {
147 if (res
.result
.some(item
=> item
.errorCode
=== 'UnknownModule' || item
.errorCode
=== 'UnknownName')) {
148 debug('unknown module, attempting full recompile')
149 return Psc
.compile(psModule
)
150 .then(() => PsModuleMap
.makeMap(options
.src
).then(map
=> {
151 debug('rebuilt module map');
152 cache
.psModuleMap
= map
;
154 .then(() => request({ command: 'load' }))
156 .catch(() => reject('psc-ide rebuild failed'))
158 cache
.errors
= compileMessages
.join('\n')
159 reject('psc-ide rebuild failed')
161 cache
.warnings
= compileMessages
.join('\n')
167 ideClient
.stdin
.write(JSON
.stringify(body
))
168 ideClient
.stdin
.write('\n')
174 file: psModule
.srcPath
,
178 module
.exports
.rebuild
= rebuild
;
180 function formatIdeResult(result
, options
, index
, length
) {
181 let numAndErr
= `[${index+1}/${length} ${result.errorCode}]`
182 numAndErr
= options
.pscIdeColors
? colors
.yellow(numAndErr
) : numAndErr
184 function makeResult() {
185 return Promise
.resolve(`\n${numAndErr} ${result.message}`)
188 function makeResultSnippet(filename
, pos
) {
189 const srcPath
= path
.relative(options
.context
, filename
);
190 const fileAndPos
= `${srcPath}:${pos.startLine}:${pos.startColumn}`
192 return fs
.readFileAsync(filename
, 'utf8').then(source
=> {
193 const lines
= source
.split('\n').slice(pos
.startLine
- 1, pos
.endLine
)
194 const endsOnNewline
= pos
.endColumn
=== 1 && pos
.startLine
!== pos
.endLine
195 const up
= options
.pscIdeColors
? colors
.red('^') : '^'
196 const down
= options
.pscIdeColors
? colors
.red('v') : 'v'
197 let trimmed
= lines
.slice(0)
200 lines
.splice(lines
.length
- 1, 1)
201 pos
.endLine
= pos
.endLine
- 1
202 pos
.endColumn
= lines
[lines
.length
- 1].length
|| 1
205 // strip newlines at the end
207 trimmed
= lines
.reverse().reduce((trimmed
, line
, i
) => {
208 if (i
=== 0 && line
=== '') trimmed
.trimming
= true
209 if (!trimmed
.trimming
) trimmed
.push(line
)
210 if (trimmed
.trimming
&& line
!== '') {
211 trimmed
.trimming
= false
216 pos
.endLine
= pos
.endLine
- (lines
.length
- trimmed
.length
)
217 pos
.endColumn
= trimmed
[trimmed
.length
- 1].length
|| 1
220 const spaces
= ' '.repeat(String(pos
.endLine
).length
)
221 let snippet
= trimmed
.map((line
, i
) => {
222 return ` ${pos.startLine + i} ${line}`
225 if (trimmed
.length
=== 1) {
226 snippet
+= `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}`
228 snippet
= ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}`
229 snippet
+= `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}`
232 return Promise
.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`)
236 return result
.filename
&& result
.position
? makeResultSnippet(result
.filename
, result
.position
) : makeResult();