]>
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 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) {
80 console
.warn('Failed to connect to or start psc-ide-server. A full compilation will occur on rebuild');
82 return Promise
.resolve(psModule
)
94 module
.exports
.connect
= connect
;
96 function rebuild(psModule
) {
97 const options
= psModule
.options
98 const cache
= psModule
.cache
100 debug('attempting rebuild with psc-ide-client %s', psModule
.srcPath
)
102 const request
= (body
) => new Promise((resolve
, reject
) => {
103 const args
= dargs(options
.pscIdeArgs
)
104 const ideClient
= spawn('psc-ide-client', args
)
109 ideClient
.stdout
.on('data', data
=> {
110 stdout
= stdout
+ data
.toString()
113 ideClient
.stderr
.on('data', data
=> {
114 stderr
= stderr
+ data
.toString()
117 ideClient
.on('close', code
=> {
119 const error
= stderr
=== '' ? 'Failed to spawn psc-ide-client' : stderr
120 return reject(new Error(error
))
126 res
= JSON
.parse(stdout
.toString())
132 if (res
&& !Array
.isArray(res
.result
)) {
133 return resolve(psModule
);
136 Promise
.map(res
.result
, (item
, i
) => {
138 return formatIdeResult(item
, options
, i
, res
.result
.length
)
140 .then(compileMessages
=> {
141 if (res
.resultType
=== 'error') {
142 if (res
.result
.some(item
=> {
143 const isUnknownModule
= item
.errorCode
=== 'UnknownModule';
145 const isUnknownModuleImport
= item
.errorCode
=== 'UnknownName' && /Unknown module
/.test(item
.message
);
147 return isUnknownModule
|| isUnknownModuleImport
;
149 debug('unknown module, attempting full recompile')
150 return Psc
.compile(psModule
)
151 .then(() => PsModuleMap
.makeMap(options
.src
).then(map
=> {
152 debug('rebuilt module map');
153 cache
.psModuleMap
= map
;
155 .then(() => request({ command: 'load' }))
157 .catch(() => resolve(psModule
))
159 cache
.errors
= compileMessages
.join('\n')
162 cache
.warnings
= compileMessages
.join('\n')
168 ideClient
.stdin
.write(JSON
.stringify(body
))
169 ideClient
.stdin
.write('\n')
175 file: psModule
.srcPath
,
179 module
.exports
.rebuild
= rebuild
;
181 function formatIdeResult(result
, options
, index
, length
) {
182 let numAndErr
= `[${index+1}/${length} ${result.errorCode}]`
183 numAndErr
= options
.pscIdeColors
? colors
.yellow(numAndErr
) : numAndErr
185 function makeResult() {
186 return Promise
.resolve(`\n${numAndErr} ${result.message}`)
189 function makeResultSnippet(filename
, pos
) {
190 const srcPath
= path
.relative(options
.context
, filename
);
191 const fileAndPos
= `${srcPath}:${pos.startLine}:${pos.startColumn}`
193 return fs
.readFileAsync(filename
, 'utf8').then(source
=> {
194 const lines
= source
.split('\n').slice(pos
.startLine
- 1, pos
.endLine
)
195 const endsOnNewline
= pos
.endColumn
=== 1 && pos
.startLine
!== pos
.endLine
196 const up
= options
.pscIdeColors
? colors
.red('^') : '^'
197 const down
= options
.pscIdeColors
? colors
.red('v') : 'v'
198 let trimmed
= lines
.slice(0)
201 lines
.splice(lines
.length
- 1, 1)
202 pos
.endLine
= pos
.endLine
- 1
203 pos
.endColumn
= lines
[lines
.length
- 1].length
|| 1
206 // strip newlines at the end
208 trimmed
= lines
.reverse().reduce((trimmed
, line
, i
) => {
209 if (i
=== 0 && line
=== '') trimmed
.trimming
= true
210 if (!trimmed
.trimming
) trimmed
.push(line
)
211 if (trimmed
.trimming
&& line
!== '') {
212 trimmed
.trimming
= false
217 pos
.endLine
= pos
.endLine
- (lines
.length
- trimmed
.length
)
218 pos
.endColumn
= trimmed
[trimmed
.length
- 1].length
|| 1
221 const spaces
= ' '.repeat(String(pos
.endLine
).length
)
222 let snippet
= trimmed
.map((line
, i
) => {
223 return ` ${pos.startLine + i} ${line}`
226 if (trimmed
.length
=== 1) {
227 snippet
+= `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}`
229 snippet
= ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}`
230 snippet
+= `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}`
233 return Promise
.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`)
237 return result
.filename
&& result
.position
? makeResultSnippet(result
.filename
, result
.position
) : makeResult();