]>
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')('purs-loader');
17 const dargs
= require('./dargs');
19 const Psc
= require('./Psc');
21 const PsModuleMap
= require('./PsModuleMap');
23 module
.exports
.connect
= 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 run purs ide client: %o', args
)
36 const ideClient
= spawn('purs', ['ide', 'client'].concat(args
))
38 ideClient
.stderr
.on('data', data
=> {
39 debug(data
.toString())
40 cache
.ideServer
= false
41 reject(new Error('purs 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('purs ide client failed'))
55 cache
.ideServer
= false
56 reject(new Error('purs 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 purs ide server: %o', serverArgs
)
71 const ideServer
= cache
.ideServer
= spawn('purs', ['ide', 'server'].concat(serverArgs
))
73 ideServer
.stdout
.on('data', data
=> {
74 debug('purs ide server stdout: %s', data
.toString());
77 ideServer
.stderr
.on('data', data
=> {
78 debug('purs ide server stderr: %s', data
.toString());
81 ideServer
.on('error', error
=> {
82 debug('purs ide server error: %o', error
);
85 ideServer
.on('close', (code
, signal
) => {
86 debug('purs 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 purs ide server. A full compilation will occur on rebuild');
96 return Promise
.resolve(psModule
)
109 module
.exports
.rebuild
= function rebuild(psModule
) {
110 const options
= psModule
.options
111 const cache
= psModule
.cache
113 debug('attempting rebuild with purs ide client %s', psModule
.srcPath
)
115 const request
= (body
) => new Promise((resolve
, reject
) => {
116 const args
= dargs(options
.pscIdeArgs
)
117 const ideClient
= spawn('purs', ['ide', 'client'].concat(args
))
122 ideClient
.stdout
.on('data', data
=> {
123 stdout
= stdout
+ data
.toString()
126 ideClient
.stderr
.on('data', data
=> {
127 stderr
= stderr
+ data
.toString()
130 ideClient
.on('close', code
=> {
132 const error
= stderr
=== '' ? 'Failed to spawn purs ide client' : stderr
133 return reject(new Error(error
))
139 res
= JSON
.parse(stdout
.toString())
145 if (res
&& !Array
.isArray(res
.result
)) {
146 return resolve(psModule
);
149 Promise
.map(res
.result
, (item
, i
) => {
151 return formatIdeResult(item
, options
, i
, res
.result
.length
)
153 .then(compileMessages
=> {
154 if (res
.resultType
=== 'error') {
155 if (res
.result
.some(item
=> {
156 const isModuleNotFound
= item
.errorCode
=== 'ModuleNotFound';
158 const isUnknownModule
= item
.errorCode
=== 'UnknownModule';
160 const isUnknownModuleImport
= item
.errorCode
=== 'UnknownName' && /Unknown module
/.test(item
.message
);
162 return isModuleNotFound
|| isUnknownModule
|| isUnknownModuleImport
;
164 debug('unknown module, attempting full recompile')
165 return Psc
.compile(psModule
)
166 .then(() => PsModuleMap
.makeMap(options
.src
).then(map
=> {
167 debug('rebuilt module map after unknown module forced a recompile');
168 cache
.psModuleMap
= map
;
170 .then(() => request({ command: 'load' }))
172 .catch(() => resolve(psModule
))
174 const errorMessage
= compileMessages
.join('\n');
175 if (errorMessage
.length
) {
176 psModule
.emitError(errorMessage
);
180 const warningMessage
= compileMessages
.join('\n');
181 if (options
.warnings
&& warningMessage
.length
) {
182 psModule
.emitWarning(warningMessage
);
189 debug('purs ide client stdin: %o', body
);
191 ideClient
.stdin
.write(JSON
.stringify(body
))
192 ideClient
.stdin
.write('\n')
198 file: psModule
.srcPath
,
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();