Skip to content

Commit 0a2734b

Browse files
authored
Merge pull request #4304 from dotty-staging/topic/start-lsp-from-vscode
Let VSCode start the language server
2 parents fb655f7 + 8a48286 commit 0a2734b

File tree

3 files changed

+125
-52
lines changed

3 files changed

+125
-52
lines changed

project/Build.scala

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ object Build {
5454
}
5555
val dottyNonBootstrappedVersion = dottyVersion + "-nonbootstrapped"
5656

57+
val sbtDottyName = "sbt-dotty"
58+
val sbtDottyVersion = {
59+
val base = "0.2.3"
60+
if (isRelease) base else base + "-SNAPSHOT"
61+
}
62+
5763
val agentOptions = List(
5864
// "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"
5965
// "-agentpath:/home/dark/opt/yjp-2013-build-13072/bin/linux-x86-64/libyjpagent.so"
@@ -931,11 +937,8 @@ object Build {
931937
enablePlugins(SbtPlugin).
932938
settings(commonSettings).
933939
settings(
934-
version := {
935-
val base = "0.2.3"
936-
if (isRelease) base else base + "-SNAPSHOT"
937-
},
938-
940+
name := sbtDottyName,
941+
version := sbtDottyVersion,
939942
// Keep in sync with inject-sbt-dotty.sbt
940943
libraryDependencies ++= Seq(
941944
Dependencies.`jackson-databind`,
@@ -973,9 +976,16 @@ object Build {
973976
publishArtifact := false,
974977
includeFilter in unmanagedSources := NothingFilter | "*.ts" | "**.json",
975978
watchSources in Global ++= (unmanagedSources in Compile).value,
976-
compile in Compile := {
979+
resourceGenerators in Compile += Def.task {
980+
val defaultIDEConfig = baseDirectory.value / "out" / "default-dotty-ide-config"
981+
IO.write(defaultIDEConfig, dottyVersion)
982+
val dottyPluginSbtFile = baseDirectory.value / "out" / "dotty-plugin.sbt"
983+
IO.write(dottyPluginSbtFile, s"""addSbtPlugin("$dottyOrganization" % "$sbtDottyName" % "$sbtDottyVersion")""")
984+
Seq(defaultIDEConfig, dottyPluginSbtFile)
985+
},
986+
compile in Compile := Def.task {
977987
val workingDir = baseDirectory.value
978-
val coursier = workingDir / "out/coursier"
988+
val coursier = workingDir / "out" / "coursier"
979989
val packageJson = workingDir / "package.json"
980990
if (!coursier.exists || packageJson.lastModified > coursier.lastModified)
981991
runProcess(Seq("npm", "install"), wait = true, directory = workingDir)
@@ -988,7 +998,7 @@ object Build {
988998
runProcess(codeCommand.value ++ Seq("--install-extension", "daltonjorge.scala"), wait = true)
989999

9901000
sbt.internal.inc.Analysis.Empty
991-
},
1001+
}.dependsOn(managedResources in Compile).value,
9921002
sbt.Keys.`package`:= {
9931003
runProcess(Seq("vsce", "package"), wait = true, directory = baseDirectory.value)
9941004

vscode-dotty/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
],
2525
"main": "./out/src/extension",
2626
"activationEvents": [
27+
"onLanguage:scala",
2728
"workspaceContains:.dotty-ide.json"
2829
],
2930
"languages": [

vscode-dotty/src/extension.ts

Lines changed: 106 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,73 +16,135 @@ export function activate(context: ExtensionContext) {
1616
extensionContext = context
1717
outputChannel = vscode.window.createOutputChannel('Dotty Language Client');
1818

19-
const artifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact`
20-
fs.readFile(artifactFile, (err, data) => {
21-
if (err) {
22-
outputChannel.append(`Unable to parse ${artifactFile}`)
23-
throw err
24-
}
25-
const artifact = data.toString().trim()
26-
27-
if (process.env['DLS_DEV_MODE']) {
28-
const portFile = `${vscode.workspace.rootPath}/.dotty-ide-dev-port`
29-
fs.readFile(portFile, (err, port) => {
30-
if (err) {
31-
outputChannel.append(`Unable to parse ${portFile}`)
32-
throw err
19+
const sbtArtifact = "org.scala-sbt:sbt-launch:1.2.3"
20+
const buildSbtFile = `${vscode.workspace.rootPath}/build.sbt`
21+
const dottyPluginSbtFile = path.join(extensionContext.extensionPath, './out/dotty-plugin.sbt')
22+
const disableDottyIDEFile = `${vscode.workspace.rootPath}/.dotty-ide-disabled`
23+
const languageServerArtifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact`
24+
const languageServerDefaultConfigFile = path.join(extensionContext.extensionPath, './out/default-dotty-ide-config')
25+
const coursierPath = path.join(extensionContext.extensionPath, './out/coursier');
26+
27+
if (process.env['DLS_DEV_MODE']) {
28+
const portFile = `${vscode.workspace.rootPath}/.dotty-ide-dev-port`
29+
fs.readFile(portFile, (err, port) => {
30+
if (err) {
31+
outputChannel.append(`Unable to parse ${portFile}`)
32+
throw err
33+
}
34+
35+
run({
36+
module: context.asAbsolutePath('out/src/passthrough-server.js'),
37+
args: [ port.toString() ]
38+
})
39+
})
40+
41+
} else {
42+
// Check whether `.dotty-ide-artifact` exists. If it does, start the language server,
43+
// otherwise, try propose to start it if there's no build.sbt
44+
if (fs.existsSync(languageServerArtifactFile)) {
45+
runLanguageServer(coursierPath, languageServerArtifactFile)
46+
} else if (!fs.existsSync(disableDottyIDEFile) && !fs.existsSync(buildSbtFile)) {
47+
vscode.window.showInformationMessage(
48+
"This looks like an unconfigured Scala project. Would you like to start the Dotty IDE?",
49+
"Yes", "No"
50+
).then(choice => {
51+
if (choice == "Yes") {
52+
fs.readFile(languageServerDefaultConfigFile, (err, data) => {
53+
if (err) throw err
54+
else {
55+
const languageServerScalaVersion = data.toString().trim()
56+
fetchAndConfigure(coursierPath, sbtArtifact, languageServerScalaVersion, dottyPluginSbtFile).then(() => {
57+
runLanguageServer(coursierPath, languageServerArtifactFile)
58+
})
59+
}
60+
})
61+
} else {
62+
fs.appendFile(disableDottyIDEFile, "", _ => {})
3363
}
64+
})
65+
}
66+
}
67+
}
3468

69+
function runLanguageServer(coursierPath: string, languageServerArtifactFile: string) {
70+
fs.readFile(languageServerArtifactFile, (err, data) => {
71+
if (err) throw err
72+
else {
73+
const languageServerArtifact = data.toString().trim()
74+
fetchWithCoursier(coursierPath, languageServerArtifact).then((languageServerClasspath) => {
3575
run({
36-
module: context.asAbsolutePath('out/src/passthrough-server.js'),
37-
args: [ port.toString() ]
76+
command: "java",
77+
args: ["-classpath", languageServerClasspath, "dotty.tools.languageserver.Main", "-stdio"]
3878
})
3979
})
40-
} else {
41-
fetchAndRun(artifact)
4280
}
4381
})
4482
}
4583

46-
function fetchAndRun(artifact: string) {
47-
const coursierPath = path.join(extensionContext.extensionPath, './out/coursier');
48-
49-
vscode.window.withProgress({
50-
location: vscode.ProgressLocation.Window,
51-
title: 'Fetching the Dotty Language Server'
52-
}, (progress) => {
84+
function fetchAndConfigure(coursierPath: string, sbtArtifact: string, languageServerScalaVersion: string, dottyPluginSbtFile: string) {
85+
return fetchWithCoursier(coursierPath, sbtArtifact).then((sbtClasspath) => {
86+
return configureIDE(sbtClasspath, languageServerScalaVersion, dottyPluginSbtFile)
87+
})
88+
}
5389

54-
const coursierPromise =
55-
cpp.spawn("java", [
90+
function fetchWithCoursier(coursierPath: string, artifact: string, extra: string[] = []) {
91+
return vscode.window.withProgress({
92+
location: vscode.ProgressLocation.Window,
93+
title: `Fetching ${ artifact }`
94+
}, (progress) => {
95+
const args = [
5696
"-jar", coursierPath,
5797
"fetch",
5898
"-p",
5999
artifact
60-
])
61-
const coursierProc = coursierPromise.childProcess
100+
].concat(extra)
101+
const coursierPromise = cpp.spawn("java", args)
102+
const coursierProc = coursierPromise.childProcess
62103

63-
let classPath = ""
104+
let classPath = ""
64105

65-
coursierProc.stdout.on('data', (data: Buffer) => {
66-
classPath += data.toString().trim()
67-
})
68-
coursierProc.stderr.on('data', (data: Buffer) => {
69-
let msg = data.toString()
70-
outputChannel.append(msg)
106+
coursierProc.stdout.on('data', (data: Buffer) => {
107+
classPath += data.toString().trim()
108+
})
109+
110+
coursierProc.on('close', (code: number) => {
111+
if (code != 0) {
112+
let msg = `Couldn't fetch '${ artifact }' (exit code ${ code }).`
113+
outputChannel.append(msg)
114+
throw new Error(msg)
115+
}
116+
})
117+
return coursierPromise.then(() => { return classPath })
71118
})
119+
}
120+
121+
function configureIDE(sbtClasspath: string, languageServerScalaVersion: string, dottyPluginSbtFile: string) {
122+
return vscode.window.withProgress({
123+
location: vscode.ProgressLocation.Window,
124+
title: 'Configuring the IDE for Dotty...'
125+
}, (progress) => {
126+
127+
// Run sbt to configure the IDE. If the `DottyPlugin` is not present, dynamically load it and
128+
// eventually run `configureIDE`.
129+
const sbtPromise =
130+
cpp.spawn("java", [
131+
"-classpath", sbtClasspath,
132+
"xsbt.boot.Boot",
133+
`--addPluginSbtFile=${dottyPluginSbtFile}`,
134+
`set every scalaVersion := "${languageServerScalaVersion}"`,
135+
"configureIDE"
136+
])
72137

73-
coursierProc.on('close', (code: number) => {
138+
const sbtProc = sbtPromise.childProcess
139+
sbtProc.on('close', (code: number) => {
74140
if (code != 0) {
75-
let msg = "Fetching the language server failed."
141+
const msg = "Configuring the IDE failed."
76142
outputChannel.append(msg)
77143
throw new Error(msg)
78144
}
79-
80-
run({
81-
command: "java",
82-
args: ["-classpath", classPath, "dotty.tools.languageserver.Main", "-stdio"]
83-
})
84145
})
85-
return coursierPromise
146+
147+
return sbtPromise
86148
})
87149
}
88150

0 commit comments

Comments
 (0)