@@ -14,7 +14,7 @@ import DottyPlugin.autoImport._
1414
1515object DottyIDEPlugin extends AutoPlugin {
1616 // 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 ] = {
1818 val buf = new mutable.ListBuffer [A ]
1919 val seen = mutable.Set [B ]()
2020 xs foreach { x =>
@@ -27,20 +27,102 @@ object DottyIDEPlugin extends AutoPlugin {
2727 buf.toList
2828 }
2929
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
35120 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)
42122 }
43123 }.join
124+
125+ runTask(joinedTask, state)
44126 }
45127
46128 private val projectConfig = taskKey[Option [ProjectConfig ]](" " )
@@ -57,6 +139,7 @@ object DottyIDEPlugin extends AutoPlugin {
57139 override def requires : Plugins = plugins.JvmPlugin
58140 override def trigger = allRequirements
59141
142+
60143 override def projectSettings : Seq [Setting [_]] = Seq (
61144 // Use Def.derive so `projectConfig` is only defined in the configurations where the
62145 // tasks/settings it depends on are defined.
@@ -70,7 +153,6 @@ object DottyIDEPlugin extends AutoPlugin {
70153
71154 val id = s " ${thisProject.value.id}/ ${configuration.value.name}"
72155 val compilerVersion = scalaVersion.value
73- .replace(" -nonbootstrapped" , " " ) // The language server is only published bootstrapped
74156 val compilerArguments = scalacOptions.value
75157 val sourceDirectories = unmanagedSourceDirectories.value ++ managedSourceDirectories.value
76158 val depClasspath = Attributed .data(dependencyClasspath.value)
@@ -89,40 +171,40 @@ object DottyIDEPlugin extends AutoPlugin {
89171 )
90172
91173 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+
108184 // Write the version of the Dotty Language Server to use in a file by itself.
109185 // This could be a field in the JSON config file, but that would require all
110186 // 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(" ." )
111190 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 }" )
113192 pwArtifact.close()
114193
115194 val mapper = new ObjectMapper
116195 mapper.writerWithDefaultPrettyPrinter()
117196 .writeValue(new File (" .dotty-ide.json" ), configs.toArray)
118197 }
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,
126208
127209 runCode := {
128210 val exitCode = new ProcessBuilder (" code" , " --install-extension" , " lampepfl.dotty" )
0 commit comments