@@ -5,6 +5,7 @@ import sbt.Def.Initialize
5
5
import sbt .Keys ._
6
6
import java .io ._
7
7
import java .lang .ProcessBuilder
8
+ import java .lang .ProcessBuilder .Redirect
8
9
import scala .collection .mutable
9
10
import scala .util .Properties .{ isWin , isMac }
10
11
@@ -146,23 +147,66 @@ object DottyIDEPlugin extends AutoPlugin {
146
147
if (isWin) Seq (" cmd.exe" , " /C" ) ++ cmd
147
148
else cmd
148
149
149
- /** Run `cmd`.
150
- * @param wait If true, wait for `cmd` to return and throw an exception if the exit code is non-zero.
151
- * @param directory If not null, run `cmd` in this directory.
150
+ /** Run the command `cmd`.
151
+ *
152
+ * @param wait If true, wait for the command to return and throw an exception if the exit code is non-zero.
153
+ * @param directory If not None, run the command in this directory.
154
+ * @param outputCallback If not None, pass the command output to this callback instead of writing it to stdout.
152
155
*/
153
- def runProcess (cmd : Seq [String ], wait : Boolean = false , directory : File = null ): Unit = {
154
- val pb = new ProcessBuilder (prepareCommand(cmd): _* ).inheritIO()
155
- if (directory != null ) pb.directory(directory)
156
+ def runProcess (cmd : Seq [String ], wait : Boolean = false , directory : Option [File ] = None , outputCallback : Option [BufferedReader => Unit ] = None ): Unit = {
157
+ val pb = new ProcessBuilder (prepareCommand(cmd): _* )
158
+
159
+ directory match {
160
+ case Some (dir) =>
161
+ pb.directory(dir)
162
+ case None =>
163
+ }
164
+
165
+ pb.redirectInput(Redirect .INHERIT )
166
+ .redirectError(Redirect .INHERIT )
167
+ .redirectOutput(
168
+ outputCallback match {
169
+ case Some (_) =>
170
+ Redirect .PIPE
171
+ case None =>
172
+ Redirect .INHERIT
173
+ })
174
+
175
+ val process = pb.start()
176
+ outputCallback match {
177
+ case Some (callback) =>
178
+ callback(new BufferedReader (new InputStreamReader (process.getInputStream)))
179
+ case None =>
180
+ }
156
181
if (wait) {
157
- val exitCode = pb.start() .waitFor()
182
+ val exitCode = process .waitFor()
158
183
if (exitCode != 0 ) {
159
184
val cmdString = cmd.mkString(" " )
160
185
val description = if (directory != null ) s """ in directory " $directory" """ else " "
161
186
throw new MessageOnlyException (s """ Running command " ${cmdString}" ${description} failed. """ )
162
187
}
163
188
}
164
- else
165
- pb.start()
189
+ }
190
+
191
+ /** Install or upgrade Code extension `name`.
192
+ *
193
+ * We start by trying to install or upgrade the extension. If this fails we
194
+ * check if an existing version of the extension exists. If this also fails
195
+ * we throw an exception. This ensures that we're always running the latest
196
+ * version of the extension but that we can still work offline.
197
+ */
198
+ def installCodeExtension (codeCmd : Seq [String ], name : String ): Unit = {
199
+ try {
200
+ runProcess(codeCmd ++ Seq (" --install-extension" , name), wait = true )
201
+ } catch {
202
+ case e : Exception =>
203
+ var alreadyInstalled : Boolean = false
204
+ runProcess(codeCmd ++ Seq (" --list-extensions" ), wait = true , outputCallback = Some ({ br =>
205
+ alreadyInstalled = br.lines.filter(_ == name).findFirst.isPresent
206
+ }))
207
+ if (! alreadyInstalled)
208
+ throw e
209
+ }
166
210
}
167
211
168
212
private val projectConfig = taskKey[Option [ProjectConfig ]](" " )
@@ -270,8 +314,9 @@ object DottyIDEPlugin extends AutoPlugin {
270
314
271
315
runCode := {
272
316
try {
273
- runProcess(codeCommand.value ++ Seq (" --install-extension" , " lampepfl.dotty" ), wait = true )
274
- runProcess(codeCommand.value ++ Seq (" ." ), directory = baseDirectory.value)
317
+ installCodeExtension(codeCommand.value, " lampepfl.dotty" )
318
+
319
+ runProcess(codeCommand.value ++ Seq (" ." ), directory = Some (baseDirectory.value))
275
320
} catch {
276
321
case ioex : IOException if ioex.getMessage.startsWith(""" Cannot run program "code"""" ) =>
277
322
val log = streams.value.log
0 commit comments