@@ -5,6 +5,7 @@ import sbt.Def.Initialize
55import sbt .Keys ._
66import java .io ._
77import java .lang .ProcessBuilder
8+ import java .lang .ProcessBuilder .Redirect
89import scala .collection .mutable
910import scala .util .Properties .{ isWin , isMac }
1011
@@ -146,23 +147,66 @@ object DottyIDEPlugin extends AutoPlugin {
146147 if (isWin) Seq (" cmd.exe" , " /C" ) ++ cmd
147148 else cmd
148149
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.
152155 */
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+ }
156181 if (wait) {
157- val exitCode = pb.start() .waitFor()
182+ val exitCode = process .waitFor()
158183 if (exitCode != 0 ) {
159184 val cmdString = cmd.mkString(" " )
160185 val description = if (directory != null ) s """ in directory " $directory" """ else " "
161186 throw new MessageOnlyException (s """ Running command " ${cmdString}" ${description} failed. """ )
162187 }
163188 }
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+ }
166210 }
167211
168212 private val projectConfig = taskKey[Option [ProjectConfig ]](" " )
@@ -270,8 +314,9 @@ object DottyIDEPlugin extends AutoPlugin {
270314
271315 runCode := {
272316 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))
275320 } catch {
276321 case ioex : IOException if ioex.getMessage.startsWith(""" Cannot run program "code"""" ) =>
277322 val log = streams.value.log
0 commit comments