From 3f2b15fb199dc24c490ea376fdca7a0036ab7f7a Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 9 Apr 2018 07:37:32 +0200 Subject: [PATCH 1/9] Let VSCode start the language server We used to start the language server with `launchIDE`. However, this means that the language server is dependent of sbt being running and configured, which is not a good user experience for newcomers. For newcomers, we want the language server to start when they create a new Scala file in an empty directory, for instance, which means that no build is already configured. This commits takes us closer to this experience by using `load-plugin` to inject the Dotty plugin. This means that the Dotty plugin can now be loaded even if there's no build configured, which happens if the user opens VSCode in an empty directory, for instance. --- vscode-dotty/package.json | 3 ++- vscode-dotty/src/extension.ts | 49 +++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/vscode-dotty/package.json b/vscode-dotty/package.json index e0a361a350af..1705547a8dfc 100644 --- a/vscode-dotty/package.json +++ b/vscode-dotty/package.json @@ -24,6 +24,7 @@ ], "main": "./out/src/extension", "activationEvents": [ + "onLanguage:scala", "workspaceContains:.dotty-ide.json" ], "languages": [ @@ -50,7 +51,7 @@ "vscode:prepublish": "npm install && ./node_modules/.bin/tsc -p ./", "compile": "./node_modules/.bin/tsc -p ./", "test": "node ./node_modules/vscode/bin/test", - "postinstall": "node ./node_modules/vscode/bin/install && curl -L -o out/coursier https://github.com/coursier/coursier/raw/v1.0.3/coursier" + "postinstall": "node ./node_modules/vscode/bin/install && curl -L -o out/coursier https://github.com/coursier/coursier/raw/v1.0.3/coursier && curl -L -o out/load-plugin.jar https://github.com/scalacenter/load-plugin/releases/download/v0.1.0/load-plugin_2.12-0.1.0.jar" }, "extensionDependencies": [ "daltonjorge.scala" diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index a117194c3f44..ca9323371287 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -17,12 +17,9 @@ export function activate(context: ExtensionContext) { outputChannel = vscode.window.createOutputChannel('Dotty Language Client'); const artifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` + const defaultArtifact = "ch.epfl.lamp:dotty-language-server_0.8:0.8.0-bin-SNAPSHOT" fs.readFile(artifactFile, (err, data) => { - if (err) { - outputChannel.append(`Unable to parse ${artifactFile}`) - throw err - } - const artifact = data.toString().trim() + const artifact = err ? defaultArtifact : data.toString().trim() if (process.env['DLS_DEV_MODE']) { const portFile = `${vscode.workspace.rootPath}/.dotty-ide-dev-port` @@ -77,15 +74,51 @@ function fetchAndRun(artifact: string) { throw new Error(msg) } - run({ - command: "java", - args: ["-classpath", classPath, "dotty.tools.languageserver.Main", "-stdio"] + configureIDE().then((res) => { + run({ + command: "java", + args: ["-classpath", classPath, "dotty.tools.languageserver.Main", "-stdio"] + }) }) }) return coursierPromise }) } +function configureIDE() { + const coursierPath = path.join(extensionContext.extensionPath, './out/coursier'); + const loadPluginPath = path.join(extensionContext.extensionPath, './out/load-plugin.jar'); + + return vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: 'Configuring IDE...' + }, (progress) => { + + const sbtPromise = + cpp.spawn("java", [ + "-jar", coursierPath, + "launch", + "org.scala-sbt:sbt-launch:1.1.2", "--", + "apply -cp " + loadPluginPath + " ch.epfl.scala.loadplugin.LoadPlugin", + "set every scalaVersion := \"0.8.0-bin-SNAPSHOT\"", + "load-plugin ch.epfl.lamp:sbt-dotty:0.2.0-SNAPSHOT dotty.tools.sbtplugin.DottyPlugin", + "load-plugin ch.epfl.lamp:sbt-dotty:0.2.0-SNAPSHOT dotty.tools.sbtplugin.DottyIDEPlugin", + "configureIDE" + ]) + const sbtProc = sbtPromise.childProcess + + sbtProc.on('close', (code: number) => { + if (code != 0) { + let msg = "Configuring the IDE failed." + outputChannel.append(msg) + throw new Error(msg) + } + }) + + return sbtPromise; + }) +} + function run(serverOptions: ServerOptions) { const clientOptions: LanguageClientOptions = { documentSelector: [ From dd1c52d9beeb7da51dae204ed4a65197a4be7aa7 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 9 Apr 2018 11:07:35 +0200 Subject: [PATCH 2/9] Update `load-plugin` Use `if-absent` so that we don't load the plugins and apply transformations to the build if this is not necessary. --- vscode-dotty/package.json | 2 +- vscode-dotty/src/extension.ts | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/vscode-dotty/package.json b/vscode-dotty/package.json index 1705547a8dfc..f4ea0f3d1ce2 100644 --- a/vscode-dotty/package.json +++ b/vscode-dotty/package.json @@ -51,7 +51,7 @@ "vscode:prepublish": "npm install && ./node_modules/.bin/tsc -p ./", "compile": "./node_modules/.bin/tsc -p ./", "test": "node ./node_modules/vscode/bin/test", - "postinstall": "node ./node_modules/vscode/bin/install && curl -L -o out/coursier https://github.com/coursier/coursier/raw/v1.0.3/coursier && curl -L -o out/load-plugin.jar https://github.com/scalacenter/load-plugin/releases/download/v0.1.0/load-plugin_2.12-0.1.0.jar" + "postinstall": "node ./node_modules/vscode/bin/install && curl -L -o out/coursier https://github.com/coursier/coursier/raw/v1.0.3/coursier && curl -L -o out/load-plugin.jar https://oss.sonatype.org/content/repositories/releases/ch/epfl/scala/load-plugin_2.12/0.1.0+2-496ac670/load-plugin_2.12-0.1.0+2-496ac670.jar" }, "extensionDependencies": [ "daltonjorge.scala" diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index ca9323371287..cb4815ccdd42 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -94,15 +94,21 @@ function configureIDE() { title: 'Configuring IDE...' }, (progress) => { + const applyLoadPlugin = "apply -cp " + loadPluginPath + " ch.epfl.scala.loadplugin.LoadPlugin" + const ifAbsentCommands = [ + "if-absent dotty.tools.sbtplugin.DottyPlugin", + "\"set every scalaVersion := \\\"0.8.0-bin-SNAPSHOT\\\"\"", + "\"load-plugin ch.epfl.lamp:sbt-dotty:0.2.0-SNAPSHOT dotty.tools.sbtplugin.DottyPlugin\"", + "\"load-plugin ch.epfl.lamp:sbt-dotty:0.2.0-SNAPSHOT dotty.tools.sbtplugin.DottyIDEPlugin\"" + ].join(" ") + const sbtPromise = cpp.spawn("java", [ "-jar", coursierPath, "launch", "org.scala-sbt:sbt-launch:1.1.2", "--", - "apply -cp " + loadPluginPath + " ch.epfl.scala.loadplugin.LoadPlugin", - "set every scalaVersion := \"0.8.0-bin-SNAPSHOT\"", - "load-plugin ch.epfl.lamp:sbt-dotty:0.2.0-SNAPSHOT dotty.tools.sbtplugin.DottyPlugin", - "load-plugin ch.epfl.lamp:sbt-dotty:0.2.0-SNAPSHOT dotty.tools.sbtplugin.DottyIDEPlugin", + applyLoadPlugin, + ifAbsentCommands, "configureIDE" ]) const sbtProc = sbtPromise.childProcess From ed6d567c224256add1eb304ef238ba8ab6f7cd7d Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 11 May 2018 19:51:41 +0200 Subject: [PATCH 3/9] Retrieve `load-plugin` with coursier And retrieve everything (sbt, dotty-language-server, load-plugin) in parallel. --- vscode-dotty/package.json | 2 +- vscode-dotty/src/extension.ts | 120 ++++++++++++++++++---------------- 2 files changed, 65 insertions(+), 57 deletions(-) diff --git a/vscode-dotty/package.json b/vscode-dotty/package.json index f4ea0f3d1ce2..3b9e55110114 100644 --- a/vscode-dotty/package.json +++ b/vscode-dotty/package.json @@ -51,7 +51,7 @@ "vscode:prepublish": "npm install && ./node_modules/.bin/tsc -p ./", "compile": "./node_modules/.bin/tsc -p ./", "test": "node ./node_modules/vscode/bin/test", - "postinstall": "node ./node_modules/vscode/bin/install && curl -L -o out/coursier https://github.com/coursier/coursier/raw/v1.0.3/coursier && curl -L -o out/load-plugin.jar https://oss.sonatype.org/content/repositories/releases/ch/epfl/scala/load-plugin_2.12/0.1.0+2-496ac670/load-plugin_2.12-0.1.0+2-496ac670.jar" + "postinstall": "node ./node_modules/vscode/bin/install && curl -L -o out/coursier https://github.com/coursier/coursier/raw/v1.0.3/coursier" }, "extensionDependencies": [ "daltonjorge.scala" diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index cb4815ccdd42..698d023ed1fb 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -16,10 +16,12 @@ export function activate(context: ExtensionContext) { extensionContext = context outputChannel = vscode.window.createOutputChannel('Dotty Language Client'); - const artifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` - const defaultArtifact = "ch.epfl.lamp:dotty-language-server_0.8:0.8.0-bin-SNAPSHOT" - fs.readFile(artifactFile, (err, data) => { - const artifact = err ? defaultArtifact : data.toString().trim() + const sbtArtifact = "org.scala-sbt:sbt-launch:1.1.4" + const languageServerArtifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` + const languageServerDefaultArtifact = "ch.epfl.lamp:dotty-language-server_0.8:0.8.0-RC1" + const loadPluginArtifact = "ch.epfl.scala:load-plugin_2.12:0.1.0+2-496ac670" + fs.readFile(languageServerArtifactFile, (err, data) => { + const languageServerArtifact = err ? languageServerDefaultArtifact : data.toString().trim() if (process.env['DLS_DEV_MODE']) { const portFile = `${vscode.workspace.rootPath}/.dotty-ide-dev-port` @@ -35,93 +37,99 @@ export function activate(context: ExtensionContext) { }) }) } else { - fetchAndRun(artifact) + fetchAndRun(sbtArtifact, languageServerArtifact, loadPluginArtifact) } }) } -function fetchAndRun(artifact: string) { +function fetchAndRun(sbtArtifact: string, languageServerArtifact: string, loadPluginArtifact: string) { const coursierPath = path.join(extensionContext.extensionPath, './out/coursier'); - vscode.window.withProgress({ - location: vscode.ProgressLocation.Window, - title: 'Fetching the Dotty Language Server' - }, (progress) => { + const sbtPromise = fetchWithCoursier(coursierPath, sbtArtifact) + const languageServerPromise = fetchWithCoursier(coursierPath, languageServerArtifact) + const loadPluginPromise = fetchWithCoursier(coursierPath, loadPluginArtifact) + + Promise.all([sbtPromise, languageServerPromise, loadPluginPromise]).then((results) => { + const [sbtClasspath, languageServerClasspath, loadPluginJar] = results + return configureIDE(sbtClasspath, languageServerClasspath, loadPluginJar) + }).then((languageServerClasspath) => { + run({ + command: "java", + args: ["-classpath", languageServerClasspath, "dotty.tools.languageserver.Main", "-stdio"] + }) + }) - const coursierPromise = - cpp.spawn("java", [ +} + +function fetchWithCoursier(coursierPath: string, artifact: string, extra: string[] = []) { + return vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: `Fetching ${ artifact }` + }, (progress) => { + const args = [ "-jar", coursierPath, "fetch", "-p", artifact - ]) - const coursierProc = coursierPromise.childProcess + ].concat(extra) - let classPath = "" + const coursierPromise = cpp.spawn("java", args) + const coursierProc = coursierPromise.childProcess - coursierProc.stdout.on('data', (data: Buffer) => { - classPath += data.toString().trim() - }) - coursierProc.stderr.on('data', (data: Buffer) => { - let msg = data.toString() - outputChannel.append(msg) - }) + let classPath = "" - coursierProc.on('close', (code: number) => { - if (code != 0) { - let msg = "Fetching the language server failed." + coursierProc.stdout.on('data', (data: Buffer) => { + classPath += data.toString().trim() + }) + coursierProc.stderr.on('data', (data: Buffer) => { + let msg = data.toString() outputChannel.append(msg) - throw new Error(msg) - } + }) - configureIDE().then((res) => { - run({ - command: "java", - args: ["-classpath", classPath, "dotty.tools.languageserver.Main", "-stdio"] - }) + coursierProc.on('close', (code: number) => { + if (code != 0) { + let msg = `Couldn't fetch '${ artifact }' (exit code ${ code }).` + outputChannel.append(msg) + throw new Error(msg) + } }) + return coursierPromise.then(() => { + return classPath; + }); }) - return coursierPromise - }) } -function configureIDE() { - const coursierPath = path.join(extensionContext.extensionPath, './out/coursier'); - const loadPluginPath = path.join(extensionContext.extensionPath, './out/load-plugin.jar'); - +function configureIDE(sbtClasspath: string, languageServerClasspath: string, loadPluginJar: string) { return vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: 'Configuring IDE...' }, (progress) => { - - const applyLoadPlugin = "apply -cp " + loadPluginPath + " ch.epfl.scala.loadplugin.LoadPlugin" - const ifAbsentCommands = [ - "if-absent dotty.tools.sbtplugin.DottyPlugin", - "\"set every scalaVersion := \\\"0.8.0-bin-SNAPSHOT\\\"\"", - "\"load-plugin ch.epfl.lamp:sbt-dotty:0.2.0-SNAPSHOT dotty.tools.sbtplugin.DottyPlugin\"", - "\"load-plugin ch.epfl.lamp:sbt-dotty:0.2.0-SNAPSHOT dotty.tools.sbtplugin.DottyIDEPlugin\"" - ].join(" ") - + const applyLoadPlugin = `apply -cp ${ loadPluginJar } ch.epfl.scala.loadplugin.LoadPlugin` + const ifAbsentCommands = [ + "if-absent dotty.tools.sbtplugin.DottyPlugin", + "\"set every scalaVersion := \\\"0.8.0-RC1\\\"\"", + "\"load-plugin ch.epfl.lamp:sbt-dotty:0.2.2 dotty.tools.sbtplugin.DottyPlugin\"", + "\"load-plugin ch.epfl.lamp:sbt-dotty:0.2.2 dotty.tools.sbtplugin.DottyIDEPlugin\"" + ].join(" ") const sbtPromise = cpp.spawn("java", [ - "-jar", coursierPath, - "launch", - "org.scala-sbt:sbt-launch:1.1.2", "--", - applyLoadPlugin, - ifAbsentCommands, - "configureIDE" + "-classpath", sbtClasspath, + "xsbt.boot.Boot", + applyLoadPlugin, + ifAbsentCommands, + "configureIDE" ]) - const sbtProc = sbtPromise.childProcess + const sbtProc = sbtPromise.childProcess sbtProc.on('close', (code: number) => { if (code != 0) { - let msg = "Configuring the IDE failed." + const msg = "Configuring the IDE failed." outputChannel.append(msg) throw new Error(msg) } }) - return sbtPromise; + return sbtPromise.then(() => { return languageServerClasspath }); }) } From 03e33c39a2037ac0bd2f6924e260d6a74fbc7463 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 14 May 2018 11:29:58 +0200 Subject: [PATCH 4/9] Remove hardcoded versions from vscode extension --- project/Build.scala | 23 ++++++--- vscode-dotty/src/extension.ts | 95 +++++++++++++++++++++-------------- 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 8ada0ccab0a8..42529617d04d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -53,6 +53,11 @@ object Build { } val dottyNonBootstrappedVersion = dottyVersion + "-nonbootstrapped" + val sbtDottyVersion = { + val base = "0.2.3" + if (isRelease) base else base + "-SNAPSHOT" + } + val agentOptions = List( // "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" // "-agentpath:/home/dark/opt/yjp-2013-build-13072/bin/linux-x86-64/libyjpagent.so" @@ -930,11 +935,7 @@ object Build { lazy val `sbt-dotty` = project.in(file("sbt-dotty")). settings(commonSettings). settings( - version := { - val base = "0.2.3" - if (isRelease) base else base + "-SNAPSHOT" - }, - + version := sbtDottyVersion, // Keep in sync with inject-sbt-dotty.sbt libraryDependencies ++= Seq( Dependencies.`jackson-databind`, @@ -973,10 +974,16 @@ object Build { publishArtifact := false, includeFilter in unmanagedSources := NothingFilter | "*.ts" | "**.json", watchSources in Global ++= (unmanagedSources in Compile).value, - compile in Compile := { + resourceGenerators in Compile += Def.task { + val out = baseDirectory.value / "out" / "default-dotty-ide-config" + IO.writeLines(out, Seq(dottyVersion, sbtDottyVersion)) + Seq(out) + }, + compile in Compile := Def.task { val workingDir = baseDirectory.value - val coursier = workingDir / "out/coursier" + val coursier = workingDir / "out" / "coursier" val packageJson = workingDir / "package.json" + // val _ = (managedResources in Compile).value if (!coursier.exists || packageJson.lastModified > coursier.lastModified) runProcess(Seq("npm", "install"), wait = true, directory = workingDir) val tsc = workingDir / "node_modules" / ".bin" / "tsc" @@ -988,7 +995,7 @@ object Build { runProcess(codeCommand.value ++ Seq("--install-extension", "daltonjorge.scala"), wait = true) sbt.internal.inc.Analysis.Empty - }, + }.dependsOn(managedResources in Compile).value, sbt.Keys.`package`:= { runProcess(Seq("vsce", "package"), wait = true, directory = baseDirectory.value) diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index 698d023ed1fb..e7c21e45c013 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -17,46 +17,67 @@ export function activate(context: ExtensionContext) { outputChannel = vscode.window.createOutputChannel('Dotty Language Client'); const sbtArtifact = "org.scala-sbt:sbt-launch:1.1.4" - const languageServerArtifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` - const languageServerDefaultArtifact = "ch.epfl.lamp:dotty-language-server_0.8:0.8.0-RC1" const loadPluginArtifact = "ch.epfl.scala:load-plugin_2.12:0.1.0+2-496ac670" - fs.readFile(languageServerArtifactFile, (err, data) => { - const languageServerArtifact = err ? languageServerDefaultArtifact : data.toString().trim() - - if (process.env['DLS_DEV_MODE']) { - const portFile = `${vscode.workspace.rootPath}/.dotty-ide-dev-port` - fs.readFile(portFile, (err, port) => { - if (err) { - outputChannel.append(`Unable to parse ${portFile}`) - throw err + const languageServerArtifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` + const languageServerDefaultConfigFile = path.join(extensionContext.extensionPath, './out/default-dotty-ide-config') + const coursierPath = path.join(extensionContext.extensionPath, './out/coursier'); + + if (process.env['DLS_DEV_MODE']) { + const portFile = `${vscode.workspace.rootPath}/.dotty-ide-dev-port` + fs.readFile(portFile, (err, port) => { + if (err) { + outputChannel.append(`Unable to parse ${portFile}`) + throw err + } + + run({ + module: context.asAbsolutePath('out/src/passthrough-server.js'), + args: [ port.toString() ] + }) + }) + + } else { + + // Check whether `.dotty-ide-artifact` exists to know whether the IDE has been already + // configured (via `configureIDE` for instance). If not, start sbt and run `configureIDE`. + if (!fs.existsSync(languageServerArtifactFile)) { + fs.readFile(languageServerDefaultConfigFile, (err, data) => { + if (err) throw err + else { + const [languageServerScalaVersion, sbtDottyVersion] = data.toString().trim().split(/\r?\n/) + fetchAndConfigure(coursierPath, sbtArtifact, languageServerScalaVersion, sbtDottyVersion, loadPluginArtifact).then(() => { + runLanguageServer(coursierPath, languageServerArtifactFile) + }) } + }) + } else { + runLanguageServer(coursierPath, languageServerArtifactFile) + } + } +} +function runLanguageServer(coursierPath: string, languageServerArtifactFile: string) { + fs.readFile(languageServerArtifactFile, (err, data) => { + if (err) throw err + else { + const languageServerArtifact = data.toString().trim() + fetchWithCoursier(coursierPath, languageServerArtifact).then((languageServerClasspath) => { run({ - module: context.asAbsolutePath('out/src/passthrough-server.js'), - args: [ port.toString() ] + command: "java", + args: ["-classpath", languageServerClasspath, "dotty.tools.languageserver.Main", "-stdio"] }) }) - } else { - fetchAndRun(sbtArtifact, languageServerArtifact, loadPluginArtifact) } }) } -function fetchAndRun(sbtArtifact: string, languageServerArtifact: string, loadPluginArtifact: string) { - const coursierPath = path.join(extensionContext.extensionPath, './out/coursier'); - +function fetchAndConfigure(coursierPath: string, sbtArtifact: string, languageServerScalaVersion: string, sbtDottyVersion: string, loadPluginArtifact: string) { const sbtPromise = fetchWithCoursier(coursierPath, sbtArtifact) - const languageServerPromise = fetchWithCoursier(coursierPath, languageServerArtifact) const loadPluginPromise = fetchWithCoursier(coursierPath, loadPluginArtifact) - Promise.all([sbtPromise, languageServerPromise, loadPluginPromise]).then((results) => { - const [sbtClasspath, languageServerClasspath, loadPluginJar] = results - return configureIDE(sbtClasspath, languageServerClasspath, loadPluginJar) - }).then((languageServerClasspath) => { - run({ - command: "java", - args: ["-classpath", languageServerClasspath, "dotty.tools.languageserver.Main", "-stdio"] - }) + return Promise.all([sbtPromise, loadPluginPromise]).then((results) => { + const [sbtClasspath, loadPluginJar] = results + return configureIDE(sbtClasspath, languageServerScalaVersion, sbtDottyVersion, loadPluginJar) }) } @@ -72,7 +93,6 @@ function fetchWithCoursier(coursierPath: string, artifact: string, extra: string "-p", artifact ].concat(extra) - const coursierPromise = cpp.spawn("java", args) const coursierProc = coursierPromise.childProcess @@ -82,7 +102,7 @@ function fetchWithCoursier(coursierPath: string, artifact: string, extra: string classPath += data.toString().trim() }) coursierProc.stderr.on('data', (data: Buffer) => { - let msg = data.toString() + let msg = data.toString().trim() outputChannel.append(msg) }) @@ -93,13 +113,11 @@ function fetchWithCoursier(coursierPath: string, artifact: string, extra: string throw new Error(msg) } }) - return coursierPromise.then(() => { - return classPath; - }); + return coursierPromise.then(() => { return classPath }) }) } -function configureIDE(sbtClasspath: string, languageServerClasspath: string, loadPluginJar: string) { +function configureIDE(sbtClasspath: string, languageServerScalaVersion: string, sbtDottyVersion: string, loadPluginJar: string) { return vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: 'Configuring IDE...' @@ -107,10 +125,13 @@ function configureIDE(sbtClasspath: string, languageServerClasspath: string, loa const applyLoadPlugin = `apply -cp ${ loadPluginJar } ch.epfl.scala.loadplugin.LoadPlugin` const ifAbsentCommands = [ "if-absent dotty.tools.sbtplugin.DottyPlugin", - "\"set every scalaVersion := \\\"0.8.0-RC1\\\"\"", - "\"load-plugin ch.epfl.lamp:sbt-dotty:0.2.2 dotty.tools.sbtplugin.DottyPlugin\"", - "\"load-plugin ch.epfl.lamp:sbt-dotty:0.2.2 dotty.tools.sbtplugin.DottyIDEPlugin\"" + "\"set every scalaVersion := \\\"" + languageServerScalaVersion + "\\\"\"", + "\"load-plugin ch.epfl.lamp:sbt-dotty:" + sbtDottyVersion + " dotty.tools.sbtplugin.DottyPlugin\"", + "\"load-plugin ch.epfl.lamp:sbt-dotty:" + sbtDottyVersion + " dotty.tools.sbtplugin.DottyIDEPlugin\"" ].join(" ") + + // Run sbt to configure the IDE. If the `DottyPlugin` is not present, dynamically load it and + // eventually run `configureIDE`. const sbtPromise = cpp.spawn("java", [ "-classpath", sbtClasspath, @@ -129,7 +150,7 @@ function configureIDE(sbtClasspath: string, languageServerClasspath: string, loa } }) - return sbtPromise.then(() => { return languageServerClasspath }); + return sbtPromise }) } From 45e37b8accca58328f6019857d37dd07e5dce3c5 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 11 Jun 2018 13:36:47 +0200 Subject: [PATCH 5/9] Update minimal sbt version --- vscode-dotty/src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index e7c21e45c013..28a692610e81 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -16,7 +16,7 @@ export function activate(context: ExtensionContext) { extensionContext = context outputChannel = vscode.window.createOutputChannel('Dotty Language Client'); - const sbtArtifact = "org.scala-sbt:sbt-launch:1.1.4" + const sbtArtifact = "org.scala-sbt:sbt-launch:1.1.5" const loadPluginArtifact = "ch.epfl.scala:load-plugin_2.12:0.1.0+2-496ac670" const languageServerArtifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` const languageServerDefaultConfigFile = path.join(extensionContext.extensionPath, './out/default-dotty-ide-config') From 993ef22e5f8d0f0aa1081aef350096266a496b09 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 11 Jun 2018 13:36:57 +0200 Subject: [PATCH 6/9] Use `load-plugin` only when there's no build.sbt --- vscode-dotty/src/extension.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index 28a692610e81..337c6d7adf64 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -18,6 +18,7 @@ export function activate(context: ExtensionContext) { const sbtArtifact = "org.scala-sbt:sbt-launch:1.1.5" const loadPluginArtifact = "ch.epfl.scala:load-plugin_2.12:0.1.0+2-496ac670" + const buildSbtFile = `${vscode.workspace.rootPath}/build.sbt` const languageServerArtifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` const languageServerDefaultConfigFile = path.join(extensionContext.extensionPath, './out/default-dotty-ide-config') const coursierPath = path.join(extensionContext.extensionPath, './out/coursier'); @@ -37,21 +38,20 @@ export function activate(context: ExtensionContext) { }) } else { - - // Check whether `.dotty-ide-artifact` exists to know whether the IDE has been already - // configured (via `configureIDE` for instance). If not, start sbt and run `configureIDE`. - if (!fs.existsSync(languageServerArtifactFile)) { + // Check whether `.dotty-ide-artifact` exists. If it does, start the language server, + // otherwise, try to auto-configure it if there's no build.sbt + if (fs.existsSync(languageServerArtifactFile)) { + runLanguageServer(coursierPath, languageServerArtifactFile) + } else if (!fs.existsSync(buildSbtFile)) { fs.readFile(languageServerDefaultConfigFile, (err, data) => { if (err) throw err else { const [languageServerScalaVersion, sbtDottyVersion] = data.toString().trim().split(/\r?\n/) - fetchAndConfigure(coursierPath, sbtArtifact, languageServerScalaVersion, sbtDottyVersion, loadPluginArtifact).then(() => { - runLanguageServer(coursierPath, languageServerArtifactFile) - }) + fetchAndConfigure(coursierPath, sbtArtifact, languageServerScalaVersion, sbtDottyVersion, loadPluginArtifact).then(() => { + runLanguageServer(coursierPath, languageServerArtifactFile) + }) } }) - } else { - runLanguageServer(coursierPath, languageServerArtifactFile) } } } From 76a52c9fd43db4aad7b017d34c02ec4e49ea5cc0 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 11 Jun 2018 14:24:10 +0200 Subject: [PATCH 7/9] Show message to start the IDE When the extension detects that this is an un-configured project (no `.dotty-ide-artifact`, no `build.sbt`), it shows a message asking the user whether she wants the IDE to be started. --- vscode-dotty/src/extension.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index 337c6d7adf64..50ef743596ca 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -39,16 +39,23 @@ export function activate(context: ExtensionContext) { } else { // Check whether `.dotty-ide-artifact` exists. If it does, start the language server, - // otherwise, try to auto-configure it if there's no build.sbt + // otherwise, try propose to start it if there's no build.sbt if (fs.existsSync(languageServerArtifactFile)) { runLanguageServer(coursierPath, languageServerArtifactFile) } else if (!fs.existsSync(buildSbtFile)) { - fs.readFile(languageServerDefaultConfigFile, (err, data) => { - if (err) throw err - else { - const [languageServerScalaVersion, sbtDottyVersion] = data.toString().trim().split(/\r?\n/) - fetchAndConfigure(coursierPath, sbtArtifact, languageServerScalaVersion, sbtDottyVersion, loadPluginArtifact).then(() => { - runLanguageServer(coursierPath, languageServerArtifactFile) + vscode.window.showInformationMessage( + "This looks like an unconfigured project. Would you like to start Dotty IDE?", + "Yes", "No" + ).then(choice => { + if (choice == "Yes") { + fs.readFile(languageServerDefaultConfigFile, (err, data) => { + if (err) throw err + else { + const [languageServerScalaVersion, sbtDottyVersion] = data.toString().trim().split(/\r?\n/) + fetchAndConfigure(coursierPath, sbtArtifact, languageServerScalaVersion, sbtDottyVersion, loadPluginArtifact).then(() => { + runLanguageServer(coursierPath, languageServerArtifactFile) + }) + } }) } }) From aadb30f45e2d4918275c55f28a2a73b87e63a3e6 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 17 Aug 2018 08:24:30 +0200 Subject: [PATCH 8/9] Replace `load-plugin` with `--addPluginSbtFile` --- project/Build.scala | 10 ++++++--- vscode-dotty/src/extension.ts | 40 +++++++++++------------------------ 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 42529617d04d..39afc4df6b04 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -53,6 +53,7 @@ object Build { } val dottyNonBootstrappedVersion = dottyVersion + "-nonbootstrapped" + val sbtDottyName = "sbt-dotty" val sbtDottyVersion = { val base = "0.2.3" if (isRelease) base else base + "-SNAPSHOT" @@ -935,6 +936,7 @@ object Build { lazy val `sbt-dotty` = project.in(file("sbt-dotty")). settings(commonSettings). settings( + name := sbtDottyName, version := sbtDottyVersion, // Keep in sync with inject-sbt-dotty.sbt libraryDependencies ++= Seq( @@ -975,9 +977,11 @@ object Build { includeFilter in unmanagedSources := NothingFilter | "*.ts" | "**.json", watchSources in Global ++= (unmanagedSources in Compile).value, resourceGenerators in Compile += Def.task { - val out = baseDirectory.value / "out" / "default-dotty-ide-config" - IO.writeLines(out, Seq(dottyVersion, sbtDottyVersion)) - Seq(out) + val defaultIDEConfig = baseDirectory.value / "out" / "default-dotty-ide-config" + IO.write(defaultIDEConfig, dottyVersion) + val dottyPluginSbtFile = baseDirectory.value / "out" / "dotty-plugin.sbt" + IO.write(dottyPluginSbtFile, s"""addSbtPlugin("$dottyOrganization" % $sbtDottyName % "$sbtDottyVersion")""") + Seq(defaultIDEConfig, dottyPluginSbtFile) }, compile in Compile := Def.task { val workingDir = baseDirectory.value diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index 50ef743596ca..744323928057 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -16,9 +16,9 @@ export function activate(context: ExtensionContext) { extensionContext = context outputChannel = vscode.window.createOutputChannel('Dotty Language Client'); - const sbtArtifact = "org.scala-sbt:sbt-launch:1.1.5" - const loadPluginArtifact = "ch.epfl.scala:load-plugin_2.12:0.1.0+2-496ac670" + const sbtArtifact = "org.scala-sbt:sbt-launch:1.2.0" const buildSbtFile = `${vscode.workspace.rootPath}/build.sbt` + const dottyPluginSbtFile = path.join(extensionContext.extensionPath, './out/dotty-plugin.sbt') const languageServerArtifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` const languageServerDefaultConfigFile = path.join(extensionContext.extensionPath, './out/default-dotty-ide-config') const coursierPath = path.join(extensionContext.extensionPath, './out/coursier'); @@ -51,8 +51,8 @@ export function activate(context: ExtensionContext) { fs.readFile(languageServerDefaultConfigFile, (err, data) => { if (err) throw err else { - const [languageServerScalaVersion, sbtDottyVersion] = data.toString().trim().split(/\r?\n/) - fetchAndConfigure(coursierPath, sbtArtifact, languageServerScalaVersion, sbtDottyVersion, loadPluginArtifact).then(() => { + const languageServerScalaVersion = data.toString().trim() + fetchAndConfigure(coursierPath, sbtArtifact, languageServerScalaVersion, dottyPluginSbtFile).then(() => { runLanguageServer(coursierPath, languageServerArtifactFile) }) } @@ -78,15 +78,10 @@ function runLanguageServer(coursierPath: string, languageServerArtifactFile: str }) } -function fetchAndConfigure(coursierPath: string, sbtArtifact: string, languageServerScalaVersion: string, sbtDottyVersion: string, loadPluginArtifact: string) { - const sbtPromise = fetchWithCoursier(coursierPath, sbtArtifact) - const loadPluginPromise = fetchWithCoursier(coursierPath, loadPluginArtifact) - - return Promise.all([sbtPromise, loadPluginPromise]).then((results) => { - const [sbtClasspath, loadPluginJar] = results - return configureIDE(sbtClasspath, languageServerScalaVersion, sbtDottyVersion, loadPluginJar) - }) - +function fetchAndConfigure(coursierPath: string, sbtArtifact: string, languageServerScalaVersion: string, dottyPluginSbtFile: string) { + return fetchWithCoursier(coursierPath, sbtArtifact).then((sbtClasspath) => { + return configureIDE(sbtClasspath, languageServerScalaVersion, dottyPluginSbtFile) + }) } function fetchWithCoursier(coursierPath: string, artifact: string, extra: string[] = []) { @@ -108,10 +103,6 @@ function fetchWithCoursier(coursierPath: string, artifact: string, extra: string coursierProc.stdout.on('data', (data: Buffer) => { classPath += data.toString().trim() }) - coursierProc.stderr.on('data', (data: Buffer) => { - let msg = data.toString().trim() - outputChannel.append(msg) - }) coursierProc.on('close', (code: number) => { if (code != 0) { @@ -124,18 +115,11 @@ function fetchWithCoursier(coursierPath: string, artifact: string, extra: string }) } -function configureIDE(sbtClasspath: string, languageServerScalaVersion: string, sbtDottyVersion: string, loadPluginJar: string) { +function configureIDE(sbtClasspath: string, languageServerScalaVersion: string, dottyPluginSbtFile: string) { return vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: 'Configuring IDE...' }, (progress) => { - const applyLoadPlugin = `apply -cp ${ loadPluginJar } ch.epfl.scala.loadplugin.LoadPlugin` - const ifAbsentCommands = [ - "if-absent dotty.tools.sbtplugin.DottyPlugin", - "\"set every scalaVersion := \\\"" + languageServerScalaVersion + "\\\"\"", - "\"load-plugin ch.epfl.lamp:sbt-dotty:" + sbtDottyVersion + " dotty.tools.sbtplugin.DottyPlugin\"", - "\"load-plugin ch.epfl.lamp:sbt-dotty:" + sbtDottyVersion + " dotty.tools.sbtplugin.DottyIDEPlugin\"" - ].join(" ") // Run sbt to configure the IDE. If the `DottyPlugin` is not present, dynamically load it and // eventually run `configureIDE`. @@ -143,8 +127,8 @@ function configureIDE(sbtClasspath: string, languageServerScalaVersion: string, cpp.spawn("java", [ "-classpath", sbtClasspath, "xsbt.boot.Boot", - applyLoadPlugin, - ifAbsentCommands, + `--addPluginSbtFile=${dottyPluginSbtFile}`, + `set every scalaVersion := "${languageServerScalaVersion}"`, "configureIDE" ]) @@ -157,7 +141,7 @@ function configureIDE(sbtClasspath: string, languageServerScalaVersion: string, } }) - return sbtPromise + return sbtPromise }) } From 8a48286b23a56c1f7cbcf6567e5f9dcddcbd9b67 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 14 Sep 2018 17:41:06 +0200 Subject: [PATCH 9/9] Address review comments --- project/Build.scala | 3 +-- vscode-dotty/src/extension.ts | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 39afc4df6b04..a8368657d21e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -980,14 +980,13 @@ object Build { val defaultIDEConfig = baseDirectory.value / "out" / "default-dotty-ide-config" IO.write(defaultIDEConfig, dottyVersion) val dottyPluginSbtFile = baseDirectory.value / "out" / "dotty-plugin.sbt" - IO.write(dottyPluginSbtFile, s"""addSbtPlugin("$dottyOrganization" % $sbtDottyName % "$sbtDottyVersion")""") + IO.write(dottyPluginSbtFile, s"""addSbtPlugin("$dottyOrganization" % "$sbtDottyName" % "$sbtDottyVersion")""") Seq(defaultIDEConfig, dottyPluginSbtFile) }, compile in Compile := Def.task { val workingDir = baseDirectory.value val coursier = workingDir / "out" / "coursier" val packageJson = workingDir / "package.json" - // val _ = (managedResources in Compile).value if (!coursier.exists || packageJson.lastModified > coursier.lastModified) runProcess(Seq("npm", "install"), wait = true, directory = workingDir) val tsc = workingDir / "node_modules" / ".bin" / "tsc" diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index 744323928057..3ce21b10fcdc 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -16,9 +16,10 @@ export function activate(context: ExtensionContext) { extensionContext = context outputChannel = vscode.window.createOutputChannel('Dotty Language Client'); - const sbtArtifact = "org.scala-sbt:sbt-launch:1.2.0" + const sbtArtifact = "org.scala-sbt:sbt-launch:1.2.3" const buildSbtFile = `${vscode.workspace.rootPath}/build.sbt` const dottyPluginSbtFile = path.join(extensionContext.extensionPath, './out/dotty-plugin.sbt') + const disableDottyIDEFile = `${vscode.workspace.rootPath}/.dotty-ide-disabled` const languageServerArtifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` const languageServerDefaultConfigFile = path.join(extensionContext.extensionPath, './out/default-dotty-ide-config') const coursierPath = path.join(extensionContext.extensionPath, './out/coursier'); @@ -42,9 +43,9 @@ export function activate(context: ExtensionContext) { // otherwise, try propose to start it if there's no build.sbt if (fs.existsSync(languageServerArtifactFile)) { runLanguageServer(coursierPath, languageServerArtifactFile) - } else if (!fs.existsSync(buildSbtFile)) { + } else if (!fs.existsSync(disableDottyIDEFile) && !fs.existsSync(buildSbtFile)) { vscode.window.showInformationMessage( - "This looks like an unconfigured project. Would you like to start Dotty IDE?", + "This looks like an unconfigured Scala project. Would you like to start the Dotty IDE?", "Yes", "No" ).then(choice => { if (choice == "Yes") { @@ -57,6 +58,8 @@ export function activate(context: ExtensionContext) { }) } }) + } else { + fs.appendFile(disableDottyIDEFile, "", _ => {}) } }) } @@ -118,7 +121,7 @@ function fetchWithCoursier(coursierPath: string, artifact: string, extra: string function configureIDE(sbtClasspath: string, languageServerScalaVersion: string, dottyPluginSbtFile: string) { return vscode.window.withProgress({ location: vscode.ProgressLocation.Window, - title: 'Configuring IDE...' + title: 'Configuring the IDE for Dotty...' }, (progress) => { // Run sbt to configure the IDE. If the `DottyPlugin` is not present, dynamically load it and