diff --git a/project/Build.scala b/project/Build.scala index 8ada0ccab0a8..a8368657d21e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -53,6 +53,12 @@ object Build { } val dottyNonBootstrappedVersion = dottyVersion + "-nonbootstrapped" + val sbtDottyName = "sbt-dotty" + 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 +936,8 @@ 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" - }, - + name := sbtDottyName, + version := sbtDottyVersion, // Keep in sync with inject-sbt-dotty.sbt libraryDependencies ++= Seq( Dependencies.`jackson-databind`, @@ -973,9 +976,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 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 - val coursier = workingDir / "out/coursier" + val coursier = workingDir / "out" / "coursier" val packageJson = workingDir / "package.json" if (!coursier.exists || packageJson.lastModified > coursier.lastModified) runProcess(Seq("npm", "install"), wait = true, directory = workingDir) @@ -988,7 +998,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/package.json b/vscode-dotty/package.json index e0a361a350af..3b9e55110114 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": [ diff --git a/vscode-dotty/src/extension.ts b/vscode-dotty/src/extension.ts index a117194c3f44..3ce21b10fcdc 100644 --- a/vscode-dotty/src/extension.ts +++ b/vscode-dotty/src/extension.ts @@ -16,73 +16,135 @@ export function activate(context: ExtensionContext) { extensionContext = context outputChannel = vscode.window.createOutputChannel('Dotty Language Client'); - const artifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact` - fs.readFile(artifactFile, (err, data) => { - if (err) { - outputChannel.append(`Unable to parse ${artifactFile}`) - throw err - } - const artifact = 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 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'); + + 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. If it does, start the language server, + // otherwise, try propose to start it if there's no build.sbt + if (fs.existsSync(languageServerArtifactFile)) { + runLanguageServer(coursierPath, languageServerArtifactFile) + } else if (!fs.existsSync(disableDottyIDEFile) && !fs.existsSync(buildSbtFile)) { + vscode.window.showInformationMessage( + "This looks like an unconfigured Scala project. Would you like to start the Dotty IDE?", + "Yes", "No" + ).then(choice => { + if (choice == "Yes") { + fs.readFile(languageServerDefaultConfigFile, (err, data) => { + if (err) throw err + else { + const languageServerScalaVersion = data.toString().trim() + fetchAndConfigure(coursierPath, sbtArtifact, languageServerScalaVersion, dottyPluginSbtFile).then(() => { + runLanguageServer(coursierPath, languageServerArtifactFile) + }) + } + }) + } else { + fs.appendFile(disableDottyIDEFile, "", _ => {}) } + }) + } + } +} +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(artifact) } }) } -function fetchAndRun(artifact: string) { - const coursierPath = path.join(extensionContext.extensionPath, './out/coursier'); - - vscode.window.withProgress({ - location: vscode.ProgressLocation.Window, - title: 'Fetching the Dotty Language Server' - }, (progress) => { +function fetchAndConfigure(coursierPath: string, sbtArtifact: string, languageServerScalaVersion: string, dottyPluginSbtFile: string) { + return fetchWithCoursier(coursierPath, sbtArtifact).then((sbtClasspath) => { + return configureIDE(sbtClasspath, languageServerScalaVersion, dottyPluginSbtFile) + }) +} - 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) + const coursierPromise = cpp.spawn("java", args) + const coursierProc = coursierPromise.childProcess - let classPath = "" + let classPath = "" - coursierProc.stdout.on('data', (data: Buffer) => { - classPath += data.toString().trim() - }) - coursierProc.stderr.on('data', (data: Buffer) => { - let msg = data.toString() - outputChannel.append(msg) + coursierProc.stdout.on('data', (data: Buffer) => { + classPath += data.toString().trim() + }) + + 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 }) }) +} + +function configureIDE(sbtClasspath: string, languageServerScalaVersion: string, dottyPluginSbtFile: string) { + return vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: 'Configuring the IDE for Dotty...' + }, (progress) => { + + // 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, + "xsbt.boot.Boot", + `--addPluginSbtFile=${dottyPluginSbtFile}`, + `set every scalaVersion := "${languageServerScalaVersion}"`, + "configureIDE" + ]) - coursierProc.on('close', (code: number) => { + const sbtProc = sbtPromise.childProcess + sbtProc.on('close', (code: number) => { if (code != 0) { - let msg = "Fetching the language server failed." + const msg = "Configuring the IDE failed." outputChannel.append(msg) throw new Error(msg) } - - run({ - command: "java", - args: ["-classpath", classPath, "dotty.tools.languageserver.Main", "-stdio"] - }) }) - return coursierPromise + + return sbtPromise }) }