@@ -14,7 +14,7 @@ import DottyPlugin.autoImport._
14
14
15
15
object DottyIDEPlugin extends AutoPlugin {
16
16
// Adapted from scala-reflect
17
- private [ this ] def distinctBy [A , B ](xs : Seq [A ])(f : A => B ): Seq [A ] = {
17
+ private def distinctBy [A , B ](xs : Seq [A ])(f : A => B ): Seq [A ] = {
18
18
val buf = new mutable.ListBuffer [A ]
19
19
val seen = mutable.Set [B ]()
20
20
xs foreach { x =>
@@ -27,46 +27,162 @@ object DottyIDEPlugin extends AutoPlugin {
27
27
buf.toList
28
28
}
29
29
30
- private def inAllDottyConfigurations [A ](key : TaskKey [A ], state : State ): Task [Seq [A ]] = {
31
- val struct = Project .structure(state)
32
- val settings = struct.data
33
- struct.allProjectRefs.flatMap { projRef =>
34
- val project = Project .getProjectForReference(projRef, struct).get
30
+ private def isDottyVersion (version : String ) =
31
+ version.startsWith(" 0." )
32
+
33
+
34
+ /** Return a new state derived from `state` such that scalaVersion returns `newScalaVersion` in all
35
+ * projects in `projRefs` (`state` is returned if no setting needed to be updated).
36
+ */
37
+ private def updateScalaVersion (state : State , projRefs : Seq [ProjectRef ], newScalaVersion : String ): State = {
38
+ val extracted = Project .extract(state)
39
+ val settings = extracted.structure.data
40
+
41
+ if (projRefs.forall(projRef => scalaVersion.in(projRef).get(settings).get == newScalaVersion))
42
+ state
43
+ else {
44
+ def matchingSetting (setting : Setting [_]) =
45
+ setting.key.key == scalaVersion.key &&
46
+ setting.key.scope.project.fold(ref => projRefs.contains(ref), ifGlobal = true , ifThis = true )
47
+
48
+ val newSettings = extracted.session.mergeSettings.collect {
49
+ case setting if matchingSetting(setting) =>
50
+ scalaVersion in setting.key.scope := newScalaVersion
51
+ }
52
+ val newSession = extracted.session.appendRaw(newSettings)
53
+ BuiltinCommands .reapply(newSession, extracted.structure, state)
54
+ }
55
+ }
56
+
57
+ /** Setup to run in all dotty projects.
58
+ * Return a triplet of:
59
+ * (1) A version of dotty
60
+ * (2) A list of dotty projects
61
+ * (3) A state where `scalaVersion` is set to (1) in all projects in (2)
62
+ */
63
+ private def dottySetup (state : State ): (String , Seq [ProjectRef ], State ) = {
64
+ val structure = Project .structure(state)
65
+ val settings = structure.data
66
+
67
+ // FIXME: this function uses `sorted` to order versions but this is incorrect,
68
+ // we need an Ordering for version numbers, like the one in Coursier.
69
+
70
+ val (dottyVersions, dottyProjRefs) =
71
+ structure.allProjectRefs.flatMap { projRef =>
72
+ val version = scalaVersion.in(projRef).get(settings).get
73
+ if (isDottyVersion(version))
74
+ Some ((version, projRef))
75
+ else
76
+ crossScalaVersions.in(projRef).get(settings).get.filter(isDottyVersion).sorted.lastOption match {
77
+ case Some (v) =>
78
+ Some ((v, projRef))
79
+ case _ =>
80
+ None
81
+ }
82
+ }.unzip
83
+
84
+ if (dottyVersions.isEmpty)
85
+ throw new MessageOnlyException (" No Dotty project detected" )
86
+ else {
87
+ val dottyVersion = dottyVersions.sorted.last
88
+ val dottyState = updateScalaVersion(state, dottyProjRefs, dottyVersion)
89
+ (dottyVersion, dottyProjRefs, dottyState)
90
+ }
91
+ }
92
+
93
+ /** Run `task` in state `state` */
94
+ private def runTask [T ](task : Task [T ], state : State ): T = {
95
+ val extracted = Project .extract(state)
96
+ val structure = extracted.structure
97
+ val (_, result) =
98
+ EvaluateTask .withStreams(structure, state) { streams =>
99
+ EvaluateTask .runTask(task, state, streams, structure.index.triggers,
100
+ EvaluateTask .extractedTaskConfig(extracted, structure, state))(
101
+ EvaluateTask .nodeView(state, streams, Nil )
102
+ )
103
+ }
104
+ result match {
105
+ case Value (v) =>
106
+ v
107
+ case Inc (i) =>
108
+ throw i
109
+ }
110
+ }
111
+
112
+ /** Run task `key` in all configurations in all projects in `projRefs`, using state `state` */
113
+ private def runInAllConfigurations [T ](key : TaskKey [T ], projRefs : Seq [ProjectRef ], state : State ): Seq [T ] = {
114
+ val structure = Project .structure(state)
115
+ val settings = structure.data
116
+ val joinedTask = projRefs.flatMap { projRef =>
117
+ val project = Project .getProjectForReference(projRef, structure).get
35
118
project.configurations.flatMap { config =>
36
- isDotty.in(projRef, config).get(settings) match {
37
- case Some (true ) =>
38
- key.in(projRef, config).get(settings)
39
- case _ =>
40
- None
41
- }
119
+ key.in(projRef, config).get(settings)
42
120
}
43
121
}.join
122
+
123
+ runTask(joinedTask, state)
44
124
}
45
125
46
126
private val projectConfig = taskKey[Option [ProjectConfig ]](" " )
47
- private val configureIDE = taskKey[Unit ](" Generate IDE config files" )
48
- private val compileForIDE = taskKey[Unit ](" Compile all projects supported by the IDE" )
49
- private val runCode = taskKey[Unit ](" " )
50
127
51
128
object autoImport {
52
- val prepareIDE = taskKey[Unit ](" Prepare for IDE launch " )
53
- val launchIDE = taskKey[Unit ](" Run Visual Studio Code on this project" )
129
+ val runCode = taskKey[Unit ](" Start VSCode, usually called from launchIDE " )
130
+ val launchIDE = taskKey[Unit ](" Configure and run VSCode on this project" )
54
131
}
55
132
56
133
import autoImport ._
57
134
58
135
override def requires : Plugins = plugins.JvmPlugin
59
136
override def trigger = allRequirements
60
137
138
+ def configureIDE = Command .command(" configureIDE" ) { origState =>
139
+ val (dottyVersion, projRefs, dottyState) = dottySetup(origState)
140
+ val configs0 = runInAllConfigurations(projectConfig, projRefs, dottyState).flatten
141
+
142
+ // Drop configurations that do not define their own sources, but just
143
+ // inherit their sources from some other configuration.
144
+ val configs = distinctBy(configs0)(_.sourceDirectories.deep)
145
+
146
+ // Write the version of the Dotty Language Server to use in a file by itself.
147
+ // This could be a field in the JSON config file, but that would require all
148
+ // IDE plugins to parse JSON.
149
+ val dlsVersion = dottyVersion
150
+ .replace(" -nonbootstrapped" , " " ) // The language server is only published bootstrapped
151
+ val dlsBinaryVersion = dlsVersion.split(" \\ ." ).take(2 ).mkString(" ." )
152
+ val pwArtifact = new PrintWriter (" .dotty-ide-artifact" )
153
+ try {
154
+ pwArtifact.println(s " ch.epfl.lamp:dotty-language-server_ ${dlsBinaryVersion}: ${dlsVersion}" )
155
+ } finally {
156
+ pwArtifact.close()
157
+ }
158
+
159
+ val mapper = new ObjectMapper
160
+ mapper.writerWithDefaultPrettyPrinter()
161
+ .writeValue(new File (" .dotty-ide.json" ), configs.toArray)
162
+
163
+ origState
164
+ }
165
+
166
+ def compileForIDE = Command .command(" compileForIDE" ) { origState =>
167
+ val (dottyVersion, projRefs, dottyState) = dottySetup(origState)
168
+ runInAllConfigurations(compile, projRefs, dottyState)
169
+
170
+ origState
171
+ }
172
+
61
173
override def projectSettings : Seq [Setting [_]] = Seq (
62
174
// Use Def.derive so `projectConfig` is only defined in the configurations where the
63
175
// tasks/settings it depends on are defined.
64
176
Def .derive(projectConfig := {
65
177
if (sources.value.isEmpty) None
66
178
else {
179
+ // Not needed to generate the config, but this guarantees that the
180
+ // generated config is usable by an IDE without any extra compilation
181
+ // step.
182
+ val _ = compile.value
183
+
67
184
val id = s " ${thisProject.value.id}/ ${configuration.value.name}"
68
185
val compilerVersion = scalaVersion.value
69
- .replace(" -nonbootstrapped" , " " ) // The language server is only published bootstrapped
70
186
val compilerArguments = scalacOptions.value
71
187
val sourceDirectories = unmanagedSourceDirectories.value ++ managedSourceDirectories.value
72
188
val depClasspath = Attributed .data(dependencyClasspath.value)
@@ -85,61 +201,20 @@ object DottyIDEPlugin extends AutoPlugin {
85
201
)
86
202
87
203
override def buildSettings : Seq [Setting [_]] = Seq (
88
- configureIDE := {
89
- val log = streams.value.log
90
-
91
- val configs0 = state.flatMap(s =>
92
- inAllDottyConfigurations(projectConfig, s)
93
- ).value.flatten
94
- // Drop configurations who do not define their own sources, but just
95
- // inherit their sources from some other configuration.
96
- val configs = distinctBy(configs0)(_.sourceDirectories.deep)
97
-
98
- if (configs.isEmpty) {
99
- log.error(" No Dotty project detected" )
100
- } else {
101
- // If different versions of Dotty are used by subprojects, choose the latest one
102
- // FIXME: use a proper version number Ordering that knows that "0.1.1-M1" < "0.1.1"
103
- val ideVersion = configs.map(_.compilerVersion).sorted.last
104
- // Write the version of the Dotty Language Server to use in a file by itself.
105
- // This could be a field in the JSON config file, but that would require all
106
- // IDE plugins to parse JSON.
107
- val pwArtifact = new PrintWriter (" .dotty-ide-artifact" )
108
- pwArtifact.println(s " ch.epfl.lamp:dotty-language-server_0.1: ${ideVersion}" )
109
- pwArtifact.close()
110
-
111
- val mapper = new ObjectMapper
112
- mapper.writerWithDefaultPrettyPrinter()
113
- .writeValue(new File (" .dotty-ide.json" ), configs.toArray)
114
- }
115
- },
116
-
117
- compileForIDE := {
118
- val _ = state.flatMap(s =>
119
- inAllDottyConfigurations(compile, s)
120
- ).value
121
- },
204
+ commands ++= Seq (configureIDE, compileForIDE),
122
205
123
206
runCode := {
124
207
val exitCode = new ProcessBuilder (" code" , " --install-extension" , " lampepfl.dotty" )
125
208
.inheritIO()
126
209
.start()
127
210
.waitFor()
128
211
if (exitCode != 0 )
129
- throw new FeedbackProvidedException {
130
- override def toString = " Installing the Dotty support for VSCode failed"
131
- }
212
+ throw new MessageOnlyException (" Installing the Dotty support for VSCode failed" )
132
213
133
214
new ProcessBuilder (" code" , baseDirectory.value.getAbsolutePath)
134
215
.inheritIO()
135
216
.start()
136
- },
137
-
138
- prepareIDE := {
139
- val x1 = configureIDE.value
140
- val x2 = compileForIDE.value
141
- },
142
-
143
- launchIDE := runCode.dependsOn(prepareIDE).value
144
- )
217
+ }
218
+
219
+ ) ++ addCommandAlias(" launchIDE" , " ;configureIDE;runCode" )
145
220
}
0 commit comments