diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index 4d037a63c86..832f9f01d58 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -35,6 +35,7 @@ import scala.meta.pc.HoverSignature import scala.meta.pc.OffsetParams import scala.meta.pc.PresentationCompiler import scala.meta.pc.SymbolSearch +import scala.meta.pc.SyntheticDecoration import ch.epfl.scala.bsp4j.BuildTargetIdentifier import ch.epfl.scala.bsp4j.CompileReport @@ -573,6 +574,22 @@ class Compilers( path.toNIO.toUri().toString(), compiler.scalaVersion(), ) + + def adjustDecorations( + decorations: ju.List[SyntheticDecoration] + ): ju.List[DecorationOptions] = { + val withCorrectStart = decorations.asScala.dropWhile { d => + val adjusted = adjust + .adjustPos(d.range().getStart(), adjustToZero = false) + adjusted.getLine() < 0 || adjusted.getCharacter() < 0 + } + withCorrectStart.map { decoration => + DecorationOptions( + decoration.label(), + adjust.adjustRange(decoration.range()), + ) + }.asJava + } val vFile = CompilerVirtualFileParams(path.toNIO.toUri(), input.text, token) @@ -588,12 +605,7 @@ class Compilers( .syntheticDecorations(pcParams) .asScala .map { decorations => - decorations.map { decoration => - DecorationOptions( - decoration.label(), - adjust.adjustRange(decoration.range()), - ) - } + adjustDecorations(decorations) } } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcSyntheticDecorationsProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcSyntheticDecorationsProvider.scala index e7bc1930792..dfbc95b0a34 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcSyntheticDecorationsProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcSyntheticDecorationsProvider.scala @@ -22,7 +22,7 @@ final class PcSyntheticDecorationsProvider( def tpdTree = unit.lastBody def provide(): List[SyntheticDecoration] = - traverse(Synthetics.empty, tpdTree).decorations + traverse(Synthetics.empty, tpdTree).result() def collectDecorations( tree: Tree, @@ -220,11 +220,22 @@ final class PcSyntheticDecorationsProvider( def containsDef(offset: Int): Boolean = definitions(offset) def add(decoration: Decoration, offset: Int): Synthetics = copy( - decorations = decoration :: decorations, + decorations = addDecoration(decoration), definitions = definitions + offset ) def add(decoration: Decoration): Synthetics = - copy(decorations = decoration :: decorations) + copy( + decorations = addDecoration(decoration), + definitions = definitions + ) + // If method has both type parameter and implicit parameter, we want the type parameter decoration to be displayed first, + // but it's added second. This method adds the decoration to the right position in the list. + private def addDecoration(decoration: Decoration): List[Decoration] = { + val atSamePos = + decorations.takeWhile(_.range.getStart() == decoration.range.getStart()) + (atSamePos :+ decoration) ++ decorations.drop(atSamePos.size) + } + def result(): List[Decoration] = decorations.reverse } object Synthetics { diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/PcSyntheticDecorationProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/PcSyntheticDecorationProvider.scala index 112422db5b1..ac15b51ed60 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/PcSyntheticDecorationProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/PcSyntheticDecorationProvider.scala @@ -39,10 +39,9 @@ class PcSyntheticDecorationsProvider( val unit = driver.currentCtx.run.units.head def tpdTree = unit.tpdTree - def provide(): List[SyntheticDecoration] = val deepFolder = DeepFolder[Synthetics](collectDecorations) - deepFolder(Synthetics.empty, tpdTree).decorations + deepFolder(Synthetics.empty, tpdTree).result() def collectDecorations( decorations: Synthetics, @@ -255,11 +254,25 @@ case class Synthetics( def containsDef(offset: Int) = definitions(offset) def add(decoration: Decoration, offset: Int) = copy( - decorations = decoration :: decorations, + decorations = addDecoration(decoration), definitions = definitions + offset, ) def add(decoration: Decoration) = - copy(decorations = decoration :: decorations) + copy( + decorations = addDecoration(decoration), + definitions = definitions, + ) + + // If method has both type parameter and implicit parameter, we want the type parameter decoration to be displayed first, + // but it's added second. This method adds the decoration to the right position in the list. + private def addDecoration(decoration: Decoration): List[Decoration] = + val atSamePos = + decorations.takeWhile(_.range.getStart() == decoration.range.getStart()) + (atSamePos :+ decoration) ++ decorations.drop(atSamePos.size) + + def result(): List[Decoration] = decorations.reverse + +end Synthetics object Synthetics: def empty: Synthetics = Synthetics(Nil, Set.empty) diff --git a/tests/cross/src/test/scala/tests/pc/SyntheticDecorationsSuite.scala b/tests/cross/src/test/scala/tests/pc/SyntheticDecorationsSuite.scala index 539e329ffea..105e3f352d2 100644 --- a/tests/cross/src/test/scala/tests/pc/SyntheticDecorationsSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/SyntheticDecorationsSuite.scala @@ -537,4 +537,15 @@ class SyntheticDecorationsSuite extends BaseSyntheticDecorationsSuite { |""".stripMargin ) + check( + "ord", + """|object Main { + | val ordered = "acb".sorted + |} + |""".stripMargin, + """|object Main { + | val ordered: String = augmentString("acb").sorted[Char](Char) + |} + |""".stripMargin + ) } diff --git a/tests/slow/src/test/scala/tests/scalacli/ScalaCliSuite.scala b/tests/slow/src/test/scala/tests/scalacli/ScalaCliSuite.scala index ccc3f76cd69..e0269f152fe 100644 --- a/tests/slow/src/test/scala/tests/scalacli/ScalaCliSuite.scala +++ b/tests/slow/src/test/scala/tests/scalacli/ScalaCliSuite.scala @@ -3,6 +3,7 @@ package tests.scalacli import scala.concurrent.Future import scala.meta.internal.metals.FileOutOfScalaCliBspScope +import scala.meta.internal.metals.InitializationOptions import scala.meta.internal.metals.Messages import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.MetalsServerConfig @@ -17,6 +18,14 @@ class ScalaCliSuite extends BaseScalaCliSuite(V.scala3) { override def serverConfig: MetalsServerConfig = MetalsServerConfig.default.copy(slowTask = SlowTaskConfig.on) + override protected def initializationOptions: Option[InitializationOptions] = + Some( + InitializationOptions.Default.copy( + inlineDecorationProvider = Some(true), + decorationProvider = Some(true), + ) + ) + private def simpleFileTest(useBsp: Boolean): Future[Unit] = for { _ <- scalaCliInitialize(useBsp)(simpleFileLayout) @@ -169,6 +178,41 @@ class ScalaCliSuite extends BaseScalaCliSuite(V.scala3) { |""".stripMargin, ) + _ <- server.didChangeConfiguration( + """{ + | "show-implicit-arguments": true, + | "show-implicit-conversions-and-classes": true, + | "show-inferred-type": true + |} + |""".stripMargin + ) + + _ <- server.didOpen("MyTests.sc") + _ = assertNoDiff( + client.syntheticDecorations, + s"""#!/usr/bin/env -S scala-cli shebang --java-opt -Xms256m --java-opt -XX:MaxRAMPercentage=80 + |//> using scala "$scalaVersion" + |//> using lib "com.lihaoyi::utest::0.7.10" + |//> using lib com.lihaoyi::pprint::0.6.6 + | + |import foo.Foo + |import utest._ + | + |pprint.log[Int](2)(generate, generate) // top-level statement should be fine in a script + | + |object MyTests extends TestSuite { + | pprint.log[Int](2)(generate, generate) + | val tests: Tests = Tests { + | test("foo") { + | assert(2 + 2 == 4) + | } + | test("nope") { + | assert(2 + 2 == (new Foo).value) + | } + | } + |} + |""".stripMargin, + ) } yield () private val simpleFileLayout =