]>
Commit | Line | Data |
---|---|---|
531c751f | 1 | 'use strict'; |
2 | ||
3 | const path = require('path'); | |
4 | ||
5 | const Promise = require('bluebird'); | |
6 | ||
7 | const fs = Promise.promisifyAll(require('fs')); | |
8 | ||
9 | const retryPromise = require('promise-retry'); | |
10 | ||
11 | const spawn = require('cross-spawn'); | |
12 | ||
13 | const colors = require('chalk'); | |
14 | ||
15 | const debug = require('debug')('purs-loader'); | |
16 | ||
17 | const dargs = require('./dargs'); | |
18 | ||
19 | const Psc = require('./Psc'); | |
20 | ||
21 | const PsModuleMap = require('./PsModuleMap'); | |
22 | ||
23 | function connect(psModule) { | |
24 | const options = psModule.options | |
25 | const cache = psModule.cache | |
26 | ||
27 | if (cache.ideServer) return Promise.resolve(psModule) | |
28 | ||
29 | cache.ideServer = true | |
30 | ||
31 | const connect_ = () => new Promise((resolve, reject) => { | |
32 | const args = dargs(options.pscIdeArgs) | |
33 | ||
34 | debug('attempting to connect to psc-ide-server', args) | |
35 | ||
36 | const ideClient = spawn('psc-ide-client', args) | |
37 | ||
38 | ideClient.stderr.on('data', data => { | |
39 | debug(data.toString()) | |
40 | cache.ideServer = false | |
41 | reject(true) | |
42 | }) | |
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 | |
49 | resolve(psModule) | |
50 | } else { | |
51 | cache.ideServer = ideServer | |
52 | reject(true) | |
53 | } | |
54 | } else { | |
55 | cache.ideServer = false | |
56 | reject(true) | |
57 | } | |
58 | }) | |
59 | ideClient.stdin.resume() | |
60 | ideClient.stdin.write(JSON.stringify({ command: 'load' })) | |
61 | ideClient.stdin.write('\n') | |
62 | }) | |
63 | ||
64 | const args = dargs(Object.assign({ | |
65 | outputDirectory: options.output, | |
66 | }, options.pscIdeArgs)) | |
67 | ||
68 | debug('attempting to start psc-ide-server', args) | |
69 | ||
70 | const ideServer = cache.ideServer = spawn('psc-ide-server', []) | |
71 | ideServer.stderr.on('data', data => { | |
72 | debug(data.toString()) | |
73 | }) | |
74 | ||
75 | return retryPromise((retry, number) => { | |
76 | return connect_().catch(error => { | |
77 | if (!cache.ideServer && number === 9) { | |
78 | debug(error) | |
79 | ||
80 | console.log( | |
81 | 'failed to connect to or start psc-ide-server, ' + | |
82 | 'full compilation will occur on rebuild' | |
83 | ) | |
84 | ||
85 | return Promise.resolve(psModule) | |
86 | } | |
87 | ||
88 | return retry(error) | |
89 | }) | |
90 | }, { | |
91 | retries: 9, | |
92 | factor: 1, | |
93 | minTimeout: 333, | |
94 | maxTimeout: 333, | |
95 | }) | |
96 | } | |
97 | module.exports.connect = connect; | |
98 | ||
99 | function rebuild(psModule) { | |
100 | const options = psModule.options | |
101 | const cache = psModule.cache | |
102 | ||
103 | debug('attempting rebuild with psc-ide-client %s', psModule.srcPath) | |
104 | ||
105 | const request = (body) => new Promise((resolve, reject) => { | |
106 | const args = dargs(options.pscIdeArgs) | |
107 | const ideClient = spawn('psc-ide-client', args) | |
108 | ||
109 | var stdout = '' | |
110 | var stderr = '' | |
111 | ||
112 | ideClient.stdout.on('data', data => { | |
113 | stdout = stdout + data.toString() | |
114 | }) | |
115 | ||
116 | ideClient.stderr.on('data', data => { | |
117 | stderr = stderr + data.toString() | |
118 | }) | |
119 | ||
120 | ideClient.on('close', code => { | |
121 | if (code !== 0) { | |
122 | const error = stderr === '' ? 'Failed to spawn psc-ide-client' : stderr | |
123 | return reject(new Error(error)) | |
124 | } | |
125 | ||
126 | let res = null | |
127 | ||
128 | try { | |
129 | res = JSON.parse(stdout.toString()) | |
130 | debug(res) | |
131 | } catch (err) { | |
132 | return reject(err) | |
133 | } | |
134 | ||
135 | if (res && !Array.isArray(res.result)) { | |
136 | return res.resultType === 'success' | |
137 | ? resolve(psModule) | |
138 | : reject('psc-ide rebuild failed') | |
139 | } | |
140 | ||
141 | Promise.map(res.result, (item, i) => { | |
142 | debug(item) | |
143 | return formatIdeResult(item, options, i, res.result.length) | |
144 | }) | |
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; | |
153 | })) | |
154 | .then(() => request({ command: 'load' })) | |
155 | .then(resolve) | |
156 | .catch(() => reject('psc-ide rebuild failed')) | |
157 | } | |
158 | cache.errors = compileMessages.join('\n') | |
159 | reject('psc-ide rebuild failed') | |
160 | } else { | |
161 | cache.warnings = compileMessages.join('\n') | |
162 | resolve(psModule) | |
163 | } | |
164 | }) | |
165 | }) | |
166 | ||
167 | ideClient.stdin.write(JSON.stringify(body)) | |
168 | ideClient.stdin.write('\n') | |
169 | }) | |
170 | ||
171 | return request({ | |
172 | command: 'rebuild', | |
173 | params: { | |
174 | file: psModule.srcPath, | |
175 | } | |
176 | }) | |
177 | } | |
178 | module.exports.rebuild = rebuild; | |
179 | ||
180 | function formatIdeResult(result, options, index, length) { | |
531c751f | 181 | let numAndErr = `[${index+1}/${length} ${result.errorCode}]` |
182 | numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr | |
183 | ||
055d127b | 184 | function makeResult() { |
185 | return Promise.resolve(`\n${numAndErr} ${result.message}`) | |
186 | } | |
187 | ||
188 | function makeResultSnippet(filename, pos) { | |
189 | const srcPath = path.relative(options.context, filename); | |
190 | const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}` | |
191 | ||
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) | |
198 | ||
199 | if (endsOnNewline) { | |
200 | lines.splice(lines.length - 1, 1) | |
201 | pos.endLine = pos.endLine - 1 | |
202 | pos.endColumn = lines[lines.length - 1].length || 1 | |
203 | } | |
531c751f | 204 | |
055d127b | 205 | // strip newlines at the end |
206 | if (endsOnNewline) { | |
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 | |
212 | trimmed.push(line) | |
213 | } | |
214 | return trimmed | |
215 | }, []).reverse() | |
216 | pos.endLine = pos.endLine - (lines.length - trimmed.length) | |
217 | pos.endColumn = trimmed[trimmed.length - 1].length || 1 | |
218 | } | |
531c751f | 219 | |
055d127b | 220 | const spaces = ' '.repeat(String(pos.endLine).length) |
221 | let snippet = trimmed.map((line, i) => { | |
222 | return ` ${pos.startLine + i} ${line}` | |
223 | }).join('\n') | |
531c751f | 224 | |
055d127b | 225 | if (trimmed.length === 1) { |
226 | snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}` | |
227 | } else { | |
228 | snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}` | |
229 | snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}` | |
230 | } | |
531c751f | 231 | |
055d127b | 232 | return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`) |
233 | }) | |
234 | } | |
235 | ||
236 | return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult(); | |
531c751f | 237 | } |