@@ -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,20 +27,102 @@ 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 FeedbackProvidedException {
86
+ override def toString = " No Dotty project detected"
87
+ }
88
+ else {
89
+ val dottyVersion = dottyVersions.sorted.last
90
+ val dottyState = updateScalaVersion(state, dottyProjRefs, dottyVersion)
91
+ (dottyVersion, dottyProjRefs, dottyState)
92
+ }
93
+ }
94
+
95
+ /** Run `task` in state `state` */
96
+ private def runTask [T ](task : Task [T ], state : State ): T = {
97
+ val extracted = Project .extract(state)
98
+ val structure = extracted.structure
99
+ val (_, result) =
100
+ EvaluateTask .withStreams(structure, state) { streams =>
101
+ EvaluateTask .runTask(task, state, streams, structure.index.triggers,
102
+ EvaluateTask .extractedTaskConfig(extracted, structure, state))(
103
+ EvaluateTask .nodeView(state, streams, Nil )
104
+ )
105
+ }
106
+ result match {
107
+ case Value (v) =>
108
+ v
109
+ case Inc (i) =>
110
+ throw i
111
+ }
112
+ }
113
+
114
+ /** Run task `key` in all configurations in all projects in `projRefs`, using state `state` */
115
+ private def runInAllConfigurations [T ](key : TaskKey [T ], projRefs : Seq [ProjectRef ], state : State ): Seq [T ] = {
116
+ val structure = Project .structure(state)
117
+ val settings = structure.data
118
+ val joinedTask = projRefs.flatMap { projRef =>
119
+ val project = Project .getProjectForReference(projRef, structure).get
35
120
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
- }
121
+ key.in(projRef, config).get(settings)
42
122
}
43
123
}.join
124
+
125
+ runTask(joinedTask, state)
44
126
}
45
127
46
128
private val projectConfig = taskKey[Option [ProjectConfig ]](" " )
@@ -57,6 +139,7 @@ object DottyIDEPlugin extends AutoPlugin {
57
139
override def requires : Plugins = plugins.JvmPlugin
58
140
override def trigger = allRequirements
59
141
142
+
60
143
override def projectSettings : Seq [Setting [_]] = Seq (
61
144
// Use Def.derive so `projectConfig` is only defined in the configurations where the
62
145
// tasks/settings it depends on are defined.
@@ -70,7 +153,6 @@ object DottyIDEPlugin extends AutoPlugin {
70
153
71
154
val id = s " ${thisProject.value.id}/ ${configuration.value.name}"
72
155
val compilerVersion = scalaVersion.value
73
- .replace(" -nonbootstrapped" , " " ) // The language server is only published bootstrapped
74
156
val compilerArguments = scalacOptions.value
75
157
val sourceDirectories = unmanagedSourceDirectories.value ++ managedSourceDirectories.value
76
158
val depClasspath = Attributed .data(dependencyClasspath.value)
@@ -89,40 +171,40 @@ object DottyIDEPlugin extends AutoPlugin {
89
171
)
90
172
91
173
override def buildSettings : Seq [Setting [_]] = Seq (
92
- configureIDE := {
93
- val log = streams.value.log
94
-
95
- val configs0 = state.flatMap(s =>
96
- inAllDottyConfigurations(projectConfig, s)
97
- ).value.flatten
98
- // Drop configurations who do not define their own sources, but just
99
- // inherit their sources from some other configuration.
100
- val configs = distinctBy(configs0)(_.sourceDirectories.deep)
101
-
102
- if (configs.isEmpty) {
103
- log.error(" No Dotty project detected" )
104
- } else {
105
- // If different versions of Dotty are used by subprojects, choose the latest one
106
- // FIXME: use a proper version number Ordering that knows that "0.1.1-M1" < "0.1.1"
107
- val ideVersion = configs.map(_.compilerVersion).sorted.last
174
+ configureIDE := Def .taskDyn {
175
+ val origState = state.value
176
+ Def .task {
177
+ val (dottyVersion, projRefs, dottyState) = dottySetup(origState)
178
+ val configs0 = runInAllConfigurations(projectConfig, projRefs, dottyState).flatten
179
+
180
+ // Drop configurations that do not define their own sources, but just
181
+ // inherit their sources from some other configuration.
182
+ val configs = distinctBy(configs0)(_.sourceDirectories.deep)
183
+
108
184
// Write the version of the Dotty Language Server to use in a file by itself.
109
185
// This could be a field in the JSON config file, but that would require all
110
186
// IDE plugins to parse JSON.
187
+ val dlsVersion = dottyVersion
188
+ .replace(" -nonbootstrapped" , " " ) // The language server is only published bootstrapped
189
+ val dlsBinaryVersion = dlsVersion.split(" \\ ." ).take(2 ).mkString(" ." )
111
190
val pwArtifact = new PrintWriter (" .dotty-ide-artifact" )
112
- pwArtifact.println(s " ch.epfl.lamp:dotty-language-server_0.1 : ${ideVersion }" )
191
+ pwArtifact.println(s " ch.epfl.lamp:dotty-language-server_ ${dlsBinaryVersion} : ${dlsVersion }" )
113
192
pwArtifact.close()
114
193
115
194
val mapper = new ObjectMapper
116
195
mapper.writerWithDefaultPrettyPrinter()
117
196
.writeValue(new File (" .dotty-ide.json" ), configs.toArray)
118
197
}
119
- },
120
-
121
- compileForIDE := {
122
- val _ = state.flatMap(s =>
123
- inAllDottyConfigurations(compile, s)
124
- ).value
125
- },
198
+ }.value,
199
+
200
+ compileForIDE := Def .taskDyn {
201
+ val origState = state.value
202
+ Def .task {
203
+ val (dottyVersion, projRefs, dottyState) = dottySetup(origState)
204
+ runInAllConfigurations(compile, projRefs, dottyState)
205
+ ()
206
+ }
207
+ }.value,
126
208
127
209
runCode := {
128
210
val exitCode = new ProcessBuilder (" code" , " --install-extension" , " lampepfl.dotty" )
0 commit comments